From 475ea361068d237680f8f6127577b39cbeee42cc Mon Sep 17 00:00:00 2001 From: vizhur Date: Tue, 9 Jul 2019 22:02:57 +0000 Subject: [PATCH] update samples from Release-132 as a part of 1.0.48 SDK release --- configuration.ipynb | 6 +- configuration.yml | 4 + contrib/datadrift/azure-ml-datadrift.ipynb | 24 +- contrib/datadrift/azure-ml-datadrift.yml | 8 + .../automated-machine-learning/automl_env.yml | 1 + ...uto-ml-classification-bank-marketing.ipynb | 1452 +++--- .../auto-ml-classification-bank-marketing.yml | 8 + ...-ml-classification-credit-card-fraud.ipynb | 1418 +++--- ...to-ml-classification-credit-card-fraud.yml | 8 + ...auto-ml-classification-with-deployment.yml | 8 + .../auto-ml-classification-with-onnx.yml | 9 + ...to-ml-classification-with-whitelisting.yml | 8 + .../classification/auto-ml-classification.yml | 8 + .../auto-ml-dataprep-remote-execution.yml | 8 + .../dataprep/auto-ml-dataprep.ipynb | 14 +- .../dataprep/auto-ml-dataprep.yml | 8 + .../auto-ml-exploring-previous-runs.yml | 8 + .../auto-ml-forecasting-bike-share.ipynb | 213 +- .../auto-ml-forecasting-bike-share.yml | 9 + .../auto-ml-forecasting-energy-demand.ipynb | 167 +- .../auto-ml-forecasting-energy-demand.yml | 10 + ...to-ml-forecasting-orange-juice-sales.ipynb | 25 +- ...auto-ml-forecasting-orange-juice-sales.yml | 9 + ...ssing-data-blacklist-early-termination.yml | 8 + .../auto-ml-model-explanation.yml | 9 + ...auto-ml-regression-concrete-strength.ipynb | 1594 +++--- .../auto-ml-regression-concrete-strength.yml | 8 + ...o-ml-regression-hardware-performance.ipynb | 1594 +++--- ...uto-ml-regression-hardware-performance.yml | 8 + .../regression/auto-ml-regression.yml | 9 + .../auto-ml-remote-amlcompute.yml | 8 + .../sample-weight/auto-ml-sample-weight.yml | 8 + .../auto-ml-sparse-data-train-test-split.yml | 8 + .../energy-demand/ForecastEnergyDemand.sql | 23 + .../energy-demand/TrainEnergyDemand.sql | 16 +- .../auto-ml-sql-energy-demand.ipynb | 276 +- .../sql-server/setup/AutoMLForecast.sql | 92 + .../sql-server/setup/AutoMLTrain.sql | 14 +- .../sql-server/setup/auto-ml-sql-setup.ipynb | 1118 ++--- .../subsampling/auto-ml-subsampling-local.yml | 8 + .../data-drift/azure-ml-datadrift.ipynb | 1412 +++--- .../model-register-and-deploy.yml | 4 + ...ble-app-insights-in-production-service.yml | 4 + ...able-data-collection-for-models-in-aks.yml | 4 + .../onnx/onnx-convert-aml-deploy-tinyyolo.yml | 6 + ...e-facial-expression-recognition-deploy.yml | 9 + .../onnx/onnx-inference-mnist-deploy.yml | 9 + .../onnx-modelzoo-aml-deploy-resnet50.yml | 4 + .../onnx-train-pytorch-aml-deploy-mnist.yml | 5 + .../production-deploy-to-aks.yml | 8 + ...ster-model-create-image-deploy-service.yml | 8 + .../regression-sklearn-on-amlcompute.yml | 6 + ...in-local-sklearn-binary-classification.yml | 6 + ...ocal-sklearn-multiclass-classification.yml | 6 + .../explain-local-sklearn-regression.yml | 6 + .../explain-sklearn-raw-features.yml | 7 + ...ain-run-history-sklearn-classification.yml | 6 + ...explain-run-history-sklearn-regression.yml | 6 + .../aml-pipelines-data-transfer.yml | 5 + .../aml-pipelines-getting-started.yml | 5 + ...aml-pipelines-how-to-use-estimatorstep.yml | 5 + ...es-publish-and-run-using-rest-endpoint.yml | 6 + ...s-with-automated-machine-learning-step.yml | 8 + ...l-pipelines-with-data-dependency-steps.yml | 5 + ...-taxi-data-regression-model-building.ipynb | 4 +- ...yc-taxi-data-regression-model-building.yml | 9 + .../pipeline-batch-scoring.yml | 7 + .../pipeline-style-transfer/neural_style.py | 3 - .../pipeline-style-transfer.yml | 6 + .../authentication-in-azureml.ipynb | 57 +- .../images/svc-pr-1.PNG | Bin 38043 -> 99315 bytes .../images/svc-pr-2.PNG | Bin 69230 -> 84045 bytes .../images/svc-pr-3.PNG | Bin 68199 -> 63736 bytes .../distributed-chainer.ipynb | 5 +- .../distributed-chainer.yml | 5 + .../distributed-cntk-with-custom-docker.ipynb | 2 +- .../distributed-cntk-with-custom-docker.yml | 6 + .../distributed-pytorch-with-horovod.ipynb | 5 +- .../distributed-pytorch-with-horovod.yml | 5 + .../distributed-tensorflow-with-horovod.ipynb | 3 +- .../distributed-tensorflow-with-horovod.yml | 5 + ...ted-tensorflow-with-parameter-server.ipynb | 5 +- ...buted-tensorflow-with-parameter-server.yml | 5 + .../export-run-history-to-tensorboard.yml | 9 + .../how-to-use-estimator.ipynb | 7 +- .../how-to-use-estimator.yml | 6 + .../tensorboard/tensorboard.yml | 6 + ...erparameter-tune-deploy-with-chainer.ipynb | 5 +- ...yperparameter-tune-deploy-with-chainer.yml | 7 + ...yperparameter-tune-deploy-with-keras.ipynb | 7 +- ...-hyperparameter-tune-deploy-with-keras.yml | 8 + ...erparameter-tune-deploy-with-pytorch.ipynb | 5 +- ...yperparameter-tune-deploy-with-pytorch.yml | 9 + ...arameter-tune-deploy-with-tensorflow.ipynb | 5 +- ...rparameter-tune-deploy-with-tensorflow.yml | 8 + how-to-use-azureml/training/README.md | 5 +- .../training/logging-api/logging-api.ipynb | 2 +- .../training/logging-api/logging-api.yml | 8 + .../training/manage-runs/manage-runs.yml | 4 + ...erparameter-tune-deploy-with-sklearn.ipynb | 82 +- ...yperparameter-tune-deploy-with-sklearn.yml | 6 + .../train_iris.py | 5 + .../train-on-amlcompute.yml | 6 + .../train-on-local/train-on-local.yml | 7 + .../train-on-remote-vm.ipynb | 10 +- .../train-on-remote-vm/train-on-remote-vm.yml | 8 + .../train-within-notebook.ipynb | 4 +- .../train-within-notebook.yml | 8 + .../using-environments/using-environments.yml | 4 + how-to-use-azureml/using-mlflow/README.md | 1 + .../deploy-model/deploy-model.ipynb | 2 +- .../deploy-model/deploy-model.yml | 8 + .../train-and-deploy-pytorch.yml | 8 + .../using-mlflow/train-local/train-local.yml | 7 + .../train-remote/train-remote.yml | 4 + setup-environment/configuration.ipynb | 18 +- setup-environment/configuration.yml | 4 + .../img-classification-part1-training.yml | 7 + tutorials/img-classification-part2-deploy.yml | 6 + tutorials/regression-part1-data-prep.yml | 5 + tutorials/regression-part2-automated-ml.yml | 10 + work-with-data/README.md | 9 + work-with-data/dataprep/README.md | 255 + .../new-york-taxi/new-york-taxi.ipynb | 514 ++ .../new-york-taxi_scale-out.ipynb | 135 + .../dataprep/data/adls-dpreptestfiles.crt | 45 + .../dataprep/data/chicago-aldermen-2015.csv | 54 + work-with-data/dataprep/data/crime-dirty.csv | 15 + work-with-data/dataprep/data/crime-full.csv | 1001 ++++ work-with-data/dataprep/data/crime-spring.csv | 11 + work-with-data/dataprep/data/crime-winter.csv | 11 + work-with-data/dataprep/data/crime.dprep | 204 + work-with-data/dataprep/data/crime.parquet | Bin 0 -> 3607 bytes work-with-data/dataprep/data/crime.txt | 10 + work-with-data/dataprep/data/crime.xlsx | Bin 0 -> 16109 bytes work-with-data/dataprep/data/crime.zip | Bin 0 -> 3685 bytes .../dataprep/data/crime_duplicate_headers.csv | 12 + .../dataprep/data/crime_fixed_width_file.txt | 10 + .../data/crime_multiple_separators.csv | 11 + .../dataprep/data/crime_partfiles/_SUCCESS | 0 ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 914 ++++ ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 921 ++++ ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 930 ++++ ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 953 ++++ ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 923 ++++ ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 887 ++++ ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 971 ++++ ...8e77b-f17a-4c20-972c-aa382e830fca-c000.csv | 759 +++ work-with-data/dataprep/data/json.json | 1306 +++++ work-with-data/dataprep/data/large_dflow.json | 4415 +++++++++++++++++ work-with-data/dataprep/data/map_func.py | 4 + .../dataprep/data/median_income.csv | 251 + .../data/median_income_transformed.csv | 251 + work-with-data/dataprep/data/parquet.parquet | Bin 0 -> 3091 bytes ...7a7-c3cd-4926-92b2-ba2dcd3f95b7.gz.parquet | Bin 0 -> 6078 bytes ...7a7-c3cd-4926-92b2-ba2dcd3f95b7.gz.parquet | Bin 0 -> 5083 bytes work-with-data/dataprep/data/secrets.dprep | 63 + work-with-data/dataprep/data/stream-path.csv | 11 + .../add-column-using-expression.ipynb | 360 ++ .../append-columns-and-rows.ipynb | 251 + .../dataprep/how-to-guides/assertions.ipynb | 133 + .../how-to-guides/auto-read-file.ipynb | 189 + .../dataprep/how-to-guides/cache.ipynb | 194 + .../how-to-guides/column-manipulations.ipynb | 563 +++ .../column-type-transforms.ipynb | 473 ++ .../custom-python-transforms.ipynb | 231 + .../how-to-guides/data-ingestion.ipynb | 978 ++++ .../dataprep/how-to-guides/data-profile.ipynb | 179 + .../dataprep/how-to-guides/datastore.ipynb | 215 + .../derive-column-by-example.ipynb | 187 + .../how-to-guides/external-references.ipynb | 118 + .../dataprep/how-to-guides/filtering.ipynb | 220 + .../dataprep/how-to-guides/fuzzy-group.ipynb | 211 + .../how-to-guides/impute-missing-values.ipynb | 147 + .../dataprep/how-to-guides/join.ipynb | 265 + .../how-to-guides/label-encoder.ipynb | 168 + .../how-to-guides/min-max-scaler.ipynb | 239 + .../how-to-guides/one-hot-encoder.ipynb | 179 + .../how-to-guides/open-save-dataflows.ipynb | 171 + .../quantile-transformation.ipynb | 91 + .../dataprep/how-to-guides/random-split.ipynb | 145 + ...replace-datasource-replace-reference.ipynb | 130 + .../how-to-guides/replace-fill-error.ipynb | 239 + .../dataprep/how-to-guides/secrets.ipynb | 140 + .../how-to-guides/semantic-types.ipynb | 164 + .../split-column-by-example.ipynb | 220 + .../how-to-guides/subsetting-sampling.ipynb | 217 + .../dataprep/how-to-guides/summarize.ipynb | 590 +++ .../working-with-file-streams.ipynb | 192 + .../dataprep/how-to-guides/writing-data.ipynb | 183 + .../getting-started/getting-started.ipynb | 441 ++ work-with-data/datasets/README.md | 159 + .../datasets-tutorial/datasets-tutorial.ipynb | 376 ++ .../train-dataset/Titanic.csv | 892 ++++ .../datasets-tutorial/train-dataset/train.py | 37 + 195 files changed, 31305 insertions(+), 4675 deletions(-) create mode 100644 configuration.yml create mode 100644 contrib/datadrift/azure-ml-datadrift.yml create mode 100644 how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.yml create mode 100644 how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.yml create mode 100644 how-to-use-azureml/automated-machine-learning/classification-with-deployment/auto-ml-classification-with-deployment.yml create mode 100644 how-to-use-azureml/automated-machine-learning/classification-with-onnx/auto-ml-classification-with-onnx.yml create mode 100644 how-to-use-azureml/automated-machine-learning/classification-with-whitelisting/auto-ml-classification-with-whitelisting.yml create mode 100644 how-to-use-azureml/automated-machine-learning/classification/auto-ml-classification.yml create mode 100644 how-to-use-azureml/automated-machine-learning/dataprep-remote-execution/auto-ml-dataprep-remote-execution.yml create mode 100644 how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.yml create mode 100644 how-to-use-azureml/automated-machine-learning/exploring-previous-runs/auto-ml-exploring-previous-runs.yml create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.yml create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.yml create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.yml create mode 100644 how-to-use-azureml/automated-machine-learning/missing-data-blacklist-early-termination/auto-ml-missing-data-blacklist-early-termination.yml create mode 100644 how-to-use-azureml/automated-machine-learning/model-explanation/auto-ml-model-explanation.yml create mode 100644 how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.yml create mode 100644 how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.yml create mode 100644 how-to-use-azureml/automated-machine-learning/regression/auto-ml-regression.yml create mode 100644 how-to-use-azureml/automated-machine-learning/remote-amlcompute/auto-ml-remote-amlcompute.yml create mode 100644 how-to-use-azureml/automated-machine-learning/sample-weight/auto-ml-sample-weight.yml create mode 100644 how-to-use-azureml/automated-machine-learning/sparse-data-train-test-split/auto-ml-sparse-data-train-test-split.yml create mode 100644 how-to-use-azureml/automated-machine-learning/sql-server/energy-demand/ForecastEnergyDemand.sql create mode 100644 how-to-use-azureml/automated-machine-learning/sql-server/setup/AutoMLForecast.sql create mode 100644 how-to-use-azureml/automated-machine-learning/subsampling/auto-ml-subsampling-local.yml create mode 100644 how-to-use-azureml/deploy-to-cloud/model-register-and-deploy.yml create mode 100644 how-to-use-azureml/deployment/enable-app-insights-in-production-service/enable-app-insights-in-production-service.yml create mode 100644 how-to-use-azureml/deployment/enable-data-collection-for-models-in-aks/enable-data-collection-for-models-in-aks.yml create mode 100644 how-to-use-azureml/deployment/onnx/onnx-convert-aml-deploy-tinyyolo.yml create mode 100644 how-to-use-azureml/deployment/onnx/onnx-inference-facial-expression-recognition-deploy.yml create mode 100644 how-to-use-azureml/deployment/onnx/onnx-inference-mnist-deploy.yml create mode 100644 how-to-use-azureml/deployment/onnx/onnx-modelzoo-aml-deploy-resnet50.yml create mode 100644 how-to-use-azureml/deployment/onnx/onnx-train-pytorch-aml-deploy-mnist.yml create mode 100644 how-to-use-azureml/deployment/production-deploy-to-aks/production-deploy-to-aks.yml create mode 100644 how-to-use-azureml/deployment/register-model-create-image-deploy-service/register-model-create-image-deploy-service.yml create mode 100644 how-to-use-azureml/explain-model/explain-on-amlcompute/regression-sklearn-on-amlcompute.yml create mode 100644 how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-binary-classification.yml create mode 100644 how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-multiclass-classification.yml create mode 100644 how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-regression.yml create mode 100644 how-to-use-azureml/explain-model/explain-tabular-data-raw-features/explain-sklearn-raw-features.yml create mode 100644 how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-classification.yml create mode 100644 how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-regression.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-data-transfer.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-getting-started.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-how-to-use-estimatorstep.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-publish-and-run-using-rest-endpoint.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-automated-machine-learning-step.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-data-dependency-steps.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/nyc-taxi-data-regression-model-building/nyc-taxi-data-regression-model-building.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/pipeline-batch-scoring/pipeline-batch-scoring.yml create mode 100644 how-to-use-azureml/machine-learning-pipelines/pipeline-style-transfer/pipeline-style-transfer.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/distributed-chainer/distributed-chainer.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/distributed-cntk-with-custom-docker/distributed-cntk-with-custom-docker.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/distributed-pytorch-with-horovod/distributed-pytorch-with-horovod.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/distributed-tensorflow-with-horovod/distributed-tensorflow-with-horovod.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/distributed-tensorflow-with-parameter-server/distributed-tensorflow-with-parameter-server.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/export-run-history-to-tensorboard/export-run-history-to-tensorboard.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/how-to-use-estimator/how-to-use-estimator.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/tensorboard/tensorboard.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/train-hyperparameter-tune-deploy-with-chainer/train-hyperparameter-tune-deploy-with-chainer.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/train-hyperparameter-tune-deploy-with-keras/train-hyperparameter-tune-deploy-with-keras.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/train-hyperparameter-tune-deploy-with-pytorch/train-hyperparameter-tune-deploy-with-pytorch.yml create mode 100644 how-to-use-azureml/training-with-deep-learning/train-hyperparameter-tune-deploy-with-tensorflow/train-hyperparameter-tune-deploy-with-tensorflow.yml create mode 100644 how-to-use-azureml/training/logging-api/logging-api.yml create mode 100644 how-to-use-azureml/training/manage-runs/manage-runs.yml create mode 100644 how-to-use-azureml/training/train-hyperparameter-tune-deploy-with-sklearn/train-hyperparameter-tune-deploy-with-sklearn.yml create mode 100644 how-to-use-azureml/training/train-on-amlcompute/train-on-amlcompute.yml create mode 100644 how-to-use-azureml/training/train-on-local/train-on-local.yml create mode 100644 how-to-use-azureml/training/train-on-remote-vm/train-on-remote-vm.yml create mode 100644 how-to-use-azureml/training/train-within-notebook/train-within-notebook.yml create mode 100644 how-to-use-azureml/training/using-environments/using-environments.yml create mode 100644 how-to-use-azureml/using-mlflow/deploy-model/deploy-model.yml create mode 100644 how-to-use-azureml/using-mlflow/train-deploy-pytorch/train-and-deploy-pytorch.yml create mode 100644 how-to-use-azureml/using-mlflow/train-local/train-local.yml create mode 100644 how-to-use-azureml/using-mlflow/train-remote/train-remote.yml create mode 100644 setup-environment/configuration.yml create mode 100644 tutorials/img-classification-part1-training.yml create mode 100644 tutorials/img-classification-part2-deploy.yml create mode 100644 tutorials/regression-part1-data-prep.yml create mode 100644 tutorials/regression-part2-automated-ml.yml create mode 100644 work-with-data/README.md create mode 100644 work-with-data/dataprep/README.md create mode 100644 work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi.ipynb create mode 100644 work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi_scale-out.ipynb create mode 100644 work-with-data/dataprep/data/adls-dpreptestfiles.crt create mode 100644 work-with-data/dataprep/data/chicago-aldermen-2015.csv create mode 100644 work-with-data/dataprep/data/crime-dirty.csv create mode 100644 work-with-data/dataprep/data/crime-full.csv create mode 100644 work-with-data/dataprep/data/crime-spring.csv create mode 100644 work-with-data/dataprep/data/crime-winter.csv create mode 100644 work-with-data/dataprep/data/crime.dprep create mode 100644 work-with-data/dataprep/data/crime.parquet create mode 100644 work-with-data/dataprep/data/crime.txt create mode 100644 work-with-data/dataprep/data/crime.xlsx create mode 100644 work-with-data/dataprep/data/crime.zip create mode 100644 work-with-data/dataprep/data/crime_duplicate_headers.csv create mode 100644 work-with-data/dataprep/data/crime_fixed_width_file.txt create mode 100644 work-with-data/dataprep/data/crime_multiple_separators.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/_SUCCESS create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00000-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00001-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00002-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00003-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00004-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00005-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00006-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/crime_partfiles/part-00007-0b08e77b-f17a-4c20-972c-aa382e830fca-c000.csv create mode 100644 work-with-data/dataprep/data/json.json create mode 100644 work-with-data/dataprep/data/large_dflow.json create mode 100644 work-with-data/dataprep/data/map_func.py create mode 100644 work-with-data/dataprep/data/median_income.csv create mode 100644 work-with-data/dataprep/data/median_income_transformed.csv create mode 100644 work-with-data/dataprep/data/parquet.parquet create mode 100644 work-with-data/dataprep/data/parquet_dataset/Arrest=false/part-00000-34f8a7a7-c3cd-4926-92b2-ba2dcd3f95b7.gz.parquet create mode 100644 work-with-data/dataprep/data/parquet_dataset/Arrest=true/part-00000-34f8a7a7-c3cd-4926-92b2-ba2dcd3f95b7.gz.parquet create mode 100644 work-with-data/dataprep/data/secrets.dprep create mode 100644 work-with-data/dataprep/data/stream-path.csv create mode 100644 work-with-data/dataprep/how-to-guides/add-column-using-expression.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/append-columns-and-rows.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/assertions.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/auto-read-file.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/cache.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/column-manipulations.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/column-type-transforms.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/custom-python-transforms.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/data-ingestion.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/data-profile.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/datastore.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/derive-column-by-example.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/external-references.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/filtering.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/fuzzy-group.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/impute-missing-values.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/join.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/label-encoder.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/min-max-scaler.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/one-hot-encoder.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/open-save-dataflows.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/quantile-transformation.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/random-split.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/replace-datasource-replace-reference.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/replace-fill-error.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/secrets.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/semantic-types.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/split-column-by-example.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/subsetting-sampling.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/summarize.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/working-with-file-streams.ipynb create mode 100644 work-with-data/dataprep/how-to-guides/writing-data.ipynb create mode 100644 work-with-data/dataprep/tutorials/getting-started/getting-started.ipynb create mode 100644 work-with-data/datasets/README.md create mode 100644 work-with-data/datasets/datasets-tutorial/datasets-tutorial.ipynb create mode 100644 work-with-data/datasets/datasets-tutorial/train-dataset/Titanic.csv create mode 100644 work-with-data/datasets/datasets-tutorial/train-dataset/train.py diff --git a/configuration.ipynb b/configuration.ipynb index 32bec92b..023aab23 100644 --- a/configuration.ipynb +++ b/configuration.ipynb @@ -58,7 +58,7 @@ "\n", "### What is an Azure Machine Learning workspace\n", "\n", - "An Azure ML Workspace is an Azure resource that organizes and coordinates the actions of many other Azure resources to assist in executing and sharing machine learning workflows. In particular, an Azure ML Workspace coordinates storage, databases, and compute resources providing added functionality for machine learning experimentation, deployment, inferencing, and the monitoring of deployed models." + "An Azure ML Workspace is an Azure resource that organizes and coordinates the actions of many other Azure resources to assist in executing and sharing machine learning workflows. In particular, an Azure ML Workspace coordinates storage, databases, and compute resources providing added functionality for machine learning experimentation, deployment, inference, and the monitoring of deployed models." ] }, { @@ -103,7 +103,7 @@ "source": [ "import azureml.core\n", "\n", - "print(\"This notebook was created using version 1.0.45 of the Azure ML SDK\")\n", + "print(\"This notebook was created using version 1.0.48 of the Azure ML SDK\")\n", "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" ] }, @@ -258,7 +258,7 @@ "```shell\n", "az vm list-skus -o tsv\n", "```\n", - "* min_nodes - this sets the minimum size of the cluster. If you set the minimum to 0 the cluster will shut down all nodes while note in use. Setting this number to a value higher than 0 will allow for faster start-up times, but you will also be billed when the cluster is not in use.\n", + "* min_nodes - this sets the minimum size of the cluster. If you set the minimum to 0 the cluster will shut down all nodes while not in use. Setting this number to a value higher than 0 will allow for faster start-up times, but you will also be billed when the cluster is not in use.\n", "* max_nodes - this sets the maximum size of the cluster. Setting this to a larger number allows for more concurrency and a greater distributed processing of scale-out jobs.\n", "\n", "\n", diff --git a/configuration.yml b/configuration.yml new file mode 100644 index 00000000..4b1aed29 --- /dev/null +++ b/configuration.yml @@ -0,0 +1,4 @@ +name: configuration +dependencies: +- pip: + - azureml-sdk diff --git a/contrib/datadrift/azure-ml-datadrift.ipynb b/contrib/datadrift/azure-ml-datadrift.ipynb index 7922bc77..2bfe28d7 100644 --- a/contrib/datadrift/azure-ml-datadrift.ipynb +++ b/contrib/datadrift/azure-ml-datadrift.ipynb @@ -33,10 +33,9 @@ "source": [ "## Install the DataDrift package\n", "\n", - "Install the azureml-contrib-datadrift, azureml-contrib-opendatasets and lightgbm packages before running this notebook.\n", + "Install the azureml-contrib-datadrift, azureml-opendatasets and lightgbm packages before running this notebook.\n", "```\n", "pip install azureml-contrib-datadrift\n", - "pip install azureml-contrib-datasets\n", "pip install lightgbm\n", "```" ] @@ -63,7 +62,7 @@ "import pandas as pd\n", "import requests\n", "from azureml.contrib.datadrift import DataDriftDetector, AlertConfiguration\n", - "from azureml.contrib.opendatasets import NoaaIsdWeather\n", + "from azureml.opendatasets import NoaaIsdWeather\n", "from azureml.core import Dataset, Workspace, Run\n", "from azureml.core.compute import AksCompute, ComputeTarget\n", "from azureml.core.conda_dependencies import CondaDependencies\n", @@ -259,8 +258,7 @@ "trainingDataset = Dataset.auto_read_files(dpath, include_path=True)\n", "trainingDataset = trainingDataset.register(workspace=ws, name=dataset_name, description=\"dset\", exist_ok=True)\n", "\n", - "trainingDataSnapshot = trainingDataset.create_snapshot(snapshot_name=snapshot_name, compute_target=None, create_data_snapshot=True)\n", - "datasets = [(Dataset.Scenario.TRAINING, trainingDataSnapshot)]\n", + "datasets = [(Dataset.Scenario.TRAINING, trainingDataset)]\n", "print(\"dataset registration done.\\n\")\n", "datasets" ] @@ -574,6 +572,22 @@ " time.sleep(3)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We need to wait up to 10 minutes for the Model Data Collector to dump the model input and inference data to storage in the Workspace, where it's used by the DataDriftDetector job." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "time.sleep(600)" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/contrib/datadrift/azure-ml-datadrift.yml b/contrib/datadrift/azure-ml-datadrift.yml new file mode 100644 index 00000000..5d80f7b7 --- /dev/null +++ b/contrib/datadrift/azure-ml-datadrift.yml @@ -0,0 +1,8 @@ +name: azure-ml-datadrift +dependencies: +- pip: + - azureml-sdk + - azureml-contrib-datadrift + - azureml-opendatasets + - lightgbm + - azureml-widgets diff --git a/how-to-use-azureml/automated-machine-learning/automl_env.yml b/how-to-use-azureml/automated-machine-learning/automl_env.yml index 0dc80e8f..07b7c974 100644 --- a/how-to-use-azureml/automated-machine-learning/automl_env.yml +++ b/how-to-use-azureml/automated-machine-learning/automl_env.yml @@ -2,6 +2,7 @@ name: azure_automl dependencies: # The python interpreter version. # Currently Azure ML only supports 3.5.2 and later. +- pip - python>=3.5.2,<3.6.8 - nb_conda - matplotlib==2.1.0 diff --git a/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.ipynb b/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.ipynb index 4989e52e..efaccf23 100644 --- a/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.ipynb +++ b/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.ipynb @@ -1,729 +1,729 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Automated Machine Learning\n", - "_**Classification with Deployment using a Bank Marketing Dataset**_\n", - "\n", - "## Contents\n", - "1. [Introduction](#Introduction)\n", - "1. [Setup](#Setup)\n", - "1. [Train](#Train)\n", - "1. [Results](#Results)\n", - "1. [Deploy](#Deploy)\n", - "1. [Test](#Test)\n", - "1. [Acknowledgements](#Acknowledgements)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Introduction\n", - "\n", - "In this example we use the UCI Bank Marketing dataset to showcase how you can use AutoML for a classification problem and deploy it to an Azure Container Instance (ACI). The classification goal is to predict if the client will subscribe to a term deposit with the bank.\n", - "\n", - "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", - "\n", - "In this notebook you will learn how to:\n", - "1. Create an experiment using an existing workspace.\n", - "2. Configure AutoML using `AutoMLConfig`.\n", - "3. Train the model using local compute.\n", - "4. Explore the results.\n", - "5. Register the model.\n", - "6. Create a container image.\n", - "7. Create an Azure Container Instance (ACI) service.\n", - "8. Test the ACI service." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup\n", - "\n", - "As part of the setup you have already created an Azure ML `Workspace` object. For AutoML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "import logging\n", - "\n", - "from matplotlib import pyplot as plt\n", - "import numpy as np\n", - "import pandas as pd\n", - "import os\n", - "from sklearn import datasets\n", - "import azureml.dataprep as dprep\n", - "from sklearn.model_selection import train_test_split\n", - "\n", - "import azureml.core\n", - "from azureml.core.experiment import Experiment\n", - "from azureml.core.workspace import Workspace\n", - "from azureml.train.automl import AutoMLConfig\n", - "from azureml.train.automl.run import AutoMLRun" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ws = Workspace.from_config()\n", - "\n", - "# choose a name for experiment\n", - "experiment_name = 'automl-classification-bmarketing'\n", - "# project folder\n", - "project_folder = './sample_projects/automl-classification-bankmarketing'\n", - "\n", - "experiment=Experiment(ws, experiment_name)\n", - "\n", - "output = {}\n", - "output['SDK version'] = azureml.core.VERSION\n", - "output['Subscription ID'] = ws.subscription_id\n", - "output['Workspace'] = ws.name\n", - "output['Resource Group'] = ws.resource_group\n", - "output['Location'] = ws.location\n", - "output['Project Directory'] = project_folder\n", - "output['Experiment Name'] = experiment.name\n", - "pd.set_option('display.max_colwidth', -1)\n", - "outputDf = pd.DataFrame(data = output, index = [''])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create or Attach existing AmlCompute\n", - "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", - "#### Creation of AmlCompute takes approximately 5 minutes. \n", - "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", - "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this article on the default limits and how to request more quota." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.compute import AmlCompute\n", - "from azureml.core.compute import ComputeTarget\n", - "\n", - "# Choose a name for your cluster.\n", - "amlcompute_cluster_name = \"automlcl\"\n", - "\n", - "found = False\n", - "# Check if this compute target already exists in the workspace.\n", - "cts = ws.compute_targets\n", - "if amlcompute_cluster_name in cts and cts[amlcompute_cluster_name].type == 'AmlCompute':\n", - " found = True\n", - " print('Found existing compute target.')\n", - " compute_target = cts[amlcompute_cluster_name]\n", - " \n", - "if not found:\n", - " print('Creating a new compute target...')\n", - " provisioning_config = AmlCompute.provisioning_configuration(vm_size = \"STANDARD_D2_V2\", # for GPU, use \"STANDARD_NC6\"\n", - " #vm_priority = 'lowpriority', # optional\n", - " max_nodes = 6)\n", - "\n", - " # Create the cluster.\n", - " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, provisioning_config)\n", - " \n", - " # Can poll for a minimum number of nodes and for a specific timeout.\n", - " # If no min_node_count is provided, it will use the scale settings for the cluster.\n", - " compute_target.wait_for_completion(show_output = True, min_node_count = None, timeout_in_minutes = 20)\n", - " \n", - " # For a more detailed view of current AmlCompute status, use get_status()." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Data\n", - "\n", - "Here load the data in the get_data() script to be utilized in azure compute. To do this first load all the necessary libraries and dependencies to set up paths for the data and to create the conda_Run_config." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "if not os.path.isdir('data'):\n", - " os.mkdir('data')\n", - " \n", - "if not os.path.exists(project_folder):\n", - " os.makedirs(project_folder)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.runconfig import RunConfiguration\n", - "from azureml.core.conda_dependencies import CondaDependencies\n", - "\n", - "# create a new RunConfig object\n", - "conda_run_config = RunConfiguration(framework=\"python\")\n", - "\n", - "# Set compute target to AmlCompute\n", - "conda_run_config.target = compute_target\n", - "conda_run_config.environment.docker.enabled = True\n", - "conda_run_config.environment.docker.base_image = azureml.core.runconfig.DEFAULT_CPU_IMAGE\n", - "\n", - "\n", - "cd = CondaDependencies.create(pip_packages=['azureml-sdk[automl]'], conda_packages=['numpy','py-xgboost<=0.80'])\n", - "conda_run_config.environment.python.conda_dependencies = cd" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load Data\n", - "\n", - "Here we create the script to be run in azure comput for loading the data, we load the bank marketing dataset into X_train and y_train. Next X_train and y_train is returned for training the model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/bankmarketing_train.csv\"\n", - "dflow = dprep.auto_read_file(data)\n", - "dflow.get_profile()\n", - "X_train = dflow.drop_columns(columns=['y'])\n", - "y_train = dflow.keep_columns(columns=['y'], validate_column_exists=True)\n", - "dflow.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Train\n", - "\n", - "Instantiate a AutoMLConfig object. This defines the settings and data used to run the experiment.\n", - "\n", - "|Property|Description|\n", - "|-|-|\n", - "|**task**|classification or regression|\n", - "|**primary_metric**|This is the metric that you want to optimize. Classification supports the following primary metrics:
accuracy
AUC_weighted
average_precision_score_weighted
norm_macro_recall
precision_score_weighted|\n", - "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", - "|**iterations**|Number of iterations. In each iteration AutoML trains a specific pipeline with the data.|\n", - "|**n_cross_validations**|Number of cross validation splits.|\n", - "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", - "|**y**|(sparse) array-like, shape = [n_samples, ], Multi-class targets.|\n", - "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder.|\n", - "\n", - "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "automl_settings = {\n", - " \"iteration_timeout_minutes\": 5,\n", - " \"iterations\": 10,\n", - " \"n_cross_validations\": 2,\n", - " \"primary_metric\": 'AUC_weighted',\n", - " \"preprocess\": True,\n", - " \"max_concurrent_iterations\": 5,\n", - " \"verbosity\": logging.INFO,\n", - "}\n", - "\n", - "automl_config = AutoMLConfig(task = 'classification',\n", - " debug_log = 'automl_errors.log',\n", - " path = project_folder,\n", - " run_configuration=conda_run_config,\n", - " X = X_train,\n", - " y = y_train,\n", - " **automl_settings\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Call the `submit` method on the experiment object and pass the run configuration. Execution of local runs is synchronous. Depending on the data and the number of iterations this can run for a while.\n", - "In this example, we specify `show_output = True` to print currently running iterations to the console." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run = experiment.submit(automl_config, show_output = True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Results" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Widget for Monitoring Runs\n", - "\n", - "The widget will first report a \"loading\" status while running the first iteration. After completing the first iteration, an auto-updating graph and table will be shown. The widget will refresh once per minute, so you should see the graph update as child runs complete.\n", - "\n", - "**Note:** The widget displays a link at the bottom. Use this link to open a web interface to explore the individual run details" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.widgets import RunDetails\n", - "RunDetails(remote_run).show() " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Deploy\n", - "\n", - "### Retrieve the Best Model\n", - "\n", - "Below we select the best pipeline from our iterations. The `get_output` method on `automl_classifier` returns the best run and the fitted model for the last invocation. Overloads on `get_output` allow you to retrieve the best run and fitted model for *any* logged metric or for a particular *iteration*." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "best_run, fitted_model = remote_run.get_output()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Register the Fitted Model for Deployment\n", - "If neither `metric` nor `iteration` are specified in the `register_model` call, the iteration with the best primary metric is registered." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "description = 'AutoML Model trained on bank marketing data to predict if a client will subscribe to a term deposit'\n", - "tags = None\n", - "model = remote_run.register_model(description = description, tags = tags)\n", - "\n", - "print(remote_run.model_id) # This will be written to the script file later in the notebook." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create Scoring Script\n", - "The scoring script is required to generate the image for deployment. It contains the code to do the predictions on input data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%writefile score.py\n", - "import pickle\n", - "import json\n", - "import numpy\n", - "import azureml.train.automl\n", - "from sklearn.externals import joblib\n", - "from azureml.core.model import Model\n", - "\n", - "\n", - "def init():\n", - " global model\n", - " model_path = Model.get_model_path(model_name = '<>') # this name is model.id of model that we want to deploy\n", - " # deserialize the model file back into a sklearn model\n", - " model = joblib.load(model_path)\n", - "\n", - "def run(rawdata):\n", - " try:\n", - " data = json.loads(rawdata)['data']\n", - " data = numpy.array(data)\n", - " result = model.predict(data)\n", - " except Exception as e:\n", - " result = str(e)\n", - " return json.dumps({\"error\": result})\n", - " return json.dumps({\"result\":result.tolist()})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create a YAML File for the Environment" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To ensure the fit results are consistent with the training results, the SDK dependency versions need to be the same as the environment that trains the model. Details about retrieving the versions can be found in notebook [12.auto-ml-retrieve-the-training-sdk-versions](12.auto-ml-retrieve-the-training-sdk-versions.ipynb)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dependencies = remote_run.get_run_sdk_dependencies(iteration = 1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for p in ['azureml-train-automl', 'azureml-sdk', 'azureml-core']:\n", - " print('{}\\t{}'.format(p, dependencies[p]))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.conda_dependencies import CondaDependencies\n", - "\n", - "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn','py-xgboost<=0.80'],\n", - " pip_packages=['azureml-sdk[automl]'])\n", - "\n", - "conda_env_file_name = 'myenv.yml'\n", - "myenv.save_to_file('.', conda_env_file_name)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Substitute the actual version number in the environment file.\n", - "# This is not strictly needed in this notebook because the model should have been generated using the current SDK version.\n", - "# However, we include this in case this code is used on an experiment from a previous SDK version.\n", - "\n", - "with open(conda_env_file_name, 'r') as cefr:\n", - " content = cefr.read()\n", - "\n", - "with open(conda_env_file_name, 'w') as cefw:\n", - " cefw.write(content.replace(azureml.core.VERSION, dependencies['azureml-sdk']))\n", - "\n", - "# Substitute the actual model id in the script file.\n", - "\n", - "script_file_name = 'score.py'\n", - "\n", - "with open(script_file_name, 'r') as cefr:\n", - " content = cefr.read()\n", - "\n", - "with open(script_file_name, 'w') as cefw:\n", - " cefw.write(content.replace('<>', remote_run.model_id))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create a Container Image\n", - "\n", - "Next use Azure Container Instances for deploying models as a web service for quickly deploying and validating your model\n", - "or when testing a model that is under development." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.image import Image, ContainerImage\n", - "\n", - "image_config = ContainerImage.image_configuration(runtime= \"python\",\n", - " execution_script = script_file_name,\n", - " conda_file = conda_env_file_name,\n", - " tags = {'area': \"bmData\", 'type': \"automl_classification\"},\n", - " description = \"Image for automl classification sample\")\n", - "\n", - "image = Image.create(name = \"automlsampleimage\",\n", - " # this is the model object \n", - " models = [model],\n", - " image_config = image_config, \n", - " workspace = ws)\n", - "\n", - "image.wait_for_creation(show_output = True)\n", - "\n", - "if image.creation_state == 'Failed':\n", - " print(\"Image build log at: \" + image.image_build_log_uri)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Deploy the Image as a Web Service on Azure Container Instance\n", - "\n", - "Deploy an image that contains the model and other assets needed by the service." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.webservice import AciWebservice\n", - "\n", - "aciconfig = AciWebservice.deploy_configuration(cpu_cores = 1, \n", - " memory_gb = 1, \n", - " tags = {'area': \"bmData\", 'type': \"automl_classification\"}, \n", - " description = 'sample service for Automl Classification')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.webservice import Webservice\n", - "\n", - "aci_service_name = 'automl-sample-bankmarketing'\n", - "print(aci_service_name)\n", - "aci_service = Webservice.deploy_from_image(deployment_config = aciconfig,\n", - " image = image,\n", - " name = aci_service_name,\n", - " workspace = ws)\n", - "aci_service.wait_for_deployment(True)\n", - "print(aci_service.state)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Delete a Web Service\n", - "\n", - "Deletes the specified web service." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#aci_service.delete()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Get Logs from a Deployed Web Service\n", - "\n", - "Gets logs from a deployed web service." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#aci_service.get_logs()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Test\n", - "\n", - "Now that the model is trained split our data in the same way the data was split for training (The difference here is the data is being split locally) and then run the test data through the trained model to get the predicted values." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Load the bank marketing datasets.\n", - "from sklearn.datasets import load_diabetes\n", - "from sklearn.model_selection import train_test_split\n", - "from numpy import array" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/bankmarketing_validate.csv\"\n", - "dflow = dprep.auto_read_file(data)\n", - "dflow.get_profile()\n", - "X_test = dflow.drop_columns(columns=['y'])\n", - "y_test = dflow.keep_columns(columns=['y'], validate_column_exists=True)\n", - "dflow.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "X_test = X_test.to_pandas_dataframe()\n", - "y_test = y_test.to_pandas_dataframe()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "y_pred = fitted_model.predict(X_test)\n", - "actual = array(y_test)\n", - "actual = actual[:,0]\n", - "print(y_pred.shape, \" \", actual.shape)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Calculate metrics for the prediction\n", - "\n", - "Now visualize the data on a scatter plot to show what our truth (actual) values are compared to the predicted values \n", - "from the trained model that was returned." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib notebook\n", - "test_pred = plt.scatter(actual, y_pred, color='b')\n", - "test_test = plt.scatter(actual, actual, color='g')\n", - "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Acknowledgements" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This Bank Marketing dataset is made available under the Creative Commons (CCO: Public Domain) License: https://creativecommons.org/publicdomain/zero/1.0/. Any rights in individual contents of the database are licensed under the Database Contents License: https://creativecommons.org/publicdomain/zero/1.0/ and is available at: https://www.kaggle.com/janiobachmann/bank-marketing-dataset .\n", - "\n", - "_**Acknowledgements**_\n", - "This data set is originally available within the UCI Machine Learning Database: https://archive.ics.uci.edu/ml/datasets/bank+marketing\n", - "\n", - "[Moro et al., 2014] S. Moro, P. Cortez and P. Rita. A Data-Driven Approach to Predict the Success of Bank Telemarketing. Decision Support Systems, Elsevier, 62:22-31, June 2014" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "v-rasav" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated Machine Learning\n", + "_**Classification with Deployment using a Bank Marketing Dataset**_\n", + "\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "1. [Setup](#Setup)\n", + "1. [Train](#Train)\n", + "1. [Results](#Results)\n", + "1. [Deploy](#Deploy)\n", + "1. [Test](#Test)\n", + "1. [Acknowledgements](#Acknowledgements)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "\n", + "In this example we use the UCI Bank Marketing dataset to showcase how you can use AutoML for a classification problem and deploy it to an Azure Container Instance (ACI). The classification goal is to predict if the client will subscribe to a term deposit with the bank.\n", + "\n", + "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", + "\n", + "In this notebook you will learn how to:\n", + "1. Create an experiment using an existing workspace.\n", + "2. Configure AutoML using `AutoMLConfig`.\n", + "3. Train the model using local compute.\n", + "4. Explore the results.\n", + "5. Register the model.\n", + "6. Create a container image.\n", + "7. Create an Azure Container Instance (ACI) service.\n", + "8. Test the ACI service." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "As part of the setup you have already created an Azure ML `Workspace` object. For AutoML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import logging\n", + "\n", + "from matplotlib import pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "import os\n", + "from sklearn import datasets\n", + "import azureml.dataprep as dprep\n", + "from sklearn.model_selection import train_test_split\n", + "\n", + "import azureml.core\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.train.automl import AutoMLConfig\n", + "from azureml.train.automl.run import AutoMLRun" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "\n", + "# choose a name for experiment\n", + "experiment_name = 'automl-classification-bmarketing'\n", + "# project folder\n", + "project_folder = './sample_projects/automl-classification-bankmarketing'\n", + "\n", + "experiment=Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output['SDK version'] = azureml.core.VERSION\n", + "output['Subscription ID'] = ws.subscription_id\n", + "output['Workspace'] = ws.name\n", + "output['Resource Group'] = ws.resource_group\n", + "output['Location'] = ws.location\n", + "output['Project Directory'] = project_folder\n", + "output['Experiment Name'] = experiment.name\n", + "pd.set_option('display.max_colwidth', -1)\n", + "outputDf = pd.DataFrame(data = output, index = [''])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create or Attach existing AmlCompute\n", + "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "#### Creation of AmlCompute takes approximately 5 minutes. \n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", + "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this article on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import AmlCompute\n", + "from azureml.core.compute import ComputeTarget\n", + "\n", + "# Choose a name for your cluster.\n", + "amlcompute_cluster_name = \"automlcl\"\n", + "\n", + "found = False\n", + "# Check if this compute target already exists in the workspace.\n", + "cts = ws.compute_targets\n", + "if amlcompute_cluster_name in cts and cts[amlcompute_cluster_name].type == 'AmlCompute':\n", + " found = True\n", + " print('Found existing compute target.')\n", + " compute_target = cts[amlcompute_cluster_name]\n", + " \n", + "if not found:\n", + " print('Creating a new compute target...')\n", + " provisioning_config = AmlCompute.provisioning_configuration(vm_size = \"STANDARD_D2_V2\", # for GPU, use \"STANDARD_NC6\"\n", + " #vm_priority = 'lowpriority', # optional\n", + " max_nodes = 6)\n", + "\n", + " # Create the cluster.\n", + " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, provisioning_config)\n", + " \n", + " # Can poll for a minimum number of nodes and for a specific timeout.\n", + " # If no min_node_count is provided, it will use the scale settings for the cluster.\n", + " compute_target.wait_for_completion(show_output = True, min_node_count = None, timeout_in_minutes = 20)\n", + " \n", + " # For a more detailed view of current AmlCompute status, use get_status()." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data\n", + "\n", + "Here load the data in the get_data() script to be utilized in azure compute. To do this first load all the necessary libraries and dependencies to set up paths for the data and to create the conda_Run_config." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if not os.path.isdir('data'):\n", + " os.mkdir('data')\n", + " \n", + "if not os.path.exists(project_folder):\n", + " os.makedirs(project_folder)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.runconfig import RunConfiguration\n", + "from azureml.core.conda_dependencies import CondaDependencies\n", + "\n", + "# create a new RunConfig object\n", + "conda_run_config = RunConfiguration(framework=\"python\")\n", + "\n", + "# Set compute target to AmlCompute\n", + "conda_run_config.target = compute_target\n", + "conda_run_config.environment.docker.enabled = True\n", + "conda_run_config.environment.docker.base_image = azureml.core.runconfig.DEFAULT_CPU_IMAGE\n", + "\n", + "\n", + "cd = CondaDependencies.create(pip_packages=['azureml-sdk[automl]'], conda_packages=['numpy','py-xgboost<=0.80'])\n", + "conda_run_config.environment.python.conda_dependencies = cd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load Data\n", + "\n", + "Here we create the script to be run in azure comput for loading the data, we load the bank marketing dataset into X_train and y_train. Next X_train and y_train is returned for training the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/bankmarketing_train.csv\"\n", + "dflow = dprep.auto_read_file(data)\n", + "dflow.get_profile()\n", + "X_train = dflow.drop_columns(columns=['y'])\n", + "y_train = dflow.keep_columns(columns=['y'], validate_column_exists=True)\n", + "dflow.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train\n", + "\n", + "Instantiate a AutoMLConfig object. This defines the settings and data used to run the experiment.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**task**|classification or regression|\n", + "|**primary_metric**|This is the metric that you want to optimize. Classification supports the following primary metrics:
accuracy
AUC_weighted
average_precision_score_weighted
norm_macro_recall
precision_score_weighted|\n", + "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", + "|**iterations**|Number of iterations. In each iteration AutoML trains a specific pipeline with the data.|\n", + "|**n_cross_validations**|Number of cross validation splits.|\n", + "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", + "|**y**|(sparse) array-like, shape = [n_samples, ], Multi-class targets.|\n", + "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder.|\n", + "\n", + "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "automl_settings = {\n", + " \"iteration_timeout_minutes\": 5,\n", + " \"iterations\": 10,\n", + " \"n_cross_validations\": 2,\n", + " \"primary_metric\": 'AUC_weighted',\n", + " \"preprocess\": True,\n", + " \"max_concurrent_iterations\": 5,\n", + " \"verbosity\": logging.INFO,\n", + "}\n", + "\n", + "automl_config = AutoMLConfig(task = 'classification',\n", + " debug_log = 'automl_errors.log',\n", + " path = project_folder,\n", + " run_configuration=conda_run_config,\n", + " X = X_train,\n", + " y = y_train,\n", + " **automl_settings\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Call the `submit` method on the experiment object and pass the run configuration. Execution of local runs is synchronous. Depending on the data and the number of iterations this can run for a while.\n", + "In this example, we specify `show_output = True` to print currently running iterations to the console." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run = experiment.submit(automl_config, show_output = True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Widget for Monitoring Runs\n", + "\n", + "The widget will first report a \"loading\" status while running the first iteration. After completing the first iteration, an auto-updating graph and table will be shown. The widget will refresh once per minute, so you should see the graph update as child runs complete.\n", + "\n", + "**Note:** The widget displays a link at the bottom. Use this link to open a web interface to explore the individual run details" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.widgets import RunDetails\n", + "RunDetails(remote_run).show() " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Deploy\n", + "\n", + "### Retrieve the Best Model\n", + "\n", + "Below we select the best pipeline from our iterations. The `get_output` method on `automl_classifier` returns the best run and the fitted model for the last invocation. Overloads on `get_output` allow you to retrieve the best run and fitted model for *any* logged metric or for a particular *iteration*." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "best_run, fitted_model = remote_run.get_output()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Register the Fitted Model for Deployment\n", + "If neither `metric` nor `iteration` are specified in the `register_model` call, the iteration with the best primary metric is registered." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "description = 'AutoML Model trained on bank marketing data to predict if a client will subscribe to a term deposit'\n", + "tags = None\n", + "model = remote_run.register_model(description = description, tags = tags)\n", + "\n", + "print(remote_run.model_id) # This will be written to the script file later in the notebook." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create Scoring Script\n", + "The scoring script is required to generate the image for deployment. It contains the code to do the predictions on input data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile score.py\n", + "import pickle\n", + "import json\n", + "import numpy\n", + "import azureml.train.automl\n", + "from sklearn.externals import joblib\n", + "from azureml.core.model import Model\n", + "\n", + "\n", + "def init():\n", + " global model\n", + " model_path = Model.get_model_path(model_name = '<>') # this name is model.id of model that we want to deploy\n", + " # deserialize the model file back into a sklearn model\n", + " model = joblib.load(model_path)\n", + "\n", + "def run(rawdata):\n", + " try:\n", + " data = json.loads(rawdata)['data']\n", + " data = numpy.array(data)\n", + " result = model.predict(data)\n", + " except Exception as e:\n", + " result = str(e)\n", + " return json.dumps({\"error\": result})\n", + " return json.dumps({\"result\":result.tolist()})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a YAML File for the Environment" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To ensure the fit results are consistent with the training results, the SDK dependency versions need to be the same as the environment that trains the model. Details about retrieving the versions can be found in notebook [12.auto-ml-retrieve-the-training-sdk-versions](12.auto-ml-retrieve-the-training-sdk-versions.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dependencies = remote_run.get_run_sdk_dependencies(iteration = 1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for p in ['azureml-train-automl', 'azureml-sdk', 'azureml-core']:\n", + " print('{}\\t{}'.format(p, dependencies[p]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.conda_dependencies import CondaDependencies\n", + "\n", + "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn','py-xgboost<=0.80'],\n", + " pip_packages=['azureml-sdk[automl]'])\n", + "\n", + "conda_env_file_name = 'myenv.yml'\n", + "myenv.save_to_file('.', conda_env_file_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Substitute the actual version number in the environment file.\n", + "# This is not strictly needed in this notebook because the model should have been generated using the current SDK version.\n", + "# However, we include this in case this code is used on an experiment from a previous SDK version.\n", + "\n", + "with open(conda_env_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(conda_env_file_name, 'w') as cefw:\n", + " cefw.write(content.replace(azureml.core.VERSION, dependencies['azureml-sdk']))\n", + "\n", + "# Substitute the actual model id in the script file.\n", + "\n", + "script_file_name = 'score.py'\n", + "\n", + "with open(script_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(script_file_name, 'w') as cefw:\n", + " cefw.write(content.replace('<>', remote_run.model_id))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a Container Image\n", + "\n", + "Next use Azure Container Instances for deploying models as a web service for quickly deploying and validating your model\n", + "or when testing a model that is under development." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.image import Image, ContainerImage\n", + "\n", + "image_config = ContainerImage.image_configuration(runtime= \"python\",\n", + " execution_script = script_file_name,\n", + " conda_file = conda_env_file_name,\n", + " tags = {'area': \"bmData\", 'type': \"automl_classification\"},\n", + " description = \"Image for automl classification sample\")\n", + "\n", + "image = Image.create(name = \"automlsampleimage\",\n", + " # this is the model object \n", + " models = [model],\n", + " image_config = image_config, \n", + " workspace = ws)\n", + "\n", + "image.wait_for_creation(show_output = True)\n", + "\n", + "if image.creation_state == 'Failed':\n", + " print(\"Image build log at: \" + image.image_build_log_uri)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deploy the Image as a Web Service on Azure Container Instance\n", + "\n", + "Deploy an image that contains the model and other assets needed by the service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import AciWebservice\n", + "\n", + "aciconfig = AciWebservice.deploy_configuration(cpu_cores = 1, \n", + " memory_gb = 1, \n", + " tags = {'area': \"bmData\", 'type': \"automl_classification\"}, \n", + " description = 'sample service for Automl Classification')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import Webservice\n", + "\n", + "aci_service_name = 'automl-sample-bankmarketing'\n", + "print(aci_service_name)\n", + "aci_service = Webservice.deploy_from_image(deployment_config = aciconfig,\n", + " image = image,\n", + " name = aci_service_name,\n", + " workspace = ws)\n", + "aci_service.wait_for_deployment(True)\n", + "print(aci_service.state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delete a Web Service\n", + "\n", + "Deletes the specified web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.delete()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Get Logs from a Deployed Web Service\n", + "\n", + "Gets logs from a deployed web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.get_logs()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test\n", + "\n", + "Now that the model is trained split our data in the same way the data was split for training (The difference here is the data is being split locally) and then run the test data through the trained model to get the predicted values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load the bank marketing datasets.\n", + "from sklearn.datasets import load_diabetes\n", + "from sklearn.model_selection import train_test_split\n", + "from numpy import array" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/bankmarketing_validate.csv\"\n", + "dflow = dprep.auto_read_file(data)\n", + "dflow.get_profile()\n", + "X_test = dflow.drop_columns(columns=['y'])\n", + "y_test = dflow.keep_columns(columns=['y'], validate_column_exists=True)\n", + "dflow.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_test = X_test.to_pandas_dataframe()\n", + "y_test = y_test.to_pandas_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "y_pred = fitted_model.predict(X_test)\n", + "actual = array(y_test)\n", + "actual = actual[:,0]\n", + "print(y_pred.shape, \" \", actual.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Calculate metrics for the prediction\n", + "\n", + "Now visualize the data on a scatter plot to show what our truth (actual) values are compared to the predicted values \n", + "from the trained model that was returned." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "test_pred = plt.scatter(actual, y_pred, color='b')\n", + "test_test = plt.scatter(actual, actual, color='g')\n", + "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Acknowledgements" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This Bank Marketing dataset is made available under the Creative Commons (CCO: Public Domain) License: https://creativecommons.org/publicdomain/zero/1.0/. Any rights in individual contents of the database are licensed under the Database Contents License: https://creativecommons.org/publicdomain/zero/1.0/ and is available at: https://www.kaggle.com/janiobachmann/bank-marketing-dataset .\n", + "\n", + "_**Acknowledgements**_\n", + "This data set is originally available within the UCI Machine Learning Database: https://archive.ics.uci.edu/ml/datasets/bank+marketing\n", + "\n", + "[Moro et al., 2014] S. Moro, P. Cortez and P. Rita. A Data-Driven Approach to Predict the Success of Bank Telemarketing. Decision Support Systems, Elsevier, 62:22-31, June 2014" + ] + } ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" + "metadata": { + "authors": [ + { + "name": "v-rasav" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.yml b/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.yml new file mode 100644 index 00000000..a46c905b --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/classification-bank-marketing/auto-ml-classification-bank-marketing.yml @@ -0,0 +1,8 @@ +name: auto-ml-classification-bank-marketing +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.ipynb b/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.ipynb index 0ac64b88..9bd1342a 100644 --- a/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.ipynb +++ b/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.ipynb @@ -1,712 +1,712 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Automated Machine Learning\n", - "_**Classification with Deployment using Credit Card Dataset**_\n", - "\n", - "## Contents\n", - "1. [Introduction](#Introduction)\n", - "1. [Setup](#Setup)\n", - "1. [Train](#Train)\n", - "1. [Results](#Results)\n", - "1. [Deploy](#Deploy)\n", - "1. [Test](#Test)\n", - "1. [Acknowledgements](#Acknowledgements)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Introduction\n", - "\n", - "In this example we use the associated credit card dataset to showcase how you can use AutoML for a simple classification problem and deploy it to an Azure Container Instance (ACI). The classification goal is to predict if a creditcard transaction is or is not considered a fraudulent charge.\n", - "\n", - "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", - "\n", - "In this notebook you will learn how to:\n", - "1. Create an experiment using an existing workspace.\n", - "2. Configure AutoML using `AutoMLConfig`.\n", - "3. Train the model using local compute.\n", - "4. Explore the results.\n", - "5. Register the model.\n", - "6. Create a container image.\n", - "7. Create an Azure Container Instance (ACI) service.\n", - "8. Test the ACI service." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup\n", - "\n", - "As part of the setup you have already created an Azure ML `Workspace` object. For AutoML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import logging\n", - "\n", - "from matplotlib import pyplot as plt\n", - "import pandas as pd\n", - "import os\n", - "from sklearn.model_selection import train_test_split\n", - "import azureml.dataprep as dprep\n", - "\n", - "import azureml.core\n", - "from azureml.core.experiment import Experiment\n", - "from azureml.core.workspace import Workspace\n", - "from azureml.train.automl import AutoMLConfig\n", - "from azureml.train.automl.run import AutoMLRun" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ws = Workspace.from_config()\n", - "\n", - "# choose a name for experiment\n", - "experiment_name = 'automl-classification-ccard'\n", - "# project folder\n", - "project_folder = './sample_projects/automl-classification-creditcard'\n", - "\n", - "experiment=Experiment(ws, experiment_name)\n", - "\n", - "output = {}\n", - "output['SDK version'] = azureml.core.VERSION\n", - "output['Subscription ID'] = ws.subscription_id\n", - "output['Workspace'] = ws.name\n", - "output['Resource Group'] = ws.resource_group\n", - "output['Location'] = ws.location\n", - "output['Project Directory'] = project_folder\n", - "output['Experiment Name'] = experiment.name\n", - "pd.set_option('display.max_colwidth', -1)\n", - "outputDf = pd.DataFrame(data = output, index = [''])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create or Attach existing AmlCompute\n", - "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", - "#### Creation of AmlCompute takes approximately 5 minutes. \n", - "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", - "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this article on the default limits and how to request more quota." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.compute import AmlCompute\n", - "from azureml.core.compute import ComputeTarget\n", - "\n", - "# Choose a name for your cluster.\n", - "amlcompute_cluster_name = \"automlcl\"\n", - "\n", - "found = False\n", - "# Check if this compute target already exists in the workspace.\n", - "cts = ws.compute_targets\n", - "if amlcompute_cluster_name in cts and cts[amlcompute_cluster_name].type == 'AmlCompute':\n", - " found = True\n", - " print('Found existing compute target.')\n", - " compute_target = cts[amlcompute_cluster_name]\n", - " \n", - "if not found:\n", - " print('Creating a new compute target...')\n", - " provisioning_config = AmlCompute.provisioning_configuration(vm_size = \"STANDARD_D2_V2\", # for GPU, use \"STANDARD_NC6\"\n", - " #vm_priority = 'lowpriority', # optional\n", - " max_nodes = 6)\n", - "\n", - " # Create the cluster.\n", - " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, provisioning_config)\n", - " \n", - " # Can poll for a minimum number of nodes and for a specific timeout.\n", - " # If no min_node_count is provided, it will use the scale settings for the cluster.\n", - " compute_target.wait_for_completion(show_output = True, min_node_count = None, timeout_in_minutes = 20)\n", - " \n", - " # For a more detailed view of current AmlCompute status, use get_status()." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Data\n", - "\n", - "Here load the data in the get_data script to be utilized in azure compute. To do this, first load all the necessary libraries and dependencies to set up paths for the data and to create the conda_run_config." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "if not os.path.isdir('data'):\n", - " os.mkdir('data')\n", - " \n", - "if not os.path.exists(project_folder):\n", - " os.makedirs(project_folder)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.runconfig import RunConfiguration\n", - "from azureml.core.conda_dependencies import CondaDependencies\n", - "\n", - "# create a new RunConfig object\n", - "conda_run_config = RunConfiguration(framework=\"python\")\n", - "\n", - "# Set compute target to AmlCompute\n", - "conda_run_config.target = compute_target\n", - "conda_run_config.environment.docker.enabled = True\n", - "conda_run_config.environment.docker.base_image = azureml.core.runconfig.DEFAULT_CPU_IMAGE\n", - "\n", - "\n", - "cd = CondaDependencies.create(pip_packages=['azureml-sdk[automl]'], conda_packages=['numpy','py-xgboost<=0.80'])\n", - "conda_run_config.environment.python.conda_dependencies = cd" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load Data\n", - "\n", - "Here create the script to be run in azure compute for loading the data, load the credit card dataset into cards and store the Class column (y) in the y variable and store the remaining data in the x variable. Next split the data using train_test_split and return X_train and y_train for training the model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/creditcard.csv\"\n", - "dflow = dprep.auto_read_file(data)\n", - "dflow.get_profile()\n", - "X = dflow.drop_columns(columns=['Class'])\n", - "y = dflow.keep_columns(columns=['Class'], validate_column_exists=True)\n", - "X_train, X_test = X.random_split(percentage=0.8, seed=223)\n", - "y_train, y_test = y.random_split(percentage=0.8, seed=223)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Train\n", - "\n", - "Instantiate a AutoMLConfig object. This defines the settings and data used to run the experiment.\n", - "\n", - "|Property|Description|\n", - "|-|-|\n", - "|**task**|classification or regression|\n", - "|**primary_metric**|This is the metric that you want to optimize. Classification supports the following primary metrics:
accuracy
AUC_weighted
average_precision_score_weighted
norm_macro_recall
precision_score_weighted|\n", - "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", - "|**iterations**|Number of iterations. In each iteration AutoML trains a specific pipeline with the data.|\n", - "|**n_cross_validations**|Number of cross validation splits.|\n", - "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", - "|**y**|(sparse) array-like, shape = [n_samples, ], Multi-class targets.|\n", - "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder.|\n", - "\n", - "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### If you would like to see even better results increase \"iteration_time_out minutes\" to 10+ mins and increase \"iterations\" to a minimum of 30" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "automl_settings = {\n", - " \"iteration_timeout_minutes\": 5,\n", - " \"iterations\": 10,\n", - " \"n_cross_validations\": 2,\n", - " \"primary_metric\": 'average_precision_score_weighted',\n", - " \"preprocess\": True,\n", - " \"max_concurrent_iterations\": 5,\n", - " \"verbosity\": logging.INFO,\n", - "}\n", - "\n", - "automl_config = AutoMLConfig(task = 'classification',\n", - " debug_log = 'automl_errors_20190417.log',\n", - " path = project_folder,\n", - " run_configuration=conda_run_config,\n", - " X = X_train,\n", - " y = y_train,\n", - " **automl_settings\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Call the `submit` method on the experiment object and pass the run configuration. Execution of local runs is synchronous. Depending on the data and the number of iterations this can run for a while.\n", - "In this example, we specify `show_output = True` to print currently running iterations to the console." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run = experiment.submit(automl_config, show_output = True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Results" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Widget for Monitoring Runs\n", - "\n", - "The widget will first report a \"loading\" status while running the first iteration. After completing the first iteration, an auto-updating graph and table will be shown. The widget will refresh once per minute, so you should see the graph update as child runs complete.\n", - "\n", - "**Note:** The widget displays a link at the bottom. Use this link to open a web interface to explore the individual run details" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.widgets import RunDetails\n", - "RunDetails(remote_run).show() " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Deploy\n", - "\n", - "### Retrieve the Best Model\n", - "\n", - "Below we select the best pipeline from our iterations. The `get_output` method on `automl_classifier` returns the best run and the fitted model for the last invocation. Overloads on `get_output` allow you to retrieve the best run and fitted model for *any* logged metric or for a particular *iteration*." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "best_run, fitted_model = remote_run.get_output()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Register the Fitted Model for Deployment\n", - "If neither `metric` nor `iteration` are specified in the `register_model` call, the iteration with the best primary metric is registered." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "description = 'AutoML Model'\n", - "tags = None\n", - "model = remote_run.register_model(description = description, tags = tags)\n", - "\n", - "print(remote_run.model_id) # This will be written to the script file later in the notebook." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create Scoring Script\n", - "The scoring script is required to generate the image for deployment. It contains the code to do the predictions on input data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%writefile score.py\n", - "import pickle\n", - "import json\n", - "import numpy\n", - "import azureml.train.automl\n", - "from sklearn.externals import joblib\n", - "from azureml.core.model import Model\n", - "\n", - "def init():\n", - " global model\n", - " model_path = Model.get_model_path(model_name = '<>') # this name is model.id of model that we want to deploy\n", - " # deserialize the model file back into a sklearn model\n", - " model = joblib.load(model_path)\n", - "\n", - "def run(rawdata):\n", - " try:\n", - " data = json.loads(rawdata)['data']\n", - " data = numpy.array(data)\n", - " result = model.predict(data)\n", - " except Exception as e:\n", - " result = str(e)\n", - " return json.dumps({\"error\": result})\n", - " return json.dumps({\"result\":result.tolist()})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create a YAML File for the Environment" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To ensure the fit results are consistent with the training results, the SDK dependency versions need to be the same as the environment that trains the model. Details about retrieving the versions can be found in notebook [12.auto-ml-retrieve-the-training-sdk-versions](12.auto-ml-retrieve-the-training-sdk-versions.ipynb)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dependencies = remote_run.get_run_sdk_dependencies(iteration = 1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for p in ['azureml-train-automl', 'azureml-sdk', 'azureml-core']:\n", - " print('{}\\t{}'.format(p, dependencies[p]))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn','py-xgboost<=0.80'],\n", - " pip_packages=['azureml-sdk[automl]'])\n", - "\n", - "conda_env_file_name = 'myenv.yml'\n", - "myenv.save_to_file('.', conda_env_file_name)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Substitute the actual version number in the environment file.\n", - "# This is not strictly needed in this notebook because the model should have been generated using the current SDK version.\n", - "# However, we include this in case this code is used on an experiment from a previous SDK version.\n", - "\n", - "with open(conda_env_file_name, 'r') as cefr:\n", - " content = cefr.read()\n", - "\n", - "with open(conda_env_file_name, 'w') as cefw:\n", - " cefw.write(content.replace(azureml.core.VERSION, dependencies['azureml-sdk']))\n", - "\n", - "# Substitute the actual model id in the script file.\n", - "\n", - "script_file_name = 'score.py'\n", - "\n", - "with open(script_file_name, 'r') as cefr:\n", - " content = cefr.read()\n", - "\n", - "with open(script_file_name, 'w') as cefw:\n", - " cefw.write(content.replace('<>', remote_run.model_id))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create a Container Image\n", - "\n", - "Next use Azure Container Instances for deploying models as a web service for quickly deploying and validating your model\n", - "or when testing a model that is under development." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.image import Image, ContainerImage\n", - "\n", - "image_config = ContainerImage.image_configuration(runtime= \"python\",\n", - " execution_script = script_file_name,\n", - " conda_file = conda_env_file_name,\n", - " tags = {'area': \"cards\", 'type': \"automl_classification\"},\n", - " description = \"Image for automl classification sample\")\n", - "\n", - "image = Image.create(name = \"automlsampleimage\",\n", - " # this is the model object \n", - " models = [model],\n", - " image_config = image_config, \n", - " workspace = ws)\n", - "\n", - "image.wait_for_creation(show_output = True)\n", - "\n", - "if image.creation_state == 'Failed':\n", - " print(\"Image build log at: \" + image.image_build_log_uri)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Deploy the Image as a Web Service on Azure Container Instance\n", - "\n", - "Deploy an image that contains the model and other assets needed by the service." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.webservice import AciWebservice\n", - "\n", - "aciconfig = AciWebservice.deploy_configuration(cpu_cores = 1, \n", - " memory_gb = 1, \n", - " tags = {'area': \"cards\", 'type': \"automl_classification\"}, \n", - " description = 'sample service for Automl Classification')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.webservice import Webservice\n", - "\n", - "aci_service_name = 'automl-sample-creditcard'\n", - "print(aci_service_name)\n", - "aci_service = Webservice.deploy_from_image(deployment_config = aciconfig,\n", - " image = image,\n", - " name = aci_service_name,\n", - " workspace = ws)\n", - "aci_service.wait_for_deployment(True)\n", - "print(aci_service.state)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Delete a Web Service\n", - "\n", - "Deletes the specified web service." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#aci_service.delete()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Get Logs from a Deployed Web Service\n", - "\n", - "Gets logs from a deployed web service." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#aci_service.get_logs()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Test\n", - "\n", - "Now that the model is trained, split the data in the same way the data was split for training (The difference here is the data is being split locally) and then run the test data through the trained model to get the predicted values." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#Randomly select and test\n", - "X_test = X_test.to_pandas_dataframe()\n", - "y_test = y_test.to_pandas_dataframe()\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "y_pred = fitted_model.predict(X_test)\n", - "y_pred" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Calculate metrics for the prediction\n", - "\n", - "Now visualize the data on a scatter plot to show what our truth (actual) values are compared to the predicted values \n", - "from the trained model that was returned." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#Randomly select and test\n", - "# Plot outputs\n", - "%matplotlib notebook\n", - "test_pred = plt.scatter(y_test, y_pred, color='b')\n", - "test_test = plt.scatter(y_test, y_test, color='g')\n", - "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", - "plt.show()\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Acknowledgements" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This Credit Card fraud Detection dataset is made available under the Open Database License: http://opendatacommons.org/licenses/odbl/1.0/. Any rights in individual contents of the database are licensed under the Database Contents License: http://opendatacommons.org/licenses/dbcl/1.0/ and is available at: https://www.kaggle.com/mlg-ulb/creditcardfraud\n", - "\n", - "\n", - "The dataset has been collected and analysed during a research collaboration of Worldline and the Machine Learning Group (http://mlg.ulb.ac.be) of ULB (Université Libre de Bruxelles) on big data mining and fraud detection. More details on current and past projects on related topics are available on https://www.researchgate.net/project/Fraud-detection-5 and the page of the DefeatFraud project\n", - "Please cite the following works: \n", - "•\tAndrea Dal Pozzolo, Olivier Caelen, Reid A. Johnson and Gianluca Bontempi. Calibrating Probability with Undersampling for Unbalanced Classification. In Symposium on Computational Intelligence and Data Mining (CIDM), IEEE, 2015\n", - "•\tDal Pozzolo, Andrea; Caelen, Olivier; Le Borgne, Yann-Ael; Waterschoot, Serge; Bontempi, Gianluca. Learned lessons in credit card fraud detection from a practitioner perspective, Expert systems with applications,41,10,4915-4928,2014, Pergamon\n", - "•\tDal Pozzolo, Andrea; Boracchi, Giacomo; Caelen, Olivier; Alippi, Cesare; Bontempi, Gianluca. Credit card fraud detection: a realistic modeling and a novel learning strategy, IEEE transactions on neural networks and learning systems,29,8,3784-3797,2018,IEEE\n", - "o\tDal Pozzolo, Andrea Adaptive Machine learning for credit card fraud detection ULB MLG PhD thesis (supervised by G. Bontempi)\n", - "•\tCarcillo, Fabrizio; Dal Pozzolo, Andrea; Le Borgne, Yann-Aël; Caelen, Olivier; Mazzer, Yannis; Bontempi, Gianluca. Scarff: a scalable framework for streaming credit card fraud detection with Spark, Information fusion,41, 182-194,2018,Elsevier\n", - "•\tCarcillo, Fabrizio; Le Borgne, Yann-Aël; Caelen, Olivier; Bontempi, Gianluca. Streaming active learning strategies for real-life credit card fraud detection: assessment and visualization, International Journal of Data Science and Analytics, 5,4,285-300,2018,Springer International Publishing" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "v-rasav" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated Machine Learning\n", + "_**Classification with Deployment using Credit Card Dataset**_\n", + "\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "1. [Setup](#Setup)\n", + "1. [Train](#Train)\n", + "1. [Results](#Results)\n", + "1. [Deploy](#Deploy)\n", + "1. [Test](#Test)\n", + "1. [Acknowledgements](#Acknowledgements)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "\n", + "In this example we use the associated credit card dataset to showcase how you can use AutoML for a simple classification problem and deploy it to an Azure Container Instance (ACI). The classification goal is to predict if a creditcard transaction is or is not considered a fraudulent charge.\n", + "\n", + "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", + "\n", + "In this notebook you will learn how to:\n", + "1. Create an experiment using an existing workspace.\n", + "2. Configure AutoML using `AutoMLConfig`.\n", + "3. Train the model using local compute.\n", + "4. Explore the results.\n", + "5. Register the model.\n", + "6. Create a container image.\n", + "7. Create an Azure Container Instance (ACI) service.\n", + "8. Test the ACI service." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "As part of the setup you have already created an Azure ML `Workspace` object. For AutoML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "\n", + "from matplotlib import pyplot as plt\n", + "import pandas as pd\n", + "import os\n", + "from sklearn.model_selection import train_test_split\n", + "import azureml.dataprep as dprep\n", + "\n", + "import azureml.core\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.train.automl import AutoMLConfig\n", + "from azureml.train.automl.run import AutoMLRun" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "\n", + "# choose a name for experiment\n", + "experiment_name = 'automl-classification-ccard'\n", + "# project folder\n", + "project_folder = './sample_projects/automl-classification-creditcard'\n", + "\n", + "experiment=Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output['SDK version'] = azureml.core.VERSION\n", + "output['Subscription ID'] = ws.subscription_id\n", + "output['Workspace'] = ws.name\n", + "output['Resource Group'] = ws.resource_group\n", + "output['Location'] = ws.location\n", + "output['Project Directory'] = project_folder\n", + "output['Experiment Name'] = experiment.name\n", + "pd.set_option('display.max_colwidth', -1)\n", + "outputDf = pd.DataFrame(data = output, index = [''])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create or Attach existing AmlCompute\n", + "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "#### Creation of AmlCompute takes approximately 5 minutes. \n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", + "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this article on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import AmlCompute\n", + "from azureml.core.compute import ComputeTarget\n", + "\n", + "# Choose a name for your cluster.\n", + "amlcompute_cluster_name = \"automlcl\"\n", + "\n", + "found = False\n", + "# Check if this compute target already exists in the workspace.\n", + "cts = ws.compute_targets\n", + "if amlcompute_cluster_name in cts and cts[amlcompute_cluster_name].type == 'AmlCompute':\n", + " found = True\n", + " print('Found existing compute target.')\n", + " compute_target = cts[amlcompute_cluster_name]\n", + " \n", + "if not found:\n", + " print('Creating a new compute target...')\n", + " provisioning_config = AmlCompute.provisioning_configuration(vm_size = \"STANDARD_D2_V2\", # for GPU, use \"STANDARD_NC6\"\n", + " #vm_priority = 'lowpriority', # optional\n", + " max_nodes = 6)\n", + "\n", + " # Create the cluster.\n", + " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, provisioning_config)\n", + " \n", + " # Can poll for a minimum number of nodes and for a specific timeout.\n", + " # If no min_node_count is provided, it will use the scale settings for the cluster.\n", + " compute_target.wait_for_completion(show_output = True, min_node_count = None, timeout_in_minutes = 20)\n", + " \n", + " # For a more detailed view of current AmlCompute status, use get_status()." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data\n", + "\n", + "Here load the data in the get_data script to be utilized in azure compute. To do this, first load all the necessary libraries and dependencies to set up paths for the data and to create the conda_run_config." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if not os.path.isdir('data'):\n", + " os.mkdir('data')\n", + " \n", + "if not os.path.exists(project_folder):\n", + " os.makedirs(project_folder)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.runconfig import RunConfiguration\n", + "from azureml.core.conda_dependencies import CondaDependencies\n", + "\n", + "# create a new RunConfig object\n", + "conda_run_config = RunConfiguration(framework=\"python\")\n", + "\n", + "# Set compute target to AmlCompute\n", + "conda_run_config.target = compute_target\n", + "conda_run_config.environment.docker.enabled = True\n", + "conda_run_config.environment.docker.base_image = azureml.core.runconfig.DEFAULT_CPU_IMAGE\n", + "\n", + "\n", + "cd = CondaDependencies.create(pip_packages=['azureml-sdk[automl]'], conda_packages=['numpy','py-xgboost<=0.80'])\n", + "conda_run_config.environment.python.conda_dependencies = cd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load Data\n", + "\n", + "Here create the script to be run in azure compute for loading the data, load the credit card dataset into cards and store the Class column (y) in the y variable and store the remaining data in the x variable. Next split the data using train_test_split and return X_train and y_train for training the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/creditcard.csv\"\n", + "dflow = dprep.auto_read_file(data)\n", + "dflow.get_profile()\n", + "X = dflow.drop_columns(columns=['Class'])\n", + "y = dflow.keep_columns(columns=['Class'], validate_column_exists=True)\n", + "X_train, X_test = X.random_split(percentage=0.8, seed=223)\n", + "y_train, y_test = y.random_split(percentage=0.8, seed=223)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train\n", + "\n", + "Instantiate a AutoMLConfig object. This defines the settings and data used to run the experiment.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**task**|classification or regression|\n", + "|**primary_metric**|This is the metric that you want to optimize. Classification supports the following primary metrics:
accuracy
AUC_weighted
average_precision_score_weighted
norm_macro_recall
precision_score_weighted|\n", + "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", + "|**iterations**|Number of iterations. In each iteration AutoML trains a specific pipeline with the data.|\n", + "|**n_cross_validations**|Number of cross validation splits.|\n", + "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", + "|**y**|(sparse) array-like, shape = [n_samples, ], Multi-class targets.|\n", + "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder.|\n", + "\n", + "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### If you would like to see even better results increase \"iteration_time_out minutes\" to 10+ mins and increase \"iterations\" to a minimum of 30" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "automl_settings = {\n", + " \"iteration_timeout_minutes\": 5,\n", + " \"iterations\": 10,\n", + " \"n_cross_validations\": 2,\n", + " \"primary_metric\": 'average_precision_score_weighted',\n", + " \"preprocess\": True,\n", + " \"max_concurrent_iterations\": 5,\n", + " \"verbosity\": logging.INFO,\n", + "}\n", + "\n", + "automl_config = AutoMLConfig(task = 'classification',\n", + " debug_log = 'automl_errors_20190417.log',\n", + " path = project_folder,\n", + " run_configuration=conda_run_config,\n", + " X = X_train,\n", + " y = y_train,\n", + " **automl_settings\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Call the `submit` method on the experiment object and pass the run configuration. Execution of local runs is synchronous. Depending on the data and the number of iterations this can run for a while.\n", + "In this example, we specify `show_output = True` to print currently running iterations to the console." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run = experiment.submit(automl_config, show_output = True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Widget for Monitoring Runs\n", + "\n", + "The widget will first report a \"loading\" status while running the first iteration. After completing the first iteration, an auto-updating graph and table will be shown. The widget will refresh once per minute, so you should see the graph update as child runs complete.\n", + "\n", + "**Note:** The widget displays a link at the bottom. Use this link to open a web interface to explore the individual run details" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.widgets import RunDetails\n", + "RunDetails(remote_run).show() " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Deploy\n", + "\n", + "### Retrieve the Best Model\n", + "\n", + "Below we select the best pipeline from our iterations. The `get_output` method on `automl_classifier` returns the best run and the fitted model for the last invocation. Overloads on `get_output` allow you to retrieve the best run and fitted model for *any* logged metric or for a particular *iteration*." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "best_run, fitted_model = remote_run.get_output()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Register the Fitted Model for Deployment\n", + "If neither `metric` nor `iteration` are specified in the `register_model` call, the iteration with the best primary metric is registered." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "description = 'AutoML Model'\n", + "tags = None\n", + "model = remote_run.register_model(description = description, tags = tags)\n", + "\n", + "print(remote_run.model_id) # This will be written to the script file later in the notebook." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create Scoring Script\n", + "The scoring script is required to generate the image for deployment. It contains the code to do the predictions on input data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile score.py\n", + "import pickle\n", + "import json\n", + "import numpy\n", + "import azureml.train.automl\n", + "from sklearn.externals import joblib\n", + "from azureml.core.model import Model\n", + "\n", + "def init():\n", + " global model\n", + " model_path = Model.get_model_path(model_name = '<>') # this name is model.id of model that we want to deploy\n", + " # deserialize the model file back into a sklearn model\n", + " model = joblib.load(model_path)\n", + "\n", + "def run(rawdata):\n", + " try:\n", + " data = json.loads(rawdata)['data']\n", + " data = numpy.array(data)\n", + " result = model.predict(data)\n", + " except Exception as e:\n", + " result = str(e)\n", + " return json.dumps({\"error\": result})\n", + " return json.dumps({\"result\":result.tolist()})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a YAML File for the Environment" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To ensure the fit results are consistent with the training results, the SDK dependency versions need to be the same as the environment that trains the model. Details about retrieving the versions can be found in notebook [12.auto-ml-retrieve-the-training-sdk-versions](12.auto-ml-retrieve-the-training-sdk-versions.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dependencies = remote_run.get_run_sdk_dependencies(iteration = 1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for p in ['azureml-train-automl', 'azureml-sdk', 'azureml-core']:\n", + " print('{}\\t{}'.format(p, dependencies[p]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn','py-xgboost<=0.80'],\n", + " pip_packages=['azureml-sdk[automl]'])\n", + "\n", + "conda_env_file_name = 'myenv.yml'\n", + "myenv.save_to_file('.', conda_env_file_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Substitute the actual version number in the environment file.\n", + "# This is not strictly needed in this notebook because the model should have been generated using the current SDK version.\n", + "# However, we include this in case this code is used on an experiment from a previous SDK version.\n", + "\n", + "with open(conda_env_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(conda_env_file_name, 'w') as cefw:\n", + " cefw.write(content.replace(azureml.core.VERSION, dependencies['azureml-sdk']))\n", + "\n", + "# Substitute the actual model id in the script file.\n", + "\n", + "script_file_name = 'score.py'\n", + "\n", + "with open(script_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(script_file_name, 'w') as cefw:\n", + " cefw.write(content.replace('<>', remote_run.model_id))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a Container Image\n", + "\n", + "Next use Azure Container Instances for deploying models as a web service for quickly deploying and validating your model\n", + "or when testing a model that is under development." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.image import Image, ContainerImage\n", + "\n", + "image_config = ContainerImage.image_configuration(runtime= \"python\",\n", + " execution_script = script_file_name,\n", + " conda_file = conda_env_file_name,\n", + " tags = {'area': \"cards\", 'type': \"automl_classification\"},\n", + " description = \"Image for automl classification sample\")\n", + "\n", + "image = Image.create(name = \"automlsampleimage\",\n", + " # this is the model object \n", + " models = [model],\n", + " image_config = image_config, \n", + " workspace = ws)\n", + "\n", + "image.wait_for_creation(show_output = True)\n", + "\n", + "if image.creation_state == 'Failed':\n", + " print(\"Image build log at: \" + image.image_build_log_uri)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deploy the Image as a Web Service on Azure Container Instance\n", + "\n", + "Deploy an image that contains the model and other assets needed by the service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import AciWebservice\n", + "\n", + "aciconfig = AciWebservice.deploy_configuration(cpu_cores = 1, \n", + " memory_gb = 1, \n", + " tags = {'area': \"cards\", 'type': \"automl_classification\"}, \n", + " description = 'sample service for Automl Classification')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import Webservice\n", + "\n", + "aci_service_name = 'automl-sample-creditcard'\n", + "print(aci_service_name)\n", + "aci_service = Webservice.deploy_from_image(deployment_config = aciconfig,\n", + " image = image,\n", + " name = aci_service_name,\n", + " workspace = ws)\n", + "aci_service.wait_for_deployment(True)\n", + "print(aci_service.state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delete a Web Service\n", + "\n", + "Deletes the specified web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.delete()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Get Logs from a Deployed Web Service\n", + "\n", + "Gets logs from a deployed web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.get_logs()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test\n", + "\n", + "Now that the model is trained, split the data in the same way the data was split for training (The difference here is the data is being split locally) and then run the test data through the trained model to get the predicted values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#Randomly select and test\n", + "X_test = X_test.to_pandas_dataframe()\n", + "y_test = y_test.to_pandas_dataframe()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "y_pred = fitted_model.predict(X_test)\n", + "y_pred" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Calculate metrics for the prediction\n", + "\n", + "Now visualize the data on a scatter plot to show what our truth (actual) values are compared to the predicted values \n", + "from the trained model that was returned." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#Randomly select and test\n", + "# Plot outputs\n", + "%matplotlib notebook\n", + "test_pred = plt.scatter(y_test, y_pred, color='b')\n", + "test_test = plt.scatter(y_test, y_test, color='g')\n", + "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", + "plt.show()\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Acknowledgements" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This Credit Card fraud Detection dataset is made available under the Open Database License: http://opendatacommons.org/licenses/odbl/1.0/. Any rights in individual contents of the database are licensed under the Database Contents License: http://opendatacommons.org/licenses/dbcl/1.0/ and is available at: https://www.kaggle.com/mlg-ulb/creditcardfraud\n", + "\n", + "\n", + "The dataset has been collected and analysed during a research collaboration of Worldline and the Machine Learning Group (http://mlg.ulb.ac.be) of ULB (Universit\u00c3\u00a9 Libre de Bruxelles) on big data mining and fraud detection. More details on current and past projects on related topics are available on https://www.researchgate.net/project/Fraud-detection-5 and the page of the DefeatFraud project\n", + "Please cite the following works: \n", + "\u00e2\u20ac\u00a2\tAndrea Dal Pozzolo, Olivier Caelen, Reid A. Johnson and Gianluca Bontempi. Calibrating Probability with Undersampling for Unbalanced Classification. In Symposium on Computational Intelligence and Data Mining (CIDM), IEEE, 2015\n", + "\u00e2\u20ac\u00a2\tDal Pozzolo, Andrea; Caelen, Olivier; Le Borgne, Yann-Ael; Waterschoot, Serge; Bontempi, Gianluca. Learned lessons in credit card fraud detection from a practitioner perspective, Expert systems with applications,41,10,4915-4928,2014, Pergamon\n", + "\u00e2\u20ac\u00a2\tDal Pozzolo, Andrea; Boracchi, Giacomo; Caelen, Olivier; Alippi, Cesare; Bontempi, Gianluca. Credit card fraud detection: a realistic modeling and a novel learning strategy, IEEE transactions on neural networks and learning systems,29,8,3784-3797,2018,IEEE\n", + "o\tDal Pozzolo, Andrea Adaptive Machine learning for credit card fraud detection ULB MLG PhD thesis (supervised by G. Bontempi)\n", + "\u00e2\u20ac\u00a2\tCarcillo, Fabrizio; Dal Pozzolo, Andrea; Le Borgne, Yann-A\u00c3\u00abl; Caelen, Olivier; Mazzer, Yannis; Bontempi, Gianluca. Scarff: a scalable framework for streaming credit card fraud detection with Spark, Information fusion,41, 182-194,2018,Elsevier\n", + "\u00e2\u20ac\u00a2\tCarcillo, Fabrizio; Le Borgne, Yann-A\u00c3\u00abl; Caelen, Olivier; Bontempi, Gianluca. Streaming active learning strategies for real-life credit card fraud detection: assessment and visualization, International Journal of Data Science and Analytics, 5,4,285-300,2018,Springer International Publishing" + ] + } ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" + "metadata": { + "authors": [ + { + "name": "v-rasav" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.yml b/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.yml new file mode 100644 index 00000000..14c8fe46 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.yml @@ -0,0 +1,8 @@ +name: auto-ml-classification-credit-card-fraud +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/classification-with-deployment/auto-ml-classification-with-deployment.yml b/how-to-use-azureml/automated-machine-learning/classification-with-deployment/auto-ml-classification-with-deployment.yml new file mode 100644 index 00000000..e0d810ab --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/classification-with-deployment/auto-ml-classification-with-deployment.yml @@ -0,0 +1,8 @@ +name: auto-ml-classification-with-deployment +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/classification-with-onnx/auto-ml-classification-with-onnx.yml b/how-to-use-azureml/automated-machine-learning/classification-with-onnx/auto-ml-classification-with-onnx.yml new file mode 100644 index 00000000..04a552c8 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/classification-with-onnx/auto-ml-classification-with-onnx.yml @@ -0,0 +1,9 @@ +name: auto-ml-classification-with-onnx +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml + - onnxruntime diff --git a/how-to-use-azureml/automated-machine-learning/classification-with-whitelisting/auto-ml-classification-with-whitelisting.yml b/how-to-use-azureml/automated-machine-learning/classification-with-whitelisting/auto-ml-classification-with-whitelisting.yml new file mode 100644 index 00000000..32631c0a --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/classification-with-whitelisting/auto-ml-classification-with-whitelisting.yml @@ -0,0 +1,8 @@ +name: auto-ml-classification-with-whitelisting +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/classification/auto-ml-classification.yml b/how-to-use-azureml/automated-machine-learning/classification/auto-ml-classification.yml new file mode 100644 index 00000000..c099a8b9 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/classification/auto-ml-classification.yml @@ -0,0 +1,8 @@ +name: auto-ml-classification +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/dataprep-remote-execution/auto-ml-dataprep-remote-execution.yml b/how-to-use-azureml/automated-machine-learning/dataprep-remote-execution/auto-ml-dataprep-remote-execution.yml new file mode 100644 index 00000000..c9e18056 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/dataprep-remote-execution/auto-ml-dataprep-remote-execution.yml @@ -0,0 +1,8 @@ +name: auto-ml-dataprep-remote-execution +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.ipynb b/how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.ipynb index c8812808..b409f6e6 100644 --- a/how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.ipynb +++ b/how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.ipynb @@ -1,5 +1,12 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -9,13 +16,6 @@ "Licensed under the MIT License." ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.png)" - ] - }, { "cell_type": "markdown", "metadata": {}, diff --git a/how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.yml b/how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.yml new file mode 100644 index 00000000..4f7748d9 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.yml @@ -0,0 +1,8 @@ +name: auto-ml-dataprep +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/exploring-previous-runs/auto-ml-exploring-previous-runs.yml b/how-to-use-azureml/automated-machine-learning/exploring-previous-runs/auto-ml-exploring-previous-runs.yml new file mode 100644 index 00000000..e6e638f1 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/exploring-previous-runs/auto-ml-exploring-previous-runs.yml @@ -0,0 +1,8 @@ +name: auto-ml-exploring-previous-runs +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.ipynb b/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.ipynb index 7c980bc5..dabce312 100644 --- a/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.ipynb +++ b/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.ipynb @@ -36,19 +36,17 @@ "metadata": {}, "source": [ "## Introduction\n", - "In this example, we show how AutoML can be used for bike share forecasting.\n", + "This notebook demonstrates demand forecasting for a bike-sharing service using AutoML.\n", "\n", - "The purpose is to demonstrate how to take advantage of the built-in holiday featurization, access the feature names, and further demonstrate how to work with the `forecast` function. Please also look at the additional forecasting notebooks, which document lagging, rolling windows, forecast quantiles, other ways to use the forecast function, and forecaster deployment.\n", + "AutoML highlights here include built-in holiday featurization, accessing engineered feature names, and working with the `forecast` function. Please also look at the additional forecasting notebooks, which document lagging, rolling windows, forecast quantiles, other ways to use the forecast function, and forecaster deployment.\n", "\n", "Make sure you have executed the [configuration](../../../configuration.ipynb) before running this notebook.\n", "\n", - "In this notebook you would see\n", + "Notebook synopsis:\n", "1. Creating an Experiment in an existing Workspace\n", - "2. Instantiating AutoMLConfig with new task type \"forecasting\" for timeseries data training, and other timeseries related settings: for this dataset we use the basic one: \"time_column_name\" \n", - "3. Training the Model using local compute\n", - "4. Exploring the results\n", - "5. Viewing the engineered names for featurized data and featurization summary for all raw features\n", - "6. Testing the fitted model" + "2. Configuration and local run of AutoML for a time-series model with lag and holiday features \n", + "3. Viewing the engineered names for featurized data and featurization summary for all raw features\n", + "4. Evaluating the fitted model using a rolling test " ] }, { @@ -69,6 +67,9 @@ "import numpy as np\n", "import logging\n", "import warnings\n", + "\n", + "from pandas.tseries.frequencies import to_offset\n", + "\n", "# Squash warning messages for cleaner output in the notebook\n", "warnings.showwarning = lambda *args, **kwargs: None\n", "\n", @@ -83,7 +84,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As part of the setup you have already created a Workspace. For AutoML you would need to create an Experiment. An Experiment is a named object in a Workspace, which is used to run experiments." + "As part of the setup you have already created a Workspace. To run AutoML, you also need to create an Experiment. An Experiment corresponds to a prediction problem you are trying to solve, while a Run corresponds to a specific approach to the problem." ] }, { @@ -128,14 +129,15 @@ "metadata": {}, "outputs": [], "source": [ - "data = pd.read_csv('bike-no.csv', parse_dates=['date'])" + "data = pd.read_csv('bike-no.csv', parse_dates=['date'])\n", + "data.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Let's set up what we know abou the dataset. \n", + "Let's set up what we know about the dataset. \n", "\n", "**Target column** is what we want to forecast.\n", "\n", @@ -193,8 +195,7 @@ "source": [ "### Setting forecaster maximum horizon \n", "\n", - "Assuming your test data forms a full and regular time series(regular time intervals and no holes), \n", - "the maximum horizon you will need to forecast is the length of the longest grain in your test set." + "The forecast horizon is the number of periods into the future that the model should predict. Here, we set the horizon to 14 periods (i.e. 14 days). Notice that this is much shorter than the number of days in the test set; we will need to use a rolling test to evaluate the performance on the whole test set. For more discussion of forecast horizons and guiding principles for setting them, please see the [energy demand notebook](https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand). " ] }, { @@ -203,10 +204,7 @@ "metadata": {}, "outputs": [], "source": [ - "if len(grain_column_names) == 0:\n", - " max_horizon = len(X_test)\n", - "else:\n", - " max_horizon = X_test.groupby(grain_column_names)[time_column_name].count().max()" + "max_horizon = 14" ] }, { @@ -236,26 +234,25 @@ "metadata": {}, "outputs": [], "source": [ - "time_column_name = 'date'\n", "automl_settings = {\n", - " \"time_column_name\": time_column_name,\n", - " # these columns are a breakdown of the total and therefore a leak\n", - " \"drop_column_names\": ['casual', 'registered'],\n", + " 'time_column_name': time_column_name,\n", + " 'max_horizon': max_horizon,\n", " # knowing the country/region allows Automated ML to bring in holidays\n", - " \"country_or_region\" : 'US',\n", - " \"max_horizon\" : max_horizon,\n", - " \"target_lags\": 1 \n", + " 'country_or_region': 'US',\n", + " 'target_lags': 1,\n", + " # these columns are a breakdown of the total and therefore a leak\n", + " 'drop_column_names': ['casual', 'registered']\n", "}\n", "\n", - "automl_config = AutoMLConfig(task = 'forecasting', \n", + "automl_config = AutoMLConfig(task='forecasting', \n", " primary_metric='normalized_root_mean_squared_error',\n", - " iterations = 10,\n", - " iteration_timeout_minutes = 5,\n", - " X = X_train,\n", - " y = y_train,\n", - " n_cross_validations = 3, \n", + " iterations=10,\n", + " iteration_timeout_minutes=5,\n", + " X=X_train,\n", + " y=y_train,\n", + " n_cross_validations=3, \n", " path=project_folder,\n", - " verbosity = logging.INFO,\n", + " verbosity=logging.INFO,\n", " **automl_settings)" ] }, @@ -263,7 +260,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We will now run the experiment, starting with 10 iterations of model search. Experiment can be continued for more iterations if the results are not yet good. You will see the currently running iterations printing to the console." + "We will now run the experiment, starting with 10 iterations of model search. The experiment can be continued for more iterations if more accurate results are required. You will see the currently running iterations printing to the console." ] }, { @@ -355,11 +352,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Test the Best Fitted Model\n", + "## Evaluate" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now use the best fitted model from the AutoML Run to make forecasts for the test set. \n", "\n", - "Predict on training and test set, and calculate residual values.\n", - "\n", - "We always score on the original dataset whose schema matches the scheme of the training dataset." + "We always score on the original dataset whose schema matches the training set schema." ] }, { @@ -371,21 +373,12 @@ "X_test.head()" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "y_query = y_test.copy().astype(np.float)\n", - "y_query.fill(np.NaN)\n", - "y_fcst, X_trans = fitted_model.forecast(X_test, y_query)" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ + "We now define some functions for aligning output to input and for producing rolling forecasts over the full test set. As previously stated, the forecast horizon of 14 days is shorter than the length of the test set - which is about 120 days. To get predictions over the full test set, we iterate over the test set, making forecasts 14 days at a time and combining the results. We also make sure that each 14-day forecast uses up-to-date actuals - the current context - to construct lag features. \n", + "\n", "It is a good practice to always align the output explicitly to the input, as the count and order of the rows may have changed during transformations that span multiple rows." ] }, @@ -395,7 +388,8 @@ "metadata": {}, "outputs": [], "source": [ - "def align_outputs(y_predicted, X_trans, X_test, y_test, predicted_column_name = 'predicted'):\n", + "def align_outputs(y_predicted, X_trans, X_test, y_test, predicted_column_name='predicted',\n", + " horizon_colname='horizon_origin'):\n", " \"\"\"\n", " Demonstrates how to get the output aligned to the inputs\n", " using pandas indexes. Helps understand what happened if\n", @@ -407,7 +401,8 @@ " * model was asked to predict past max_horizon -> increase max horizon\n", " * data at start of X_test was needed for lags -> provide previous periods\n", " \"\"\"\n", - " df_fcst = pd.DataFrame({predicted_column_name : y_predicted})\n", + " df_fcst = pd.DataFrame({predicted_column_name : y_predicted,\n", + " horizon_colname: X_trans[horizon_colname]})\n", " # y and X outputs are aligned by forecast() function contract\n", " df_fcst.index = X_trans.index\n", " \n", @@ -426,7 +421,49 @@ " clean = together[together[[target_column_name, predicted_column_name]].notnull().all(axis=1)]\n", " return(clean)\n", "\n", - "df_all = align_outputs(y_fcst, X_trans, X_test, y_test)\n" + "def do_rolling_forecast(fitted_model, X_test, y_test, max_horizon, freq='D'):\n", + " \"\"\"\n", + " Produce forecasts on a rolling origin over the given test set.\n", + " \n", + " Each iteration makes a forecast for the next 'max_horizon' periods \n", + " with respect to the current origin, then advances the origin by the horizon time duration. \n", + " The prediction context for each forecast is set so that the forecaster uses \n", + " the actual target values prior to the current origin time for constructing lag features.\n", + " \n", + " This function returns a concatenated DataFrame of rolling forecasts.\n", + " \"\"\"\n", + " df_list = []\n", + " origin_time = X_test[time_column_name].min()\n", + " while origin_time <= X_test[time_column_name].max():\n", + " # Set the horizon time - end date of the forecast\n", + " horizon_time = origin_time + max_horizon * to_offset(freq)\n", + " \n", + " # Extract test data from an expanding window up-to the horizon \n", + " expand_wind = (X_test[time_column_name] < horizon_time)\n", + " X_test_expand = X_test[expand_wind]\n", + " y_query_expand = np.zeros(len(X_test_expand)).astype(np.float)\n", + " y_query_expand.fill(np.NaN)\n", + " \n", + " if origin_time != X_test[time_column_name].min():\n", + " # Set the context by including actuals up-to the origin time\n", + " test_context_expand_wind = (X_test[time_column_name] < origin_time)\n", + " context_expand_wind = (X_test_expand[time_column_name] < origin_time)\n", + " y_query_expand[context_expand_wind] = y_test[test_context_expand_wind]\n", + " \n", + " # Make a forecast out to the maximum horizon\n", + " y_fcst, X_trans = fitted_model.forecast(X_test_expand, y_query_expand)\n", + " \n", + " # Align forecast with test set for dates within the current rolling window \n", + " trans_tindex = X_trans.index.get_level_values(time_column_name)\n", + " trans_roll_wind = (trans_tindex >= origin_time) & (trans_tindex < horizon_time)\n", + " test_roll_wind = expand_wind & (X_test[time_column_name] >= origin_time)\n", + " df_list.append(align_outputs(y_fcst[trans_roll_wind], X_trans[trans_roll_wind],\n", + " X_test[test_roll_wind], y_test[test_roll_wind]))\n", + " \n", + " # Advance the origin time\n", + " origin_time = horizon_time\n", + " \n", + " return pd.concat(df_list, ignore_index=True)" ] }, { @@ -435,6 +472,30 @@ "metadata": {}, "outputs": [], "source": [ + "df_all = do_rolling_forecast(fitted_model, X_test, y_test, max_horizon)\n", + "df_all" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now calculate some error metrics for the forecasts and vizualize the predictions vs. the actuals." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def APE(actual, pred):\n", + " \"\"\"\n", + " Calculate absolute percentage error.\n", + " Returns a vector of APE values with same length as actual/pred.\n", + " \"\"\"\n", + " return 100*np.abs((actual - pred)/actual)\n", + "\n", "def MAPE(actual, pred):\n", " \"\"\"\n", " Calculate mean absolute percentage error.\n", @@ -444,8 +505,7 @@ " not_zero = ~np.isclose(actual, 0.0)\n", " actual_safe = actual[not_na & not_zero]\n", " pred_safe = pred[not_na & not_zero]\n", - " APE = 100*np.abs((actual_safe - pred_safe)/actual_safe)\n", - " return np.mean(APE)" + " return np.mean(APE(actual_safe, pred_safe))" ] }, { @@ -468,12 +528,57 @@ "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", "plt.show()" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The MAPE seems high; it is being skewed by an actual with a small absolute value. For a more informative evaluation, we can calculate the metrics by forecast horizon:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df_all.groupby('horizon_origin').apply(\n", + " lambda df: pd.Series({'MAPE': MAPE(df[target_column_name], df['predicted']),\n", + " 'RMSE': np.sqrt(mean_squared_error(df[target_column_name], df['predicted'])),\n", + " 'MAE': mean_absolute_error(df[target_column_name], df['predicted'])}))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It's also interesting to see the distributions of APE (absolute percentage error) by horizon. On a log scale, the outlying APE in the horizon-3 group is clear." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df_all_APE = df_all.assign(APE=APE(df_all[target_column_name], df_all['predicted']))\n", + "APEs = [df_all_APE[df_all['horizon_origin'] == h].APE.values for h in range(1, max_horizon + 1)]\n", + "\n", + "%matplotlib notebook\n", + "plt.boxplot(APEs)\n", + "plt.yscale('log')\n", + "plt.xlabel('horizon')\n", + "plt.ylabel('APE (%)')\n", + "plt.title('Absolute Percentage Errors by Forecast Horizon')\n", + "\n", + "plt.show()" + ] } ], "metadata": { "authors": [ { - "name": "xiaga@microsoft.com, tosingli@microsoft.com" + "name": "xiaga@microsoft.com, tosingli@microsoft.com, erwright@microsoft.com" } ], "kernelspec": { @@ -491,7 +596,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.6.8" } }, "nbformat": 4, diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.yml b/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.yml new file mode 100644 index 00000000..ad74b802 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.yml @@ -0,0 +1,9 @@ +name: auto-ml-forecasting-bike-share +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml + - statsmodels diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.ipynb b/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.ipynb index d54d6693..88f42473 100644 --- a/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.ipynb +++ b/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.ipynb @@ -35,17 +35,16 @@ "metadata": {}, "source": [ "## Introduction\n", - "In this example, we show how AutoML can be used for energy demand forecasting.\n", + "In this example, we show how AutoML can be used to forecast a single time-series in the energy demand application area. \n", "\n", "Make sure you have executed the [configuration](../../../configuration.ipynb) before running this notebook.\n", "\n", - "In this notebook you would see\n", + "Notebook synopsis:\n", "1. Creating an Experiment in an existing Workspace\n", - "2. Instantiating AutoMLConfig with new task type \"forecasting\" for timeseries data training, and other timeseries related settings: for this dataset we use the basic one: \"time_column_name\" \n", - "3. Training the Model using local compute\n", - "4. Exploring the results\n", - "5. Viewing the engineered names for featurized data and featurization summary for all raw features\n", - "6. Testing the fitted model" + "2. Configuration and local run of AutoML for a simple time-series model\n", + "3. View engineered features and prediction results\n", + "4. Configuration and local run of AutoML for a time-series model with lag and rolling window features\n", + "5. Estimate feature importance" ] }, { @@ -65,6 +64,10 @@ "import pandas as pd\n", "import numpy as np\n", "import logging\n", + "import warnings\n", + "\n", + "# Squash warning messages for cleaner output in the notebook\n", + "warnings.showwarning = lambda *args, **kwargs: None\n", "\n", "from azureml.core.workspace import Workspace\n", "from azureml.core.experiment import Experiment\n", @@ -77,7 +80,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As part of the setup you have already created a Workspace. For AutoML you would need to create an Experiment. An Experiment is a named object in a Workspace, which is used to run experiments." + "As part of the setup you have already created a Workspace. To run AutoML, you also need to create an Experiment. An Experiment corresponds to a prediction problem you are trying to solve, while a Run corresponds to a specific approach to the problem." ] }, { @@ -113,7 +116,7 @@ "metadata": {}, "source": [ "## Data\n", - "Read energy demanding data from file, and preview data." + "We will use energy consumption data from New York City for model training. The data is stored in a tabular format and includes energy demand and basic weather data at an hourly frequency. Pandas CSV reader is used to read the file into memory. Special attention is given to the \"timeStamp\" column in the data since it contains text which should be parsed as datetime-type objects. " ] }, { @@ -126,13 +129,20 @@ "data.head()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We must now define the schema of this dataset. Every time-series must have a time column and a target. The target quantity is what will be eventually forecasted by a trained model. In this case, the target is the \"demand\" column. The other columns, \"temp\" and \"precip,\" are implicitly designated as features." + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# let's take note of what columns means what in the data\n", + "# Dataset schema\n", "time_column_name = 'timeStamp'\n", "target_column_name = 'demand'" ] @@ -141,7 +151,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Split the data into train and test sets\n" + "### Forecast Horizon\n", + "\n", + "In addition to the data schema, we must also specify the forecast horizon. A forecast horizon is a time span into the future (or just beyond the latest date in the training data) where forecasts of the target quantity are needed. Choosing a forecast horizon is application specific, but a rule-of-thumb is that **the horizon should be the time-frame where you need actionable decisions based on the forecast.** The horizon usually has a strong relationship with the frequency of the time-series data, that is, the sampling interval of the target quantity and the features. For instance, the NYC energy demand data has an hourly frequency. A decision that requires a demand forecast to the hour is unlikely to be made weeks or months in advance, particularly if we expect weather to be a strong determinant of demand. We may have fairly accurate meteorological forecasts of the hourly temperature and precipitation on a the time-scale of a day or two, however.\n", + "\n", + "Given the above discussion, we generally recommend that users set forecast horizons to less than 100 time periods (i.e. less than 100 hours in the NYC energy example). Furthermore, **AutoML's memory use and computation time increase in proportion to the length of the horizon**, so the user should consider carefully how they set this value. If a long horizon forecast really is necessary, it may be good practice to aggregate the series to a coarser time scale. \n", + "\n", + "\n", + "Forecast horizons in AutoML are given as integer multiples of the time-series frequency. In this example, we set the horizon to 48 hours." ] }, { @@ -150,8 +167,32 @@ "metadata": {}, "outputs": [], "source": [ - "X_train = data[data[time_column_name] < '2017-02-01']\n", - "X_test = data[data[time_column_name] >= '2017-02-01']\n", + "max_horizon = 48" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Split the data into train and test sets\n", + "We now split the data into a train and a test set so that we may evaluate model performance. We note that the tail of the dataset contains a large number of NA values in the target column, so we designate the test set as the 48 hour window ending on the latest date of known energy demand. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Find time point to split on\n", + "latest_known_time = data[~pd.isnull(data[target_column_name])][time_column_name].max()\n", + "split_time = latest_known_time - pd.Timedelta(hours=max_horizon)\n", + "\n", + "# Split into train/test sets\n", + "X_train = data[data[time_column_name] <= split_time]\n", + "X_test = data[(data[time_column_name] > split_time) & (data[time_column_name] <= latest_known_time)]\n", + "\n", + "# Move the target values into their own arrays \n", "y_train = X_train.pop(target_column_name).values\n", "y_test = X_test.pop(target_column_name).values" ] @@ -162,7 +203,7 @@ "source": [ "## Train\n", "\n", - "Instantiate a AutoMLConfig object. This defines the settings and data used to run the experiment.\n", + "We now instantiate an AutoMLConfig object. This config defines the settings and data used to run the experiment. For forecasting tasks, we must provide extra configuration related to the time-series data schema and forecasting context. Here, only the name of the time column and the maximum forecast horizon are needed. Other settings are described below:\n", "\n", "|Property|Description|\n", "|-|-|\n", @@ -172,7 +213,7 @@ "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", "|**y**|(sparse) array-like, shape = [n_samples, ], targets values.|\n", - "|**n_cross_validations**|Number of cross validation splits.|\n", + "|**n_cross_validations**|Number of cross validation splits. Rolling Origin Validation is used to split time-series in a temporally consistent way.|\n", "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder. " ] }, @@ -182,22 +223,22 @@ "metadata": {}, "outputs": [], "source": [ - "automl_settings = {\n", - " \"time_column_name\": time_column_name \n", + "time_series_settings = {\n", + " 'time_column_name': time_column_name,\n", + " 'max_horizon': max_horizon\n", "}\n", "\n", - "\n", - "automl_config = AutoMLConfig(task = 'forecasting',\n", - " debug_log = 'automl_nyc_energy_errors.log',\n", + "automl_config = AutoMLConfig(task='forecasting',\n", + " debug_log='automl_nyc_energy_errors.log',\n", " primary_metric='normalized_root_mean_squared_error',\n", - " iterations = 10,\n", - " iteration_timeout_minutes = 5,\n", - " X = X_train,\n", - " y = y_train,\n", - " n_cross_validations = 3,\n", + " iterations=10,\n", + " iteration_timeout_minutes=5,\n", + " X=X_train,\n", + " y=y_train,\n", + " n_cross_validations=3,\n", " path=project_folder,\n", " verbosity = logging.INFO,\n", - " **automl_settings)" + " **time_series_settings)" ] }, { @@ -354,7 +395,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Calculate accuracy metrics\n" + "### Calculate accuracy metrics\n", + "Finally, we calculate some accuracy metrics for the forecast and plot the predictions vs. the actuals over the time range in the test set." ] }, { @@ -391,9 +433,12 @@ "\n", "# Plot outputs\n", "%matplotlib notebook\n", - "test_pred = plt.scatter(df_all[target_column_name], df_all['predicted'], color='b')\n", - "test_test = plt.scatter(y_test, y_test, color='g')\n", - "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", + "pred, = plt.plot(df_all[time_column_name], df_all['predicted'], color='b')\n", + "actual, = plt.plot(df_all[time_column_name], df_all[target_column_name], color='g')\n", + "plt.xticks(fontsize=8)\n", + "plt.legend((pred, actual), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", + "plt.title('Prediction vs. Actual Time-Series')\n", + "\n", "plt.show()" ] }, @@ -408,16 +453,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Using lags and rolling window features to improve the forecast" + "### Using lags and rolling window features" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "We did not use lags in the previous model specification. In effect, the prediction was the result of a simple regression on date, grain and any additional features. This is often a very good prediction as common time series patterns like seasonality and trends can be captured in this manner. Such simple regression is horizon-less: it doesn't matter how far into the future we are predicting, because we are not using past data.\n", + "We did not use lags in the previous model specification. In effect, the prediction was the result of a simple regression on date, grain and any additional features. This is often a very good prediction as common time series patterns like seasonality and trends can be captured in this manner. Such simple regression is horizon-less: it doesn't matter how far into the future we are predicting, because we are not using past data. In the previous example, the horizon was only used to split the data for cross-validation.\n", "\n", - "Now that we configured target lags, that is the previous values of the target variables, and the prediction is no longer horizon-less. We therefore must specify the `max_horizon` that the model will learn to forecast. The `target_lags` keyword specifies how far back we will construct the lags of the target variable, and the `target_rolling_window_size` specifies the size of the rolling window over which we will generate the `max`, `min` and `sum` features." + "Now that we configured target lags, that is the previous values of the target variables, and the prediction is no longer horizon-less. We therefore must still specify the `max_horizon` that the model will learn to forecast. The `target_lags` keyword specifies how far back we will construct the lags of the target variable, and the `target_rolling_window_size` specifies the size of the rolling window over which we will generate the `max`, `min` and `sum` features." ] }, { @@ -426,27 +471,32 @@ "metadata": {}, "outputs": [], "source": [ - "automl_settings_lags = {\n", + "time_series_settings_with_lags = {\n", " 'time_column_name': time_column_name,\n", - " 'target_lags': 1,\n", - " 'target_rolling_window_size': 5,\n", - " # you MUST set the max_horizon when using lags and rolling windows\n", - " # it is optional when looking-back features are not used \n", - " 'max_horizon': len(y_test), # only one grain\n", + " 'max_horizon': max_horizon,\n", + " 'target_lags': 12,\n", + " 'target_rolling_window_size': 4\n", "}\n", "\n", - "\n", - "automl_config_lags = AutoMLConfig(task = 'forecasting',\n", - " debug_log = 'automl_nyc_energy_errors.log',\n", - " primary_metric='normalized_root_mean_squared_error',\n", - " iterations = 10,\n", - " iteration_timeout_minutes = 5,\n", - " X = X_train,\n", - " y = y_train,\n", - " n_cross_validations = 3,\n", - " path=project_folder,\n", - " verbosity = logging.INFO,\n", - " **automl_settings_lags)" + "automl_config_lags = AutoMLConfig(task='forecasting',\n", + " debug_log='automl_nyc_energy_errors.log',\n", + " primary_metric='normalized_root_mean_squared_error',\n", + " blacklist_models=['ElasticNet'],\n", + " iterations=10,\n", + " iteration_timeout_minutes=10,\n", + " X=X_train,\n", + " y=y_train,\n", + " n_cross_validations=3,\n", + " path=project_folder,\n", + " verbosity=logging.INFO,\n", + " **time_series_settings_with_lags)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now start a new local run, this time with lag and rolling window featurization. AutoML applies featurizations in the setup stage, prior to iterating over ML models. The full training set is featurized first, followed by featurization of each of the CV splits. Lag and rolling window features introduce additional complexity, so the run will take longer than in the previous example that lacked these featurizations." ] }, { @@ -494,9 +544,10 @@ "\n", "# Plot outputs\n", "%matplotlib notebook\n", - "test_pred = plt.scatter(df_lags[target_column_name], df_lags['predicted'], color='b')\n", - "test_test = plt.scatter(y_test, y_test, color='g')\n", - "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", + "pred, = plt.plot(df_lags[time_column_name], df_lags['predicted'], color='b')\n", + "actual, = plt.plot(df_lags[time_column_name], df_lags[target_column_name], color='g')\n", + "plt.xticks(fontsize=8)\n", + "plt.legend((pred, actual), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", "plt.show()" ] }, @@ -516,8 +567,8 @@ "from azureml.train.automl.automlexplainer import explain_model\n", "\n", "# feature names are everything in the transformed data except the target\n", - "features = X_trans.columns[:-1]\n", - "expl = explain_model(fitted_model, X_train, X_test, features = features, best_run=best_run_lags, y_train = y_train)\n", + "features = X_trans_lags.columns[:-1]\n", + "expl = explain_model(fitted_model_lags, X_train.copy(), X_test.copy(), features=features, best_run=best_run_lags, y_train=y_train)\n", "# unpack the tuple\n", "shap_values, expected_values, feat_overall_imp, feat_names, per_class_summary, per_class_imp = expl\n", "best_run_lags" @@ -536,7 +587,7 @@ "metadata": { "authors": [ { - "name": "xiaga, tosingli" + "name": "xiaga, tosingli, erwright" } ], "kernelspec": { @@ -554,7 +605,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.6.8" } }, "nbformat": 4, diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.yml b/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.yml new file mode 100644 index 00000000..5a2fda3d --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.yml @@ -0,0 +1,10 @@ +name: auto-ml-forecasting-energy-demand +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml + - statsmodels + - azureml-explain-model diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.ipynb b/how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.ipynb index 98f17ad4..e129c731 100644 --- a/how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.ipynb +++ b/how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.ipynb @@ -37,16 +37,10 @@ "metadata": {}, "source": [ "## Introduction\n", - "In this example, we use AutoML to find and tune a time-series forecasting model.\n", + "In this example, we use AutoML to train, select, and operationalize a time-series forecasting model for multiple time-series.\n", "\n", "Make sure you have executed the [configuration notebook](../../../configuration.ipynb) before running this notebook.\n", "\n", - "In this notebook, you will:\n", - "1. Create an Experiment in an existing Workspace\n", - "2. Instantiate an AutoMLConfig \n", - "3. Find and train a forecasting model using local compute\n", - "4. Evaluate the performance of the model\n", - "\n", "The examples in the follow code samples use the University of Chicago's Dominick's Finer Foods dataset to forecast orange juice sales. Dominick's was a grocery chain in the Chicago metropolitan area." ] }, @@ -67,6 +61,10 @@ "import pandas as pd\n", "import numpy as np\n", "import logging\n", + "import warnings\n", + "\n", + "# Squash warning messages for cleaner output in the notebook\n", + "warnings.showwarning = lambda *args, **kwargs: None\n", "\n", "from azureml.core.workspace import Workspace\n", "from azureml.core.experiment import Experiment\n", @@ -78,7 +76,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As part of the setup you have already created a Workspace. To run AutoML, you also need to create an Experiment. An Experiment is a named object in a Workspace which represents a predictive task, the output of which is a trained model and a set of evaluation metrics for the model. " + "As part of the setup you have already created a Workspace. To run AutoML, you also need to create an Experiment. An Experiment corresponds to a prediction problem you are trying to solve, while a Run corresponds to a specific approach to the problem. " ] }, { @@ -232,7 +230,7 @@ "\n", "For forecasting tasks, there are some additional parameters that can be set: the name of the column holding the date/time, the grain column names, and the maximum forecast horizon. A time column is required for forecasting, while the grain is optional. If a grain is not given, AutoML assumes that the whole dataset is a single time-series. We also pass a list of columns to drop prior to modeling. The _logQuantity_ column is completely correlated with the target quantity, so it must be removed to prevent a target leak.\n", "\n", - "The forecast horizon is given in units of the time-series frequency; for instance, the OJ series frequency is weekly, so a horizon of 20 means that a trained model will estimate sales up-to 20 weeks beyond the latest date in the training data for each series. In this example, we set the maximum horizon to the number of samples per series in the test set (n_test_periods). Generally, the value of this parameter will be dictated by business needs. For example, a demand planning organizaion that needs to estimate the next month of sales would set the horizon accordingly. \n", + "The forecast horizon is given in units of the time-series frequency; for instance, the OJ series frequency is weekly, so a horizon of 20 means that a trained model will estimate sales up-to 20 weeks beyond the latest date in the training data for each series. In this example, we set the maximum horizon to the number of samples per series in the test set (n_test_periods). Generally, the value of this parameter will be dictated by business needs. For example, a demand planning organizaion that needs to estimate the next month of sales would set the horizon accordingly. Please see the [energy_demand notebook](https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand) for more discussion of forecast horizon.\n", "\n", "Finally, a note about the cross-validation (CV) procedure for time-series data. AutoML uses out-of-sample error estimates to select a best pipeline/model, so it is important that the CV fold splitting is done correctly. Time-series can violate the basic statistical assumptions of the canonical K-Fold CV strategy, so AutoML implements a [rolling origin validation](https://robjhyndman.com/hyndsight/tscv/) procedure to create CV folds for time-series data. To use this procedure, you just need to specify the desired number of CV folds in the AutoMLConfig object. It is also possible to bypass CV and use your own validation set by setting the *X_valid* and *y_valid* parameters of AutoMLConfig.\n", "\n", @@ -265,7 +263,7 @@ " 'time_column_name': time_column_name,\n", " 'grain_column_names': grain_column_names,\n", " 'drop_column_names': ['logQuantity'],\n", - " 'max_horizon': n_test_periods # optional\n", + " 'max_horizon': n_test_periods\n", "}\n", "\n", "automl_config = AutoMLConfig(task='forecasting',\n", @@ -274,7 +272,7 @@ " iterations=10,\n", " X=X_train,\n", " y=y_train,\n", - " n_cross_validations=5,\n", + " n_cross_validations=3,\n", " enable_ensembling=False,\n", " path=project_folder,\n", " verbosity=logging.INFO,\n", @@ -320,7 +318,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Predict\n", + "# Forecasting\n", + "\n", "Now that we have retrieved the best pipeline/model, it can be used to make predictions on test data. First, we remove the target values from the test set:" ] }, @@ -848,7 +847,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.6.8" } }, "nbformat": 4, diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.yml b/how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.yml new file mode 100644 index 00000000..a9ab52bd --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales/auto-ml-forecasting-orange-juice-sales.yml @@ -0,0 +1,9 @@ +name: auto-ml-forecasting-orange-juice-sales +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml + - statsmodels diff --git a/how-to-use-azureml/automated-machine-learning/missing-data-blacklist-early-termination/auto-ml-missing-data-blacklist-early-termination.yml b/how-to-use-azureml/automated-machine-learning/missing-data-blacklist-early-termination/auto-ml-missing-data-blacklist-early-termination.yml new file mode 100644 index 00000000..b3d14116 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/missing-data-blacklist-early-termination/auto-ml-missing-data-blacklist-early-termination.yml @@ -0,0 +1,8 @@ +name: auto-ml-missing-data-blacklist-early-termination +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/model-explanation/auto-ml-model-explanation.yml b/how-to-use-azureml/automated-machine-learning/model-explanation/auto-ml-model-explanation.yml new file mode 100644 index 00000000..1c4e89af --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/model-explanation/auto-ml-model-explanation.yml @@ -0,0 +1,9 @@ +name: auto-ml-model-explanation +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml + - azureml-explain-model diff --git a/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.ipynb b/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.ipynb index 72fbf0b1..6b1fc201 100644 --- a/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.ipynb +++ b/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.ipynb @@ -1,800 +1,800 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Automated Machine Learning\n", - "_**Regression with Deployment using Hardware Performance Dataset**_\n", - "\n", - "## Contents\n", - "1. [Introduction](#Introduction)\n", - "1. [Setup](#Setup)\n", - "1. [Data](#Data)\n", - "1. [Train](#Train)\n", - "1. [Results](#Results)\n", - "1. [Test](#Test)\n", - "1. [Acknowledgements](#Acknowledgements)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Introduction\n", - "In this example we use the Predicting Compressive Strength of Concrete Dataset to showcase how you can use AutoML for a regression problem. The regression goal is to predict the compressive strength of concrete based off of different ingredient combinations and the quantities of those ingredients.\n", - "\n", - "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", - "\n", - "In this notebook you will learn how to:\n", - "1. Create an `Experiment` in an existing `Workspace`.\n", - "2. Configure AutoML using `AutoMLConfig`.\n", - "3. Train the model using local compute.\n", - "4. Explore the results.\n", - "5. Test the best fitted model." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup\n", - "As part of the setup you have already created an Azure ML Workspace object. For AutoML you will need to create an Experiment object, which is a named object in a Workspace used to run experiments." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import logging\n", - "\n", - "from matplotlib import pyplot as plt\n", - "import numpy as np\n", - "import pandas as pd\n", - "import os\n", - "from sklearn.model_selection import train_test_split\n", - "import azureml.dataprep as dprep\n", - " \n", - "\n", - "import azureml.core\n", - "from azureml.core.experiment import Experiment\n", - "from azureml.core.workspace import Workspace\n", - "from azureml.train.automl import AutoMLConfig" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ws = Workspace.from_config()\n", - "\n", - "# Choose a name for the experiment and specify the project folder.\n", - "experiment_name = 'automl-regression-concrete'\n", - "project_folder = './sample_projects/automl-regression-concrete'\n", - "\n", - "experiment = Experiment(ws, experiment_name)\n", - "\n", - "output = {}\n", - "output['SDK version'] = azureml.core.VERSION\n", - "output['Subscription ID'] = ws.subscription_id\n", - "output['Workspace Name'] = ws.name\n", - "output['Resource Group'] = ws.resource_group\n", - "output['Location'] = ws.location\n", - "output['Project Directory'] = project_folder\n", - "output['Experiment Name'] = experiment.name\n", - "pd.set_option('display.max_colwidth', -1)\n", - "outputDf = pd.DataFrame(data = output, index = [''])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create or Attach existing AmlCompute\n", - "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", - "#### Creation of AmlCompute takes approximately 5 minutes. \n", - "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", - "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this article on the default limits and how to request more quota." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.compute import AmlCompute\n", - "from azureml.core.compute import ComputeTarget\n", - "\n", - "# Choose a name for your cluster.\n", - "amlcompute_cluster_name = \"automlcl\"\n", - "\n", - "found = False\n", - "# Check if this compute target already exists in the workspace.\n", - "cts = ws.compute_targets\n", - "if amlcompute_cluster_name in cts and cts[amlcompute_cluster_name].type == 'AmlCompute':\n", - " found = True\n", - " print('Found existing compute target.')\n", - " compute_target = cts[amlcompute_cluster_name]\n", - " \n", - "if not found:\n", - " print('Creating a new compute target...')\n", - " provisioning_config = AmlCompute.provisioning_configuration(vm_size = \"STANDARD_D2_V2\", # for GPU, use \"STANDARD_NC6\"\n", - " #vm_priority = 'lowpriority', # optional\n", - " max_nodes = 6)\n", - "\n", - " # Create the cluster.\n", - " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, provisioning_config)\n", - " \n", - " # Can poll for a minimum number of nodes and for a specific timeout.\n", - " # If no min_node_count is provided, it will use the scale settings for the cluster.\n", - " compute_target.wait_for_completion(show_output = True, min_node_count = None, timeout_in_minutes = 20)\n", - " \n", - " # For a more detailed view of current AmlCompute status, use get_status()." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Data\n", - "\n", - "Here load the data in the get_data script to be utilized in azure compute. To do this, first load all the necessary libraries and dependencies to set up paths for the data and to create the conda_run_config." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "if not os.path.isdir('data'):\n", - " os.mkdir('data')\n", - " \n", - "if not os.path.exists(project_folder):\n", - " os.makedirs(project_folder)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.runconfig import RunConfiguration\n", - "from azureml.core.conda_dependencies import CondaDependencies\n", - "\n", - "# create a new RunConfig object\n", - "conda_run_config = RunConfiguration(framework=\"python\")\n", - "\n", - "# Set compute target to AmlCompute\n", - "conda_run_config.target = compute_target\n", - "conda_run_config.environment.docker.enabled = True\n", - "conda_run_config.environment.docker.base_image = azureml.core.runconfig.DEFAULT_CPU_IMAGE\n", - "\n", - "\n", - "cd = CondaDependencies.create(pip_packages=['azureml-sdk[automl]'], conda_packages=['numpy'])\n", - "conda_run_config.environment.python.conda_dependencies = cd" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load Data\n", - "\n", - "Here create the script to be run in azure compute for loading the data, load the concrete strength dataset into the X and y variables. Next, split the data using train_test_split and return X_train and y_train for training the model. Finally, return X_train and y_train for training the model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/compresive_strength_concrete.csv\"\n", - "dflow = dprep.auto_read_file(data)\n", - "dflow.get_profile()\n", - "X = dflow.drop_columns(columns=['CONCRETE'])\n", - "y = dflow.keep_columns(columns=['CONCRETE'], validate_column_exists=True)\n", - "X_train, X_test = X.random_split(percentage=0.8, seed=223)\n", - "y_train, y_test = y.random_split(percentage=0.8, seed=223) \n", - "dflow.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Train\n", - "\n", - "Instantiate an `AutoMLConfig` object to specify the settings and data used to run the experiment.\n", - "\n", - "|Property|Description|\n", - "|-|-|\n", - "|**task**|classification or regression|\n", - "|**primary_metric**|This is the metric that you want to optimize. Regression supports the following primary metrics:
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error|\n", - "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", - "|**iterations**|Number of iterations. In each iteration AutoML trains a specific pipeline with the data.|\n", - "|**n_cross_validations**|Number of cross validation splits.|\n", - "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", - "|**y**|(sparse) array-like, shape = [n_samples, ], targets values.|\n", - "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder.|\n", - "\n", - "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### If you would like to see even better results increase \"iteration_time_out minutes\" to 10+ mins and increase \"iterations\" to a minimum of 30" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "automl_settings = {\n", - " \"iteration_timeout_minutes\": 5,\n", - " \"iterations\": 10,\n", - " \"n_cross_validations\": 5,\n", - " \"primary_metric\": 'spearman_correlation',\n", - " \"preprocess\": True,\n", - " \"max_concurrent_iterations\": 5,\n", - " \"verbosity\": logging.INFO,\n", - "}\n", - "\n", - "automl_config = AutoMLConfig(task = 'regression',\n", - " debug_log = 'automl.log',\n", - " path = project_folder,\n", - " run_configuration=conda_run_config,\n", - " X = X_train,\n", - " y = y_train,\n", - " **automl_settings\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run = experiment.submit(automl_config, show_output = True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Results\n", - "Widget for Monitoring Runs\n", - "The widget will first report a “loading status while running the first iteration. After completing the first iteration, an auto-updating graph and table will be shown. The widget will refresh once per minute, so you should see the graph update as child runs complete.\n", - "Note: The widget displays a link at the bottom. Use this link to open a web interface to explore the individual run details." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.widgets import RunDetails\n", - "RunDetails(remote_run).show() " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "Retrieve All Child Runs\n", - "You can also use SDK methods to fetch all the child runs and see individual metrics that we log." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "children = list(remote_run.get_children())\n", - "metricslist = {}\n", - "for run in children:\n", - " properties = run.get_properties()\n", - " metrics = {k: v for k, v in run.get_metrics().items() if isinstance(v, float)}\n", - " metricslist[int(properties['iteration'])] = metrics\n", - "\n", - "rundata = pd.DataFrame(metricslist).sort_index(1)\n", - "rundata" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Retrieve the Best Model\n", - "Below we select the best pipeline from our iterations. The get_output method returns the best run and the fitted model. The Model includes the pipeline and any pre-processing. Overloads on get_output allow you to retrieve the best run and fitted model for any logged metric or for a particular iteration." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "best_run, fitted_model = remote_run.get_output()\n", - "print(best_run)\n", - "print(fitted_model)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Best Model Based on Any Other Metric\n", - "Show the run and the model that has the smallest root_mean_squared_error value (which turned out to be the same as the one with largest spearman_correlation value):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "lookup_metric = \"root_mean_squared_error\"\n", - "best_run, fitted_model = remote_run.get_output(metric = lookup_metric)\n", - "print(best_run)\n", - "print(fitted_model)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "iteration = 3\n", - "third_run, third_model = remote_run.get_output(iteration = iteration)\n", - "print(third_run)\n", - "print(third_model)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Register the Fitted Model for Deployment\n", - "If neither metric nor iteration are specified in the register_model call, the iteration with the best primary metric is registered." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "description = 'AutoML Model'\n", - "tags = None\n", - "model = remote_run.register_model(description = description, tags = tags)\n", - "\n", - "print(remote_run.model_id) # This will be written to the script file later in the notebook." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create Scoring Script\n", - "The scoring script is required to generate the image for deployment. It contains the code to do the predictions on input data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%writefile score.py\n", - "import pickle\n", - "import json\n", - "import numpy\n", - "import azureml.train.automl\n", - "from sklearn.externals import joblib\n", - "from azureml.core.model import Model\n", - "\n", - "def init():\n", - " global model\n", - " model_path = Model.get_model_path(model_name = '<>') # this name is model.id of model that we want to deploy\n", - " # deserialize the model file back into a sklearn model\n", - " model = joblib.load(model_path)\n", - "\n", - "def run(rawdata):\n", - " try:\n", - " data = json.loads(rawdata)['data']\n", - " data = numpy.array(data)\n", - " result = model.predict(data)\n", - " except Exception as e:\n", - " result = str(e)\n", - " return json.dumps({\"error\": result})\n", - " return json.dumps({\"result\":result.tolist()})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create a YAML File for the Environment" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To ensure the fit results are consistent with the training results, the SDK dependency versions need to be the same as the environment that trains the model. Details about retrieving the versions can be found in notebook [12.auto-ml-retrieve-the-training-sdk-versions](12.auto-ml-retrieve-the-training-sdk-versions.ipynb)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dependencies = remote_run.get_run_sdk_dependencies(iteration = 1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for p in ['azureml-train-automl', 'azureml-sdk', 'azureml-core']:\n", - " print('{}\\t{}'.format(p, dependencies[p]))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.conda_dependencies import CondaDependencies\n", - "\n", - "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn'], pip_packages=['azureml-sdk[automl]'])\n", - "\n", - "conda_env_file_name = 'myenv.yml'\n", - "myenv.save_to_file('.', conda_env_file_name)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Substitute the actual version number in the environment file.\n", - "# This is not strictly needed in this notebook because the model should have been generated using the current SDK version.\n", - "# However, we include this in case this code is used on an experiment from a previous SDK version.\n", - "\n", - "with open(conda_env_file_name, 'r') as cefr:\n", - " content = cefr.read()\n", - "\n", - "with open(conda_env_file_name, 'w') as cefw:\n", - " cefw.write(content.replace(azureml.core.VERSION, dependencies['azureml-sdk']))\n", - "\n", - "# Substitute the actual model id in the script file.\n", - "\n", - "script_file_name = 'score.py'\n", - "\n", - "with open(script_file_name, 'r') as cefr:\n", - " content = cefr.read()\n", - "\n", - "with open(script_file_name, 'w') as cefw:\n", - " cefw.write(content.replace('<>', remote_run.model_id))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create a Container Image\n", - "\n", - "Next use Azure Container Instances for deploying models as a web service for quickly deploying and validating your model\n", - "or when testing a model that is under development." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.image import Image, ContainerImage\n", - "\n", - "image_config = ContainerImage.image_configuration(runtime= \"python\",\n", - " execution_script = script_file_name,\n", - " conda_file = conda_env_file_name,\n", - " tags = {'area': \"digits\", 'type': \"automl_regression\"},\n", - " description = \"Image for automl regression sample\")\n", - "\n", - "image = Image.create(name = \"automlsampleimage\",\n", - " # this is the model object \n", - " models = [model],\n", - " image_config = image_config, \n", - " workspace = ws)\n", - "\n", - "image.wait_for_creation(show_output = True)\n", - "\n", - "if image.creation_state == 'Failed':\n", - " print(\"Image build log at: \" + image.image_build_log_uri)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Deploy the Image as a Web Service on Azure Container Instance\n", - "\n", - "Deploy an image that contains the model and other assets needed by the service." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.webservice import AciWebservice\n", - "\n", - "aciconfig = AciWebservice.deploy_configuration(cpu_cores = 1, \n", - " memory_gb = 1, \n", - " tags = {'area': \"digits\", 'type': \"automl_regression\"}, \n", - " description = 'sample service for Automl Regression')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.webservice import Webservice\n", - "\n", - "aci_service_name = 'automl-sample-concrete'\n", - "print(aci_service_name)\n", - "aci_service = Webservice.deploy_from_image(deployment_config = aciconfig,\n", - " image = image,\n", - " name = aci_service_name,\n", - " workspace = ws)\n", - "aci_service.wait_for_deployment(True)\n", - "print(aci_service.state)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Delete a Web Service\n", - "\n", - "Deletes the specified web service." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#aci_service.delete()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Get Logs from a Deployed Web Service\n", - "\n", - "Gets logs from a deployed web service." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#aci_service.get_logs()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Test\n", - "\n", - "Now that the model is trained, split the data in the same way the data was split for training (The difference here is the data is being split locally) and then run the test data through the trained model to get the predicted values." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "X_test = X_test.to_pandas_dataframe()\n", - "y_test = y_test.to_pandas_dataframe()\n", - "y_test = np.array(y_test)\n", - "y_test = y_test[:,0]\n", - "X_train = X_train.to_pandas_dataframe()\n", - "y_train = y_train.to_pandas_dataframe()\n", - "y_train = np.array(y_train)\n", - "y_train = y_train[:,0]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### Predict on training and test set, and calculate residual values." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "y_pred_train = fitted_model.predict(X_train)\n", - "y_residual_train = y_train - y_pred_train\n", - "\n", - "y_pred_test = fitted_model.predict(X_test)\n", - "y_residual_test = y_test - y_pred_test\n", - "\n", - "y_residual_train.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "from sklearn.metrics import mean_squared_error, r2_score\n", - "\n", - "# Set up a multi-plot chart.\n", - "f, (a0, a1) = plt.subplots(1, 2, gridspec_kw = {'width_ratios':[1, 1], 'wspace':0, 'hspace': 0})\n", - "f.suptitle('Regression Residual Values', fontsize = 18)\n", - "f.set_figheight(6)\n", - "f.set_figwidth(16)\n", - "\n", - "# Plot residual values of training set.\n", - "a0.axis([0, 360, -200, 200])\n", - "a0.plot(y_residual_train, 'bo', alpha = 0.5)\n", - "a0.plot([-10,360],[0,0], 'r-', lw = 3)\n", - "a0.text(16,170,'RMSE = {0:.2f}'.format(np.sqrt(mean_squared_error(y_train, y_pred_train))), fontsize = 12)\n", - "a0.text(16,140,'R2 score = {0:.2f}'.format(r2_score(y_train, y_pred_train)), fontsize = 12)\n", - "a0.set_xlabel('Training samples', fontsize = 12)\n", - "a0.set_ylabel('Residual Values', fontsize = 12)\n", - "\n", - "# Plot a histogram.\n", - "#a0.hist(y_residual_train, orientation = 'horizontal', color = ['b']*len(y_residual_train), bins = 10, histtype = 'step')\n", - "#a0.hist(y_residual_train, orientation = 'horizontal', color = ['b']*len(y_residual_train), alpha = 0.2, bins = 10)\n", - "\n", - "# Plot residual values of test set.\n", - "a1.axis([0, 90, -200, 200])\n", - "a1.plot(y_residual_test, 'bo', alpha = 0.5)\n", - "a1.plot([-10,360],[0,0], 'r-', lw = 3)\n", - "a1.text(5,170,'RMSE = {0:.2f}'.format(np.sqrt(mean_squared_error(y_test, y_pred_test))), fontsize = 12)\n", - "a1.text(5,140,'R2 score = {0:.2f}'.format(r2_score(y_test, y_pred_test)), fontsize = 12)\n", - "a1.set_xlabel('Test samples', fontsize = 12)\n", - "a1.set_yticklabels([])\n", - "\n", - "# Plot a histogram.\n", - "#a1.hist(y_residual_test, orientation = 'horizontal', color = ['b']*len(y_residual_test), bins = 10, histtype = 'step')\n", - "#a1.hist(y_residual_test, orientation = 'horizontal', color = ['b']*len(y_residual_test), alpha = 0.2, bins = 10)\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Calculate metrics for the prediction\n", - "\n", - "Now visualize the data on a scatter plot to show what our truth (actual) values are compared to the predicted values \n", - "from the trained model that was returned." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Plot outputs\n", - "%matplotlib notebook\n", - "test_pred = plt.scatter(y_test, y_pred_test, color='b')\n", - "test_test = plt.scatter(y_test, y_test, color='g')\n", - "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Acknowledgements\n", - "\n", - "This Predicting Compressive Strength of Concrete Dataset is made available under the CC0 1.0 Universal (CC0 1.0)\n", - "Public Domain Dedication License: https://creativecommons.org/publicdomain/zero/1.0/. Any rights in individual contents of the database are licensed under the CC0 1.0 Universal (CC0 1.0)\n", - "Public Domain Dedication License: https://creativecommons.org/publicdomain/zero/1.0/ . The dataset itself can be found here: https://www.kaggle.com/pavanraj159/concrete-compressive-strength-data-set and http://archive.ics.uci.edu/ml/datasets/concrete+compressive+strength\n", - "\n", - "I-Cheng Yeh, \"Modeling of strength of high performance concrete using artificial neural networks,\" Cement and Concrete Research, Vol. 28, No. 12, pp. 1797-1808 (1998). \n", - "\n", - "Dua, D. and Graff, C. (2019). UCI Machine Learning Repository [http://archive.ics.uci.edu/ml]. Irvine, CA: University of California, School of Information and Computer Science." - ] - } - ], - "metadata": { - "authors": [ - { - "name": "v-rasav" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated Machine Learning\n", + "_**Regression with Deployment using Hardware Performance Dataset**_\n", + "\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "1. [Setup](#Setup)\n", + "1. [Data](#Data)\n", + "1. [Train](#Train)\n", + "1. [Results](#Results)\n", + "1. [Test](#Test)\n", + "1. [Acknowledgements](#Acknowledgements)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "In this example we use the Predicting Compressive Strength of Concrete Dataset to showcase how you can use AutoML for a regression problem. The regression goal is to predict the compressive strength of concrete based off of different ingredient combinations and the quantities of those ingredients.\n", + "\n", + "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", + "\n", + "In this notebook you will learn how to:\n", + "1. Create an `Experiment` in an existing `Workspace`.\n", + "2. Configure AutoML using `AutoMLConfig`.\n", + "3. Train the model using local compute.\n", + "4. Explore the results.\n", + "5. Test the best fitted model." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "As part of the setup you have already created an Azure ML Workspace object. For AutoML you will need to create an Experiment object, which is a named object in a Workspace used to run experiments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "\n", + "from matplotlib import pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "import os\n", + "from sklearn.model_selection import train_test_split\n", + "import azureml.dataprep as dprep\n", + " \n", + "\n", + "import azureml.core\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.train.automl import AutoMLConfig" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "\n", + "# Choose a name for the experiment and specify the project folder.\n", + "experiment_name = 'automl-regression-concrete'\n", + "project_folder = './sample_projects/automl-regression-concrete'\n", + "\n", + "experiment = Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output['SDK version'] = azureml.core.VERSION\n", + "output['Subscription ID'] = ws.subscription_id\n", + "output['Workspace Name'] = ws.name\n", + "output['Resource Group'] = ws.resource_group\n", + "output['Location'] = ws.location\n", + "output['Project Directory'] = project_folder\n", + "output['Experiment Name'] = experiment.name\n", + "pd.set_option('display.max_colwidth', -1)\n", + "outputDf = pd.DataFrame(data = output, index = [''])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create or Attach existing AmlCompute\n", + "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "#### Creation of AmlCompute takes approximately 5 minutes. \n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", + "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this article on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import AmlCompute\n", + "from azureml.core.compute import ComputeTarget\n", + "\n", + "# Choose a name for your cluster.\n", + "amlcompute_cluster_name = \"automlcl\"\n", + "\n", + "found = False\n", + "# Check if this compute target already exists in the workspace.\n", + "cts = ws.compute_targets\n", + "if amlcompute_cluster_name in cts and cts[amlcompute_cluster_name].type == 'AmlCompute':\n", + " found = True\n", + " print('Found existing compute target.')\n", + " compute_target = cts[amlcompute_cluster_name]\n", + " \n", + "if not found:\n", + " print('Creating a new compute target...')\n", + " provisioning_config = AmlCompute.provisioning_configuration(vm_size = \"STANDARD_D2_V2\", # for GPU, use \"STANDARD_NC6\"\n", + " #vm_priority = 'lowpriority', # optional\n", + " max_nodes = 6)\n", + "\n", + " # Create the cluster.\n", + " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, provisioning_config)\n", + " \n", + " # Can poll for a minimum number of nodes and for a specific timeout.\n", + " # If no min_node_count is provided, it will use the scale settings for the cluster.\n", + " compute_target.wait_for_completion(show_output = True, min_node_count = None, timeout_in_minutes = 20)\n", + " \n", + " # For a more detailed view of current AmlCompute status, use get_status()." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data\n", + "\n", + "Here load the data in the get_data script to be utilized in azure compute. To do this, first load all the necessary libraries and dependencies to set up paths for the data and to create the conda_run_config." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if not os.path.isdir('data'):\n", + " os.mkdir('data')\n", + " \n", + "if not os.path.exists(project_folder):\n", + " os.makedirs(project_folder)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.runconfig import RunConfiguration\n", + "from azureml.core.conda_dependencies import CondaDependencies\n", + "\n", + "# create a new RunConfig object\n", + "conda_run_config = RunConfiguration(framework=\"python\")\n", + "\n", + "# Set compute target to AmlCompute\n", + "conda_run_config.target = compute_target\n", + "conda_run_config.environment.docker.enabled = True\n", + "conda_run_config.environment.docker.base_image = azureml.core.runconfig.DEFAULT_CPU_IMAGE\n", + "\n", + "\n", + "cd = CondaDependencies.create(pip_packages=['azureml-sdk[automl]'], conda_packages=['numpy'])\n", + "conda_run_config.environment.python.conda_dependencies = cd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load Data\n", + "\n", + "Here create the script to be run in azure compute for loading the data, load the concrete strength dataset into the X and y variables. Next, split the data using train_test_split and return X_train and y_train for training the model. Finally, return X_train and y_train for training the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/compresive_strength_concrete.csv\"\n", + "dflow = dprep.auto_read_file(data)\n", + "dflow.get_profile()\n", + "X = dflow.drop_columns(columns=['CONCRETE'])\n", + "y = dflow.keep_columns(columns=['CONCRETE'], validate_column_exists=True)\n", + "X_train, X_test = X.random_split(percentage=0.8, seed=223)\n", + "y_train, y_test = y.random_split(percentage=0.8, seed=223) \n", + "dflow.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train\n", + "\n", + "Instantiate an `AutoMLConfig` object to specify the settings and data used to run the experiment.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**task**|classification or regression|\n", + "|**primary_metric**|This is the metric that you want to optimize. Regression supports the following primary metrics:
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error|\n", + "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", + "|**iterations**|Number of iterations. In each iteration AutoML trains a specific pipeline with the data.|\n", + "|**n_cross_validations**|Number of cross validation splits.|\n", + "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", + "|**y**|(sparse) array-like, shape = [n_samples, ], targets values.|\n", + "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder.|\n", + "\n", + "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### If you would like to see even better results increase \"iteration_time_out minutes\" to 10+ mins and increase \"iterations\" to a minimum of 30" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "automl_settings = {\n", + " \"iteration_timeout_minutes\": 5,\n", + " \"iterations\": 10,\n", + " \"n_cross_validations\": 5,\n", + " \"primary_metric\": 'spearman_correlation',\n", + " \"preprocess\": True,\n", + " \"max_concurrent_iterations\": 5,\n", + " \"verbosity\": logging.INFO,\n", + "}\n", + "\n", + "automl_config = AutoMLConfig(task = 'regression',\n", + " debug_log = 'automl.log',\n", + " path = project_folder,\n", + " run_configuration=conda_run_config,\n", + " X = X_train,\n", + " y = y_train,\n", + " **automl_settings\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run = experiment.submit(automl_config, show_output = True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Results\n", + "Widget for Monitoring Runs\n", + "The widget will first report a \u00e2\u20ac\u0153loading status while running the first iteration. After completing the first iteration, an auto-updating graph and table will be shown. The widget will refresh once per minute, so you should see the graph update as child runs complete.\n", + "Note: The widget displays a link at the bottom. Use this link to open a web interface to explore the individual run details." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.widgets import RunDetails\n", + "RunDetails(remote_run).show() " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Retrieve All Child Runs\n", + "You can also use SDK methods to fetch all the child runs and see individual metrics that we log." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "children = list(remote_run.get_children())\n", + "metricslist = {}\n", + "for run in children:\n", + " properties = run.get_properties()\n", + " metrics = {k: v for k, v in run.get_metrics().items() if isinstance(v, float)}\n", + " metricslist[int(properties['iteration'])] = metrics\n", + "\n", + "rundata = pd.DataFrame(metricslist).sort_index(1)\n", + "rundata" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieve the Best Model\n", + "Below we select the best pipeline from our iterations. The get_output method returns the best run and the fitted model. The Model includes the pipeline and any pre-processing. Overloads on get_output allow you to retrieve the best run and fitted model for any logged metric or for a particular iteration." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "best_run, fitted_model = remote_run.get_output()\n", + "print(best_run)\n", + "print(fitted_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Best Model Based on Any Other Metric\n", + "Show the run and the model that has the smallest root_mean_squared_error value (which turned out to be the same as the one with largest spearman_correlation value):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lookup_metric = \"root_mean_squared_error\"\n", + "best_run, fitted_model = remote_run.get_output(metric = lookup_metric)\n", + "print(best_run)\n", + "print(fitted_model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "iteration = 3\n", + "third_run, third_model = remote_run.get_output(iteration = iteration)\n", + "print(third_run)\n", + "print(third_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Register the Fitted Model for Deployment\n", + "If neither metric nor iteration are specified in the register_model call, the iteration with the best primary metric is registered." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "description = 'AutoML Model'\n", + "tags = None\n", + "model = remote_run.register_model(description = description, tags = tags)\n", + "\n", + "print(remote_run.model_id) # This will be written to the script file later in the notebook." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create Scoring Script\n", + "The scoring script is required to generate the image for deployment. It contains the code to do the predictions on input data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile score.py\n", + "import pickle\n", + "import json\n", + "import numpy\n", + "import azureml.train.automl\n", + "from sklearn.externals import joblib\n", + "from azureml.core.model import Model\n", + "\n", + "def init():\n", + " global model\n", + " model_path = Model.get_model_path(model_name = '<>') # this name is model.id of model that we want to deploy\n", + " # deserialize the model file back into a sklearn model\n", + " model = joblib.load(model_path)\n", + "\n", + "def run(rawdata):\n", + " try:\n", + " data = json.loads(rawdata)['data']\n", + " data = numpy.array(data)\n", + " result = model.predict(data)\n", + " except Exception as e:\n", + " result = str(e)\n", + " return json.dumps({\"error\": result})\n", + " return json.dumps({\"result\":result.tolist()})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a YAML File for the Environment" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To ensure the fit results are consistent with the training results, the SDK dependency versions need to be the same as the environment that trains the model. Details about retrieving the versions can be found in notebook [12.auto-ml-retrieve-the-training-sdk-versions](12.auto-ml-retrieve-the-training-sdk-versions.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dependencies = remote_run.get_run_sdk_dependencies(iteration = 1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for p in ['azureml-train-automl', 'azureml-sdk', 'azureml-core']:\n", + " print('{}\\t{}'.format(p, dependencies[p]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.conda_dependencies import CondaDependencies\n", + "\n", + "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn'], pip_packages=['azureml-sdk[automl]'])\n", + "\n", + "conda_env_file_name = 'myenv.yml'\n", + "myenv.save_to_file('.', conda_env_file_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Substitute the actual version number in the environment file.\n", + "# This is not strictly needed in this notebook because the model should have been generated using the current SDK version.\n", + "# However, we include this in case this code is used on an experiment from a previous SDK version.\n", + "\n", + "with open(conda_env_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(conda_env_file_name, 'w') as cefw:\n", + " cefw.write(content.replace(azureml.core.VERSION, dependencies['azureml-sdk']))\n", + "\n", + "# Substitute the actual model id in the script file.\n", + "\n", + "script_file_name = 'score.py'\n", + "\n", + "with open(script_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(script_file_name, 'w') as cefw:\n", + " cefw.write(content.replace('<>', remote_run.model_id))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a Container Image\n", + "\n", + "Next use Azure Container Instances for deploying models as a web service for quickly deploying and validating your model\n", + "or when testing a model that is under development." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.image import Image, ContainerImage\n", + "\n", + "image_config = ContainerImage.image_configuration(runtime= \"python\",\n", + " execution_script = script_file_name,\n", + " conda_file = conda_env_file_name,\n", + " tags = {'area': \"digits\", 'type': \"automl_regression\"},\n", + " description = \"Image for automl regression sample\")\n", + "\n", + "image = Image.create(name = \"automlsampleimage\",\n", + " # this is the model object \n", + " models = [model],\n", + " image_config = image_config, \n", + " workspace = ws)\n", + "\n", + "image.wait_for_creation(show_output = True)\n", + "\n", + "if image.creation_state == 'Failed':\n", + " print(\"Image build log at: \" + image.image_build_log_uri)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deploy the Image as a Web Service on Azure Container Instance\n", + "\n", + "Deploy an image that contains the model and other assets needed by the service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import AciWebservice\n", + "\n", + "aciconfig = AciWebservice.deploy_configuration(cpu_cores = 1, \n", + " memory_gb = 1, \n", + " tags = {'area': \"digits\", 'type': \"automl_regression\"}, \n", + " description = 'sample service for Automl Regression')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import Webservice\n", + "\n", + "aci_service_name = 'automl-sample-concrete'\n", + "print(aci_service_name)\n", + "aci_service = Webservice.deploy_from_image(deployment_config = aciconfig,\n", + " image = image,\n", + " name = aci_service_name,\n", + " workspace = ws)\n", + "aci_service.wait_for_deployment(True)\n", + "print(aci_service.state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delete a Web Service\n", + "\n", + "Deletes the specified web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.delete()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Get Logs from a Deployed Web Service\n", + "\n", + "Gets logs from a deployed web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.get_logs()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test\n", + "\n", + "Now that the model is trained, split the data in the same way the data was split for training (The difference here is the data is being split locally) and then run the test data through the trained model to get the predicted values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_test = X_test.to_pandas_dataframe()\n", + "y_test = y_test.to_pandas_dataframe()\n", + "y_test = np.array(y_test)\n", + "y_test = y_test[:,0]\n", + "X_train = X_train.to_pandas_dataframe()\n", + "y_train = y_train.to_pandas_dataframe()\n", + "y_train = np.array(y_train)\n", + "y_train = y_train[:,0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Predict on training and test set, and calculate residual values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "y_pred_train = fitted_model.predict(X_train)\n", + "y_residual_train = y_train - y_pred_train\n", + "\n", + "y_pred_test = fitted_model.predict(X_test)\n", + "y_residual_test = y_test - y_pred_test\n", + "\n", + "y_residual_train.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "from sklearn.metrics import mean_squared_error, r2_score\n", + "\n", + "# Set up a multi-plot chart.\n", + "f, (a0, a1) = plt.subplots(1, 2, gridspec_kw = {'width_ratios':[1, 1], 'wspace':0, 'hspace': 0})\n", + "f.suptitle('Regression Residual Values', fontsize = 18)\n", + "f.set_figheight(6)\n", + "f.set_figwidth(16)\n", + "\n", + "# Plot residual values of training set.\n", + "a0.axis([0, 360, -200, 200])\n", + "a0.plot(y_residual_train, 'bo', alpha = 0.5)\n", + "a0.plot([-10,360],[0,0], 'r-', lw = 3)\n", + "a0.text(16,170,'RMSE = {0:.2f}'.format(np.sqrt(mean_squared_error(y_train, y_pred_train))), fontsize = 12)\n", + "a0.text(16,140,'R2 score = {0:.2f}'.format(r2_score(y_train, y_pred_train)), fontsize = 12)\n", + "a0.set_xlabel('Training samples', fontsize = 12)\n", + "a0.set_ylabel('Residual Values', fontsize = 12)\n", + "\n", + "# Plot a histogram.\n", + "#a0.hist(y_residual_train, orientation = 'horizontal', color = ['b']*len(y_residual_train), bins = 10, histtype = 'step')\n", + "#a0.hist(y_residual_train, orientation = 'horizontal', color = ['b']*len(y_residual_train), alpha = 0.2, bins = 10)\n", + "\n", + "# Plot residual values of test set.\n", + "a1.axis([0, 90, -200, 200])\n", + "a1.plot(y_residual_test, 'bo', alpha = 0.5)\n", + "a1.plot([-10,360],[0,0], 'r-', lw = 3)\n", + "a1.text(5,170,'RMSE = {0:.2f}'.format(np.sqrt(mean_squared_error(y_test, y_pred_test))), fontsize = 12)\n", + "a1.text(5,140,'R2 score = {0:.2f}'.format(r2_score(y_test, y_pred_test)), fontsize = 12)\n", + "a1.set_xlabel('Test samples', fontsize = 12)\n", + "a1.set_yticklabels([])\n", + "\n", + "# Plot a histogram.\n", + "#a1.hist(y_residual_test, orientation = 'horizontal', color = ['b']*len(y_residual_test), bins = 10, histtype = 'step')\n", + "#a1.hist(y_residual_test, orientation = 'horizontal', color = ['b']*len(y_residual_test), alpha = 0.2, bins = 10)\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Calculate metrics for the prediction\n", + "\n", + "Now visualize the data on a scatter plot to show what our truth (actual) values are compared to the predicted values \n", + "from the trained model that was returned." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Plot outputs\n", + "%matplotlib notebook\n", + "test_pred = plt.scatter(y_test, y_pred_test, color='b')\n", + "test_test = plt.scatter(y_test, y_test, color='g')\n", + "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Acknowledgements\n", + "\n", + "This Predicting Compressive Strength of Concrete Dataset is made available under the CC0 1.0 Universal (CC0 1.0)\n", + "Public Domain Dedication License: https://creativecommons.org/publicdomain/zero/1.0/. Any rights in individual contents of the database are licensed under the CC0 1.0 Universal (CC0 1.0)\n", + "Public Domain Dedication License: https://creativecommons.org/publicdomain/zero/1.0/ . The dataset itself can be found here: https://www.kaggle.com/pavanraj159/concrete-compressive-strength-data-set and http://archive.ics.uci.edu/ml/datasets/concrete+compressive+strength\n", + "\n", + "I-Cheng Yeh, \"Modeling of strength of high performance concrete using artificial neural networks,\" Cement and Concrete Research, Vol. 28, No. 12, pp. 1797-1808 (1998). \n", + "\n", + "Dua, D. and Graff, C. (2019). UCI Machine Learning Repository [http://archive.ics.uci.edu/ml]. Irvine, CA: University of California, School of Information and Computer Science." + ] + } ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" + "metadata": { + "authors": [ + { + "name": "v-rasav" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.1" + } }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.1" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.yml b/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.yml new file mode 100644 index 00000000..eb39aa20 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/regression-concrete-strength/auto-ml-regression-concrete-strength.yml @@ -0,0 +1,8 @@ +name: auto-ml-regression-concrete-strength +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.ipynb b/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.ipynb index dcdfc01d..5376ac3d 100644 --- a/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.ipynb +++ b/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.ipynb @@ -1,800 +1,800 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Automated Machine Learning\n", - "_**Regression with Deployment using Hardware Performance Dataset**_\n", - "\n", - "## Contents\n", - "1. [Introduction](#Introduction)\n", - "1. [Setup](#Setup)\n", - "1. [Data](#Data)\n", - "1. [Train](#Train)\n", - "1. [Results](#Results)\n", - "1. [Test](#Test)\n", - "1. [Acknowledgements](#Acknowledgements)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Introduction\n", - "In this example we use the Hardware Performance Dataset to showcase how you can use AutoML for a simple regression problem. The Regression goal is to predict the performance of certain combinations of hardware parts.\n", - "\n", - "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", - "\n", - "In this notebook you will learn how to:\n", - "1. Create an `Experiment` in an existing `Workspace`.\n", - "2. Configure AutoML using `AutoMLConfig`.\n", - "3. Train the model using local compute.\n", - "4. Explore the results.\n", - "5. Test the best fitted model." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup\n", - "As part of the setup you have already created an Azure ML Workspace object. For AutoML you will need to create an Experiment object, which is a named object in a Workspace used to run experiments." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import logging\n", - "\n", - "from matplotlib import pyplot as plt\n", - "import numpy as np\n", - "import pandas as pd\n", - "import os\n", - "from sklearn.model_selection import train_test_split\n", - "import azureml.dataprep as dprep\n", - " \n", - "\n", - "import azureml.core\n", - "from azureml.core.experiment import Experiment\n", - "from azureml.core.workspace import Workspace\n", - "from azureml.train.automl import AutoMLConfig" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ws = Workspace.from_config()\n", - "\n", - "# Choose a name for the experiment and specify the project folder.\n", - "experiment_name = 'automl-regression-hardware'\n", - "project_folder = './sample_projects/automl-remote-regression'\n", - "\n", - "experiment = Experiment(ws, experiment_name)\n", - "\n", - "output = {}\n", - "output['SDK version'] = azureml.core.VERSION\n", - "output['Subscription ID'] = ws.subscription_id\n", - "output['Workspace Name'] = ws.name\n", - "output['Resource Group'] = ws.resource_group\n", - "output['Location'] = ws.location\n", - "output['Project Directory'] = project_folder\n", - "output['Experiment Name'] = experiment.name\n", - "pd.set_option('display.max_colwidth', -1)\n", - "outputDf = pd.DataFrame(data = output, index = [''])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create or Attach existing AmlCompute\n", - "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", - "#### Creation of AmlCompute takes approximately 5 minutes. \n", - "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", - "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this article on the default limits and how to request more quota." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.compute import AmlCompute\n", - "from azureml.core.compute import ComputeTarget\n", - "\n", - "# Choose a name for your cluster.\n", - "amlcompute_cluster_name = \"automlcl\"\n", - "\n", - "found = False\n", - "# Check if this compute target already exists in the workspace.\n", - "cts = ws.compute_targets\n", - "if amlcompute_cluster_name in cts and cts[amlcompute_cluster_name].type == 'AmlCompute':\n", - " found = True\n", - " print('Found existing compute target.')\n", - " compute_target = cts[amlcompute_cluster_name]\n", - " \n", - "if not found:\n", - " print('Creating a new compute target...')\n", - " provisioning_config = AmlCompute.provisioning_configuration(vm_size = \"STANDARD_D2_V2\", # for GPU, use \"STANDARD_NC6\"\n", - " #vm_priority = 'lowpriority', # optional\n", - " max_nodes = 6)\n", - "\n", - " # Create the cluster.\n", - " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, provisioning_config)\n", - " \n", - " # Can poll for a minimum number of nodes and for a specific timeout.\n", - " # If no min_node_count is provided, it will use the scale settings for the cluster.\n", - " compute_target.wait_for_completion(show_output = True, min_node_count = None, timeout_in_minutes = 20)\n", - " \n", - " # For a more detailed view of current AmlCompute status, use get_status()." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Data\n", - "\n", - "Here load the data in the get_data script to be utilized in azure compute. To do this, first load all the necessary libraries and dependencies to set up paths for the data and to create the conda_run_config." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "if not os.path.isdir('data'):\n", - " os.mkdir('data')\n", - " \n", - "if not os.path.exists(project_folder):\n", - " os.makedirs(project_folder)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.runconfig import RunConfiguration\n", - "from azureml.core.conda_dependencies import CondaDependencies\n", - "\n", - "# create a new RunConfig object\n", - "conda_run_config = RunConfiguration(framework=\"python\")\n", - "\n", - "# Set compute target to AmlCompute\n", - "conda_run_config.target = compute_target\n", - "conda_run_config.environment.docker.enabled = True\n", - "conda_run_config.environment.docker.base_image = azureml.core.runconfig.DEFAULT_CPU_IMAGE\n", - "\n", - "\n", - "cd = CondaDependencies.create(pip_packages=['azureml-sdk[automl]'], conda_packages=['numpy'])\n", - "conda_run_config.environment.python.conda_dependencies = cd" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load Data\n", - "\n", - "Here create the script to be run in azure compute for loading the data, load the hardware dataset into the X and y variables. Next split the data using train_test_split and return X_train and y_train for training the model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/machineData.csv\"\n", - "dflow = dprep.auto_read_file(data)\n", - "dflow.get_profile()\n", - "X = dflow.drop_columns(columns=['ERP'])\n", - "y = dflow.keep_columns(columns=['ERP'], validate_column_exists=True)\n", - "X_train, X_test = X.random_split(percentage=0.8, seed=223)\n", - "y_train, y_test = y.random_split(percentage=0.8, seed=223) \n", - "dflow.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## Train\n", - "\n", - "Instantiate an `AutoMLConfig` object to specify the settings and data used to run the experiment.\n", - "\n", - "|Property|Description|\n", - "|-|-|\n", - "|**task**|classification or regression|\n", - "|**primary_metric**|This is the metric that you want to optimize. Regression supports the following primary metrics:
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error|\n", - "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", - "|**iterations**|Number of iterations. In each iteration AutoML trains a specific pipeline with the data.|\n", - "|**n_cross_validations**|Number of cross validation splits.|\n", - "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", - "|**y**|(sparse) array-like, shape = [n_samples, ], targets values.|\n", - "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder.|\n", - "\n", - "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### If you would like to see even better results increase \"iteration_time_out minutes\" to 10+ mins and increase \"iterations\" to a minimum of 30" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "automl_settings = {\n", - " \"iteration_timeout_minutes\": 5,\n", - " \"iterations\": 10,\n", - " \"n_cross_validations\": 5,\n", - " \"primary_metric\": 'spearman_correlation',\n", - " \"preprocess\": True,\n", - " \"max_concurrent_iterations\": 5,\n", - " \"verbosity\": logging.INFO,\n", - "}\n", - "\n", - "automl_config = AutoMLConfig(task = 'regression',\n", - " debug_log = 'automl_errors_20190417.log',\n", - " path = project_folder,\n", - " run_configuration=conda_run_config,\n", - " X = X_train,\n", - " y = y_train,\n", - " **automl_settings\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run = experiment.submit(automl_config, show_output = False)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Results" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Widget for Monitoring Runs\n", - "\n", - "The widget will first report a \"loading\" status while running the first iteration. After completing the first iteration, an auto-updating graph and table will be shown. The widget will refresh once per minute, so you should see the graph update as child runs complete.\n", - "\n", - "**Note:** The widget displays a link at the bottom. Use this link to open a web interface to explore the individual run details." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.widgets import RunDetails\n", - "RunDetails(remote_run).show() " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Wait until the run finishes.\n", - "remote_run.wait_for_completion(show_output = True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Retrieve All Child Runs\n", - "You can also use SDK methods to fetch all the child runs and see individual metrics that we log." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "children = list(remote_run.get_children())\n", - "metricslist = {}\n", - "for run in children:\n", - " properties = run.get_properties()\n", - " metrics = {k: v for k, v in run.get_metrics().items() if isinstance(v, float)}\n", - " metricslist[int(properties['iteration'])] = metrics\n", - "\n", - "rundata = pd.DataFrame(metricslist).sort_index(1)\n", - "rundata" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Retrieve the Best Model\n", - "Below we select the best pipeline from our iterations. The get_output method returns the best run and the fitted model. The Model includes the pipeline and any pre-processing. Overloads on get_output allow you to retrieve the best run and fitted model for any logged metric or for a particular iteration." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "best_run, fitted_model = remote_run.get_output()\n", - "print(best_run)\n", - "print(fitted_model)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Best Model Based on Any Other Metric\n", - "Show the run and the model that has the smallest `root_mean_squared_error` value (which turned out to be the same as the one with largest `spearman_correlation` value):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "lookup_metric = \"root_mean_squared_error\"\n", - "best_run, fitted_model = remote_run.get_output(metric = lookup_metric)\n", - "print(best_run)\n", - "print(fitted_model)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "iteration = 3\n", - "third_run, third_model = remote_run.get_output(iteration = iteration)\n", - "print(third_run)\n", - "print(third_model)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Register the Fitted Model for Deployment\n", - "If neither metric nor iteration are specified in the register_model call, the iteration with the best primary metric is registered." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "description = 'AutoML Model'\n", - "tags = None\n", - "model = remote_run.register_model(description = description, tags = tags)\n", - "\n", - "print(remote_run.model_id) # This will be written to the script file later in the notebook." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create Scoring Script\n", - "The scoring script is required to generate the image for deployment. It contains the code to do the predictions on input data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%writefile score.py\n", - "import pickle\n", - "import json\n", - "import numpy\n", - "import azureml.train.automl\n", - "from sklearn.externals import joblib\n", - "from azureml.core.model import Model\n", - "\n", - "def init():\n", - " global model\n", - " model_path = Model.get_model_path(model_name = '<>') # this name is model.id of model that we want to deploy\n", - " # deserialize the model file back into a sklearn model\n", - " model = joblib.load(model_path)\n", - "\n", - "def run(rawdata):\n", - " try:\n", - " data = json.loads(rawdata)['data']\n", - " data = numpy.array(data)\n", - " result = model.predict(data)\n", - " except Exception as e:\n", - " result = str(e)\n", - " return json.dumps({\"error\": result})\n", - " return json.dumps({\"result\":result.tolist()})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create a YAML File for the Environment" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To ensure the fit results are consistent with the training results, the SDK dependency versions need to be the same as the environment that trains the model. Details about retrieving the versions can be found in notebook [12.auto-ml-retrieve-the-training-sdk-versions](12.auto-ml-retrieve-the-training-sdk-versions.ipynb)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dependencies = remote_run.get_run_sdk_dependencies(iteration = 1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for p in ['azureml-train-automl', 'azureml-sdk', 'azureml-core']:\n", - " print('{}\\t{}'.format(p, dependencies[p]))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn'], pip_packages=['azureml-sdk[automl]'])\n", - "\n", - "conda_env_file_name = 'myenv.yml'\n", - "myenv.save_to_file('.', conda_env_file_name)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Substitute the actual version number in the environment file.\n", - "# This is not strictly needed in this notebook because the model should have been generated using the current SDK version.\n", - "# However, we include this in case this code is used on an experiment from a previous SDK version.\n", - "\n", - "with open(conda_env_file_name, 'r') as cefr:\n", - " content = cefr.read()\n", - "\n", - "with open(conda_env_file_name, 'w') as cefw:\n", - " cefw.write(content.replace(azureml.core.VERSION, dependencies['azureml-sdk']))\n", - "\n", - "# Substitute the actual model id in the script file.\n", - "\n", - "script_file_name = 'score.py'\n", - "\n", - "with open(script_file_name, 'r') as cefr:\n", - " content = cefr.read()\n", - "\n", - "with open(script_file_name, 'w') as cefw:\n", - " cefw.write(content.replace('<>', remote_run.model_id))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create a Container Image\n", - "\n", - "Next use Azure Container Instances for deploying models as a web service for quickly deploying and validating your model\n", - "or when testing a model that is under development." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.image import Image, ContainerImage\n", - "\n", - "image_config = ContainerImage.image_configuration(runtime= \"python\",\n", - " execution_script = script_file_name,\n", - " conda_file = conda_env_file_name,\n", - " tags = {'area': \"digits\", 'type': \"automl_regression\"},\n", - " description = \"Image for automl regression sample\")\n", - "\n", - "image = Image.create(name = \"automlsampleimage\",\n", - " # this is the model object \n", - " models = [model],\n", - " image_config = image_config, \n", - " workspace = ws)\n", - "\n", - "image.wait_for_creation(show_output = True)\n", - "\n", - "if image.creation_state == 'Failed':\n", - " print(\"Image build log at: \" + image.image_build_log_uri)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Deploy the Image as a Web Service on Azure Container Instance\n", - "\n", - "Deploy an image that contains the model and other assets needed by the service." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.webservice import AciWebservice\n", - "\n", - "aciconfig = AciWebservice.deploy_configuration(cpu_cores = 1, \n", - " memory_gb = 1, \n", - " tags = {'area': \"digits\", 'type': \"automl_regression\"}, \n", - " description = 'sample service for Automl Regression')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.webservice import Webservice\n", - "\n", - "aci_service_name = 'automl-sample-hardware'\n", - "print(aci_service_name)\n", - "aci_service = Webservice.deploy_from_image(deployment_config = aciconfig,\n", - " image = image,\n", - " name = aci_service_name,\n", - " workspace = ws)\n", - "aci_service.wait_for_deployment(True)\n", - "print(aci_service.state)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Delete a Web Service\n", - "\n", - "Deletes the specified web service." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#aci_service.delete()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Get Logs from a Deployed Web Service\n", - "\n", - "Gets logs from a deployed web service." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#aci_service.get_logs()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Test\n", - "\n", - "Now that the model is trained, split the data in the same way the data was split for training (The difference here is the data is being split locally) and then run the test data through the trained model to get the predicted values." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "X_test = X_test.to_pandas_dataframe()\n", - "y_test = y_test.to_pandas_dataframe()\n", - "y_test = np.array(y_test)\n", - "y_test = y_test[:,0]\n", - "X_train = X_train.to_pandas_dataframe()\n", - "y_train = y_train.to_pandas_dataframe()\n", - "y_train = np.array(y_train)\n", - "y_train = y_train[:,0]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### Predict on training and test set, and calculate residual values." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "y_pred_train = fitted_model.predict(X_train)\n", - "y_residual_train = y_train - y_pred_train\n", - "\n", - "y_pred_test = fitted_model.predict(X_test)\n", - "y_residual_test = y_test - y_pred_test" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Calculate metrics for the prediction\n", - "\n", - "Now visualize the data on a scatter plot to show what our truth (actual) values are compared to the predicted values \n", - "from the trained model that was returned." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "from sklearn.metrics import mean_squared_error, r2_score\n", - "\n", - "# Set up a multi-plot chart.\n", - "f, (a0, a1) = plt.subplots(1, 2, gridspec_kw = {'width_ratios':[1, 1], 'wspace':0, 'hspace': 0})\n", - "f.suptitle('Regression Residual Values', fontsize = 18)\n", - "f.set_figheight(6)\n", - "f.set_figwidth(16)\n", - "\n", - "# Plot residual values of training set.\n", - "a0.axis([0, 360, -200, 200])\n", - "a0.plot(y_residual_train, 'bo', alpha = 0.5)\n", - "a0.plot([-10,360],[0,0], 'r-', lw = 3)\n", - "a0.text(16,170,'RMSE = {0:.2f}'.format(np.sqrt(mean_squared_error(y_train, y_pred_train))), fontsize = 12)\n", - "a0.text(16,140,'R2 score = {0:.2f}'.format(r2_score(y_train, y_pred_train)),fontsize = 12)\n", - "a0.set_xlabel('Training samples', fontsize = 12)\n", - "a0.set_ylabel('Residual Values', fontsize = 12)\n", - "\n", - "# Plot residual values of test set.\n", - "a1.axis([0, 90, -200, 200])\n", - "a1.plot(y_residual_test, 'bo', alpha = 0.5)\n", - "a1.plot([-10,360],[0,0], 'r-', lw = 3)\n", - "a1.text(5,170,'RMSE = {0:.2f}'.format(np.sqrt(mean_squared_error(y_test, y_pred_test))), fontsize = 12)\n", - "a1.text(5,140,'R2 score = {0:.2f}'.format(r2_score(y_test, y_pred_test)),fontsize = 12)\n", - "a1.set_xlabel('Test samples', fontsize = 12)\n", - "a1.set_yticklabels([])\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib notebook\n", - "test_pred = plt.scatter(y_test, y_pred_test, color='')\n", - "test_test = plt.scatter(y_test, y_test, color='g')\n", - "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Acknowledgements\n", - "This Predicting Hardware Performance Dataset is made available under the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication License: https://creativecommons.org/publicdomain/zero/1.0/. Any rights in individual contents of the database are licensed under the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication License: https://creativecommons.org/publicdomain/zero/1.0/ . The dataset itself can be found here: https://www.kaggle.com/faizunnabi/comp-hardware-performance and https://archive.ics.uci.edu/ml/datasets/Computer+Hardware\n", - "\n", - "_**Citation Found Here**_\n" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "v-rasav" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated Machine Learning\n", + "_**Regression with Deployment using Hardware Performance Dataset**_\n", + "\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "1. [Setup](#Setup)\n", + "1. [Data](#Data)\n", + "1. [Train](#Train)\n", + "1. [Results](#Results)\n", + "1. [Test](#Test)\n", + "1. [Acknowledgements](#Acknowledgements)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "In this example we use the Hardware Performance Dataset to showcase how you can use AutoML for a simple regression problem. The Regression goal is to predict the performance of certain combinations of hardware parts.\n", + "\n", + "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", + "\n", + "In this notebook you will learn how to:\n", + "1. Create an `Experiment` in an existing `Workspace`.\n", + "2. Configure AutoML using `AutoMLConfig`.\n", + "3. Train the model using local compute.\n", + "4. Explore the results.\n", + "5. Test the best fitted model." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "As part of the setup you have already created an Azure ML Workspace object. For AutoML you will need to create an Experiment object, which is a named object in a Workspace used to run experiments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "\n", + "from matplotlib import pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "import os\n", + "from sklearn.model_selection import train_test_split\n", + "import azureml.dataprep as dprep\n", + " \n", + "\n", + "import azureml.core\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.train.automl import AutoMLConfig" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "\n", + "# Choose a name for the experiment and specify the project folder.\n", + "experiment_name = 'automl-regression-hardware'\n", + "project_folder = './sample_projects/automl-remote-regression'\n", + "\n", + "experiment = Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output['SDK version'] = azureml.core.VERSION\n", + "output['Subscription ID'] = ws.subscription_id\n", + "output['Workspace Name'] = ws.name\n", + "output['Resource Group'] = ws.resource_group\n", + "output['Location'] = ws.location\n", + "output['Project Directory'] = project_folder\n", + "output['Experiment Name'] = experiment.name\n", + "pd.set_option('display.max_colwidth', -1)\n", + "outputDf = pd.DataFrame(data = output, index = [''])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create or Attach existing AmlCompute\n", + "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "#### Creation of AmlCompute takes approximately 5 minutes. \n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", + "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this article on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import AmlCompute\n", + "from azureml.core.compute import ComputeTarget\n", + "\n", + "# Choose a name for your cluster.\n", + "amlcompute_cluster_name = \"automlcl\"\n", + "\n", + "found = False\n", + "# Check if this compute target already exists in the workspace.\n", + "cts = ws.compute_targets\n", + "if amlcompute_cluster_name in cts and cts[amlcompute_cluster_name].type == 'AmlCompute':\n", + " found = True\n", + " print('Found existing compute target.')\n", + " compute_target = cts[amlcompute_cluster_name]\n", + " \n", + "if not found:\n", + " print('Creating a new compute target...')\n", + " provisioning_config = AmlCompute.provisioning_configuration(vm_size = \"STANDARD_D2_V2\", # for GPU, use \"STANDARD_NC6\"\n", + " #vm_priority = 'lowpriority', # optional\n", + " max_nodes = 6)\n", + "\n", + " # Create the cluster.\n", + " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, provisioning_config)\n", + " \n", + " # Can poll for a minimum number of nodes and for a specific timeout.\n", + " # If no min_node_count is provided, it will use the scale settings for the cluster.\n", + " compute_target.wait_for_completion(show_output = True, min_node_count = None, timeout_in_minutes = 20)\n", + " \n", + " # For a more detailed view of current AmlCompute status, use get_status()." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data\n", + "\n", + "Here load the data in the get_data script to be utilized in azure compute. To do this, first load all the necessary libraries and dependencies to set up paths for the data and to create the conda_run_config." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if not os.path.isdir('data'):\n", + " os.mkdir('data')\n", + " \n", + "if not os.path.exists(project_folder):\n", + " os.makedirs(project_folder)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.runconfig import RunConfiguration\n", + "from azureml.core.conda_dependencies import CondaDependencies\n", + "\n", + "# create a new RunConfig object\n", + "conda_run_config = RunConfiguration(framework=\"python\")\n", + "\n", + "# Set compute target to AmlCompute\n", + "conda_run_config.target = compute_target\n", + "conda_run_config.environment.docker.enabled = True\n", + "conda_run_config.environment.docker.base_image = azureml.core.runconfig.DEFAULT_CPU_IMAGE\n", + "\n", + "\n", + "cd = CondaDependencies.create(pip_packages=['azureml-sdk[automl]'], conda_packages=['numpy'])\n", + "conda_run_config.environment.python.conda_dependencies = cd" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load Data\n", + "\n", + "Here create the script to be run in azure compute for loading the data, load the hardware dataset into the X and y variables. Next split the data using train_test_split and return X_train and y_train for training the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/machineData.csv\"\n", + "dflow = dprep.auto_read_file(data)\n", + "dflow.get_profile()\n", + "X = dflow.drop_columns(columns=['ERP'])\n", + "y = dflow.keep_columns(columns=['ERP'], validate_column_exists=True)\n", + "X_train, X_test = X.random_split(percentage=0.8, seed=223)\n", + "y_train, y_test = y.random_split(percentage=0.8, seed=223) \n", + "dflow.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## Train\n", + "\n", + "Instantiate an `AutoMLConfig` object to specify the settings and data used to run the experiment.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**task**|classification or regression|\n", + "|**primary_metric**|This is the metric that you want to optimize. Regression supports the following primary metrics:
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error|\n", + "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", + "|**iterations**|Number of iterations. In each iteration AutoML trains a specific pipeline with the data.|\n", + "|**n_cross_validations**|Number of cross validation splits.|\n", + "|**X**|(sparse) array-like, shape = [n_samples, n_features]|\n", + "|**y**|(sparse) array-like, shape = [n_samples, ], targets values.|\n", + "|**path**|Relative path to the project folder. AutoML stores configuration files for the experiment under this folder. You can specify a new empty folder.|\n", + "\n", + "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### If you would like to see even better results increase \"iteration_time_out minutes\" to 10+ mins and increase \"iterations\" to a minimum of 30" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "automl_settings = {\n", + " \"iteration_timeout_minutes\": 5,\n", + " \"iterations\": 10,\n", + " \"n_cross_validations\": 5,\n", + " \"primary_metric\": 'spearman_correlation',\n", + " \"preprocess\": True,\n", + " \"max_concurrent_iterations\": 5,\n", + " \"verbosity\": logging.INFO,\n", + "}\n", + "\n", + "automl_config = AutoMLConfig(task = 'regression',\n", + " debug_log = 'automl_errors_20190417.log',\n", + " path = project_folder,\n", + " run_configuration=conda_run_config,\n", + " X = X_train,\n", + " y = y_train,\n", + " **automl_settings\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run = experiment.submit(automl_config, show_output = False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Widget for Monitoring Runs\n", + "\n", + "The widget will first report a \"loading\" status while running the first iteration. After completing the first iteration, an auto-updating graph and table will be shown. The widget will refresh once per minute, so you should see the graph update as child runs complete.\n", + "\n", + "**Note:** The widget displays a link at the bottom. Use this link to open a web interface to explore the individual run details." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.widgets import RunDetails\n", + "RunDetails(remote_run).show() " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Wait until the run finishes.\n", + "remote_run.wait_for_completion(show_output = True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieve All Child Runs\n", + "You can also use SDK methods to fetch all the child runs and see individual metrics that we log." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "children = list(remote_run.get_children())\n", + "metricslist = {}\n", + "for run in children:\n", + " properties = run.get_properties()\n", + " metrics = {k: v for k, v in run.get_metrics().items() if isinstance(v, float)}\n", + " metricslist[int(properties['iteration'])] = metrics\n", + "\n", + "rundata = pd.DataFrame(metricslist).sort_index(1)\n", + "rundata" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieve the Best Model\n", + "Below we select the best pipeline from our iterations. The get_output method returns the best run and the fitted model. The Model includes the pipeline and any pre-processing. Overloads on get_output allow you to retrieve the best run and fitted model for any logged metric or for a particular iteration." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "best_run, fitted_model = remote_run.get_output()\n", + "print(best_run)\n", + "print(fitted_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Best Model Based on Any Other Metric\n", + "Show the run and the model that has the smallest `root_mean_squared_error` value (which turned out to be the same as the one with largest `spearman_correlation` value):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lookup_metric = \"root_mean_squared_error\"\n", + "best_run, fitted_model = remote_run.get_output(metric = lookup_metric)\n", + "print(best_run)\n", + "print(fitted_model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "iteration = 3\n", + "third_run, third_model = remote_run.get_output(iteration = iteration)\n", + "print(third_run)\n", + "print(third_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Register the Fitted Model for Deployment\n", + "If neither metric nor iteration are specified in the register_model call, the iteration with the best primary metric is registered." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "description = 'AutoML Model'\n", + "tags = None\n", + "model = remote_run.register_model(description = description, tags = tags)\n", + "\n", + "print(remote_run.model_id) # This will be written to the script file later in the notebook." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create Scoring Script\n", + "The scoring script is required to generate the image for deployment. It contains the code to do the predictions on input data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile score.py\n", + "import pickle\n", + "import json\n", + "import numpy\n", + "import azureml.train.automl\n", + "from sklearn.externals import joblib\n", + "from azureml.core.model import Model\n", + "\n", + "def init():\n", + " global model\n", + " model_path = Model.get_model_path(model_name = '<>') # this name is model.id of model that we want to deploy\n", + " # deserialize the model file back into a sklearn model\n", + " model = joblib.load(model_path)\n", + "\n", + "def run(rawdata):\n", + " try:\n", + " data = json.loads(rawdata)['data']\n", + " data = numpy.array(data)\n", + " result = model.predict(data)\n", + " except Exception as e:\n", + " result = str(e)\n", + " return json.dumps({\"error\": result})\n", + " return json.dumps({\"result\":result.tolist()})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a YAML File for the Environment" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To ensure the fit results are consistent with the training results, the SDK dependency versions need to be the same as the environment that trains the model. Details about retrieving the versions can be found in notebook [12.auto-ml-retrieve-the-training-sdk-versions](12.auto-ml-retrieve-the-training-sdk-versions.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dependencies = remote_run.get_run_sdk_dependencies(iteration = 1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for p in ['azureml-train-automl', 'azureml-sdk', 'azureml-core']:\n", + " print('{}\\t{}'.format(p, dependencies[p]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn'], pip_packages=['azureml-sdk[automl]'])\n", + "\n", + "conda_env_file_name = 'myenv.yml'\n", + "myenv.save_to_file('.', conda_env_file_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Substitute the actual version number in the environment file.\n", + "# This is not strictly needed in this notebook because the model should have been generated using the current SDK version.\n", + "# However, we include this in case this code is used on an experiment from a previous SDK version.\n", + "\n", + "with open(conda_env_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(conda_env_file_name, 'w') as cefw:\n", + " cefw.write(content.replace(azureml.core.VERSION, dependencies['azureml-sdk']))\n", + "\n", + "# Substitute the actual model id in the script file.\n", + "\n", + "script_file_name = 'score.py'\n", + "\n", + "with open(script_file_name, 'r') as cefr:\n", + " content = cefr.read()\n", + "\n", + "with open(script_file_name, 'w') as cefw:\n", + " cefw.write(content.replace('<>', remote_run.model_id))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a Container Image\n", + "\n", + "Next use Azure Container Instances for deploying models as a web service for quickly deploying and validating your model\n", + "or when testing a model that is under development." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.image import Image, ContainerImage\n", + "\n", + "image_config = ContainerImage.image_configuration(runtime= \"python\",\n", + " execution_script = script_file_name,\n", + " conda_file = conda_env_file_name,\n", + " tags = {'area': \"digits\", 'type': \"automl_regression\"},\n", + " description = \"Image for automl regression sample\")\n", + "\n", + "image = Image.create(name = \"automlsampleimage\",\n", + " # this is the model object \n", + " models = [model],\n", + " image_config = image_config, \n", + " workspace = ws)\n", + "\n", + "image.wait_for_creation(show_output = True)\n", + "\n", + "if image.creation_state == 'Failed':\n", + " print(\"Image build log at: \" + image.image_build_log_uri)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deploy the Image as a Web Service on Azure Container Instance\n", + "\n", + "Deploy an image that contains the model and other assets needed by the service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import AciWebservice\n", + "\n", + "aciconfig = AciWebservice.deploy_configuration(cpu_cores = 1, \n", + " memory_gb = 1, \n", + " tags = {'area': \"digits\", 'type': \"automl_regression\"}, \n", + " description = 'sample service for Automl Regression')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.webservice import Webservice\n", + "\n", + "aci_service_name = 'automl-sample-hardware'\n", + "print(aci_service_name)\n", + "aci_service = Webservice.deploy_from_image(deployment_config = aciconfig,\n", + " image = image,\n", + " name = aci_service_name,\n", + " workspace = ws)\n", + "aci_service.wait_for_deployment(True)\n", + "print(aci_service.state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delete a Web Service\n", + "\n", + "Deletes the specified web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.delete()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Get Logs from a Deployed Web Service\n", + "\n", + "Gets logs from a deployed web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#aci_service.get_logs()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test\n", + "\n", + "Now that the model is trained, split the data in the same way the data was split for training (The difference here is the data is being split locally) and then run the test data through the trained model to get the predicted values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_test = X_test.to_pandas_dataframe()\n", + "y_test = y_test.to_pandas_dataframe()\n", + "y_test = np.array(y_test)\n", + "y_test = y_test[:,0]\n", + "X_train = X_train.to_pandas_dataframe()\n", + "y_train = y_train.to_pandas_dataframe()\n", + "y_train = np.array(y_train)\n", + "y_train = y_train[:,0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Predict on training and test set, and calculate residual values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "y_pred_train = fitted_model.predict(X_train)\n", + "y_residual_train = y_train - y_pred_train\n", + "\n", + "y_pred_test = fitted_model.predict(X_test)\n", + "y_residual_test = y_test - y_pred_test" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Calculate metrics for the prediction\n", + "\n", + "Now visualize the data on a scatter plot to show what our truth (actual) values are compared to the predicted values \n", + "from the trained model that was returned." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "from sklearn.metrics import mean_squared_error, r2_score\n", + "\n", + "# Set up a multi-plot chart.\n", + "f, (a0, a1) = plt.subplots(1, 2, gridspec_kw = {'width_ratios':[1, 1], 'wspace':0, 'hspace': 0})\n", + "f.suptitle('Regression Residual Values', fontsize = 18)\n", + "f.set_figheight(6)\n", + "f.set_figwidth(16)\n", + "\n", + "# Plot residual values of training set.\n", + "a0.axis([0, 360, -200, 200])\n", + "a0.plot(y_residual_train, 'bo', alpha = 0.5)\n", + "a0.plot([-10,360],[0,0], 'r-', lw = 3)\n", + "a0.text(16,170,'RMSE = {0:.2f}'.format(np.sqrt(mean_squared_error(y_train, y_pred_train))), fontsize = 12)\n", + "a0.text(16,140,'R2 score = {0:.2f}'.format(r2_score(y_train, y_pred_train)),fontsize = 12)\n", + "a0.set_xlabel('Training samples', fontsize = 12)\n", + "a0.set_ylabel('Residual Values', fontsize = 12)\n", + "\n", + "# Plot residual values of test set.\n", + "a1.axis([0, 90, -200, 200])\n", + "a1.plot(y_residual_test, 'bo', alpha = 0.5)\n", + "a1.plot([-10,360],[0,0], 'r-', lw = 3)\n", + "a1.text(5,170,'RMSE = {0:.2f}'.format(np.sqrt(mean_squared_error(y_test, y_pred_test))), fontsize = 12)\n", + "a1.text(5,140,'R2 score = {0:.2f}'.format(r2_score(y_test, y_pred_test)),fontsize = 12)\n", + "a1.set_xlabel('Test samples', fontsize = 12)\n", + "a1.set_yticklabels([])\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "test_pred = plt.scatter(y_test, y_pred_test, color='')\n", + "test_test = plt.scatter(y_test, y_test, color='g')\n", + "plt.legend((test_pred, test_test), ('prediction', 'truth'), loc='upper left', fontsize=8)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Acknowledgements\n", + "This Predicting Hardware Performance Dataset is made available under the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication License: https://creativecommons.org/publicdomain/zero/1.0/. Any rights in individual contents of the database are licensed under the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication License: https://creativecommons.org/publicdomain/zero/1.0/ . The dataset itself can be found here: https://www.kaggle.com/faizunnabi/comp-hardware-performance and https://archive.ics.uci.edu/ml/datasets/Computer+Hardware\n", + "\n", + "_**Citation Found Here**_\n" + ] + } ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" + "metadata": { + "authors": [ + { + "name": "v-rasav" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.1" + } }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.1" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.yml b/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.yml new file mode 100644 index 00000000..ddc29fa8 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/regression-hardware-performance/auto-ml-regression-hardware-performance.yml @@ -0,0 +1,8 @@ +name: auto-ml-regression-hardware-performance +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/regression/auto-ml-regression.yml b/how-to-use-azureml/automated-machine-learning/regression/auto-ml-regression.yml new file mode 100644 index 00000000..18789ff3 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/regression/auto-ml-regression.yml @@ -0,0 +1,9 @@ +name: auto-ml-regression +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml + - paramiko<2.5.0 diff --git a/how-to-use-azureml/automated-machine-learning/remote-amlcompute/auto-ml-remote-amlcompute.yml b/how-to-use-azureml/automated-machine-learning/remote-amlcompute/auto-ml-remote-amlcompute.yml new file mode 100644 index 00000000..41b4f214 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/remote-amlcompute/auto-ml-remote-amlcompute.yml @@ -0,0 +1,8 @@ +name: auto-ml-remote-amlcompute +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/sample-weight/auto-ml-sample-weight.yml b/how-to-use-azureml/automated-machine-learning/sample-weight/auto-ml-sample-weight.yml new file mode 100644 index 00000000..954b57f0 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/sample-weight/auto-ml-sample-weight.yml @@ -0,0 +1,8 @@ +name: auto-ml-sample-weight +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/sparse-data-train-test-split/auto-ml-sparse-data-train-test-split.yml b/how-to-use-azureml/automated-machine-learning/sparse-data-train-test-split/auto-ml-sparse-data-train-test-split.yml new file mode 100644 index 00000000..4039b384 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/sparse-data-train-test-split/auto-ml-sparse-data-train-test-split.yml @@ -0,0 +1,8 @@ +name: auto-ml-sparse-data-train-test-split +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/automated-machine-learning/sql-server/energy-demand/ForecastEnergyDemand.sql b/how-to-use-azureml/automated-machine-learning/sql-server/energy-demand/ForecastEnergyDemand.sql new file mode 100644 index 00000000..ca671d88 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/sql-server/energy-demand/ForecastEnergyDemand.sql @@ -0,0 +1,23 @@ +-- This shows using the AutoMLForecast stored procedure to predict using a forecasting model for the nyc_energy dataset. + +DECLARE @Model NVARCHAR(MAX) = (SELECT TOP 1 Model FROM dbo.aml_model + WHERE ExperimentName = 'automl-sql-forecast' + ORDER BY CreatedDate DESC) + +DECLARE @max_horizon INT = 48 +DECLARE @split_time NVARCHAR(22) = (SELECT DATEADD(hour, -@max_horizon, MAX(timeStamp)) FROM nyc_energy WHERE demand IS NOT NULL) + +DECLARE @TestDataQuery NVARCHAR(MAX) = ' +SELECT CAST(timeStamp AS NVARCHAR(30)) AS timeStamp, + demand, + precip, + temp +FROM nyc_energy +WHERE demand IS NOT NULL AND precip IS NOT NULL AND temp IS NOT NULL +AND timeStamp > ''' + @split_time + '''' + +EXEC dbo.AutoMLForecast @input_query=@TestDataQuery, +@label_column='demand', +@time_column_name='timeStamp', +@model=@model +WITH RESULT SETS ((timeStamp DATETIME, grain NVARCHAR(255), predicted_demand FLOAT, precip FLOAT, temp FLOAT, actual_demand FLOAT)) diff --git a/how-to-use-azureml/automated-machine-learning/sql-server/energy-demand/TrainEnergyDemand.sql b/how-to-use-azureml/automated-machine-learning/sql-server/energy-demand/TrainEnergyDemand.sql index a38e5232..0dcf9f81 100644 --- a/how-to-use-azureml/automated-machine-learning/sql-server/energy-demand/TrainEnergyDemand.sql +++ b/how-to-use-azureml/automated-machine-learning/sql-server/energy-demand/TrainEnergyDemand.sql @@ -1,21 +1,25 @@ -- This shows using the AutoMLTrain stored procedure to create a forecasting model for the nyc_energy dataset. -INSERT INTO dbo.aml_model(RunId, ExperimentName, Model, LogFileText, WorkspaceName) -EXEC dbo.AutoMLTrain @input_query=' +DECLARE @max_horizon INT = 48 +DECLARE @split_time NVARCHAR(22) = (SELECT DATEADD(hour, -@max_horizon, MAX(timeStamp)) FROM nyc_energy WHERE demand IS NOT NULL) + +DECLARE @TrainDataQuery NVARCHAR(MAX) = ' SELECT CAST(timeStamp as NVARCHAR(30)) as timeStamp, demand, precip, - temp, - CASE WHEN timeStamp < ''2017-01-01'' THEN 0 ELSE 1 END AS is_validate_column + temp FROM nyc_energy WHERE demand IS NOT NULL AND precip IS NOT NULL AND temp IS NOT NULL -and timeStamp < ''2017-02-01''', +and timeStamp < ''' + @split_time + '''' + +INSERT INTO dbo.aml_model(RunId, ExperimentName, Model, LogFileText, WorkspaceName) +EXEC dbo.AutoMLTrain @input_query= @TrainDataQuery, @label_column='demand', @task='forecasting', @iterations=10, @iteration_timeout_minutes=5, @time_column_name='timeStamp', -@is_validate_column='is_validate_column', +@max_horizon=@max_horizon, @experiment_name='automl-sql-forecast', @primary_metric='normalized_root_mean_squared_error' diff --git a/how-to-use-azureml/automated-machine-learning/sql-server/energy-demand/auto-ml-sql-energy-demand.ipynb b/how-to-use-azureml/automated-machine-learning/sql-server/energy-demand/auto-ml-sql-energy-demand.ipynb index 3136f29f..2d8e9115 100644 --- a/how-to-use-azureml/automated-machine-learning/sql-server/energy-demand/auto-ml-sql-energy-demand.ipynb +++ b/how-to-use-azureml/automated-machine-learning/sql-server/energy-demand/auto-ml-sql-energy-demand.ipynb @@ -1,141 +1,141 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Train a model and use it for prediction\r\n", - "\r\n", - "Before running this notebook, run the auto-ml-sql-setup.ipynb notebook." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/sql-server/energy-demand/auto-ml-sql-energy-demand.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Set the default database" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "USE [automl]\r\n", - "GO" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Use the AutoMLTrain stored procedure to create a forecasting model for the nyc_energy dataset." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "INSERT INTO dbo.aml_model(RunId, ExperimentName, Model, LogFileText, WorkspaceName)\r\n", - "EXEC dbo.AutoMLTrain @input_query='\r\n", - "SELECT CAST(timeStamp as NVARCHAR(30)) as timeStamp,\r\n", - " demand,\r\n", - "\t precip,\r\n", - "\t temp,\r\n", - "\t CASE WHEN timeStamp < ''2017-01-01'' THEN 0 ELSE 1 END AS is_validate_column\r\n", - "FROM nyc_energy\r\n", - "WHERE demand IS NOT NULL AND precip IS NOT NULL AND temp IS NOT NULL\r\n", - "and timeStamp < ''2017-02-01''',\r\n", - "@label_column='demand',\r\n", - "@task='forecasting',\r\n", - "@iterations=10,\r\n", - "@iteration_timeout_minutes=5,\r\n", - "@time_column_name='timeStamp',\r\n", - "@is_validate_column='is_validate_column',\r\n", - "@experiment_name='automl-sql-forecast',\r\n", - "@primary_metric='normalized_root_mean_squared_error'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Use the AutoMLPredict stored procedure to predict using the forecasting model for the nyc_energy dataset." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "DECLARE @Model NVARCHAR(MAX) = (SELECT TOP 1 Model FROM dbo.aml_model\r\n", - " WHERE ExperimentName = 'automl-sql-forecast'\r\n", - "\t\t\t\t\t\t\t\tORDER BY CreatedDate DESC)\r\n", - "\r\n", - "EXEC dbo.AutoMLPredict @input_query='\r\n", - "SELECT CAST(timeStamp AS NVARCHAR(30)) AS timeStamp,\r\n", - " demand,\r\n", - "\t precip,\r\n", - "\t temp\r\n", - "FROM nyc_energy\r\n", - "WHERE demand IS NOT NULL AND precip IS NOT NULL AND temp IS NOT NULL\r\n", - "AND timeStamp >= ''2017-02-01''',\r\n", - "@label_column='demand',\r\n", - "@model=@model\r\n", - "WITH RESULT SETS ((timeStamp NVARCHAR(30), actual_demand FLOAT, precip FLOAT, temp FLOAT, predicted_demand FLOAT))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## List all the metrics for all iterations for the most recent training run." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "DECLARE @RunId NVARCHAR(43)\r\n", - "DECLARE @ExperimentName NVARCHAR(255)\r\n", - "\r\n", - "SELECT TOP 1 @ExperimentName=ExperimentName, @RunId=SUBSTRING(RunId, 1, 43)\r\n", - "FROM aml_model\r\n", - "ORDER BY CreatedDate DESC\r\n", - "\r\n", - "EXEC dbo.AutoMLGetMetrics @RunId, @ExperimentName" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "jeffshep" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Train a model and use it for prediction\r\n", + "\r\n", + "Before running this notebook, run the auto-ml-sql-setup.ipynb notebook." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/sql-server/energy-demand/auto-ml-sql-energy-demand.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set the default database" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "USE [automl]\r\n", + "GO" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use the AutoMLTrain stored procedure to create a forecasting model for the nyc_energy dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "INSERT INTO dbo.aml_model(RunId, ExperimentName, Model, LogFileText, WorkspaceName)\r\n", + "EXEC dbo.AutoMLTrain @input_query='\r\n", + "SELECT CAST(timeStamp as NVARCHAR(30)) as timeStamp,\r\n", + " demand,\r\n", + "\t precip,\r\n", + "\t temp,\r\n", + "\t CASE WHEN timeStamp < ''2017-01-01'' THEN 0 ELSE 1 END AS is_validate_column\r\n", + "FROM nyc_energy\r\n", + "WHERE demand IS NOT NULL AND precip IS NOT NULL AND temp IS NOT NULL\r\n", + "and timeStamp < ''2017-02-01''',\r\n", + "@label_column='demand',\r\n", + "@task='forecasting',\r\n", + "@iterations=10,\r\n", + "@iteration_timeout_minutes=5,\r\n", + "@time_column_name='timeStamp',\r\n", + "@is_validate_column='is_validate_column',\r\n", + "@experiment_name='automl-sql-forecast',\r\n", + "@primary_metric='normalized_root_mean_squared_error'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use the AutoMLPredict stored procedure to predict using the forecasting model for the nyc_energy dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "DECLARE @Model NVARCHAR(MAX) = (SELECT TOP 1 Model FROM dbo.aml_model\r\n", + " WHERE ExperimentName = 'automl-sql-forecast'\r\n", + "\t\t\t\t\t\t\t\tORDER BY CreatedDate DESC)\r\n", + "\r\n", + "EXEC dbo.AutoMLPredict @input_query='\r\n", + "SELECT CAST(timeStamp AS NVARCHAR(30)) AS timeStamp,\r\n", + " demand,\r\n", + "\t precip,\r\n", + "\t temp\r\n", + "FROM nyc_energy\r\n", + "WHERE demand IS NOT NULL AND precip IS NOT NULL AND temp IS NOT NULL\r\n", + "AND timeStamp >= ''2017-02-01''',\r\n", + "@label_column='demand',\r\n", + "@model=@model\r\n", + "WITH RESULT SETS ((timeStamp NVARCHAR(30), actual_demand FLOAT, precip FLOAT, temp FLOAT, predicted_demand FLOAT))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## List all the metrics for all iterations for the most recent training run." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "DECLARE @RunId NVARCHAR(43)\r\n", + "DECLARE @ExperimentName NVARCHAR(255)\r\n", + "\r\n", + "SELECT TOP 1 @ExperimentName=ExperimentName, @RunId=SUBSTRING(RunId, 1, 43)\r\n", + "FROM aml_model\r\n", + "ORDER BY CreatedDate DESC\r\n", + "\r\n", + "EXEC dbo.AutoMLGetMetrics @RunId, @ExperimentName" + ] + } ], - "kernelspec": { - "display_name": "SQL", - "language": "sql", - "name": "SQL" + "metadata": { + "authors": [ + { + "name": "jeffshep" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "sql", + "name": "python36" + }, + "language_info": { + "name": "sql", + "version": "" + } }, - "language_info": { - "name": "sql", - "version": "" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/automated-machine-learning/sql-server/setup/AutoMLForecast.sql b/how-to-use-azureml/automated-machine-learning/sql-server/setup/AutoMLForecast.sql new file mode 100644 index 00000000..2635bf62 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/sql-server/setup/AutoMLForecast.sql @@ -0,0 +1,92 @@ +-- This procedure forecast values based on a forecasting model returned by AutoMLTrain. +-- It returns a dataset with the forecasted values. +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +CREATE OR ALTER PROCEDURE [dbo].[AutoMLForecast] + ( + @input_query NVARCHAR(MAX), -- A SQL query returning data to predict on. + @model NVARCHAR(MAX), -- A model returned from AutoMLTrain. + @time_column_name NVARCHAR(255)='', -- The name of the timestamp column for forecasting. + @label_column NVARCHAR(255)='', -- Optional name of the column from input_query, which should be ignored when predicting + @y_query_column NVARCHAR(255)='', -- Optional value column that can be used for predicting. + -- If specified, this can contain values for past times (after the model was trained) + -- and contain Nan for future times. + @forecast_column_name NVARCHAR(255) = 'predicted' + -- The name of the output column containing the forecast value. + ) AS +BEGIN + + EXEC sp_execute_external_script @language = N'Python', @script = N'import pandas as pd +import azureml.core +import numpy as np +from azureml.train.automl import AutoMLConfig +import pickle +import codecs + +model_obj = pickle.loads(codecs.decode(model.encode(), "base64")) + +test_data = input_data.copy() + +if label_column != "" and label_column is not None: + y_test = test_data.pop(label_column).values +else: + y_test = None + +if y_query_column != "" and y_query_column is not None: + y_query = test_data.pop(y_query_column).values +else: + y_query = np.repeat(np.nan, len(test_data)) + +X_test = test_data + +if time_column_name != "" and time_column_name is not None: + X_test[time_column_name] = pd.to_datetime(X_test[time_column_name]) + +y_fcst, X_trans = model_obj.forecast(X_test, y_query) + +def align_outputs(y_forecast, X_trans, X_test, y_test, forecast_column_name): + # Demonstrates how to get the output aligned to the inputs + # using pandas indexes. Helps understand what happened if + # the output shape differs from the input shape, or if + # the data got re-sorted by time and grain during forecasting. + + # Typical causes of misalignment are: + # * we predicted some periods that were missing in actuals -> drop from eval + # * model was asked to predict past max_horizon -> increase max horizon + # * data at start of X_test was needed for lags -> provide previous periods + + df_fcst = pd.DataFrame({forecast_column_name : y_forecast}) + # y and X outputs are aligned by forecast() function contract + df_fcst.index = X_trans.index + + # align original X_test to y_test + X_test_full = X_test.copy() + if y_test is not None: + X_test_full[label_column] = y_test + + # X_test_full does not include origin, so reset for merge + df_fcst.reset_index(inplace=True) + X_test_full = X_test_full.reset_index().drop(columns=''index'') + together = df_fcst.merge(X_test_full, how=''right'') + + # drop rows where prediction or actuals are nan + # happens because of missing actuals + # or at edges of time due to lags/rolling windows + clean = together[together[[label_column, forecast_column_name]].notnull().all(axis=1)] + return(clean) + +combined_output = align_outputs(y_fcst, X_trans, X_test, y_test, forecast_column_name) + +' + , @input_data_1 = @input_query + , @input_data_1_name = N'input_data' + , @output_data_1_name = N'combined_output' + , @params = N'@model NVARCHAR(MAX), @time_column_name NVARCHAR(255), @label_column NVARCHAR(255), @y_query_column NVARCHAR(255), @forecast_column_name NVARCHAR(255)' + , @model = @model + , @time_column_name = @time_column_name + , @label_column = @label_column + , @y_query_column = @y_query_column + , @forecast_column_name = @forecast_column_name +END diff --git a/how-to-use-azureml/automated-machine-learning/sql-server/setup/AutoMLTrain.sql b/how-to-use-azureml/automated-machine-learning/sql-server/setup/AutoMLTrain.sql index 02bc975b..d0840ac2 100644 --- a/how-to-use-azureml/automated-machine-learning/sql-server/setup/AutoMLTrain.sql +++ b/how-to-use-azureml/automated-machine-learning/sql-server/setup/AutoMLTrain.sql @@ -69,7 +69,10 @@ CREATE OR ALTER PROCEDURE [dbo].[AutoMLTrain] @is_validate_column NVARCHAR(255)='', -- The name of the column in the result of @input_query that indicates if the row is for training or validation. -- In the values of the column, 0 means for training and 1 means for validation. @time_column_name NVARCHAR(255)='', -- The name of the timestamp column for forecasting. - @connection_name NVARCHAR(255)='default' -- The AML connection to use. + @connection_name NVARCHAR(255)='default', -- The AML connection to use. + @max_horizon INT = 0 -- A forecast horizon is a time span into the future (or just beyond the latest date in the training data) + -- where forecasts of the target quantity are needed. + -- For example, if data is recorded daily and max_horizon is 5, we will predict 5 days ahead. ) AS BEGIN @@ -151,8 +154,10 @@ if __name__.startswith("sqlindb"): if time_column_name != "" and time_column_name is not None: automl_settings = { "time_column_name": time_column_name } preprocess = False + if max_horizon > 0: + automl_settings["max_horizon"] = max_horizon - log_file_name = "automl_errors.log" + log_file_name = "automl_sqlindb_errors.log" automl_config = AutoMLConfig(task = task, debug_log = log_file_name, @@ -163,7 +168,6 @@ if __name__.startswith("sqlindb"): n_cross_validations = n_cross_validations, preprocess = preprocess, verbosity = logging.INFO, - enable_ensembling = False, X = X_train, y = y_train, path = project_folder, @@ -211,7 +215,8 @@ if __name__.startswith("sqlindb"): @tenantid NVARCHAR(255), @appid NVARCHAR(255), @password NVARCHAR(255), - @config_file NVARCHAR(255)' + @config_file NVARCHAR(255), + @max_horizon INT' , @label_column = @label_column , @primary_metric = @primary_metric , @iterations = @iterations @@ -230,5 +235,6 @@ if __name__.startswith("sqlindb"): , @appid = @appid , @password = @password , @config_file = @config_file + , @max_horizon = @max_horizon WITH RESULT SETS ((best_run NVARCHAR(250), experiment_name NVARCHAR(100), fitted_model VARCHAR(MAX), log_file_text NVARCHAR(MAX), workspace NVARCHAR(100))) END \ No newline at end of file diff --git a/how-to-use-azureml/automated-machine-learning/sql-server/setup/auto-ml-sql-setup.ipynb b/how-to-use-azureml/automated-machine-learning/sql-server/setup/auto-ml-sql-setup.ipynb index 902818a4..8bf4e0d3 100644 --- a/how-to-use-azureml/automated-machine-learning/sql-server/setup/auto-ml-sql-setup.ipynb +++ b/how-to-use-azureml/automated-machine-learning/sql-server/setup/auto-ml-sql-setup.ipynb @@ -1,562 +1,562 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Set up Azure ML Automated Machine Learning on SQL Server 2019 CTP 2.4 big data cluster\r\n", - "\r\n", - "\\# Prerequisites: \r\n", - "\\# - An Azure subscription and resource group \r\n", - "\\# - An Azure Machine Learning workspace \r\n", - "\\# - A SQL Server 2019 CTP 2.4 big data cluster with Internet access and a database named 'automl' \r\n", - "\\# - Azure CLI \r\n", - "\\# - kubectl command \r\n", - "\\# - The https://github.com/Azure/MachineLearningNotebooks repository downloaded (cloned) to your local machine\r\n", - "\r\n", - "\\# In the 'automl' database, create a table named 'dbo.nyc_energy' as follows: \r\n", - "\\# - In SQL Server Management Studio, right-click the 'automl' database, select Tasks, then Import Flat File. \r\n", - "\\# - Select the file AzureMlCli\\notebooks\\how-to-use-azureml\\automated-machine-learning\\forecasting-energy-demand\\nyc_energy.csv. \r\n", - "\\# - Using the \"Modify Columns\" page, allow nulls for all columns. \r\n", - "\r\n", - "\\# Create an Azure Machine Learning Workspace using the instructions at https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-workspace \r\n", - "\r\n", - "\\# Create an Azure service principal. You can do this with the following commands: \r\n", - "\r\n", - "az login \r\n", - "az account set --subscription *subscriptionid* \r\n", - "\r\n", - "\\# The following command prints out the **appId** and **tenant**, \r\n", - "\\# which you insert into the indicated cell later in this notebook \r\n", - "\\# to allow AutoML to authenticate with Azure: \r\n", - "\r\n", - "az ad sp create-for-rbac --name *principlename* --password *password*\r\n", - "\r\n", - "\\# Log into the master instance of SQL Server 2019 CTP 2.4: \r\n", - "kubectl exec -it mssql-master-pool-0 -n *clustername* -c mssql-server -- /bin/bash\r\n", - "\r\n", - "mkdir /tmp/aml\r\n", - "\r\n", - "cd /tmp/aml\r\n", - "\r\n", - "\\# **Modify** the following with your subscription_id, resource_group, and workspace_name: \r\n", - "cat > config.json << EOF \r\n", - "{ \r\n", - " \"subscription_id\": \"123456ab-78cd-0123-45ef-abcd12345678\", \r\n", - " \"resource_group\": \"myrg1\", \r\n", - " \"workspace_name\": \"myws1\" \r\n", - "} \r\n", - "EOF\r\n", - "\r\n", - "\\# The directory referenced below is appropriate for the master instance of SQL Server 2019 CTP 2.4.\r\n", - "\r\n", - "cd /opt/mssql/mlservices/runtime/python/bin\r\n", - "\r\n", - "./python -m pip install azureml-sdk[automl]\r\n", - "\r\n", - "./python -m pip install --upgrade numpy \r\n", - "\r\n", - "./python -m pip install --upgrade sklearn\r\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/sql-server/setup/auto-ml-sql-setup.png)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "-- Enable external scripts to allow invoking Python\r\n", - "sp_configure 'external scripts enabled',1 \r\n", - "reconfigure with override \r\n", - "GO\r\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "-- Use database 'automl'\r\n", - "USE [automl]\r\n", - "GO" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "-- This is a table to hold the Azure ML connection information.\r\n", - "SET ANSI_NULLS ON\r\n", - "GO\r\n", - "\r\n", - "SET QUOTED_IDENTIFIER ON\r\n", - "GO\r\n", - "\r\n", - "CREATE TABLE [dbo].[aml_connection](\r\n", - " [Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,\r\n", - "\t[ConnectionName] [nvarchar](255) NULL,\r\n", - "\t[TenantId] [nvarchar](255) NULL,\r\n", - "\t[AppId] [nvarchar](255) NULL,\r\n", - "\t[Password] [nvarchar](255) NULL,\r\n", - "\t[ConfigFile] [nvarchar](255) NULL\r\n", - ") ON [PRIMARY]\r\n", - "GO" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Copy the values from create-for-rbac above into the cell below" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "-- Use the following values:\r\n", - "-- Leave the name as 'Default'\r\n", - "-- Insert returned by create-for-rbac above\r\n", - "-- Insert returned by create-for-rbac above\r\n", - "-- Insert used in create-for-rbac above\r\n", - "-- Leave as '/tmp/aml/config.json'\r\n", - "INSERT INTO [dbo].[aml_connection] \r\n", - "VALUES (\r\n", - " N'Default', -- Name\r\n", - " N'11111111-2222-3333-4444-555555555555', -- Tenant\r\n", - " N'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', -- AppId\r\n", - " N'insertpasswordhere', -- Password\r\n", - " N'/tmp/aml/config.json' -- Path\r\n", - " );\r\n", - "GO" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "-- This is a table to hold the results from the AutoMLTrain procedure.\r\n", - "SET ANSI_NULLS ON\r\n", - "GO\r\n", - "\r\n", - "SET QUOTED_IDENTIFIER ON\r\n", - "GO\r\n", - "\r\n", - "CREATE TABLE [dbo].[aml_model](\r\n", - " [Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,\r\n", - " [Model] [varchar](max) NOT NULL, -- The model, which can be passed to AutoMLPredict for testing or prediction.\r\n", - " [RunId] [nvarchar](250) NULL, -- The RunId, which can be used to view the model in the Azure Portal.\r\n", - " [CreatedDate] [datetime] NULL,\r\n", - " [ExperimentName] [nvarchar](100) NULL, -- Azure ML Experiment Name\r\n", - " [WorkspaceName] [nvarchar](100) NULL, -- Azure ML Workspace Name\r\n", - "\t[LogFileText] [nvarchar](max) NULL\r\n", - ") \r\n", - "GO\r\n", - "\r\n", - "ALTER TABLE [dbo].[aml_model] ADD DEFAULT (getutcdate()) FOR [CreatedDate]\r\n", - "GO\r\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "-- This stored procedure uses automated machine learning to train several models\r\n", - "-- and return the best model.\r\n", - "--\r\n", - "-- The result set has several columns:\r\n", - "-- best_run - ID of the best model found\r\n", - "-- experiment_name - training run name\r\n", - "-- fitted_model - best model found\r\n", - "-- log_file_text - console output\r\n", - "-- workspace - name of the Azure ML workspace where run history is stored\r\n", - "--\r\n", - "-- An example call for a classification problem is:\r\n", - "-- insert into dbo.aml_model(RunId, ExperimentName, Model, LogFileText, WorkspaceName)\r\n", - "-- exec dbo.AutoMLTrain @input_query='\r\n", - "-- SELECT top 100000 \r\n", - "-- CAST([pickup_datetime] AS NVARCHAR(30)) AS pickup_datetime\r\n", - "-- ,CAST([dropoff_datetime] AS NVARCHAR(30)) AS dropoff_datetime\r\n", - "-- ,[passenger_count]\r\n", - "-- ,[trip_time_in_secs]\r\n", - "-- ,[trip_distance]\r\n", - "-- ,[payment_type]\r\n", - "-- ,[tip_class]\r\n", - "-- FROM [dbo].[nyctaxi_sample] order by [hack_license] ',\r\n", - "-- @label_column = 'tip_class',\r\n", - "-- @iterations=10\r\n", - "-- \r\n", - "-- An example call for forecasting is:\r\n", - "-- insert into dbo.aml_model(RunId, ExperimentName, Model, LogFileText, WorkspaceName)\r\n", - "-- exec dbo.AutoMLTrain @input_query='\r\n", - "-- select cast(timeStamp as nvarchar(30)) as timeStamp,\r\n", - "-- demand,\r\n", - "-- \t precip,\r\n", - "-- \t temp,\r\n", - "-- case when timeStamp < ''2017-01-01'' then 0 else 1 end as is_validate_column\r\n", - "-- from nyc_energy\r\n", - "-- where demand is not null and precip is not null and temp is not null\r\n", - "-- and timeStamp < ''2017-02-01''',\r\n", - "-- @label_column='demand',\r\n", - "-- @task='forecasting',\r\n", - "-- @iterations=10,\r\n", - "-- @iteration_timeout_minutes=5,\r\n", - "-- @time_column_name='timeStamp',\r\n", - "-- @is_validate_column='is_validate_column',\r\n", - "-- @experiment_name='automl-sql-forecast',\r\n", - "-- @primary_metric='normalized_root_mean_squared_error'\r\n", - "\r\n", - "SET ANSI_NULLS ON\r\n", - "GO\r\n", - "SET QUOTED_IDENTIFIER ON\r\n", - "GO\r\n", - "CREATE OR ALTER PROCEDURE [dbo].[AutoMLTrain]\r\n", - " (\r\n", - " @input_query NVARCHAR(MAX), -- The SQL Query that will return the data to train and validate the model.\r\n", - " @label_column NVARCHAR(255)='Label', -- The name of the column in the result of @input_query that is the label.\r\n", - " @primary_metric NVARCHAR(40)='AUC_weighted', -- The metric to optimize.\r\n", - " @iterations INT=100, -- The maximum number of pipelines to train.\r\n", - " @task NVARCHAR(40)='classification', -- The type of task. Can be classification, regression or forecasting.\r\n", - " @experiment_name NVARCHAR(32)='automl-sql-test', -- This can be used to find the experiment in the Azure Portal.\r\n", - " @iteration_timeout_minutes INT = 15, -- The maximum time in minutes for training a single pipeline. \r\n", - " @experiment_timeout_minutes INT = 60, -- The maximum time in minutes for training all pipelines.\r\n", - " @n_cross_validations INT = 3, -- The number of cross validations.\r\n", - " @blacklist_models NVARCHAR(MAX) = '', -- A comma separated list of algos that will not be used.\r\n", - " -- The list of possible models can be found at:\r\n", - " -- https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#configure-your-experiment-settings\r\n", - " @whitelist_models NVARCHAR(MAX) = '', -- A comma separated list of algos that can be used.\r\n", - " -- The list of possible models can be found at:\r\n", - " -- https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#configure-your-experiment-settings\r\n", - " @experiment_exit_score FLOAT = 0, -- Stop the experiment if this score is acheived.\r\n", - " @sample_weight_column NVARCHAR(255)='', -- The name of the column in the result of @input_query that gives a sample weight.\r\n", - " @is_validate_column NVARCHAR(255)='', -- The name of the column in the result of @input_query that indicates if the row is for training or validation.\r\n", - "\t -- In the values of the column, 0 means for training and 1 means for validation.\r\n", - " @time_column_name NVARCHAR(255)='', -- The name of the timestamp column for forecasting.\r\n", - "\t@connection_name NVARCHAR(255)='default' -- The AML connection to use.\r\n", - " ) AS\r\n", - "BEGIN\r\n", - "\r\n", - " DECLARE @tenantid NVARCHAR(255)\r\n", - " DECLARE @appid NVARCHAR(255)\r\n", - " DECLARE @password NVARCHAR(255)\r\n", - " DECLARE @config_file NVARCHAR(255)\r\n", - "\r\n", - "\tSELECT @tenantid=TenantId, @appid=AppId, @password=Password, @config_file=ConfigFile\r\n", - "\tFROM aml_connection\r\n", - "\tWHERE ConnectionName = @connection_name;\r\n", - "\r\n", - "\tEXEC sp_execute_external_script @language = N'Python', @script = N'import pandas as pd\r\n", - "import logging \r\n", - "import azureml.core \r\n", - "import pandas as pd\r\n", - "import numpy as np\r\n", - "from azureml.core.experiment import Experiment \r\n", - "from azureml.train.automl import AutoMLConfig \r\n", - "from sklearn import datasets \r\n", - "import pickle\r\n", - "import codecs\r\n", - "from azureml.core.authentication import ServicePrincipalAuthentication \r\n", - "from azureml.core.workspace import Workspace \r\n", - "\r\n", - "if __name__.startswith(\"sqlindb\"):\r\n", - " auth = ServicePrincipalAuthentication(tenantid, appid, password) \r\n", - " \r\n", - " ws = Workspace.from_config(path=config_file, auth=auth) \r\n", - " \r\n", - " project_folder = \"./sample_projects/\" + experiment_name\r\n", - " \r\n", - " experiment = Experiment(ws, experiment_name) \r\n", - "\r\n", - " data_train = input_data\r\n", - " X_valid = None\r\n", - " y_valid = None\r\n", - " sample_weight_valid = None\r\n", - "\r\n", - " if is_validate_column != \"\" and is_validate_column is not None:\r\n", - " data_train = input_data[input_data[is_validate_column] <= 0]\r\n", - " data_valid = input_data[input_data[is_validate_column] > 0]\r\n", - " data_train.pop(is_validate_column)\r\n", - " data_valid.pop(is_validate_column)\r\n", - " y_valid = data_valid.pop(label_column).values\r\n", - " if sample_weight_column != \"\" and sample_weight_column is not None:\r\n", - " sample_weight_valid = data_valid.pop(sample_weight_column).values\r\n", - " X_valid = data_valid\r\n", - " n_cross_validations = None\r\n", - "\r\n", - " y_train = data_train.pop(label_column).values\r\n", - "\r\n", - " sample_weight = None\r\n", - " if sample_weight_column != \"\" and sample_weight_column is not None:\r\n", - " sample_weight = data_train.pop(sample_weight_column).values\r\n", - "\r\n", - " X_train = data_train\r\n", - "\r\n", - " if experiment_timeout_minutes == 0:\r\n", - " experiment_timeout_minutes = None\r\n", - "\r\n", - " if experiment_exit_score == 0:\r\n", - " experiment_exit_score = None\r\n", - "\r\n", - " if blacklist_models == \"\":\r\n", - " blacklist_models = None\r\n", - "\r\n", - " if blacklist_models is not None:\r\n", - " blacklist_models = blacklist_models.replace(\" \", \"\").split(\",\")\r\n", - "\r\n", - " if whitelist_models == \"\":\r\n", - " whitelist_models = None\r\n", - "\r\n", - " if whitelist_models is not None:\r\n", - " whitelist_models = whitelist_models.replace(\" \", \"\").split(\",\")\r\n", - "\r\n", - " automl_settings = {}\r\n", - " preprocess = True\r\n", - " if time_column_name != \"\" and time_column_name is not None:\r\n", - " automl_settings = { \"time_column_name\": time_column_name }\r\n", - " preprocess = False\r\n", - "\r\n", - " log_file_name = \"automl_errors.log\"\r\n", - "\t \r\n", - " automl_config = AutoMLConfig(task = task, \r\n", - " debug_log = log_file_name, \r\n", - " primary_metric = primary_metric, \r\n", - " iteration_timeout_minutes = iteration_timeout_minutes, \r\n", - " experiment_timeout_minutes = experiment_timeout_minutes,\r\n", - " iterations = iterations, \r\n", - " n_cross_validations = n_cross_validations, \r\n", - " preprocess = preprocess,\r\n", - " verbosity = logging.INFO, \r\n", - " enable_ensembling = False,\r\n", - " X = X_train, \r\n", - " y = y_train, \r\n", - " path = project_folder,\r\n", - " blacklist_models = blacklist_models,\r\n", - " whitelist_models = whitelist_models,\r\n", - " experiment_exit_score = experiment_exit_score,\r\n", - " sample_weight = sample_weight,\r\n", - " X_valid = X_valid,\r\n", - " y_valid = y_valid,\r\n", - " sample_weight_valid = sample_weight_valid,\r\n", - " **automl_settings) \r\n", - " \r\n", - " local_run = experiment.submit(automl_config, show_output = True) \r\n", - "\r\n", - " best_run, fitted_model = local_run.get_output()\r\n", - "\r\n", - " pickled_model = codecs.encode(pickle.dumps(fitted_model), \"base64\").decode()\r\n", - "\r\n", - " log_file_text = \"\"\r\n", - "\r\n", - " try:\r\n", - " with open(log_file_name, \"r\") as log_file:\r\n", - " log_file_text = log_file.read()\r\n", - " except:\r\n", - " log_file_text = \"Log file not found\"\r\n", - "\r\n", - " returned_model = pd.DataFrame({\"best_run\": [best_run.id], \"experiment_name\": [experiment_name], \"fitted_model\": [pickled_model], \"log_file_text\": [log_file_text], \"workspace\": [ws.name]}, dtype=np.dtype(np.str))\r\n", - "'\r\n", - "\t, @input_data_1 = @input_query\r\n", - "\t, @input_data_1_name = N'input_data'\r\n", - "\t, @output_data_1_name = N'returned_model'\r\n", - "\t, @params = N'@label_column NVARCHAR(255), \r\n", - "\t @primary_metric NVARCHAR(40),\r\n", - "\t\t\t\t @iterations INT, @task NVARCHAR(40),\r\n", - "\t\t\t\t @experiment_name NVARCHAR(32),\r\n", - "\t\t\t\t @iteration_timeout_minutes INT,\r\n", - "\t\t\t\t @experiment_timeout_minutes INT,\r\n", - "\t\t\t\t @n_cross_validations INT,\r\n", - "\t\t\t\t @blacklist_models NVARCHAR(MAX),\r\n", - "\t\t\t\t @whitelist_models NVARCHAR(MAX),\r\n", - "\t\t\t\t @experiment_exit_score FLOAT,\r\n", - "\t\t\t\t @sample_weight_column NVARCHAR(255),\r\n", - "\t\t\t\t @is_validate_column NVARCHAR(255),\r\n", - "\t\t\t\t @time_column_name NVARCHAR(255),\r\n", - "\t\t\t\t @tenantid NVARCHAR(255),\r\n", - "\t\t\t\t @appid NVARCHAR(255),\r\n", - "\t\t\t\t @password NVARCHAR(255),\r\n", - "\t\t\t\t @config_file NVARCHAR(255)'\r\n", - "\t, @label_column = @label_column\r\n", - "\t, @primary_metric = @primary_metric\r\n", - "\t, @iterations = @iterations\r\n", - "\t, @task = @task\r\n", - "\t, @experiment_name = @experiment_name\r\n", - "\t, @iteration_timeout_minutes = @iteration_timeout_minutes\r\n", - "\t, @experiment_timeout_minutes = @experiment_timeout_minutes\r\n", - "\t, @n_cross_validations = @n_cross_validations\r\n", - "\t, @blacklist_models = @blacklist_models\r\n", - "\t, @whitelist_models = @whitelist_models\r\n", - "\t, @experiment_exit_score = @experiment_exit_score\r\n", - "\t, @sample_weight_column = @sample_weight_column\r\n", - "\t, @is_validate_column = @is_validate_column\r\n", - "\t, @time_column_name = @time_column_name\r\n", - "\t, @tenantid = @tenantid\r\n", - "\t, @appid = @appid\r\n", - "\t, @password = @password\r\n", - "\t, @config_file = @config_file\r\n", - "WITH RESULT SETS ((best_run NVARCHAR(250), experiment_name NVARCHAR(100), fitted_model VARCHAR(MAX), log_file_text NVARCHAR(MAX), workspace NVARCHAR(100)))\r\n", - "END" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "-- This procedure returns a list of metrics for each iteration of a training run.\r\n", - "SET ANSI_NULLS ON\r\n", - "GO\r\n", - "SET QUOTED_IDENTIFIER ON\r\n", - "GO\r\n", - "CREATE OR ALTER PROCEDURE [dbo].[AutoMLGetMetrics]\r\n", - " (\r\n", - "\t@run_id NVARCHAR(250), -- The RunId\r\n", - " @experiment_name NVARCHAR(32)='automl-sql-test', -- This can be used to find the experiment in the Azure Portal.\r\n", - " @connection_name NVARCHAR(255)='default' -- The AML connection to use.\r\n", - " ) AS\r\n", - "BEGIN\r\n", - " DECLARE @tenantid NVARCHAR(255)\r\n", - " DECLARE @appid NVARCHAR(255)\r\n", - " DECLARE @password NVARCHAR(255)\r\n", - " DECLARE @config_file NVARCHAR(255)\r\n", - "\r\n", - "\tSELECT @tenantid=TenantId, @appid=AppId, @password=Password, @config_file=ConfigFile\r\n", - "\tFROM aml_connection\r\n", - "\tWHERE ConnectionName = @connection_name;\r\n", - "\r\n", - " EXEC sp_execute_external_script @language = N'Python', @script = N'import pandas as pd\r\n", - "import logging \r\n", - "import azureml.core \r\n", - "import numpy as np\r\n", - "from azureml.core.experiment import Experiment \r\n", - "from azureml.train.automl.run import AutoMLRun\r\n", - "from azureml.core.authentication import ServicePrincipalAuthentication \r\n", - "from azureml.core.workspace import Workspace \r\n", - "\r\n", - "auth = ServicePrincipalAuthentication(tenantid, appid, password) \r\n", - " \r\n", - "ws = Workspace.from_config(path=config_file, auth=auth) \r\n", - " \r\n", - "experiment = Experiment(ws, experiment_name) \r\n", - "\r\n", - "ml_run = AutoMLRun(experiment = experiment, run_id = run_id)\r\n", - "\r\n", - "children = list(ml_run.get_children())\r\n", - "iterationlist = []\r\n", - "metricnamelist = []\r\n", - "metricvaluelist = []\r\n", - "\r\n", - "for run in children:\r\n", - " properties = run.get_properties()\r\n", - " if \"iteration\" in properties:\r\n", - " iteration = int(properties[\"iteration\"])\r\n", - " for metric_name, metric_value in run.get_metrics().items():\r\n", - " if isinstance(metric_value, float):\r\n", - " iterationlist.append(iteration)\r\n", - " metricnamelist.append(metric_name)\r\n", - " metricvaluelist.append(metric_value)\r\n", - " \r\n", - "metrics = pd.DataFrame({\"iteration\": iterationlist, \"metric_name\": metricnamelist, \"metric_value\": metricvaluelist})\r\n", - "'\r\n", - " , @output_data_1_name = N'metrics'\r\n", - "\t, @params = N'@run_id NVARCHAR(250), \r\n", - "\t\t\t\t @experiment_name NVARCHAR(32),\r\n", - " \t\t\t\t @tenantid NVARCHAR(255),\r\n", - "\t\t\t\t @appid NVARCHAR(255),\r\n", - "\t\t\t\t @password NVARCHAR(255),\r\n", - "\t\t\t\t @config_file NVARCHAR(255)'\r\n", - " , @run_id = @run_id\r\n", - "\t, @experiment_name = @experiment_name\r\n", - "\t, @tenantid = @tenantid\r\n", - "\t, @appid = @appid\r\n", - "\t, @password = @password\r\n", - "\t, @config_file = @config_file\r\n", - "WITH RESULT SETS ((iteration INT, metric_name NVARCHAR(100), metric_value FLOAT))\r\n", - "END" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "-- This procedure predicts values based on a model returned by AutoMLTrain and a dataset.\r\n", - "-- It returns the dataset with a new column added, which is the predicted value.\r\n", - "SET ANSI_NULLS ON\r\n", - "GO\r\n", - "SET QUOTED_IDENTIFIER ON\r\n", - "GO\r\n", - "CREATE OR ALTER PROCEDURE [dbo].[AutoMLPredict]\r\n", - " (\r\n", - " @input_query NVARCHAR(MAX), -- A SQL query returning data to predict on.\r\n", - " @model NVARCHAR(MAX), -- A model returned from AutoMLTrain.\r\n", - " @label_column NVARCHAR(255)='' -- Optional name of the column from input_query, which should be ignored when predicting\r\n", - " ) AS \r\n", - "BEGIN \r\n", - " \r\n", - " EXEC sp_execute_external_script @language = N'Python', @script = N'import pandas as pd \r\n", - "import azureml.core \r\n", - "import numpy as np \r\n", - "from azureml.train.automl import AutoMLConfig \r\n", - "import pickle \r\n", - "import codecs \r\n", - " \r\n", - "model_obj = pickle.loads(codecs.decode(model.encode(), \"base64\")) \r\n", - " \r\n", - "test_data = input_data.copy() \r\n", - "\r\n", - "if label_column != \"\" and label_column is not None:\r\n", - " y_test = test_data.pop(label_column).values \r\n", - "X_test = test_data \r\n", - " \r\n", - "predicted = model_obj.predict(X_test) \r\n", - " \r\n", - "combined_output = input_data.assign(predicted=predicted)\r\n", - " \r\n", - "' \r\n", - " , @input_data_1 = @input_query \r\n", - " , @input_data_1_name = N'input_data' \r\n", - " , @output_data_1_name = N'combined_output' \r\n", - " , @params = N'@model NVARCHAR(MAX), @label_column NVARCHAR(255)' \r\n", - " , @model = @model \r\n", - "\t, @label_column = @label_column\r\n", - "END" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "jeffshep" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Set up Azure ML Automated Machine Learning on SQL Server 2019 CTP 2.4 big data cluster\r\n", + "\r\n", + "\\# Prerequisites: \r\n", + "\\# - An Azure subscription and resource group \r\n", + "\\# - An Azure Machine Learning workspace \r\n", + "\\# - A SQL Server 2019 CTP 2.4 big data cluster with Internet access and a database named 'automl' \r\n", + "\\# - Azure CLI \r\n", + "\\# - kubectl command \r\n", + "\\# - The https://github.com/Azure/MachineLearningNotebooks repository downloaded (cloned) to your local machine\r\n", + "\r\n", + "\\# In the 'automl' database, create a table named 'dbo.nyc_energy' as follows: \r\n", + "\\# - In SQL Server Management Studio, right-click the 'automl' database, select Tasks, then Import Flat File. \r\n", + "\\# - Select the file AzureMlCli\\notebooks\\how-to-use-azureml\\automated-machine-learning\\forecasting-energy-demand\\nyc_energy.csv. \r\n", + "\\# - Using the \"Modify Columns\" page, allow nulls for all columns. \r\n", + "\r\n", + "\\# Create an Azure Machine Learning Workspace using the instructions at https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-workspace \r\n", + "\r\n", + "\\# Create an Azure service principal. You can do this with the following commands: \r\n", + "\r\n", + "az login \r\n", + "az account set --subscription *subscriptionid* \r\n", + "\r\n", + "\\# The following command prints out the **appId** and **tenant**, \r\n", + "\\# which you insert into the indicated cell later in this notebook \r\n", + "\\# to allow AutoML to authenticate with Azure: \r\n", + "\r\n", + "az ad sp create-for-rbac --name *principlename* --password *password*\r\n", + "\r\n", + "\\# Log into the master instance of SQL Server 2019 CTP 2.4: \r\n", + "kubectl exec -it mssql-master-pool-0 -n *clustername* -c mssql-server -- /bin/bash\r\n", + "\r\n", + "mkdir /tmp/aml\r\n", + "\r\n", + "cd /tmp/aml\r\n", + "\r\n", + "\\# **Modify** the following with your subscription_id, resource_group, and workspace_name: \r\n", + "cat > config.json << EOF \r\n", + "{ \r\n", + " \"subscription_id\": \"123456ab-78cd-0123-45ef-abcd12345678\", \r\n", + " \"resource_group\": \"myrg1\", \r\n", + " \"workspace_name\": \"myws1\" \r\n", + "} \r\n", + "EOF\r\n", + "\r\n", + "\\# The directory referenced below is appropriate for the master instance of SQL Server 2019 CTP 2.4.\r\n", + "\r\n", + "cd /opt/mssql/mlservices/runtime/python/bin\r\n", + "\r\n", + "./python -m pip install azureml-sdk[automl]\r\n", + "\r\n", + "./python -m pip install --upgrade numpy \r\n", + "\r\n", + "./python -m pip install --upgrade sklearn\r\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/sql-server/setup/auto-ml-sql-setup.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "-- Enable external scripts to allow invoking Python\r\n", + "sp_configure 'external scripts enabled',1 \r\n", + "reconfigure with override \r\n", + "GO\r\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "-- Use database 'automl'\r\n", + "USE [automl]\r\n", + "GO" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "-- This is a table to hold the Azure ML connection information.\r\n", + "SET ANSI_NULLS ON\r\n", + "GO\r\n", + "\r\n", + "SET QUOTED_IDENTIFIER ON\r\n", + "GO\r\n", + "\r\n", + "CREATE TABLE [dbo].[aml_connection](\r\n", + " [Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,\r\n", + "\t[ConnectionName] [nvarchar](255) NULL,\r\n", + "\t[TenantId] [nvarchar](255) NULL,\r\n", + "\t[AppId] [nvarchar](255) NULL,\r\n", + "\t[Password] [nvarchar](255) NULL,\r\n", + "\t[ConfigFile] [nvarchar](255) NULL\r\n", + ") ON [PRIMARY]\r\n", + "GO" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Copy the values from create-for-rbac above into the cell below" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "-- Use the following values:\r\n", + "-- Leave the name as 'Default'\r\n", + "-- Insert returned by create-for-rbac above\r\n", + "-- Insert returned by create-for-rbac above\r\n", + "-- Insert used in create-for-rbac above\r\n", + "-- Leave as '/tmp/aml/config.json'\r\n", + "INSERT INTO [dbo].[aml_connection] \r\n", + "VALUES (\r\n", + " N'Default', -- Name\r\n", + " N'11111111-2222-3333-4444-555555555555', -- Tenant\r\n", + " N'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', -- AppId\r\n", + " N'insertpasswordhere', -- Password\r\n", + " N'/tmp/aml/config.json' -- Path\r\n", + " );\r\n", + "GO" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "-- This is a table to hold the results from the AutoMLTrain procedure.\r\n", + "SET ANSI_NULLS ON\r\n", + "GO\r\n", + "\r\n", + "SET QUOTED_IDENTIFIER ON\r\n", + "GO\r\n", + "\r\n", + "CREATE TABLE [dbo].[aml_model](\r\n", + " [Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,\r\n", + " [Model] [varchar](max) NOT NULL, -- The model, which can be passed to AutoMLPredict for testing or prediction.\r\n", + " [RunId] [nvarchar](250) NULL, -- The RunId, which can be used to view the model in the Azure Portal.\r\n", + " [CreatedDate] [datetime] NULL,\r\n", + " [ExperimentName] [nvarchar](100) NULL, -- Azure ML Experiment Name\r\n", + " [WorkspaceName] [nvarchar](100) NULL, -- Azure ML Workspace Name\r\n", + "\t[LogFileText] [nvarchar](max) NULL\r\n", + ") \r\n", + "GO\r\n", + "\r\n", + "ALTER TABLE [dbo].[aml_model] ADD DEFAULT (getutcdate()) FOR [CreatedDate]\r\n", + "GO\r\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "-- This stored procedure uses automated machine learning to train several models\r\n", + "-- and return the best model.\r\n", + "--\r\n", + "-- The result set has several columns:\r\n", + "-- best_run - ID of the best model found\r\n", + "-- experiment_name - training run name\r\n", + "-- fitted_model - best model found\r\n", + "-- log_file_text - console output\r\n", + "-- workspace - name of the Azure ML workspace where run history is stored\r\n", + "--\r\n", + "-- An example call for a classification problem is:\r\n", + "-- insert into dbo.aml_model(RunId, ExperimentName, Model, LogFileText, WorkspaceName)\r\n", + "-- exec dbo.AutoMLTrain @input_query='\r\n", + "-- SELECT top 100000 \r\n", + "-- CAST([pickup_datetime] AS NVARCHAR(30)) AS pickup_datetime\r\n", + "-- ,CAST([dropoff_datetime] AS NVARCHAR(30)) AS dropoff_datetime\r\n", + "-- ,[passenger_count]\r\n", + "-- ,[trip_time_in_secs]\r\n", + "-- ,[trip_distance]\r\n", + "-- ,[payment_type]\r\n", + "-- ,[tip_class]\r\n", + "-- FROM [dbo].[nyctaxi_sample] order by [hack_license] ',\r\n", + "-- @label_column = 'tip_class',\r\n", + "-- @iterations=10\r\n", + "-- \r\n", + "-- An example call for forecasting is:\r\n", + "-- insert into dbo.aml_model(RunId, ExperimentName, Model, LogFileText, WorkspaceName)\r\n", + "-- exec dbo.AutoMLTrain @input_query='\r\n", + "-- select cast(timeStamp as nvarchar(30)) as timeStamp,\r\n", + "-- demand,\r\n", + "-- \t precip,\r\n", + "-- \t temp,\r\n", + "-- case when timeStamp < ''2017-01-01'' then 0 else 1 end as is_validate_column\r\n", + "-- from nyc_energy\r\n", + "-- where demand is not null and precip is not null and temp is not null\r\n", + "-- and timeStamp < ''2017-02-01''',\r\n", + "-- @label_column='demand',\r\n", + "-- @task='forecasting',\r\n", + "-- @iterations=10,\r\n", + "-- @iteration_timeout_minutes=5,\r\n", + "-- @time_column_name='timeStamp',\r\n", + "-- @is_validate_column='is_validate_column',\r\n", + "-- @experiment_name='automl-sql-forecast',\r\n", + "-- @primary_metric='normalized_root_mean_squared_error'\r\n", + "\r\n", + "SET ANSI_NULLS ON\r\n", + "GO\r\n", + "SET QUOTED_IDENTIFIER ON\r\n", + "GO\r\n", + "CREATE OR ALTER PROCEDURE [dbo].[AutoMLTrain]\r\n", + " (\r\n", + " @input_query NVARCHAR(MAX), -- The SQL Query that will return the data to train and validate the model.\r\n", + " @label_column NVARCHAR(255)='Label', -- The name of the column in the result of @input_query that is the label.\r\n", + " @primary_metric NVARCHAR(40)='AUC_weighted', -- The metric to optimize.\r\n", + " @iterations INT=100, -- The maximum number of pipelines to train.\r\n", + " @task NVARCHAR(40)='classification', -- The type of task. Can be classification, regression or forecasting.\r\n", + " @experiment_name NVARCHAR(32)='automl-sql-test', -- This can be used to find the experiment in the Azure Portal.\r\n", + " @iteration_timeout_minutes INT = 15, -- The maximum time in minutes for training a single pipeline. \r\n", + " @experiment_timeout_minutes INT = 60, -- The maximum time in minutes for training all pipelines.\r\n", + " @n_cross_validations INT = 3, -- The number of cross validations.\r\n", + " @blacklist_models NVARCHAR(MAX) = '', -- A comma separated list of algos that will not be used.\r\n", + " -- The list of possible models can be found at:\r\n", + " -- https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#configure-your-experiment-settings\r\n", + " @whitelist_models NVARCHAR(MAX) = '', -- A comma separated list of algos that can be used.\r\n", + " -- The list of possible models can be found at:\r\n", + " -- https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#configure-your-experiment-settings\r\n", + " @experiment_exit_score FLOAT = 0, -- Stop the experiment if this score is acheived.\r\n", + " @sample_weight_column NVARCHAR(255)='', -- The name of the column in the result of @input_query that gives a sample weight.\r\n", + " @is_validate_column NVARCHAR(255)='', -- The name of the column in the result of @input_query that indicates if the row is for training or validation.\r\n", + "\t -- In the values of the column, 0 means for training and 1 means for validation.\r\n", + " @time_column_name NVARCHAR(255)='', -- The name of the timestamp column for forecasting.\r\n", + "\t@connection_name NVARCHAR(255)='default' -- The AML connection to use.\r\n", + " ) AS\r\n", + "BEGIN\r\n", + "\r\n", + " DECLARE @tenantid NVARCHAR(255)\r\n", + " DECLARE @appid NVARCHAR(255)\r\n", + " DECLARE @password NVARCHAR(255)\r\n", + " DECLARE @config_file NVARCHAR(255)\r\n", + "\r\n", + "\tSELECT @tenantid=TenantId, @appid=AppId, @password=Password, @config_file=ConfigFile\r\n", + "\tFROM aml_connection\r\n", + "\tWHERE ConnectionName = @connection_name;\r\n", + "\r\n", + "\tEXEC sp_execute_external_script @language = N'Python', @script = N'import pandas as pd\r\n", + "import logging \r\n", + "import azureml.core \r\n", + "import pandas as pd\r\n", + "import numpy as np\r\n", + "from azureml.core.experiment import Experiment \r\n", + "from azureml.train.automl import AutoMLConfig \r\n", + "from sklearn import datasets \r\n", + "import pickle\r\n", + "import codecs\r\n", + "from azureml.core.authentication import ServicePrincipalAuthentication \r\n", + "from azureml.core.workspace import Workspace \r\n", + "\r\n", + "if __name__.startswith(\"sqlindb\"):\r\n", + " auth = ServicePrincipalAuthentication(tenantid, appid, password) \r\n", + " \r\n", + " ws = Workspace.from_config(path=config_file, auth=auth) \r\n", + " \r\n", + " project_folder = \"./sample_projects/\" + experiment_name\r\n", + " \r\n", + " experiment = Experiment(ws, experiment_name) \r\n", + "\r\n", + " data_train = input_data\r\n", + " X_valid = None\r\n", + " y_valid = None\r\n", + " sample_weight_valid = None\r\n", + "\r\n", + " if is_validate_column != \"\" and is_validate_column is not None:\r\n", + " data_train = input_data[input_data[is_validate_column] <= 0]\r\n", + " data_valid = input_data[input_data[is_validate_column] > 0]\r\n", + " data_train.pop(is_validate_column)\r\n", + " data_valid.pop(is_validate_column)\r\n", + " y_valid = data_valid.pop(label_column).values\r\n", + " if sample_weight_column != \"\" and sample_weight_column is not None:\r\n", + " sample_weight_valid = data_valid.pop(sample_weight_column).values\r\n", + " X_valid = data_valid\r\n", + " n_cross_validations = None\r\n", + "\r\n", + " y_train = data_train.pop(label_column).values\r\n", + "\r\n", + " sample_weight = None\r\n", + " if sample_weight_column != \"\" and sample_weight_column is not None:\r\n", + " sample_weight = data_train.pop(sample_weight_column).values\r\n", + "\r\n", + " X_train = data_train\r\n", + "\r\n", + " if experiment_timeout_minutes == 0:\r\n", + " experiment_timeout_minutes = None\r\n", + "\r\n", + " if experiment_exit_score == 0:\r\n", + " experiment_exit_score = None\r\n", + "\r\n", + " if blacklist_models == \"\":\r\n", + " blacklist_models = None\r\n", + "\r\n", + " if blacklist_models is not None:\r\n", + " blacklist_models = blacklist_models.replace(\" \", \"\").split(\",\")\r\n", + "\r\n", + " if whitelist_models == \"\":\r\n", + " whitelist_models = None\r\n", + "\r\n", + " if whitelist_models is not None:\r\n", + " whitelist_models = whitelist_models.replace(\" \", \"\").split(\",\")\r\n", + "\r\n", + " automl_settings = {}\r\n", + " preprocess = True\r\n", + " if time_column_name != \"\" and time_column_name is not None:\r\n", + " automl_settings = { \"time_column_name\": time_column_name }\r\n", + " preprocess = False\r\n", + "\r\n", + " log_file_name = \"automl_errors.log\"\r\n", + "\t \r\n", + " automl_config = AutoMLConfig(task = task, \r\n", + " debug_log = log_file_name, \r\n", + " primary_metric = primary_metric, \r\n", + " iteration_timeout_minutes = iteration_timeout_minutes, \r\n", + " experiment_timeout_minutes = experiment_timeout_minutes,\r\n", + " iterations = iterations, \r\n", + " n_cross_validations = n_cross_validations, \r\n", + " preprocess = preprocess,\r\n", + " verbosity = logging.INFO, \r\n", + " enable_ensembling = False,\r\n", + " X = X_train, \r\n", + " y = y_train, \r\n", + " path = project_folder,\r\n", + " blacklist_models = blacklist_models,\r\n", + " whitelist_models = whitelist_models,\r\n", + " experiment_exit_score = experiment_exit_score,\r\n", + " sample_weight = sample_weight,\r\n", + " X_valid = X_valid,\r\n", + " y_valid = y_valid,\r\n", + " sample_weight_valid = sample_weight_valid,\r\n", + " **automl_settings) \r\n", + " \r\n", + " local_run = experiment.submit(automl_config, show_output = True) \r\n", + "\r\n", + " best_run, fitted_model = local_run.get_output()\r\n", + "\r\n", + " pickled_model = codecs.encode(pickle.dumps(fitted_model), \"base64\").decode()\r\n", + "\r\n", + " log_file_text = \"\"\r\n", + "\r\n", + " try:\r\n", + " with open(log_file_name, \"r\") as log_file:\r\n", + " log_file_text = log_file.read()\r\n", + " except:\r\n", + " log_file_text = \"Log file not found\"\r\n", + "\r\n", + " returned_model = pd.DataFrame({\"best_run\": [best_run.id], \"experiment_name\": [experiment_name], \"fitted_model\": [pickled_model], \"log_file_text\": [log_file_text], \"workspace\": [ws.name]}, dtype=np.dtype(np.str))\r\n", + "'\r\n", + "\t, @input_data_1 = @input_query\r\n", + "\t, @input_data_1_name = N'input_data'\r\n", + "\t, @output_data_1_name = N'returned_model'\r\n", + "\t, @params = N'@label_column NVARCHAR(255), \r\n", + "\t @primary_metric NVARCHAR(40),\r\n", + "\t\t\t\t @iterations INT, @task NVARCHAR(40),\r\n", + "\t\t\t\t @experiment_name NVARCHAR(32),\r\n", + "\t\t\t\t @iteration_timeout_minutes INT,\r\n", + "\t\t\t\t @experiment_timeout_minutes INT,\r\n", + "\t\t\t\t @n_cross_validations INT,\r\n", + "\t\t\t\t @blacklist_models NVARCHAR(MAX),\r\n", + "\t\t\t\t @whitelist_models NVARCHAR(MAX),\r\n", + "\t\t\t\t @experiment_exit_score FLOAT,\r\n", + "\t\t\t\t @sample_weight_column NVARCHAR(255),\r\n", + "\t\t\t\t @is_validate_column NVARCHAR(255),\r\n", + "\t\t\t\t @time_column_name NVARCHAR(255),\r\n", + "\t\t\t\t @tenantid NVARCHAR(255),\r\n", + "\t\t\t\t @appid NVARCHAR(255),\r\n", + "\t\t\t\t @password NVARCHAR(255),\r\n", + "\t\t\t\t @config_file NVARCHAR(255)'\r\n", + "\t, @label_column = @label_column\r\n", + "\t, @primary_metric = @primary_metric\r\n", + "\t, @iterations = @iterations\r\n", + "\t, @task = @task\r\n", + "\t, @experiment_name = @experiment_name\r\n", + "\t, @iteration_timeout_minutes = @iteration_timeout_minutes\r\n", + "\t, @experiment_timeout_minutes = @experiment_timeout_minutes\r\n", + "\t, @n_cross_validations = @n_cross_validations\r\n", + "\t, @blacklist_models = @blacklist_models\r\n", + "\t, @whitelist_models = @whitelist_models\r\n", + "\t, @experiment_exit_score = @experiment_exit_score\r\n", + "\t, @sample_weight_column = @sample_weight_column\r\n", + "\t, @is_validate_column = @is_validate_column\r\n", + "\t, @time_column_name = @time_column_name\r\n", + "\t, @tenantid = @tenantid\r\n", + "\t, @appid = @appid\r\n", + "\t, @password = @password\r\n", + "\t, @config_file = @config_file\r\n", + "WITH RESULT SETS ((best_run NVARCHAR(250), experiment_name NVARCHAR(100), fitted_model VARCHAR(MAX), log_file_text NVARCHAR(MAX), workspace NVARCHAR(100)))\r\n", + "END" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "-- This procedure returns a list of metrics for each iteration of a training run.\r\n", + "SET ANSI_NULLS ON\r\n", + "GO\r\n", + "SET QUOTED_IDENTIFIER ON\r\n", + "GO\r\n", + "CREATE OR ALTER PROCEDURE [dbo].[AutoMLGetMetrics]\r\n", + " (\r\n", + "\t@run_id NVARCHAR(250), -- The RunId\r\n", + " @experiment_name NVARCHAR(32)='automl-sql-test', -- This can be used to find the experiment in the Azure Portal.\r\n", + " @connection_name NVARCHAR(255)='default' -- The AML connection to use.\r\n", + " ) AS\r\n", + "BEGIN\r\n", + " DECLARE @tenantid NVARCHAR(255)\r\n", + " DECLARE @appid NVARCHAR(255)\r\n", + " DECLARE @password NVARCHAR(255)\r\n", + " DECLARE @config_file NVARCHAR(255)\r\n", + "\r\n", + "\tSELECT @tenantid=TenantId, @appid=AppId, @password=Password, @config_file=ConfigFile\r\n", + "\tFROM aml_connection\r\n", + "\tWHERE ConnectionName = @connection_name;\r\n", + "\r\n", + " EXEC sp_execute_external_script @language = N'Python', @script = N'import pandas as pd\r\n", + "import logging \r\n", + "import azureml.core \r\n", + "import numpy as np\r\n", + "from azureml.core.experiment import Experiment \r\n", + "from azureml.train.automl.run import AutoMLRun\r\n", + "from azureml.core.authentication import ServicePrincipalAuthentication \r\n", + "from azureml.core.workspace import Workspace \r\n", + "\r\n", + "auth = ServicePrincipalAuthentication(tenantid, appid, password) \r\n", + " \r\n", + "ws = Workspace.from_config(path=config_file, auth=auth) \r\n", + " \r\n", + "experiment = Experiment(ws, experiment_name) \r\n", + "\r\n", + "ml_run = AutoMLRun(experiment = experiment, run_id = run_id)\r\n", + "\r\n", + "children = list(ml_run.get_children())\r\n", + "iterationlist = []\r\n", + "metricnamelist = []\r\n", + "metricvaluelist = []\r\n", + "\r\n", + "for run in children:\r\n", + " properties = run.get_properties()\r\n", + " if \"iteration\" in properties:\r\n", + " iteration = int(properties[\"iteration\"])\r\n", + " for metric_name, metric_value in run.get_metrics().items():\r\n", + " if isinstance(metric_value, float):\r\n", + " iterationlist.append(iteration)\r\n", + " metricnamelist.append(metric_name)\r\n", + " metricvaluelist.append(metric_value)\r\n", + " \r\n", + "metrics = pd.DataFrame({\"iteration\": iterationlist, \"metric_name\": metricnamelist, \"metric_value\": metricvaluelist})\r\n", + "'\r\n", + " , @output_data_1_name = N'metrics'\r\n", + "\t, @params = N'@run_id NVARCHAR(250), \r\n", + "\t\t\t\t @experiment_name NVARCHAR(32),\r\n", + " \t\t\t\t @tenantid NVARCHAR(255),\r\n", + "\t\t\t\t @appid NVARCHAR(255),\r\n", + "\t\t\t\t @password NVARCHAR(255),\r\n", + "\t\t\t\t @config_file NVARCHAR(255)'\r\n", + " , @run_id = @run_id\r\n", + "\t, @experiment_name = @experiment_name\r\n", + "\t, @tenantid = @tenantid\r\n", + "\t, @appid = @appid\r\n", + "\t, @password = @password\r\n", + "\t, @config_file = @config_file\r\n", + "WITH RESULT SETS ((iteration INT, metric_name NVARCHAR(100), metric_value FLOAT))\r\n", + "END" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "-- This procedure predicts values based on a model returned by AutoMLTrain and a dataset.\r\n", + "-- It returns the dataset with a new column added, which is the predicted value.\r\n", + "SET ANSI_NULLS ON\r\n", + "GO\r\n", + "SET QUOTED_IDENTIFIER ON\r\n", + "GO\r\n", + "CREATE OR ALTER PROCEDURE [dbo].[AutoMLPredict]\r\n", + " (\r\n", + " @input_query NVARCHAR(MAX), -- A SQL query returning data to predict on.\r\n", + " @model NVARCHAR(MAX), -- A model returned from AutoMLTrain.\r\n", + " @label_column NVARCHAR(255)='' -- Optional name of the column from input_query, which should be ignored when predicting\r\n", + " ) AS \r\n", + "BEGIN \r\n", + " \r\n", + " EXEC sp_execute_external_script @language = N'Python', @script = N'import pandas as pd \r\n", + "import azureml.core \r\n", + "import numpy as np \r\n", + "from azureml.train.automl import AutoMLConfig \r\n", + "import pickle \r\n", + "import codecs \r\n", + " \r\n", + "model_obj = pickle.loads(codecs.decode(model.encode(), \"base64\")) \r\n", + " \r\n", + "test_data = input_data.copy() \r\n", + "\r\n", + "if label_column != \"\" and label_column is not None:\r\n", + " y_test = test_data.pop(label_column).values \r\n", + "X_test = test_data \r\n", + " \r\n", + "predicted = model_obj.predict(X_test) \r\n", + " \r\n", + "combined_output = input_data.assign(predicted=predicted)\r\n", + " \r\n", + "' \r\n", + " , @input_data_1 = @input_query \r\n", + " , @input_data_1_name = N'input_data' \r\n", + " , @output_data_1_name = N'combined_output' \r\n", + " , @params = N'@model NVARCHAR(MAX), @label_column NVARCHAR(255)' \r\n", + " , @model = @model \r\n", + "\t, @label_column = @label_column\r\n", + "END" + ] + } ], - "kernelspec": { - "display_name": "SQL", - "language": "sql", - "name": "SQL" + "metadata": { + "authors": [ + { + "name": "jeffshep" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "sql", + "name": "python36" + }, + "language_info": { + "name": "sql", + "version": "" + } }, - "language_info": { - "name": "sql", - "version": "" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/automated-machine-learning/subsampling/auto-ml-subsampling-local.yml b/how-to-use-azureml/automated-machine-learning/subsampling/auto-ml-subsampling-local.yml new file mode 100644 index 00000000..695cd466 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/subsampling/auto-ml-subsampling-local.yml @@ -0,0 +1,8 @@ +name: auto-ml-subsampling-local +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/data-drift/azure-ml-datadrift.ipynb b/how-to-use-azureml/data-drift/azure-ml-datadrift.ipynb index 8b2177af..4f9a3737 100644 --- a/how-to-use-azureml/data-drift/azure-ml-datadrift.ipynb +++ b/how-to-use-azureml/data-drift/azure-ml-datadrift.ipynb @@ -1,709 +1,709 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Track Data Drift between Training and Inference Data in Production \n", - "\n", - "With this notebook, you will learn how to enable the DataDrift service to automatically track and determine whether your inference data is drifting from the data your model was initially trained on. The DataDrift service provides metrics and visualizations to help stakeholders identify which specific features cause the concept drift to occur.\n", - "\n", - "Please email driftfeedback@microsoft.com with any issues. A member from the DataDrift team will respond shortly. \n", - "\n", - "The DataDrift Public Preview API can be found [here](https://docs.microsoft.com/en-us/python/api/azureml-contrib-datadrift/?view=azure-ml-py). " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/contrib/datadrift/azureml-datadrift.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Prerequisites and Setup" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Install the DataDrift package\n", - "\n", - "Install the azureml-contrib-datadrift, azureml-contrib-opendatasets and lightgbm packages before running this notebook.\n", - "```\n", - "pip install azureml-contrib-datadrift\n", - "pip install azureml-contrib-datasets\n", - "pip install lightgbm\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Import Dependencies" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "import os\n", - "import time\n", - "from datetime import datetime, timedelta\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "import requests\n", - "from azureml.contrib.datadrift import DataDriftDetector, AlertConfiguration\n", - "from azureml.contrib.opendatasets import NoaaIsdWeather\n", - "from azureml.core import Dataset, Workspace, Run\n", - "from azureml.core.compute import AksCompute, ComputeTarget\n", - "from azureml.core.conda_dependencies import CondaDependencies\n", - "from azureml.core.experiment import Experiment\n", - "from azureml.core.image import ContainerImage\n", - "from azureml.core.model import Model\n", - "from azureml.core.webservice import Webservice, AksWebservice\n", - "from azureml.widgets import RunDetails\n", - "from sklearn.externals import joblib\n", - "from sklearn.model_selection import train_test_split\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Set up Configuraton and Create Azure ML Workspace\n", - "\n", - "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration notebook](../../configuration.ipynb) first if you haven't already to establish your connection to the AzureML Workspace." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Please type in your initials/alias. The prefix is prepended to the names of resources created by this notebook. \n", - "prefix = \"dd\"\n", - "\n", - "# NOTE: Please do not change the model_name, as it's required by the score.py file\n", - "model_name = \"driftmodel\"\n", - "image_name = \"{}driftimage\".format(prefix)\n", - "service_name = \"{}driftservice\".format(prefix)\n", - "\n", - "# optionally, set email address to receive an email alert for DataDrift\n", - "email_address = \"\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ws = Workspace.from_config()\n", - "print(ws.name, ws.resource_group, ws.location, ws.subscription_id, sep = '\\n')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Generate Train/Testing Data\n", - "\n", - "For this demo, we will use NOAA weather data from [Azure Open Datasets](https://azure.microsoft.com/services/open-datasets/). You may replace this step with your own dataset. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "usaf_list = ['725724', '722149', '723090', '722159', '723910', '720279',\n", - " '725513', '725254', '726430', '720381', '723074', '726682',\n", - " '725486', '727883', '723177', '722075', '723086', '724053',\n", - " '725070', '722073', '726060', '725224', '725260', '724520',\n", - " '720305', '724020', '726510', '725126', '722523', '703333',\n", - " '722249', '722728', '725483', '722972', '724975', '742079',\n", - " '727468', '722193', '725624', '722030', '726380', '720309',\n", - " '722071', '720326', '725415', '724504', '725665', '725424',\n", - " '725066']\n", - "\n", - "columns = ['usaf', 'wban', 'datetime', 'latitude', 'longitude', 'elevation', 'windAngle', 'windSpeed', 'temperature', 'stationName', 'p_k']\n", - "\n", - "def enrich_weather_noaa_data(noaa_df):\n", - " hours_in_day = 23\n", - " week_in_year = 52\n", - " \n", - "\n", - " noaa_df = noaa_df.assign(hour=noaa_df[\"datetime\"].dt.hour,\n", - " weekofyear=noaa_df[\"datetime\"].dt.week,\n", - " sine_weekofyear=noaa_df['datetime'].transform(lambda x: np.sin((2*np.pi*x.dt.week-1)/week_in_year)),\n", - " cosine_weekofyear=noaa_df['datetime'].transform(lambda x: np.cos((2*np.pi*x.dt.week-1)/week_in_year)),\n", - " sine_hourofday=noaa_df['datetime'].transform(lambda x: np.sin(2*np.pi*x.dt.hour/hours_in_day)),\n", - " cosine_hourofday=noaa_df['datetime'].transform(lambda x: np.cos(2*np.pi*x.dt.hour/hours_in_day))\n", - " )\n", - " \n", - " return noaa_df\n", - "\n", - "\n", - "def add_window_col(input_df):\n", - " shift_interval = pd.Timedelta('-7 days') # your X days interval\n", - " df_shifted = input_df.copy()\n", - " df_shifted.loc[:,'datetime'] = df_shifted['datetime'] - shift_interval\n", - " df_shifted.drop(list(input_df.columns.difference(['datetime', 'usaf', 'wban', 'sine_hourofday', 'temperature'])), axis=1, inplace=True)\n", - "\n", - " # merge, keeping only observations where -1 lag is present\n", - " df2 = pd.merge(input_df,\n", - " df_shifted,\n", - " on=['datetime', 'usaf', 'wban', 'sine_hourofday'],\n", - " how='inner', # use 'left' to keep observations without lags\n", - " suffixes=['', '-7'])\n", - " return df2\n", - "\n", - "def get_noaa_data(start_time, end_time, cols, station_list):\n", - " isd = NoaaIsdWeather(start_time, end_time, cols=cols)\n", - " # Read into Pandas data frame.\n", - " noaa_df = isd.to_pandas_dataframe()\n", - " noaa_df = noaa_df.rename(columns={\"stationName\": \"station_name\"})\n", - " \n", - " df_filtered = noaa_df[noaa_df[\"usaf\"].isin(station_list)]\n", - " df_filtered.reset_index(drop=True)\n", - " \n", - " # Enrich with time features\n", - " df_enriched = enrich_weather_noaa_data(df_filtered)\n", - " \n", - " return df_enriched\n", - "\n", - "def get_featurized_noaa_df(start_time, end_time, cols, station_list):\n", - " df_1 = get_noaa_data(start_time - timedelta(days=7), start_time - timedelta(seconds=1), cols, station_list)\n", - " df_2 = get_noaa_data(start_time, end_time, cols, station_list)\n", - " noaa_df = pd.concat([df_1, df_2])\n", - " \n", - " print(\"Adding window feature\")\n", - " df_window = add_window_col(noaa_df)\n", - " \n", - " cat_columns = df_window.dtypes == object\n", - " cat_columns = cat_columns[cat_columns == True]\n", - " \n", - " print(\"Encoding categorical columns\")\n", - " df_encoded = pd.get_dummies(df_window, columns=cat_columns.keys().tolist())\n", - " \n", - " print(\"Dropping unnecessary columns\")\n", - " df_featurized = df_encoded.drop(['windAngle', 'windSpeed', 'datetime', 'elevation'], axis=1).dropna().drop_duplicates()\n", - " \n", - " return df_featurized" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Train model on Jan 1 - 14, 2009 data\n", - "df = get_featurized_noaa_df(datetime(2009, 1, 1), datetime(2009, 1, 14, 23, 59, 59), columns, usaf_list)\n", - "df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "label = \"temperature\"\n", - "x_df = df.drop(label, axis=1)\n", - "y_df = df[[label]]\n", - "x_train, x_test, y_train, y_test = train_test_split(df, y_df, test_size=0.2, random_state=223)\n", - "print(x_train.shape, x_test.shape, y_train.shape, y_test.shape)\n", - "\n", - "training_dir = 'outputs/training'\n", - "training_file = \"training.csv\"\n", - "\n", - "# Generate training dataframe to register as Training Dataset\n", - "os.makedirs(training_dir, exist_ok=True)\n", - "training_df = pd.merge(x_train.drop(label, axis=1), y_train, left_index=True, right_index=True)\n", - "training_df.to_csv(training_dir + \"/\" + training_file)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create/Register Training Dataset" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dataset_name = \"dataset\"\n", - "name_suffix = datetime.utcnow().strftime(\"%Y-%m-%d-%H-%M-%S\")\n", - "snapshot_name = \"snapshot-{}\".format(name_suffix)\n", - "\n", - "dstore = ws.get_default_datastore()\n", - "dstore.upload(training_dir, \"data/training\", show_progress=True)\n", - "dpath = dstore.path(\"data/training/training.csv\")\n", - "trainingDataset = Dataset.auto_read_files(dpath, include_path=True)\n", - "trainingDataset = trainingDataset.register(workspace=ws, name=dataset_name, description=\"dset\", exist_ok=True)\n", - "\n", - "trainingDataSnapshot = trainingDataset.create_snapshot(snapshot_name=snapshot_name, compute_target=None, create_data_snapshot=True)\n", - "datasets = [(Dataset.Scenario.TRAINING, trainingDataSnapshot)]\n", - "print(\"dataset registration done.\\n\")\n", - "datasets" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Train and Save Model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import lightgbm as lgb\n", - "\n", - "train = lgb.Dataset(data=x_train, \n", - " label=y_train)\n", - "\n", - "test = lgb.Dataset(data=x_test, \n", - " label=y_test,\n", - " reference=train)\n", - "\n", - "params = {'learning_rate' : 0.1,\n", - " 'boosting' : 'gbdt',\n", - " 'metric' : 'rmse',\n", - " 'feature_fraction' : 1,\n", - " 'bagging_fraction' : 1,\n", - " 'max_depth': 6,\n", - " 'num_leaves' : 31,\n", - " 'objective' : 'regression',\n", - " 'bagging_freq' : 1,\n", - " \"verbose\": -1,\n", - " 'min_data_per_leaf': 100}\n", - "\n", - "model = lgb.train(params, \n", - " num_boost_round=500,\n", - " train_set=train,\n", - " valid_sets=[train, test],\n", - " verbose_eval=50,\n", - " early_stopping_rounds=25)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model_file = 'outputs/{}.pkl'.format(model_name)\n", - "\n", - "os.makedirs('outputs', exist_ok=True)\n", - "joblib.dump(model, model_file)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Register Model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model = Model.register(model_path=model_file,\n", - " model_name=model_name,\n", - " workspace=ws,\n", - " datasets=datasets)\n", - "\n", - "print(model_name, image_name, service_name, model)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Deploy Model To AKS" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prepare Environment" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn', 'joblib', 'lightgbm', 'pandas'],\n", - " pip_packages=['azureml-monitoring', 'azureml-sdk[automl]'])\n", - "\n", - "with open(\"myenv.yml\",\"w\") as f:\n", - " f.write(myenv.serialize_to_string())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create Image" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Image creation may take up to 15 minutes.\n", - "\n", - "image_name = image_name + str(model.version)\n", - "\n", - "if not image_name in ws.images:\n", - " # Use the score.py defined in this directory as the execution script\n", - " # NOTE: The Model Data Collector must be enabled in the execution script for DataDrift to run correctly\n", - " image_config = ContainerImage.image_configuration(execution_script=\"score.py\",\n", - " runtime=\"python\",\n", - " conda_file=\"myenv.yml\",\n", - " description=\"Image with weather dataset model\")\n", - " image = ContainerImage.create(name=image_name,\n", - " models=[model],\n", - " image_config=image_config,\n", - " workspace=ws)\n", - "\n", - " image.wait_for_creation(show_output=True)\n", - "else:\n", - " image = ws.images[image_name]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create Compute Target" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "aks_name = 'dd-demo-e2e'\n", - "prov_config = AksCompute.provisioning_configuration()\n", - "\n", - "if not aks_name in ws.compute_targets:\n", - " aks_target = ComputeTarget.create(workspace=ws,\n", - " name=aks_name,\n", - " provisioning_configuration=prov_config)\n", - "\n", - " aks_target.wait_for_completion(show_output=True)\n", - " print(aks_target.provisioning_state)\n", - " print(aks_target.provisioning_errors)\n", - "else:\n", - " aks_target=ws.compute_targets[aks_name]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Deploy Service" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "aks_service_name = service_name\n", - "\n", - "if not aks_service_name in ws.webservices:\n", - " aks_config = AksWebservice.deploy_configuration(collect_model_data=True, enable_app_insights=True)\n", - " aks_service = Webservice.deploy_from_image(workspace=ws,\n", - " name=aks_service_name,\n", - " image=image,\n", - " deployment_config=aks_config,\n", - " deployment_target=aks_target)\n", - " aks_service.wait_for_deployment(show_output=True)\n", - " print(aks_service.state)\n", - "else:\n", - " aks_service = ws.webservices[aks_service_name]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Run DataDrift Analysis" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Send Scoring Data to Service" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Download Scoring Data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Score Model on March 15, 2016 data\n", - "scoring_df = get_noaa_data(datetime(2016, 3, 15) - timedelta(days=7), datetime(2016, 3, 16), columns, usaf_list)\n", - "# Add the window feature column\n", - "scoring_df = add_window_col(scoring_df)\n", - "\n", - "# Drop features not used by the model\n", - "print(\"Dropping unnecessary columns\")\n", - "scoring_df = scoring_df.drop(['windAngle', 'windSpeed', 'datetime', 'elevation'], axis=1).dropna()\n", - "scoring_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# One Hot Encode the scoring dataset to match the training dataset schema\n", - "columns_dict = model.datasets[\"training\"][0].get_profile().columns\n", - "extra_cols = ('Path', 'Column1')\n", - "for k in extra_cols:\n", - " columns_dict.pop(k, None)\n", - "training_columns = list(columns_dict.keys())\n", - "\n", - "categorical_columns = scoring_df.dtypes == object\n", - "categorical_columns = categorical_columns[categorical_columns == True]\n", - "\n", - "test_df = pd.get_dummies(scoring_df[categorical_columns.keys().tolist()])\n", - "encoded_df = scoring_df.join(test_df)\n", - "\n", - "# Populate missing OHE columns with 0 values to match traning dataset schema\n", - "difference = list(set(training_columns) - set(encoded_df.columns.tolist()))\n", - "for col in difference:\n", - " encoded_df[col] = 0\n", - "encoded_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Serialize dataframe to list of row dictionaries\n", - "encoded_dict = encoded_df.to_dict('records')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Submit Scoring Data to Service" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%time\n", - "\n", - "# retreive the API keys. AML generates two keys.\n", - "key1, key2 = aks_service.get_keys()\n", - "\n", - "total_count = len(scoring_df)\n", - "i = 0\n", - "load = []\n", - "for row in encoded_dict:\n", - " load.append(row)\n", - " i = i + 1\n", - " if i % 100 == 0:\n", - " payload = json.dumps({\"data\": load})\n", - " \n", - " # construct raw HTTP request and send to the service\n", - " payload_binary = bytes(payload,encoding = 'utf8')\n", - " headers = {'Content-Type':'application/json', 'Authorization': 'Bearer ' + key1}\n", - " resp = requests.post(aks_service.scoring_uri, payload_binary, headers=headers)\n", - " \n", - " print(\"prediction:\", resp.content, \"Progress: {}/{}\".format(i, total_count)) \n", - "\n", - " load = []\n", - " time.sleep(3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Configure DataDrift" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "services = [service_name]\n", - "start = datetime.now() - timedelta(days=2)\n", - "end = datetime(year=2020, month=1, day=22, hour=15, minute=16)\n", - "feature_list = ['usaf', 'wban', 'latitude', 'longitude', 'station_name', 'p_k', 'sine_hourofday', 'cosine_hourofday', 'temperature-7']\n", - "alert_config = AlertConfiguration([email_address]) if email_address else None\n", - "\n", - "# there will be an exception indicating using get() method if DataDrift object already exist\n", - "try:\n", - " datadrift = DataDriftDetector.create(ws, model.name, model.version, services, frequency=\"Day\", alert_config=alert_config)\n", - "except KeyError:\n", - " datadrift = DataDriftDetector.get(ws, model.name, model.version)\n", - " \n", - "print(\"Details of DataDrift Object:\\n{}\".format(datadrift))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Run an Adhoc DataDriftDetector Run" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "target_date = datetime.today()\n", - "run = datadrift.run(target_date, services, feature_list=feature_list, create_compute_target=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "exp = Experiment(ws, datadrift._id)\n", - "dd_run = Run(experiment=exp, run_id=run)\n", - "RunDetails(dd_run).show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Get Drift Analysis Results" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "children = list(dd_run.get_children())\n", - "for child in children:\n", - " child.wait_for_completion()\n", - "\n", - "drift_metrics = datadrift.get_output(start_time=start, end_time=end)\n", - "drift_metrics" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Show all drift figures, one per serivice.\n", - "# If setting with_details is False (by default), only drift will be shown; if it's True, all details will be shown.\n", - "\n", - "drift_figures = datadrift.show(with_details=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Enable DataDrift Schedule" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "datadrift.enable_schedule()" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "rafarmah" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Track Data Drift between Training and Inference Data in Production \n", + "\n", + "With this notebook, you will learn how to enable the DataDrift service to automatically track and determine whether your inference data is drifting from the data your model was initially trained on. The DataDrift service provides metrics and visualizations to help stakeholders identify which specific features cause the concept drift to occur.\n", + "\n", + "Please email driftfeedback@microsoft.com with any issues. A member from the DataDrift team will respond shortly. \n", + "\n", + "The DataDrift Public Preview API can be found [here](https://docs.microsoft.com/en-us/python/api/azureml-contrib-datadrift/?view=azure-ml-py). " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/contrib/datadrift/azureml-datadrift.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Prerequisites and Setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install the DataDrift package\n", + "\n", + "Install the azureml-contrib-datadrift, azureml-contrib-opendatasets and lightgbm packages before running this notebook.\n", + "```\n", + "pip install azureml-contrib-datadrift\n", + "pip install azureml-contrib-datasets\n", + "pip install lightgbm\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Import Dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import os\n", + "import time\n", + "from datetime import datetime, timedelta\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import requests\n", + "from azureml.contrib.datadrift import DataDriftDetector, AlertConfiguration\n", + "from azureml.contrib.opendatasets import NoaaIsdWeather\n", + "from azureml.core import Dataset, Workspace, Run\n", + "from azureml.core.compute import AksCompute, ComputeTarget\n", + "from azureml.core.conda_dependencies import CondaDependencies\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.image import ContainerImage\n", + "from azureml.core.model import Model\n", + "from azureml.core.webservice import Webservice, AksWebservice\n", + "from azureml.widgets import RunDetails\n", + "from sklearn.externals import joblib\n", + "from sklearn.model_selection import train_test_split\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up Configuraton and Create Azure ML Workspace\n", + "\n", + "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, go through the [configuration notebook](../../configuration.ipynb) first if you haven't already to establish your connection to the AzureML Workspace." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Please type in your initials/alias. The prefix is prepended to the names of resources created by this notebook. \n", + "prefix = \"dd\"\n", + "\n", + "# NOTE: Please do not change the model_name, as it's required by the score.py file\n", + "model_name = \"driftmodel\"\n", + "image_name = \"{}driftimage\".format(prefix)\n", + "service_name = \"{}driftservice\".format(prefix)\n", + "\n", + "# optionally, set email address to receive an email alert for DataDrift\n", + "email_address = \"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "print(ws.name, ws.resource_group, ws.location, ws.subscription_id, sep = '\\n')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate Train/Testing Data\n", + "\n", + "For this demo, we will use NOAA weather data from [Azure Open Datasets](https://azure.microsoft.com/services/open-datasets/). You may replace this step with your own dataset. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "usaf_list = ['725724', '722149', '723090', '722159', '723910', '720279',\n", + " '725513', '725254', '726430', '720381', '723074', '726682',\n", + " '725486', '727883', '723177', '722075', '723086', '724053',\n", + " '725070', '722073', '726060', '725224', '725260', '724520',\n", + " '720305', '724020', '726510', '725126', '722523', '703333',\n", + " '722249', '722728', '725483', '722972', '724975', '742079',\n", + " '727468', '722193', '725624', '722030', '726380', '720309',\n", + " '722071', '720326', '725415', '724504', '725665', '725424',\n", + " '725066']\n", + "\n", + "columns = ['usaf', 'wban', 'datetime', 'latitude', 'longitude', 'elevation', 'windAngle', 'windSpeed', 'temperature', 'stationName', 'p_k']\n", + "\n", + "def enrich_weather_noaa_data(noaa_df):\n", + " hours_in_day = 23\n", + " week_in_year = 52\n", + " \n", + "\n", + " noaa_df = noaa_df.assign(hour=noaa_df[\"datetime\"].dt.hour,\n", + " weekofyear=noaa_df[\"datetime\"].dt.week,\n", + " sine_weekofyear=noaa_df['datetime'].transform(lambda x: np.sin((2*np.pi*x.dt.week-1)/week_in_year)),\n", + " cosine_weekofyear=noaa_df['datetime'].transform(lambda x: np.cos((2*np.pi*x.dt.week-1)/week_in_year)),\n", + " sine_hourofday=noaa_df['datetime'].transform(lambda x: np.sin(2*np.pi*x.dt.hour/hours_in_day)),\n", + " cosine_hourofday=noaa_df['datetime'].transform(lambda x: np.cos(2*np.pi*x.dt.hour/hours_in_day))\n", + " )\n", + " \n", + " return noaa_df\n", + "\n", + "\n", + "def add_window_col(input_df):\n", + " shift_interval = pd.Timedelta('-7 days') # your X days interval\n", + " df_shifted = input_df.copy()\n", + " df_shifted.loc[:,'datetime'] = df_shifted['datetime'] - shift_interval\n", + " df_shifted.drop(list(input_df.columns.difference(['datetime', 'usaf', 'wban', 'sine_hourofday', 'temperature'])), axis=1, inplace=True)\n", + "\n", + " # merge, keeping only observations where -1 lag is present\n", + " df2 = pd.merge(input_df,\n", + " df_shifted,\n", + " on=['datetime', 'usaf', 'wban', 'sine_hourofday'],\n", + " how='inner', # use 'left' to keep observations without lags\n", + " suffixes=['', '-7'])\n", + " return df2\n", + "\n", + "def get_noaa_data(start_time, end_time, cols, station_list):\n", + " isd = NoaaIsdWeather(start_time, end_time, cols=cols)\n", + " # Read into Pandas data frame.\n", + " noaa_df = isd.to_pandas_dataframe()\n", + " noaa_df = noaa_df.rename(columns={\"stationName\": \"station_name\"})\n", + " \n", + " df_filtered = noaa_df[noaa_df[\"usaf\"].isin(station_list)]\n", + " df_filtered.reset_index(drop=True)\n", + " \n", + " # Enrich with time features\n", + " df_enriched = enrich_weather_noaa_data(df_filtered)\n", + " \n", + " return df_enriched\n", + "\n", + "def get_featurized_noaa_df(start_time, end_time, cols, station_list):\n", + " df_1 = get_noaa_data(start_time - timedelta(days=7), start_time - timedelta(seconds=1), cols, station_list)\n", + " df_2 = get_noaa_data(start_time, end_time, cols, station_list)\n", + " noaa_df = pd.concat([df_1, df_2])\n", + " \n", + " print(\"Adding window feature\")\n", + " df_window = add_window_col(noaa_df)\n", + " \n", + " cat_columns = df_window.dtypes == object\n", + " cat_columns = cat_columns[cat_columns == True]\n", + " \n", + " print(\"Encoding categorical columns\")\n", + " df_encoded = pd.get_dummies(df_window, columns=cat_columns.keys().tolist())\n", + " \n", + " print(\"Dropping unnecessary columns\")\n", + " df_featurized = df_encoded.drop(['windAngle', 'windSpeed', 'datetime', 'elevation'], axis=1).dropna().drop_duplicates()\n", + " \n", + " return df_featurized" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Train model on Jan 1 - 14, 2009 data\n", + "df = get_featurized_noaa_df(datetime(2009, 1, 1), datetime(2009, 1, 14, 23, 59, 59), columns, usaf_list)\n", + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "label = \"temperature\"\n", + "x_df = df.drop(label, axis=1)\n", + "y_df = df[[label]]\n", + "x_train, x_test, y_train, y_test = train_test_split(df, y_df, test_size=0.2, random_state=223)\n", + "print(x_train.shape, x_test.shape, y_train.shape, y_test.shape)\n", + "\n", + "training_dir = 'outputs/training'\n", + "training_file = \"training.csv\"\n", + "\n", + "# Generate training dataframe to register as Training Dataset\n", + "os.makedirs(training_dir, exist_ok=True)\n", + "training_df = pd.merge(x_train.drop(label, axis=1), y_train, left_index=True, right_index=True)\n", + "training_df.to_csv(training_dir + \"/\" + training_file)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create/Register Training Dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset_name = \"dataset\"\n", + "name_suffix = datetime.utcnow().strftime(\"%Y-%m-%d-%H-%M-%S\")\n", + "snapshot_name = \"snapshot-{}\".format(name_suffix)\n", + "\n", + "dstore = ws.get_default_datastore()\n", + "dstore.upload(training_dir, \"data/training\", show_progress=True)\n", + "dpath = dstore.path(\"data/training/training.csv\")\n", + "trainingDataset = Dataset.auto_read_files(dpath, include_path=True)\n", + "trainingDataset = trainingDataset.register(workspace=ws, name=dataset_name, description=\"dset\", exist_ok=True)\n", + "\n", + "trainingDataSnapshot = trainingDataset.create_snapshot(snapshot_name=snapshot_name, compute_target=None, create_data_snapshot=True)\n", + "datasets = [(Dataset.Scenario.TRAINING, trainingDataSnapshot)]\n", + "print(\"dataset registration done.\\n\")\n", + "datasets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train and Save Model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import lightgbm as lgb\n", + "\n", + "train = lgb.Dataset(data=x_train, \n", + " label=y_train)\n", + "\n", + "test = lgb.Dataset(data=x_test, \n", + " label=y_test,\n", + " reference=train)\n", + "\n", + "params = {'learning_rate' : 0.1,\n", + " 'boosting' : 'gbdt',\n", + " 'metric' : 'rmse',\n", + " 'feature_fraction' : 1,\n", + " 'bagging_fraction' : 1,\n", + " 'max_depth': 6,\n", + " 'num_leaves' : 31,\n", + " 'objective' : 'regression',\n", + " 'bagging_freq' : 1,\n", + " \"verbose\": -1,\n", + " 'min_data_per_leaf': 100}\n", + "\n", + "model = lgb.train(params, \n", + " num_boost_round=500,\n", + " train_set=train,\n", + " valid_sets=[train, test],\n", + " verbose_eval=50,\n", + " early_stopping_rounds=25)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model_file = 'outputs/{}.pkl'.format(model_name)\n", + "\n", + "os.makedirs('outputs', exist_ok=True)\n", + "joblib.dump(model, model_file)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Register Model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model = Model.register(model_path=model_file,\n", + " model_name=model_name,\n", + " workspace=ws,\n", + " datasets=datasets)\n", + "\n", + "print(model_name, image_name, service_name, model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deploy Model To AKS" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prepare Environment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "myenv = CondaDependencies.create(conda_packages=['numpy','scikit-learn', 'joblib', 'lightgbm', 'pandas'],\n", + " pip_packages=['azureml-monitoring', 'azureml-sdk[automl]'])\n", + "\n", + "with open(\"myenv.yml\",\"w\") as f:\n", + " f.write(myenv.serialize_to_string())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create Image" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Image creation may take up to 15 minutes.\n", + "\n", + "image_name = image_name + str(model.version)\n", + "\n", + "if not image_name in ws.images:\n", + " # Use the score.py defined in this directory as the execution script\n", + " # NOTE: The Model Data Collector must be enabled in the execution script for DataDrift to run correctly\n", + " image_config = ContainerImage.image_configuration(execution_script=\"score.py\",\n", + " runtime=\"python\",\n", + " conda_file=\"myenv.yml\",\n", + " description=\"Image with weather dataset model\")\n", + " image = ContainerImage.create(name=image_name,\n", + " models=[model],\n", + " image_config=image_config,\n", + " workspace=ws)\n", + "\n", + " image.wait_for_creation(show_output=True)\n", + "else:\n", + " image = ws.images[image_name]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create Compute Target" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aks_name = 'dd-demo-e2e'\n", + "prov_config = AksCompute.provisioning_configuration()\n", + "\n", + "if not aks_name in ws.compute_targets:\n", + " aks_target = ComputeTarget.create(workspace=ws,\n", + " name=aks_name,\n", + " provisioning_configuration=prov_config)\n", + "\n", + " aks_target.wait_for_completion(show_output=True)\n", + " print(aks_target.provisioning_state)\n", + " print(aks_target.provisioning_errors)\n", + "else:\n", + " aks_target=ws.compute_targets[aks_name]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Deploy Service" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aks_service_name = service_name\n", + "\n", + "if not aks_service_name in ws.webservices:\n", + " aks_config = AksWebservice.deploy_configuration(collect_model_data=True, enable_app_insights=True)\n", + " aks_service = Webservice.deploy_from_image(workspace=ws,\n", + " name=aks_service_name,\n", + " image=image,\n", + " deployment_config=aks_config,\n", + " deployment_target=aks_target)\n", + " aks_service.wait_for_deployment(show_output=True)\n", + " print(aks_service.state)\n", + "else:\n", + " aks_service = ws.webservices[aks_service_name]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Run DataDrift Analysis" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Send Scoring Data to Service" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Download Scoring Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Score Model on March 15, 2016 data\n", + "scoring_df = get_noaa_data(datetime(2016, 3, 15) - timedelta(days=7), datetime(2016, 3, 16), columns, usaf_list)\n", + "# Add the window feature column\n", + "scoring_df = add_window_col(scoring_df)\n", + "\n", + "# Drop features not used by the model\n", + "print(\"Dropping unnecessary columns\")\n", + "scoring_df = scoring_df.drop(['windAngle', 'windSpeed', 'datetime', 'elevation'], axis=1).dropna()\n", + "scoring_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# One Hot Encode the scoring dataset to match the training dataset schema\n", + "columns_dict = model.datasets[\"training\"][0].get_profile().columns\n", + "extra_cols = ('Path', 'Column1')\n", + "for k in extra_cols:\n", + " columns_dict.pop(k, None)\n", + "training_columns = list(columns_dict.keys())\n", + "\n", + "categorical_columns = scoring_df.dtypes == object\n", + "categorical_columns = categorical_columns[categorical_columns == True]\n", + "\n", + "test_df = pd.get_dummies(scoring_df[categorical_columns.keys().tolist()])\n", + "encoded_df = scoring_df.join(test_df)\n", + "\n", + "# Populate missing OHE columns with 0 values to match traning dataset schema\n", + "difference = list(set(training_columns) - set(encoded_df.columns.tolist()))\n", + "for col in difference:\n", + " encoded_df[col] = 0\n", + "encoded_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Serialize dataframe to list of row dictionaries\n", + "encoded_dict = encoded_df.to_dict('records')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Submit Scoring Data to Service" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "\n", + "# retreive the API keys. AML generates two keys.\n", + "key1, key2 = aks_service.get_keys()\n", + "\n", + "total_count = len(scoring_df)\n", + "i = 0\n", + "load = []\n", + "for row in encoded_dict:\n", + " load.append(row)\n", + " i = i + 1\n", + " if i % 100 == 0:\n", + " payload = json.dumps({\"data\": load})\n", + " \n", + " # construct raw HTTP request and send to the service\n", + " payload_binary = bytes(payload,encoding = 'utf8')\n", + " headers = {'Content-Type':'application/json', 'Authorization': 'Bearer ' + key1}\n", + " resp = requests.post(aks_service.scoring_uri, payload_binary, headers=headers)\n", + " \n", + " print(\"prediction:\", resp.content, \"Progress: {}/{}\".format(i, total_count)) \n", + "\n", + " load = []\n", + " time.sleep(3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configure DataDrift" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "services = [service_name]\n", + "start = datetime.now() - timedelta(days=2)\n", + "end = datetime(year=2020, month=1, day=22, hour=15, minute=16)\n", + "feature_list = ['usaf', 'wban', 'latitude', 'longitude', 'station_name', 'p_k', 'sine_hourofday', 'cosine_hourofday', 'temperature-7']\n", + "alert_config = AlertConfiguration([email_address]) if email_address else None\n", + "\n", + "# there will be an exception indicating using get() method if DataDrift object already exist\n", + "try:\n", + " datadrift = DataDriftDetector.create(ws, model.name, model.version, services, frequency=\"Day\", alert_config=alert_config)\n", + "except KeyError:\n", + " datadrift = DataDriftDetector.get(ws, model.name, model.version)\n", + " \n", + "print(\"Details of DataDrift Object:\\n{}\".format(datadrift))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Run an Adhoc DataDriftDetector Run" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "target_date = datetime.today()\n", + "run = datadrift.run(target_date, services, feature_list=feature_list, create_compute_target=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "exp = Experiment(ws, datadrift._id)\n", + "dd_run = Run(experiment=exp, run_id=run)\n", + "RunDetails(dd_run).show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Get Drift Analysis Results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "children = list(dd_run.get_children())\n", + "for child in children:\n", + " child.wait_for_completion()\n", + "\n", + "drift_metrics = datadrift.get_output(start_time=start, end_time=end)\n", + "drift_metrics" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Show all drift figures, one per serivice.\n", + "# If setting with_details is False (by default), only drift will be shown; if it's True, all details will be shown.\n", + "\n", + "drift_figures = datadrift.show(with_details=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Enable DataDrift Schedule" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datadrift.enable_schedule()" + ] + } ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" + "metadata": { + "authors": [ + { + "name": "rafarmah" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.6" + } }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.6" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/how-to-use-azureml/deploy-to-cloud/model-register-and-deploy.yml b/how-to-use-azureml/deploy-to-cloud/model-register-and-deploy.yml new file mode 100644 index 00000000..ca6bae19 --- /dev/null +++ b/how-to-use-azureml/deploy-to-cloud/model-register-and-deploy.yml @@ -0,0 +1,4 @@ +name: model-register-and-deploy +dependencies: +- pip: + - azureml-sdk diff --git a/how-to-use-azureml/deployment/enable-app-insights-in-production-service/enable-app-insights-in-production-service.yml b/how-to-use-azureml/deployment/enable-app-insights-in-production-service/enable-app-insights-in-production-service.yml new file mode 100644 index 00000000..c716614a --- /dev/null +++ b/how-to-use-azureml/deployment/enable-app-insights-in-production-service/enable-app-insights-in-production-service.yml @@ -0,0 +1,4 @@ +name: enable-app-insights-in-production-service +dependencies: +- pip: + - azureml-sdk diff --git a/how-to-use-azureml/deployment/enable-data-collection-for-models-in-aks/enable-data-collection-for-models-in-aks.yml b/how-to-use-azureml/deployment/enable-data-collection-for-models-in-aks/enable-data-collection-for-models-in-aks.yml new file mode 100644 index 00000000..f72bf1dc --- /dev/null +++ b/how-to-use-azureml/deployment/enable-data-collection-for-models-in-aks/enable-data-collection-for-models-in-aks.yml @@ -0,0 +1,4 @@ +name: enable-data-collection-for-models-in-aks +dependencies: +- pip: + - azureml-sdk diff --git a/how-to-use-azureml/deployment/onnx/onnx-convert-aml-deploy-tinyyolo.yml b/how-to-use-azureml/deployment/onnx/onnx-convert-aml-deploy-tinyyolo.yml new file mode 100644 index 00000000..c6d8e84b --- /dev/null +++ b/how-to-use-azureml/deployment/onnx/onnx-convert-aml-deploy-tinyyolo.yml @@ -0,0 +1,6 @@ +name: onnx-convert-aml-deploy-tinyyolo +dependencies: +- pip: + - azureml-sdk + - git+https://github.com/apple/coremltools + - onnxmltools==1.3.1 diff --git a/how-to-use-azureml/deployment/onnx/onnx-inference-facial-expression-recognition-deploy.yml b/how-to-use-azureml/deployment/onnx/onnx-inference-facial-expression-recognition-deploy.yml new file mode 100644 index 00000000..7a41748d --- /dev/null +++ b/how-to-use-azureml/deployment/onnx/onnx-inference-facial-expression-recognition-deploy.yml @@ -0,0 +1,9 @@ +name: onnx-inference-facial-expression-recognition-deploy +dependencies: +- pip: + - azureml-sdk + - azureml-widgets + - matplotlib + - numpy + - onnx + - opencv-python diff --git a/how-to-use-azureml/deployment/onnx/onnx-inference-mnist-deploy.yml b/how-to-use-azureml/deployment/onnx/onnx-inference-mnist-deploy.yml new file mode 100644 index 00000000..614209c5 --- /dev/null +++ b/how-to-use-azureml/deployment/onnx/onnx-inference-mnist-deploy.yml @@ -0,0 +1,9 @@ +name: onnx-inference-mnist-deploy +dependencies: +- pip: + - azureml-sdk + - azureml-widgets + - matplotlib + - numpy + - onnx + - opencv-python diff --git a/how-to-use-azureml/deployment/onnx/onnx-modelzoo-aml-deploy-resnet50.yml b/how-to-use-azureml/deployment/onnx/onnx-modelzoo-aml-deploy-resnet50.yml new file mode 100644 index 00000000..a80e6bb0 --- /dev/null +++ b/how-to-use-azureml/deployment/onnx/onnx-modelzoo-aml-deploy-resnet50.yml @@ -0,0 +1,4 @@ +name: onnx-modelzoo-aml-deploy-resnet50 +dependencies: +- pip: + - azureml-sdk diff --git a/how-to-use-azureml/deployment/onnx/onnx-train-pytorch-aml-deploy-mnist.yml b/how-to-use-azureml/deployment/onnx/onnx-train-pytorch-aml-deploy-mnist.yml new file mode 100644 index 00000000..c0145ec0 --- /dev/null +++ b/how-to-use-azureml/deployment/onnx/onnx-train-pytorch-aml-deploy-mnist.yml @@ -0,0 +1,5 @@ +name: onnx-train-pytorch-aml-deploy-mnist +dependencies: +- pip: + - azureml-sdk + - azureml-widgets diff --git a/how-to-use-azureml/deployment/production-deploy-to-aks/production-deploy-to-aks.yml b/how-to-use-azureml/deployment/production-deploy-to-aks/production-deploy-to-aks.yml new file mode 100644 index 00000000..addf41c7 --- /dev/null +++ b/how-to-use-azureml/deployment/production-deploy-to-aks/production-deploy-to-aks.yml @@ -0,0 +1,8 @@ +name: production-deploy-to-aks +dependencies: +- pip: + - azureml-sdk + - matplotlib + - tqdm + - scipy + - sklearn diff --git a/how-to-use-azureml/deployment/register-model-create-image-deploy-service/register-model-create-image-deploy-service.yml b/how-to-use-azureml/deployment/register-model-create-image-deploy-service/register-model-create-image-deploy-service.yml new file mode 100644 index 00000000..509d9b40 --- /dev/null +++ b/how-to-use-azureml/deployment/register-model-create-image-deploy-service/register-model-create-image-deploy-service.yml @@ -0,0 +1,8 @@ +name: register-model-create-image-deploy-service +dependencies: +- pip: + - azureml-sdk + - matplotlib + - tqdm + - scipy + - sklearn diff --git a/how-to-use-azureml/explain-model/explain-on-amlcompute/regression-sklearn-on-amlcompute.yml b/how-to-use-azureml/explain-model/explain-on-amlcompute/regression-sklearn-on-amlcompute.yml new file mode 100644 index 00000000..49144ae8 --- /dev/null +++ b/how-to-use-azureml/explain-model/explain-on-amlcompute/regression-sklearn-on-amlcompute.yml @@ -0,0 +1,6 @@ +name: regression-sklearn-on-amlcompute +dependencies: +- pip: + - azureml-sdk + - azureml-explain-model + - azureml-contrib-explain-model diff --git a/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-binary-classification.yml b/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-binary-classification.yml new file mode 100644 index 00000000..744335c8 --- /dev/null +++ b/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-binary-classification.yml @@ -0,0 +1,6 @@ +name: explain-local-sklearn-binary-classification +dependencies: +- pip: + - azureml-sdk + - azureml-explain-model + - azureml-contrib-explain-model diff --git a/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-multiclass-classification.yml b/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-multiclass-classification.yml new file mode 100644 index 00000000..51cc039c --- /dev/null +++ b/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-multiclass-classification.yml @@ -0,0 +1,6 @@ +name: explain-local-sklearn-multiclass-classification +dependencies: +- pip: + - azureml-sdk + - azureml-explain-model + - azureml-contrib-explain-model diff --git a/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-regression.yml b/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-regression.yml new file mode 100644 index 00000000..0d91a84a --- /dev/null +++ b/how-to-use-azureml/explain-model/explain-tabular-data-local/explain-local-sklearn-regression.yml @@ -0,0 +1,6 @@ +name: explain-local-sklearn-regression +dependencies: +- pip: + - azureml-sdk + - azureml-explain-model + - azureml-contrib-explain-model diff --git a/how-to-use-azureml/explain-model/explain-tabular-data-raw-features/explain-sklearn-raw-features.yml b/how-to-use-azureml/explain-model/explain-tabular-data-raw-features/explain-sklearn-raw-features.yml new file mode 100644 index 00000000..7d213fab --- /dev/null +++ b/how-to-use-azureml/explain-model/explain-tabular-data-raw-features/explain-sklearn-raw-features.yml @@ -0,0 +1,7 @@ +name: explain-sklearn-raw-features +dependencies: +- pip: + - azureml-sdk + - azureml-explain-model + - azureml-contrib-explain-model + - sklearn-pandas diff --git a/how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-classification.yml b/how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-classification.yml new file mode 100644 index 00000000..067971f5 --- /dev/null +++ b/how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-classification.yml @@ -0,0 +1,6 @@ +name: explain-run-history-sklearn-classification +dependencies: +- pip: + - azureml-sdk + - azureml-explain-model + - azureml-contrib-explain-model diff --git a/how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-regression.yml b/how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-regression.yml new file mode 100644 index 00000000..69f81e2a --- /dev/null +++ b/how-to-use-azureml/explain-model/explain-tabular-data-run-history/explain-run-history-sklearn-regression.yml @@ -0,0 +1,6 @@ +name: explain-run-history-sklearn-regression +dependencies: +- pip: + - azureml-sdk + - azureml-explain-model + - azureml-contrib-explain-model diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-data-transfer.yml b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-data-transfer.yml new file mode 100644 index 00000000..4955c060 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-data-transfer.yml @@ -0,0 +1,5 @@ +name: aml-pipelines-data-transfer +dependencies: +- pip: + - azureml-sdk + - azureml-widgets diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-getting-started.yml b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-getting-started.yml new file mode 100644 index 00000000..3c4f7d04 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-getting-started.yml @@ -0,0 +1,5 @@ +name: aml-pipelines-getting-started +dependencies: +- pip: + - azureml-sdk + - azureml-widgets diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-how-to-use-estimatorstep.yml b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-how-to-use-estimatorstep.yml new file mode 100644 index 00000000..f6ab4807 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-how-to-use-estimatorstep.yml @@ -0,0 +1,5 @@ +name: aml-pipelines-how-to-use-estimatorstep +dependencies: +- pip: + - azureml-sdk + - azureml-widgets diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-publish-and-run-using-rest-endpoint.yml b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-publish-and-run-using-rest-endpoint.yml new file mode 100644 index 00000000..ec9828ff --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-publish-and-run-using-rest-endpoint.yml @@ -0,0 +1,6 @@ +name: aml-pipelines-publish-and-run-using-rest-endpoint +dependencies: +- pip: + - azureml-sdk + - azureml-widgets + - requests diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-automated-machine-learning-step.yml b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-automated-machine-learning-step.yml new file mode 100644 index 00000000..f3d36970 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-automated-machine-learning-step.yml @@ -0,0 +1,8 @@ +name: aml-pipelines-with-automated-machine-learning-step +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - matplotlib + - pandas_ml diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-data-dependency-steps.yml b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-data-dependency-steps.yml new file mode 100644 index 00000000..0f034033 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-with-data-dependency-steps.yml @@ -0,0 +1,5 @@ +name: aml-pipelines-with-data-dependency-steps +dependencies: +- pip: + - azureml-sdk + - azureml-widgets diff --git a/how-to-use-azureml/machine-learning-pipelines/nyc-taxi-data-regression-model-building/nyc-taxi-data-regression-model-building.ipynb b/how-to-use-azureml/machine-learning-pipelines/nyc-taxi-data-regression-model-building/nyc-taxi-data-regression-model-building.ipynb index c989192b..7b4fedf5 100644 --- a/how-to-use-azureml/machine-learning-pipelines/nyc-taxi-data-regression-model-building/nyc-taxi-data-regression-model-building.ipynb +++ b/how-to-use-azureml/machine-learning-pipelines/nyc-taxi-data-regression-model-building/nyc-taxi-data-regression-model-building.ipynb @@ -26,7 +26,7 @@ "metadata": {}, "source": [ "## Prepare data for regression modeling\n", - "First, we will prepare data for regression modeling. We will leverage the convenience of Azure Open Datasets along with the power of Azure Machine Learning service to create a regression model to predict NYC taxi fare prices. Perform `pip install azureml-contrib-opendatasets` to get the open dataset package. The Open Datasets package contains a class representing each data source (NycTlcGreen and NycTlcYellow) to easily filter date parameters before downloading.\n", + "First, we will prepare data for regression modeling. We will leverage the convenience of Azure Open Datasets along with the power of Azure Machine Learning service to create a regression model to predict NYC taxi fare prices. Perform `pip install azureml-opendatasets` to get the open dataset package. The Open Datasets package contains a class representing each data source (NycTlcGreen and NycTlcYellow) to easily filter date parameters before downloading.\n", "\n", "\n", "### Load data\n", @@ -52,7 +52,7 @@ "metadata": {}, "outputs": [], "source": [ - "from azureml.contrib.opendatasets import NycTlcGreen, NycTlcYellow\n", + "from azureml.opendatasets import NycTlcGreen, NycTlcYellow\n", "import pandas as pd\n", "from datetime import datetime\n", "from dateutil.relativedelta import relativedelta\n", diff --git a/how-to-use-azureml/machine-learning-pipelines/nyc-taxi-data-regression-model-building/nyc-taxi-data-regression-model-building.yml b/how-to-use-azureml/machine-learning-pipelines/nyc-taxi-data-regression-model-building/nyc-taxi-data-regression-model-building.yml new file mode 100644 index 00000000..dcdee696 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/nyc-taxi-data-regression-model-building/nyc-taxi-data-regression-model-building.yml @@ -0,0 +1,9 @@ +name: nyc-taxi-data-regression-model-building +dependencies: +- pip: + - azureml-sdk + - azureml-widgets + - azureml-opendatasets + - azureml-dataprep + - azureml-train-automl + - matplotlib diff --git a/how-to-use-azureml/machine-learning-pipelines/pipeline-batch-scoring/pipeline-batch-scoring.yml b/how-to-use-azureml/machine-learning-pipelines/pipeline-batch-scoring/pipeline-batch-scoring.yml new file mode 100644 index 00000000..ac67d296 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/pipeline-batch-scoring/pipeline-batch-scoring.yml @@ -0,0 +1,7 @@ +name: pipeline-batch-scoring +dependencies: +- pip: + - azureml-sdk + - azureml-widgets + - pandas + - requests diff --git a/how-to-use-azureml/machine-learning-pipelines/pipeline-style-transfer/neural_style.py b/how-to-use-azureml/machine-learning-pipelines/pipeline-style-transfer/neural_style.py index 7b774cd6..1a59c643 100644 --- a/how-to-use-azureml/machine-learning-pipelines/pipeline-style-transfer/neural_style.py +++ b/how-to-use-azureml/machine-learning-pipelines/pipeline-style-transfer/neural_style.py @@ -1,6 +1,3 @@ -# Copyright (c) Microsoft. All rights reserved. -# Licensed under the MIT license. - # Original source: https://github.com/pytorch/examples/blob/master/fast_neural_style/neural_style/neural_style.py import argparse import os diff --git a/how-to-use-azureml/machine-learning-pipelines/pipeline-style-transfer/pipeline-style-transfer.yml b/how-to-use-azureml/machine-learning-pipelines/pipeline-style-transfer/pipeline-style-transfer.yml new file mode 100644 index 00000000..a77e6922 --- /dev/null +++ b/how-to-use-azureml/machine-learning-pipelines/pipeline-style-transfer/pipeline-style-transfer.yml @@ -0,0 +1,6 @@ +name: pipeline-style-transfer +dependencies: +- pip: + - azureml-sdk + - azureml-widgets + - requests diff --git a/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azureml.ipynb b/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azureml.ipynb index 4cb487ae..047068f3 100644 --- a/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azureml.ipynb +++ b/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azureml.ipynb @@ -6,8 +6,20 @@ "source": [ "Copyright (c) Microsoft Corporation. All rights reserved.\n", "\n", - "Licensed under the MIT License.\n", - "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azureml.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "## Authentication in Azure Machine Learning\n", "\n", "This notebook shows you how to authenticate to your Azure ML Workspace using\n", @@ -19,13 +31,6 @@ "The interactive authentication is suitable for local experimentation on your own computer. Azure CLI authentication is suitable if you are already using Azure CLI for managing Azure resources, and want to sign in only once. The Service Principal authentication is suitable for automated workflows, for example as part of Azure Devops build." ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/authentication-in-azureml.png)" - ] - }, { "cell_type": "code", "execution_count": null, @@ -92,7 +97,7 @@ "source": [ "In some cases, you may see a version of the error message containing text: ```All the subscriptions that you have access to = []```\n", "\n", - "In such a case, you may have to specify the tenant ID of the Azure Active Directory you're using. An example would be accessing a subscription as a guest to a tenant that is not your default. You specify the tenant by explicitly instantiating _InteractiveLoginAuthentication_ with tenant ID as argument ([see instructions how to obtain tenant Id](#get-tenant-id))." + "In such a case, you may have to specify the tenant ID of the Azure Active Directory you're using. An example would be accessing a subscription as a guest to a tenant that is not your default. You specify the tenant by explicitly instantiating _InteractiveLoginAuthentication_ with Tenant ID as argument. The Tenant ID can be found, for example, from https://portal.azure.com under **Azure Active Directory**, **Properties** as Directory ID." ] }, { @@ -150,31 +155,27 @@ "\n", "Note that you must have administrator privileges over the Azure subscription to complete these steps.\n", "\n", - "The first step is to create a service principal. First, go to [Azure Portal](https://portal.azure.com), select **Azure Active Directory** and **App Registrations**. Then select **+New application registration**, give your service principal a name, for example _my-svc-principal_. You can leave application type as is, and specify a dummy value for Sign-on URL, such as _https://invalid_.\n", + "The first step is to create a service principal. First, go to [Azure Portal](https://portal.azure.com), select **Azure Active Directory** and **App Registrations**. Then select **+New application**, give your service principal a name, for example _my-svc-principal_. You can leave other parameters as is.\n", "\n", - "Then click **Create**.\n", + "Then click **Register**.\n", "\n", - "![service principal creation]" + "![service principal creation](images/svc-pr-1.PNG)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The next step is to obtain the _Application ID_ (also called username) and create _password_ for the service principal.\n", - "\n", - "From the page for your newly created service principal, copy the _Application ID_. Then select **Settings** and **Keys**, write a description for your key, and select duration. Then click **Save**, and copy the _password_ to a secure location.\n", - "\n", - "![application id and password](images/svc-pr-2.PNG)" + "From the page for your newly created service principal, copy the _Application ID_ and _Tenant ID_ as they are needed later.\n", + "![application and tenant id](images/svc-pr-2.PNG)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "Then select **Certificates & secrets**, and **+New client secret** write a description for your key, and select duration. Then click **Add**, and copy the value of client secret to a secure location.\n", "\n", - "Also, you need to obtain the tenant ID of your Azure subscription. Go back to **Azure Active Directory**, select **Properties** and copy _Directory ID_.\n", "\n", "![tenant id](images/svc-pr-3.PNG)" ] @@ -229,6 +230,20 @@ "\n", "print(\"Found workspace {} at location {}\".format(ws.name, ws.location))" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "See [Register an application with the Microsoft identity platform](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) quickstart for more details about application registrations. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -252,7 +267,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.6.4" } }, "nbformat": 4, diff --git a/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/images/svc-pr-1.PNG b/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/images/svc-pr-1.PNG index 0db6725e9c86fc19204e7a18272d5a4278c5ee90..26d6b335107afd478db03cd7ee273b86a6da87ca 100644 GIT binary patch literal 99315 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
v@=G^`DFXE2D`Vlr~6tWpG9w!{NCip0_< zv~U#*zYUob3rmKG5(;H+Ezs8X#z^b>dcxUsG}|psZ(i^*Eb!F!F+Crehud9_8~$Pd zyjz);Zt!pcWgxMSpd!{|1Q#czhDLzlO)0$Q0DXH}vz-rsWoPGaZ&2#9zJBmNRYa<- zm(VtsIB`Qlca|O;ga3rlmx{7wrZ&iv(P}*@At+QMspYt5WLXa{Mxz7@8DR0cjq;-y z7VWFcUQ+IyoWX33@F+&gjd2R_8#o~P8!H39sJybQOX!bueKzYc$%X`W{!IV^99yeX zkkuO&q(0c5!Ktg%s0q3pg=8Do9(kE7W-d_n+hhF)mK!Z31aTTD+=8Cf&@Oo#Sd|?y zC7izb-=T+KI|lpuGrvU|4jMX=NI=gADrJ2YRIrZokfx2~Qxp;gLzZZqhLV|hD}>gL zzmQqZamU$YH{qi-+RRV)9=evecLyWk*E7R^p?SI8gkP-eKP~*jSV!8!7k%0DEDE32 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 Z&7GfPEZ=jvv~97sE6A$6EtNJ2{vUXSYQ6vf literal 38043 zcmce;XIPV2)HRGccI+SqR0bVIKtu>dX;Bd@NK=}jDM%nfh)R=|=qNZ+MFk8wP zO;{_uxE%a{^_7$7{iUSVzmxo1QjadYBqjCm;Hl$BZLhm>x>rBi;fWK^AL?(S3fNSd zVB`-0d9q}F`9&rt_Vm)mU2pd_e7&)B*QVn^*1!5HU36Re(xfAJY5I%b zl#aYY-$bxN<|~TkkQo@>;~vY&5&ufkbYMhr@g)8k9amAvd`8bT=HaNnNlBUAuQe8p z@I?vOaBI@|2dgNLrBYH^A=U`+quZr1`|r0E+T?}wDvFi5zhT8z<#gE08~)G+>}%~i z^Q?KMN4Wo4jrz}1EHS<+;JoLXNwdslQc_1`C7(}?&WQbHb@~s#KAytd>Ur+`WCwzm z5b->$dp?|>Yt$a%Ci++qx}6HEz36e=)Wr6>blr38Y(ZpQ{P8(6S1fBTnuGT(rS2hH zN=e;FysfoN>frML9=>3c-~14EJ-VHpr!Y7C8oy7&<+y3xZY~ykos&U4%$=W`Z}U?W zdE5MTydR8(5N+d;;&dR+#qTqr>!rsUO8ohJ<|EH0Z>c~>1ix)Wf#s;GUiM}42s=h2 zgEUoEl3x-&KtWE8em=6th{tv-a7cF1tzl;zx$gBQt|HlJF2?PN@^p`Z(Y1*Wk=|Gj zCLb||FOQmwKfb{=z~*Xm!p<Uo`!H88bwjCWY>QSpp+Xj{%f8pbVM^$4i zJThwJfUcDEM=eUMrFn1?#5`QYMvfP2d5m>c92p3RRdDFCE^4?QSTPVSs82A|>e@OG z`Xv_H=3}n@$>nnO3!2%8`24H4s3joG=Yw&y{*l<{t&W8qQU?!qd)D1AZkjn=IWL~O zc{=3ljb)WFvGsec)|bC&4GStrugr`NiFh-w5&BoW!CMX(RH~f~;&`MPI&Z~|n<|a5 zdE`V=sjUO9bcWI9K(732Mc>VpHyW>hc}b~6XJuv_((GCh#~WTQ_oN(bp}y(yItOy< z&Pra(6$7wL2<^*}1NA{kkmA;c({Vcoz#|o2osq8z@WPdT**x15+oIK5*oxmjmP!5N|9XH*PuwH9hJx zKQj63lno-hhG%|6G33jptj!t5B7R?#06#xl!XFk*-(g<3Zg{mnu67G_c4@n}5!j4S z)kbNLNHg2ZQriq=$ejis9||#RQF{^B5b{f!=TYx9x6r)Pq8wZl(AloRwrE_CyQaC zZ|{0TrAy@E59jmj9xR9DPJ#e8=kJ4_alQOS-+aB)GM%8&YqiseVY>(D*INCby)sQ7 zy#8zO1bvQ#HCKYO6z^Du9aMig!0D}g(;dlF8`N5L1xpTxgRF~%^j4CF;n!$7jET5(ul#OH%&*Q2H~kD_ zpS1ZY()5%*qGrf&n_Tzo@dSV0+j0o%0l$ZC%&YZRYKA+@5BXZYE|&c?cbNUk`^yk* z{L6z{?(n2K(a}3y?9tolQ{v(+I1)bQpXqc^Sm<|4MCQS#dcwHJ2tp%ppe0;{)OSW;ZJh(u})F_=Fdfo+xKT zY@L`!>3d{*z8fLUjhU0hlO-_<>E03O0QHovaog$Y{4!tRCSItogE^k+{VRc7S1RBN zF9vl+TnkG;RfkeT4hBeLexY?R~r{IT@}z1GNeu@)yq=96osYfLYE_QzI$K-NJ zlg+Yns!%CD_2|l-kNheGgM_-TMS0xfa=*&CvGd4sm*Df>2D0*!(*dJBbFk2-#5%Z! ze9n`bzC3+8mIit2@JRXq;-p+wBke^)B16Gy@hu|o90S=LkPus zyuoI30}kO_CA&Cds9kQyiJ_5{&mnUIv=D*ktTe&5Tg2tM=2NLtjB76C%|6mj+yWj| z3#NBBd1z>~Kqp_ce$+7VOtXT*2_e^f0u`v<*%FV_kaH;-l_PW$ z$q8fIeSA4+#t`Fkhgj&Fp6pXYZV`6Hy5ypK>S`imq14+c5_PcIl(_z!EGA+<3Qb+=-*`I2ZG6hbE zHmZ|wB|_cjd8wIx@9iyb1Krtn*S+DC1689h-Fs#@qKSy)+n2q_-GwA0t{u=P~+GEl@WS-_O6Yq^6wL2-`h>-dnoLUcDSiGY;ZlI*DcdrnCMn z7zOp|Az!kbQwgt4HWLm9+s%LyYru8(PItML z9~d7bHuIQce`6GS_@vLfAm@gm<^3}#kz?QVryz_I%8M~Vt!STQ%sv*42kf77Mx7CT ztZr$>tkgvhc`#6HUpEEw`fnb{p1QVe7-w$!@_E<@XI{*`k{%EcAhZ0x^jBJ*~-3Xwab3WsYH~hR42*}7NlWtvzj4rZ9Y@?xb-bwre3ak9zI<~F~8(JJ|R$*0W^R2&cOkJFRy zd%$e63V=6ga^H&*Oi0V=;wp8rZ#YJtFZ~U`~koG-X!HtK~6avfZhmAQPkS zoJk)K+$p%WV-9PD&7tzz%G&ko@$n(rc)js#cQ{N3%_byFH5Z_qb}$G1BhvjzamjNY zKAqGU(N5vEjJk=TLeywy?EG_Wl7+(jdDEd^;U)fw>UCod4!x@ZR=AS|a|q#q)Yo6hti!hCa7O)O_)`_L)Kf4X^*uUseoO~N zHsL5l7*DjER?7><2>Y+3({`sDjJ`0b1QU2S*^}hpdj8=mnttvicIKU8<&X|%$TTl9 zQ%_YbK-ywx4(aTqEIKR)st?=@&&Q*q^xUK_r)dp*)b5WrXoLF{C+a%RS0Tr`4kM{X zK^w}G;GT6(=$cO(GdqL)^rspHAW5+j#?&#E2uccKUne}xMt{~)i&zw*(;WWwvmdk&Ai24!hZJSPV) z5Q&B|up~h!P3P+)!I1iFIRCJ~Gl#=@r74C&^)X_|UAN?ffqDFKrUIUEjeKV4@?5k_ z(R?hb2eU!UI)R@QvrdU|9hy8=kt@^&*PAl1*DB-jdYJGVf0kWxhg~y5FOZ8gA+$U% zYln|KJZCwQF)yHET_4IFCfB$Xdw2`Pb6mF}hiSjHaLPF8g5|6B&)crmyo-5uL7RXx@I?dMH#<1Mlde2~rpBMxIR zLEF<9rDbs(Zh6vhcrDk9P!d6OCE#K91Xup~S=59--B^4b&c$Y?mishg%f*#BSO?6U zkaagdm-Qf7;PE6O{I{C92buhiI)llN=VEr5%$uJi7{BarkI5@4A?Z6St8TNs*HN9c z8KaO}DkNe?!(nku=G7%qN7!r|gw(c2bj!5P+}79g;tJF3M0CM16D;jGD@_vti9pxs zwVgFr$AM~?9yRkZXu6=kI20p+7qp0OI?L!=;_uD#qaDg^T4BbHXWSyV6eA+X$CXYS zpPl$xRI*j?w-o1Vr(Tov^z&QZ4F(E@hM@);^N^8Fkym!0E_wPqRfb2j%uH{_BP zfbX?xms0IVvQWP1MEaC2_k2h;lG!piKT{72Nu{Ojmipqaa+QO#i5l3$d8Trl)DbKQ`^w8ZPf*H8n`l=tn67g!{=4^|%MVDlP}N?g zbbI45_-_v(N0wP5?j>&SZY^rq_AO2nr4ZC}5H^0C@w?dbbz`h2hUw~D04X(%!YIb< zZxd7<9(r6H2|l_?saa7n?0wLa1ODIMr0X@oL`cR`te> z=#0s3NSEOTD_d30YmXuBQGWD?Scr?-_vb2qf9}SYzlP%9WUOeF``vgxoUx|u-Y9uK z%&LC4;Pg7oO37f6G{E849l;$v=@7tszRN`K<18D}b!LMFpHOQm7ihdLx9u(I%Q;LW zPGTsoP>4`8V{F-0n@RpO{XpAhYj*&HF#Niko&)RJ9Wiz%ET1YGE(c^SP%@1hTDR`W z)3(TmYQ6J&@0lK`AG5wK%+_&x_?02IJEc@hRwC`TU^Kt#iK!k_U0pvOXcJNoZ~a(> z**`KGS|32fF{*(Niqj_dhBaoX$DzFYLV}h>ScSx|Tl{MC--2o@a%*QsH(=;?h*3)%*wLqt z;+AMjd9F~p^@q5+kTn0suglbGcVUNhFYT+9?+h&SnX;NNaLUxvYHdV?vL|5H`xGQ+ z;34Gp$4yCFRW6owu^DF$eJva$l``C9ZB+U*3;)<(W;;}*+u3tE{&BG#3)T>>*ThU9pg?fFHGGRvV3&U-0_EO{k zNY+rcb=K|^Mjd_#0n_z0u}27}2{w`5JFLO7-rgfA74S>@z6VFL7qm@q^Lq_xifz!prS~CX?pdu zq?@e~j#smGnbev=Z-edJ2^rHe8IP>H5F-uJ1=z0mrTe%HLAgl3Ca$#QXzLeSQ5<8OUMNJ-6ML-)UCsWD)IF_jwGM zhJ}SO?zeaws5`v5NF&5O;LZ8ych<$Dh7k4bhn<8Z`##6Z>eG4gQ{`7p>ALw-bgzr` zuc#m0a$%%=bx*kyb~;_2QPE3em#XfW-J%BN!=saawMP_MzXlQb%Z&4)$W&>$-UUqPs%wmZqeQb zZp7j1y2;I`@#)l9g^4c4nq`0lR_W<;{^DvjaU3J6r zaBJKJoF3ZsP`vDRRdpGZva$RLrsKuzj;j;n&ySCnyz3|}i@&G(1gyyX<-6E{OIaX4 zNlAJ7bZVLCF~s@$IWr0<6c;@l*w0Oj{OgXjf;Q+={)byKH}(@n;Z{ z)}D6#)w?U)7walgf%>NJ6Nd}0!4!AMEo1EWG{eS7sX-<YnPQ*Ob^{1FaQK-robH!|~ZGMpSRzqp@mYM$3e5OhmgPjz6_S44bi( zE62;V-f2*%rlssMj;xx_gue`ca|Lc(22y(duy;6&n{pz%sgQLg1rqx9P4o@n-MjnB zG9G=S=%l~m5?-{KSUC(M*Wq7r$*lxay5MT~xO@NLs;9@IUN!v@Els(wX5Ek7C2oc6@^R7Is%@)^~qrNx7DZP-oWtT|#b$*Y9t9qO?p(C;lwF zV4Zk@XRrNH6)H2Ia>_Q^+i7mgb9<0$E|x?Od)Xppt{ty$I}{DGQl}g1+mm2QPjtSg z#&*8vii4ZA&2ibocco=F+aGYen5Drc6zJG)^A52^?6bF?5!oGav_0#1DyLvSp(YNO zvPr+#=x(;Ax}59vi@~Sd*@Tk$waT~ewXgc8k~P~R00hbffci)Fl~ltgjo(j6?)O>_ z*6_!A1;2R(Dt%wM|$#kAJG-XtTmUDAh`#yrCDeDMJZ(KGv(d>=-EGI z)9Jxo5$IX6Ly#rQkln+lsTo9%u)Vrdj4_{hwfs*|uL7$e3%Ak>v#|VNOSd*%g9yJ_ z|5|<|%D+vnIK%BsW-z0RpHw{ZYU>%S=&M)E2z{&)rXGPB;;TT%tSGK?XEY<;{yrjG znV*oZseupZrq}Xm!G`zjtHz$X&wex5dB!KuV&3x&zfv#2a(;xklD>a+lL#l~4Uc6P!BfR(_$?k9r1@@6d0j?NrOhMo@N-4Cw`1a7y;!`T<6%?UN(= zI(T;bqz^pvj=m-i5&hOY6N?NQMSl!Lq+hXjK!-H~yCLw#frL5A#q&s!R=}cmR4>}O^qX>bYM$eg2rHhW%^m6 zt#%K8gkCkMKO}NArj_1&^VbLIV?Ph8J!n!juAh4nXH^rJz00IMJ^X|tqM;+g(t_eU z(|_em3Cz>X$;s)>pq;HZ%)$wGk&XY@s;i4;M(3BEq3ajBvu}i2gY5C+uo91^K#Ihd z=LzEvnQy`4j0MCO4F@TrUcNS>U;S-qTw^4>6eR>=kgC@6rWXS>%|+F%TXqsHwW)N- zzGaK&3ZA4U_DmdEHs@3urzV{CZm!^ul4thWNn_jfE# zE)plh)c)G|npLF}5ahY*^$j9<|72MPM_^xc~eAvxcETM@N%@o<$w zv`k7eIx0PNXHO+)<&G*t-_Pr8;doG1!Vw+SwF%2V-ctT2i8-B1f&6*$=%TSoQ21Xu zTNV#S6VW``)-g$9q4?LhvRiXgTn@y!A}9SYuq@TX&5hgIz!o(jcD|h6@tz$jCTt^<<*TEmrOb zux2=W4L0GX4K!6;YKf}`OD{|c5|Ks^Ptf*$6_sy(RIkE>TP*d`+@CATl?$c z%o(m>D8~K4ebBKgE=fD^>&G1L!xIg=>3J!xsjgVjWO3Q;dkCOA{A6Cu-!9b^J)oYk z!U$s`9pW>)h8{qMGQ#{4Cfr|Bq?5L7*?VT+=!$bMXKlR2t ziraMk>U2bmvUUdRQVjpCMlc&Le_w%j@Z@3k@AtP+;a~Z#8*ZX!P z49^H~jhfLUSmB+6y92uiBBw%l&3beKl3tm@DXrgGt99(8|9__5&2ZIlRS$W^$>2|U zSjo`20oAQ#LA`|stygC|DkY{sj8)Y}lVngH15wc%IY4snI=z;haMs6nz=1q#7kpJ| z=&`?Lr&ne~=>Dp&&x$IEdv9wyEuM%nbj4;jL}9q~_`MW}cx0`c<4iCE-abuR=i@Bg)seiEAk?W<$hyDTh9iQ_`#;8jkAYm`O*A@5 z`@1Xoj`<(`=~lR#y^Em}ytrv&ZF12s-*5*p;nP8?d-u}|tUz}%LU>M*Sd#?lYQj?2x&Y^Py@*5w=@ib=fq zIz`Bx1#h5^1MB3(Jy+1|JuU+(^nuI09?gT76d zLs?Bb6tus#WDAO$xk9tEzVWWmSr{U}BTdWWOAc6;A4j+7@@K7gYgWsyHYWYn8WgRu zc~%uFB)aF_j(;Gxv5#sqHwQ4joNQ+cl>v`R%!Rl@m2TfNT$r3)nKSOZpLPoXDhv z8kLB-_fC~VjK=}yaL!Cx)eyr#&|cXnt)}%nwt=!XX$feqWQGm{QDXE_kRj+B)_se- zrEzdV>)6mmo{dfR49|K3b?};`WTt9bPXUVfIp4?c z!r{MeENgR*QE%7L`v$ZR&(`|AmkJjEI$ZXK2TjL> zTQK!G{RtNt%3`>>*ctz8DN375eUn3NNFHLGZm=F0Q>LEuYVI-Dk~^uJ zAD4UVt=tB&GcQn30awcHsAjS1H5jEnE_ZVJJ3~5kKFO!td+9&1gDq2Xf4E9hCP|(+ z`ZZ)uhBC2`*6d9sQdp4@?&1gZ`v{e+mq+wQr(Ts5{?gh(B**1t+8>x1pF3@f_^7Hc zeC?S6(N7i;u1-GCx}~j;t`?ou?~AI9ndr;;Y>*G!2QFurn!NGW!L*KXb0#MlO2DQ=Vlqa||w`Eq}v(`p4XZwCl zXdempb%MofY?ZgfU#dHjqcB+=>uo#nHfIy9Y&~Ogv(`OB-b4*@icYT_yu5ghmvNfgBY%$s z0^aPu9+G+r>s}HsJrrjIu+!w?Pkem<=xzG~-}DByCP}m6pYysfxi-lOjE!BiN>D>^ zBoZZ<9S8>!PJ9DsRI8LO9r}kA|9ORs04eqbKhp==I(Yj?gE)aaq-yTeQS@%8i@`Deto!;q5^IG7f$^& zzdWm`c*g;am^DCOp2d+{Xe=!cz>X@#{?DKh>g66tL(iV-knVcT=&9+U3v;s_*H9kg z3>)_LgD)48j7rcU!UkG@C&y0QZjTWD4oHO<;}wP7Wr8~>uF+87pmB*@<BZYOgS>)YSDZx@-uOl#)F_(Eh?*!XY1MJr{f;)h)HiY(8Od|~b6UqgP}l9= z02Hsk2@$9bHSSu7v>N&_8s82^8kt!k4qxK!YCW9KN|6?+L!Zmogez?_W!s)~{Cq*1 zw1Yz2f?cm)KCH4w&zCQ+PmV1khC;J zLO)dryY$^h_?W;yraiZ8d=aECs%qNCYZlMn{dBXnnT%cYZl=vW%QLEjzD3^cF7a-o zG>sIZ*-Je-C-9#d+8USg3OpgZ?7w>{u*j_d&5bw?A0U@=N)2b@W1Zbtxa>5n+A_oW zUrex#(#)Q|dRxI?9&-#ZD75Q~8UGik{lHW+l|jJyU}7t>O>?q(9~2Kk3=>_;Bs@#)_egw7NJ2hck>cqv70H zF(xiWHLjFhZhz#Xf*a!FmLxS#+5I-Q1nCopuMMRK=YM#hTyS@baz0taL61BuDn5-S zi-H*NW6!qybGU&nZhbLJAxu_&q5ieKxudl^eof34%R@>Qj}B%7hGGz41#DD3BRH>& zk+&vwYS#%AslqAjjHL)b}&88JC(SceJN+X85&fEu*JL zPbMuwIaG479dBcmD zV{fd~B9b8XXTA)W8lyi@q-*OY`+;#GVSrjwGSl|fRpqdFrax?drhee%(#Q8y7p+7u z2H;j+h*I|OitaUQ)9eW~8Y-9ZJkMLiS006r8WK|Ewxkl5?$%HUSIOr1!fCH{GgIUQ z)wP*vrp#y!8$w+2d0}Z;3!3Q^j=bm@)wq@{O!X;NN#1fc@?X3hz)w_iCXOnk*`XsUo0Dt%5Wz{`kLD;>G%iq)GVa z6&UX7*QW(bj}&hDK1_v|S{_Zqd)CtQvo5D*1Lp7({;xZaE1gUro&BSIi5(O$A4xgM z`Ch$sE_v1wa$!!AAH9U^DJw`TOsSTBzZM&lN|8YXHid@ zI(Tw!XV9!k?sAJ0OnyC}X#pZHYcJ5Qt7IVIG~HA*8m%#is@VrxinaWYPtQe%!~hnh zK)KJ&_2`%}?$l6pU?N$-dljrc(sX;gzbfI**ItPT^pky@ZH#64&DZi?RWE&y^D~|lmNzvd-2HkvQp*FlODut#LmWojPwa3vN^VZm(})A;-{*j(8(Se= z(xSCi(`N~RcXO)Ky@ED$;WMzIZ0*drXTKofc-!S-X8W7zM~>@VMwFq|6a5ZN!nUev z`N4QlWV780aW}+%jpOs}*0%azJnv{6ltJYp-R!ryogOa8E6K^R$6YMdswE@ZoStrq zsM{8TsTORYZ_s0Ei!MBfqnojqzoNc%2huB9t_4qBp|o|CejnBCoo74+x;6wWXsz6) z5LtZh4|OohR>}A?UcfYFE;OrZbvG( zlnz?nF8){f=MxKj_=#)v<7r%)z=56H?L9&R&v@R zVO-1;&D<2}a;}9f;*00+(JO077wDU1*Xvpu7bihdiQ|Mgj)N6$lM8si9nd9NAK3_F zGHse38u-rj-8VgPDx;Z%FPeSq z%Uk7Vr{}UaL=^5bnqBjK^XDZ)(oo;Za;gsv{7D6FndJSCXX>eP6yo;jq$`;{UrcW_ zGaqch;A+mgd){tVP}{Me!;$}x%xpFXN$o8PkvnQ!Sx(tmGr+p1p`wj8J%(ma%q-m%4a+C%?$Lm1<2_WSLN;nWero3}{iMPA zJLgg@YwDPGa68Eq%}~~=J0xCh9h)y}y_Tg4ANU|$^=4X+M{HsV*#)PUQ`?c9C-&Ts zT@sQb%H^P%dfEqQK{ZM1D~;V=&V-0!TP$eWfc!>%iL5*FqnA&3*5(FX^5TWP3RI@M zdT!UB8Srm<<2Bu_Lj%X~XOA*JZ`le5ocyAd1)q4p3O^7X?_(P2HS`zdF%TG@;M^nY zfEVBBuU#hJH&e8P0N8^PPP3?vmQs_KX=Wo>DI3Zfm!iD%=IrHXO83M4XqpkY-um_l z@pGOHWRrTi_v7%I^rB<9qi_{f?Vfq@^oG|z=f1#mlQNnb)-E&E`$p617AnX}Mdynu z;d)|>f<=tG=`B33o`XtHi2kGSNU1l>z)g`;z{_j;6`x$;KNMuKZvQ{jto5{K-IWuO zk;g|+>3QY5e&5&zTBx8S0WPrs#B{m@CmsA%9i%$ZLc#vdfWBQan+H0;$^P%k&JPXY zpJXXvNedD(WD&=b;IkhJ-FGT0dCx}V@qhHoZDSIhdJ#$b*gIaY$W57=nhF4{CJzEd zJW=p_t>kjGRub&+ z0))f=(5*mF$v|!Vn=XM~=huz0a)|cl=a*aX7NKX!5AMlrGuu-q@xiugFGBbd8n?bi z7BY1yDxX`;vy?OxJil{3`_s5ma9?UGTsQRdK-+LBMS~DtE1PVUnZ=C577h*R`qdZZJ({i--^ zUyA^xINTZ?o~VQs^D@I^AZAtwVeba)f`s+sVY2Y|}cU{Z&lb+IEzUZ*GB>Zr6od1YV1Q98C1+NYMxL82C)fKTQ(J(-aGwPe>lnBiZT}7acqy7-{*Ak{bW~6t$SuEMcqk89&;w^6Ih4ak~063xvp?QjDi~` z+z5xd3AKFB;75;=VY8Jl-XA12z0ma4Z==y@lbJ`;Y3nW$b{upD3;R}5j(@b7@ne9Q z+1XpM#o`iVQ@On*#rJi{%;bg!jhR+y{aZBTiQ zh)bf*fwf=EJVc&#&wTQ)$~9T0Wkfi@`%}X5XdZP>G!@W^b(V%D^>R1-Dj4qr+IDb8 znm$wWW!oJK!lYq{0~*kL%k;L~rdC3&2E*96EZHj4xF#&!PgjT3?9-X?3k{<$hbrkf zEa+zvbC5AGGt2rH-upRT9!$YLv0?a%P_ba#m$|yXK3JU-JBZp5Dkm$qNp#?P^X-ji zKYa&2N%n|GaaNW3_-X-_-bUGfP8iaz=vQSjOZNLoN*5V)Z1?f~0O&o*~3)ptP zSe=@Qm_4_mL*GU?E|S<^90Iz+E8@L|@JO<;d_e|qu+F+KTju{^PYRzBDd+mA#dvNs z0HCz|QxEp$BqpmskC%qvW59*V=gzHEQig8><#$q}R4sM7%rGToLtlxWo7Hdf)KKb&=|axhg* zY^bj9<`R>`n__$^>F@HVqB_~TF8%gB^+@(@Q#4DJq`X@LY7ZfsI3mte2D-xi^6V<> z8|o(~0J#_rI#;YPK8!Fd;MgH%+~2jU=#u=#L=V&5gEOK2gH5NHepM)j z^H8a*cPAA0X7x{m6B#+x>}r&`i&`!1UM0xdWQwD+`w zDm-IG2JBtc@ulxvk-7aP!v~-f%Fy_#5Wm^UP0D#X?enff#+3L|pvB^!py^+!L`6Rj ze1+Qcdwv7~-_3UlFTJ2QxfScLp`THkvaC}>1NvT(C8vPdUMsgTW2)4(& zH(gJOSrwy!vkq-&j(r=C94}6S)+Awb)e)=L1FEAT!sX%SV zMu7zy`S$i|WD%Zt8eBbynC1h&FuhDx>HT+yilo!CL9Xl2Fl<<_>w-ud$Otl&x^SNX zemZMMQe8S0ouCYzI?TCGn1H9d&8o0OW1+Pq1$4E|v{pd!rn2aS(2uGf*5tz1e`vpx zyFq$AN2=W-Pes2p4ed>-3RI8Fbw4tMtCw2}1YieU{=-{tUUZ^s-ZAtCH=ok0GHD6z z0&ka$o@mrwaLTQm z=7mE>$tB;?*U{Iox_`sJ9N$1MuNOV3uT+;4){El}#vZl9{o{60iY%?pO51_vkY)fl z0a~Lh+;}IuwrCHU*!`dfGkVv3Ha%Xhvc1k`QoqKfcKA3g_|i;Y-%Njk+czxbT-bh48>!#( zBWRaM*=?1TOGI36zVyNdl9h(7U9$wp2TgsHr7-*#p0Q107PW!MMdPxs)xx^7XFSE* z2~7!G(oG%$!Decox}?j@Z-P?!jzIUz_)Z*uRQ^PJRcL?*+6uZ)->r%^9eJZsirLb* z#A&Gf+(d{XlOeYW)049Pv@x2dIcg}ogDQ7`cK9=05k5AJPoi5I;7=g4wRX?9#JU7` zm-+Y{`{TP!6sW-q=mXBt*q&l&!`(Q#ZQ*XT+H1CuEz%zs&zglotu?)DBE= zn>suajM~^GzT*=F#oqEDOEG>I(CT;E;kXz62pe-m#B zEIKJ^pk{t437Fwl|9X2PaNYlqtk>II-SqS=z*Za2sx6?#{|gGc^czVcc#Hn--CN%Q zXGr2w|0z)aj62ZE1DvJ)(Ed>pegHd3k5c<pQ(fYH|2GH%o_ar6n=l6il{rpAWS!BQBl=eMO+M^?CV8<>%!XG%+&c!q8^xYUQ z_3n2^l1)99;(r4{#Pd@>6x!rg=0_>}l+*-Og1#H@*W~)A0vGBy)Xf2%e+GA-2zW2J zMi(gDw{rWHBRB6QE*N@SpZ!Fr0L>5Mn;IDEfJU2su(sE>cimg)yRC8an-oY5=vELy z4^(sIkb_&!RSxQZpQ)XB3PfU0p5>3!5-^vISu3`k2FsD%H0)-(wb1#SO@+;%CTJbq zQ~lC@BK~xki@xfP!2Yu?=6`?wAw*RL=o4=T9zwQRf{!B07-s@*m1OFoC~(9WM?=#%jsY*o0QrB*2FCy&FPE25kL7=r`zPE`eC9ocVK z9X`~b+4N%98^0NpD=gyTg%2?+Q4dm4(QP6)?MIUU8Q|9lRG1hfxP4ph-;{u6b_PYmqT1&= zrnb3Cf;GT6GqLoIvIcab@6YJvY6n8qmf7y?a7+%b?rEtVjIimYtTydO8jKV*HPln< zcRl%h?SF6Wed2pc+7;t^<3OR5Nh^2%v#yO~Z#JW8ga=)VbPhce^s=|uU+M@2$TFE3 zq#xz-V2TmybY~DSltlZm=J)l~A@=M!AoZ!)uJD!)yY@CG0`L67JFVJqnBy$0PEUa; zS}-g4k`6aqs1E}6|Jf6K2D}K*16~NKy}$8hk$U2a>OWcunQ{j48XIF<-lc4+n;PLh zfDjLm*OR-@LkVdKHx~wz2({YMksoa1&Kh_*=u;^>LhpyqWQd zQc-msU2K1LpSY_`QUPJgaaN$MZ1jKaBkBa=66Itq7*Etn*%j@3VLH zHh^V<3NRk%*J%&S^=TP#-Um&U&{_D%huAlW7l6TDn)%Ml?dZvnkdO=ES0y9V=6cLs zb~1O}@7vwBf8+0$ zfV&8oH+mPOSjl~x|E+Q?y!#*L@j{JT+&=n|ZEr6!-~D{rqL$(Ui3hV@5MTfEw8c%P zpHBmm`00oKSydKUdj5XeBBSNc`n$-vjQ^5;0BzM8|w9#IPWAKV}maJr<7sbE{_?8-dum%=~`Ui8KcOV-FM*7mY`^E1DrkDW! zc(v@$A2OP{)Y<>hylu@Lx5VUmN-dJ#=o96!1iu$lz{S%5Ez{a02MgF?G@>)S(`cjw zORi+Y&g=UF<6XfPE5lNsVkm=}`oP|h|5AJEhQCA!x$~ovE?z3o*e1^K?$)aPmclZI zx`YfQ0h5>O*I8=vL-ABOIq#h1;XI!Md3rt9cQIgt*p50!iLv#V^uLopf2u=;L~zit%GKm6-a@H-!L!mL416?dzd+J zYrXEaPCGPyA>gnJn*mc$cp)N znRwKMmIt_dWYj2a;WTmoT?xl6_aVzsgJ%4;oO0rKE?JV6H@R>1?pOSzs+Uej%Pe)%@RhveEWyyKOe$q@Ivgwf~63BEG zLz!4i=y3L(=mEgxv8|9=H=Vn6x~g*F;icJuz>3Vxn6^Ug!=&2?tJiD_Ydba$?jSpz zz}72r?TxSKbl|E&!w1mH>sr!n{HTn-v= zj3;0A!EAd%+4!B}|gPX|$&qpRf{C@J$i}$fG2%&_4h)B5-8v&Q3Mb zjDLlj=n#!t*0hxK~x;2YJxh9IJ7`Tr&z8X>#C{#|E-$oNGw)&qK6m%>h1cxXg~w5Phk} zA$dsf7eB1@Va?p@*)tpDM75i31`Q`Jb%wMN2*OQtddJ1P3p!1kHJF)vhOr+*vo(jEjDBV zuRLNjs=w`;!A>9=ercOt6ANCZ37bcKWmAlAF8uytEB9sG5?c$&hL9+0KW?u4%+&uC zZ^3z5kfZ*&exbg1E=(QMH5`4c9n{Rd?IDHV_n%w` z3{3X3J+BG9n}&kqwgwwcUJD(ME2ozfz#b7Rd|gw+sq~SS0yj%5@+_kFuAHj{c}8@z z*YQY^go7{ z?PQLWJ@jr$ybO#<(NvGOGvmFXg#7<(XGt=68>&#C5)8v-gbSL-es?FQ?NrJ5^Z#`B zrcq61TeN7E9<@raIVz$cQi`PrLZlI-jU|E&LPQV{geXYgiPA$7n-UcWsE9}#0U@Nox}zmSYQ1at%Rs8_%g8^{cJ(>MTK9_EZnfNO zS#Z&re4{LODH^2)@j=H(>cqyzu5+xgkv!{$S?5!r1@1e2N2U~m0Qo6L@ z3}4^RTarX(@61;yWB$t;^+slwT?-= zv-!IWH=r(6-aUX{(v+g)S-H66Wy-QVUT8V+M){@mRv9kzIp;*uo8oc)L0is{3B+gv z{0Qg;6<2pG^zsMI9>yyKeoqaUe?Gjy`)+`DYsfhuX83--S63%5{~}^P5kL0(Cyu({ zasCPXS-;C;smss|rR(`^OPeFsvrByKCaz72rt%5zQ&v0B_7722f3j=muzIb{Z%ld^ zh!oD!a=8DN7{{UMx%gpv=DFIBFBwg;77);wXN&r6e&{g#2-N%uZ`Z0MgP%xi*W%hS zt|yLuNL{Y-Jy9QWI2eD@hQ@l}lwgruP}0>+G3AI8r{hpjBNU}n2K7$b{kaVA&QPM@x==U#wQEom4KR6g_)2qIOZC&o8BUV961ojbo|F4a!V3n%#cu2vQl3wGkna)(J~WY9`FkntuTnd16L(!pD~9m0}8` z6K6GdU&72JRUbk%ek^OXi0E<&JzmSp8LP|}&84i^sAFPWFm zXhGX|c+x;{>Rp?@odJ#wStl?P{H3!L^l)P!88%8Jt{C8p{tYK6aoVXa-yg__(s&l zQ>gHF*_+piOV``}+`1mpc;7^g*L0!d%5y_4k6p*SflYucEBO1>q&zqsyW-KUYN_}+^g)1&@iMVye!lS6&&Vw+$SzN8jvTb<#->y0Tkk0`tlZN6 zjb24aQMd(x0{6wIJF0aOBfru7BpM}%Wu$o|!%}KTy<1q@&9%KS3NLO$k z#>7%WGXCslx`FiNhDD!swpH%dgz5PTJ#%Nf98f8}QwGCqtO`z5_^k4o5{_$V1M_J>Vbv*)4CuXS#OHT}#Xl@k{Zgla^?_>vvpZyG z3i*nwLD4`1`6W?3$d+R5nxaj5|2pC-LNw=LIu~OdbH)}Jk)_hRA#Nh~p{~Qeo*-A| z_+9a#`Z5CXAtp#=`0bCa^TNe2k#%u*mk*0;1cjzbEyaw9ZfD9y^zYzR9jj@Xb<83U<0}L9UAAu3T=cy?s)(fsxWMi?(Vtb-xrLC z1qGSIkAwzd2O>1qf!5ICIu(~ojWRCu@zs`WQSrpahgC8~mfX{W4^q%hsl;lwEhEhK z>OdqAUt)S0K@?(T?*qxE7Suv@y6!PJ)nJ2cx#Z9jAQWsSt{1l51oXg-j;W!iMxfRg zwVoC6N{kM(o{8uYC!nr9TjYC~_KdZ5WrWPBb4Z`8hHui)@i@*G+NS1;moNt7v8>M) zxY1#Q)ZK1XbW(`o-K*!=mKIRq@i}pHMGUEsToDM(g!`5#JT_qWlZzpOw>*D$5OkM{ zDW@_@YewR0LQ=ax@8nw4`f_GqL0O4<5RPX`PQi5B{|i0p>nj|OQ?qBzfJT|#SwcfU zI_|Vza{mNAlD#Esa?2kW43QegHv#n3%mI$INix!7N+ez&y?xI5%ziP}jlZLKx*6h`=Ns9z2(z?49Y1eZ!87{TN5{D5bR^DPln8Y9S96I~i9nrU80CGHc~ zwiGF1IbgcFVnpC>j##L=cJX(;jD=?%J)PCzfj^UbL5B!`RV-ka04a5rh)v9~vJTAbLZim4zDI zh4_SHqz=vQ3}@C0qF2={fLGJ>~8z8h5JxRTy<}{^5nG zeDW+$gsvdpF{v9oMJV_bT@?Z}MVn}=@C(&}d!R(jLTH1eD`ZP*I=k|XCLHg_kJgQ$ zOk+8h-bVA)Q4YeuPzz`(WQ8R<4|C&_Ud$G~D*7+DZs(-XL}($fXZ%Q2(QspltGakJz;J_J_}T5=b{#kzVu4Ra@pavr~ZeCiM7YK5y)x3R-Lo`Zq7_?Oqy@uS2* zjfF$kt$ys=1v{9Od(801;W!(A{Bi17^+7wBqla;|gvOdx^c94(KXCd0pUF zLfP~zja~#Ngc&dINl|=`xYXm9bre9zH&XBa|4{K8@cjF}`2thovYD_KFCT(Gf*?M= z{krc`+;)Wo1suD4!}yE2F)y!IdqoA;eV7Tj!RoS*ilXLrXHw4H(ENA$Uoldy2ne#h z$b~`1BWdG@`^KYJ>sOo?5l#oCaso<+yI$xNTo^k#I&k2bLW&3i>wx$bt?z}BKjb_| ziSg)YRpBvH%LSjI*{3UBg&{BXSKr$l7_UI;x+dg&g*{#{a^E}#vfGOD*^~LcmLmhD z1HSc!ZD|0b$!vUNI4%Fy`ZYgLSCu|-#y$dqv8=8+;4)A3Rl0F=q0fBttpj3sa7J8u~#2c8h+2?1^uf{`M*6jFT`)M zDFANWTaB4LB$%Z$9@)&qS&DJ&H&L(I8N2}^v@%DVbm)XEZbCMfe>`E#_AR|R){X}4 zsas*F#l;EGnq{GBQBlk$R5s5rvUQPrKnDjN*YQQj-+ma+!e1f`>{Ev(LypXi3lKdu zvL{!5L+={eR3yC63X6Ut22K_N{Z-_!|3t_nZ+~rKSHE!XAgGx<-tin?{^)h;5?c2_ zOpK#z@S*|Y$%k$)ib=%vs~Rv)ZhDJzsB$x=#D`^E$=+9hxuLQn*ELjG`3%V6

m6 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 diff --git a/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/images/svc-pr-2.PNG b/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/images/svc-pr-2.PNG index efde382826d1d8270655f0b211af215cb1f1b998..086a6a0497319027da08a3e4a889b215e4a6e6e6 100644 GIT binary patch literal 84045 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�|+e_2W{`;FzLDo4^hY)3;XdpPPG^ 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 diff --git a/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/images/svc-pr-3.PNG b/how-to-use-azureml/manage-azureml-service/authentication-in-azureml/images/svc-pr-3.PNG index f861bc50174ff321217be4677aa878d606027040..3c4b8bd8e7517cf84ed534f944b1c7f88188674f 100644 GIT binary patch literal 63736 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 literal 68199 zcmcG$dpuj&_cz{|X{R;qv{O~uDq7dNm%5keg}PI9CuobJDvG!ZosM2miMtY{Mcm0y zgb-$0q$nyuS`ns{G>OoNxPtNzU1O?X}ik`@P?5os)Ro z+T`b-PW`lJ&z_&nOpR{r*|V3rXV3Rl2Yv*;=~`P41pa*&dc)+(p4x%abl_mG?`5mY zd-l{J4sP7q2OJCBH+2c!v*+-e?Z58`LFL|i_H3cej4syYNvbqdr~LGA?(HaX zu3jrf`)7@H>sNiejrC9nOl5L?$u5jJ-tG_v8CmB?!#u(Y`MNYmGcv`_p{nGtKJQC$ z!n+UPXga#aR~?XU`*kV)GA`}q&%~d15BL1IkC<{OP?~z+Y{D;BcHVwGS|a*i2M5lQ zPXE6-%!dHz&5b(4qL%KO&-b|*J3m}>9#w$o*)?*E0u=zQ$G23x(E zl4B&ITlRM6C3lu4-$%6ZSzTM*TL0B$biUN#iw&!?54TUD`b^lG2ycl}KA-uf5_+(C zkobZ(-3%Zx_8&qmX86^aHeDE3I zqbWXf^FNwD{l97cNl(b=$ue{Sh~Lw|52us{Vn52VnwVP_IiAau+mo`i(AHQjv5nt; zOA7RkE@rk(;Fv;`y!?oc0MZgNbU>x4>-0JY<$!r~L-ee-#*R27J9;>$UmJVJ+@IA* zR#}c(?2)Tl_T1u;9iozzo5~qr$lcj@xfkN%qLsM0N;&S~E}EM-X02k=vF)*@eln$} zTrJBFUCyO@Nl=->p_dKrZM=(~V%ywXk2IglZprX=5ZRPv)N-SKW; zG41yoccl`ZT{GWG2@K)MQfCJWTCcP+ELEt&O3uGu*^=#|h0oOy;3(w4rt4>bs512_0xs0!8y62K;jK^RajbDm$BOBP`H#PD}bvo(%QQRDn}T{1E4Q z#MSJv#WVK>&h8ismIe{Dji82+e`4X@SRA$Lc%_VZyKtYg){P6VyFVP2oSrGYW1cr@ zMA0YVElcUiL|XVJ_BK*I3qhvOzX-p5HR4p)#lLPNN0S1D!}2Ub%|E-$&Tz`-5I@|_ zXHv{EAhS?UZm7FHrg_pCvzgR4k%MR~q%CbufyLb-3|WG-_xY6p+7WT?&^i9OF$`^} zaGE>fJSeg7DD$wUq2`&_yZG(m62DGmfwSbMee zSwT)kgMP7XNXu#E4Wo^xzWxUNWm=e%>^0+%vpFVA|2Q$GT24CaeLKEz(4mJJYaq?h z=vX;sC|nioesR8j$9L(0=0T^&jER8=RhGv+yCtz8G|xKmw(O{zb+gCfn~-Zo(!IYV z3t_v0(NR#PwBq2x3j?S!q^{;DH@Lcp9Sq^tJ-C5<^ipu;)<>a1&BE4EQZSX@klfwb zm>C|*)ag(e3(_Bqgw&yUopB&X2~Uv}QY1O%o^~R~4=m(zH+e8e2Rq8iQ;8`&!6oaU z+`v-tAZB!wH6l4s+|BjLFY2#$eOrWn=TBinbp^oZq;h5S%(VM1B%U{u?k|qBeb9BW zD^?_TG$Q_JSN3R=<*OHV_`R8MCPd20W(9Vuh5F?#f>x5 z^WM~5e|4f-irOG&q1lh&5cL_ppH6P6>^O}#&n|lUz5EvM(~&Y{j*`bq4+&jr@BqQF zd_gw-i0$ojg2(HW)Lw>UswEj;n;4vl6*no88z=_Z?(MSFDx+}l=d#b&Su>p>{?#(i zDEbjXT1gDAj}@;_)`{yOWBmmZih529HiWz6x~>}@5*e79DDbFmXTY%hn*NyQxE&~` z4-IQ{!;($~PbPJ*2fb*D#GkII;&P&|ynScB9h9s+OOS}^l>)kRz1gBkLk2|HPleEo z>AX=*9uHRTy3rd^owzZtWe{y((5=-|*$jWL+fv=hVP(etoFnt+V!Nvwt%!>!fPc?o zw27cFQ>`odi1msApY2fai#i{OXkRiV#A2`W#wI)q&P+hsKH(T-OPh;pB?2&JXUOXt zR|smgh#TgKucdxEY%-Is(_?I6rX$aqeU`XBbh^&kr=JzxMSIZ_I&^F$%OWp4rur4C zr>H&HCU`=}hK}px3{AkqPfZ70$B8F(cZE$w-Hr7itK`XT;-@wH$nbRsxBL$2Px;mX zeyBR-+BB@Lt3@E1dF4Q^t~_76sfUh)zH(0ATdzbaMktRg*8ALOJNT|=T~D$jMsb0O zH}k7d5plJ(?Ala0I17`%b z3e-Tf^Z6NdB~@(3O>{f_xg}0s@Yjjrh1&sGk2sYpVFkR*Da2TlRGiXC%)nq*jD1Wi zj_6@u#;gTJ-8S4cEgmrKxvtl&*BlZ1872m=$LB5C5p{i0E*s7M+G)?8K9n@yZ-z|I zPYf|a2%Z!#4gtvyRG8E_30`9*nEDTd$0XOq4&{yg#?%}Ylx?cO!11a%2G&D)!IKkL zw@19_r?OB-9j{az2^cg7TZDFYT-H||e93LQ)9N?wO;lbU!m-;p?6pw&O?Qo2Xk4#% zz$$p8%pEDt=`d-tPDstoA;x;x0baxs{9u-2j{wT0* zXw!uo(bK7NA`sGXv!BfbZ7QVH+g?Z$ow99>1li=C1D zs!i#WG+SD_WO$y?hViOHb7ejbtNe)qLA2+TG$-miNTDEEybeAXa!xu<%x<_}0EXsGQh?gm+qoiLn`8f{o| zX&aq{?7)lEZ(lFz01a%PQL*DI|Ak`>tm!E~Y@3;Z-X6?;`n8?@Oc6jd-)Sa#Cwg4Q z9o^9caT)+0+c2cW<3E}=5f64|_wO`?>l;KAI&J(5@I?_QlZDoXtjsCof$f%{@J&%( zVZXNOCJ>wcZhcqT{r~q40rWlU5>wN#?An{>U!zGbub(|th44)oDR4PY&Khy%J?9ujo2eNIyT{!Pg*cqxvE(AxH z{YS&lObD~~f-Kd1swN{=3INnHRL2-tG@t28WT8B>Ht8tflJ>Pb|G30bfvPmIL4{;R z_I;-86#yDXX*$}xaum&gU|=cjmPtDxxa4f!7ZX78=4%^hCesiy)XaO zY?37W7xk!7uvZF$AhdLpWRAl}&k=+m9sKZr^aYinPKgDvJ_8_{krwF&8?9zQuyqSz zKwki~6xsHq9S6E|Q#K3I5-FMybZp=0v)(h3?9zp>suBtliluy5!BJxt@PD7WE6>F5 z6S(QA8tKW!XFWPsOsUoQW!I4v99^mubA-tHb-(zTpL=|A+_N2Be6keK%a9PFnH)L? zKj+icRB>&6Z3q!(e@}|vCmu7W#)@~_72@r?ffAy!NRXNg``UpD2`bCcOq-uXXL65{ z0Fh%>KRsDb&zZmP!<*>A)?D98U-86Up`LjKr4ylD$DM>IvdNEJ-c^(py3V=vUK0(> zj9bh=2~lt#gj?CJKZSJdj1@*cUNHS9IQf0k6Y0sdwr-#J=eP(AHw+-qUAG z@-ZxY#8^n5a@Dle=(Xm|A8`AvgaRX=IqSNj2;R$&`t%D*er&XEGVV;|dIdV$@B8lZB8$=&sRkUz61@74(fU_P zGZ`EM;PV;@wb@X~0X3HP!fCeSh#M?{NF-|&UX4@cV|RC_35Ps!>5!<9TbB;FsNsrP z*c@=WR$SjTy8FVIhI>3e%pa>d2e?Ew1Y>4nz_s^W6bY@h*^dfd_=&5U3*)Y`OV=mB zT{Eos`)-=n1YhOYat3o*l|NToSq)NY#T0ps(4CpxVYnV;?07n8rDYIZg_)1b-%AK1 zyJN5LyG4WY2lSZL4Cj!~zrQ(L1$QV@n=jK^u>Ax z!j7>iT>^86?m9y&{~Ujj%MN|b>~4pU#HaLkCuZuW{!D=FC<$$c{qY^W-a@q|fzmqJ z=#OJ}ixNd5*00oIb|y8+#L@|cMm%p1K>R9*Y4v8&leKU`yeE+E?> zkyX%ZsL$Ed2*4t{DMDEe5=_~?BI9Ht!EloeorJ9%r*KRkewnU_!guz5dLmIc09v18 z=mgo};W8Gn`LqDfbywa80?1K}Y3qaaf7U%qn6GMdAGq+bkOQhj1Tb~7k8{ah`BK+H zy9|K!zCV#{&RPGENUm(yZ_FA?p5BZ?#i~wpq$5b6#i%LGQSh2>OuIF?5(;0Nw1&kN zEH*`2S;TD953}~fiUT)y0>?!~GrNP#koa|XfA*BI|BC9o z<6IT6mly=C-}79|xu{}o2wMMhd*Z%iRuR_72Cm#5ih(t2z6z?az)p^-n$|~iDKl(> z1V;KvyM4#7N7CLTBX^IjQEak* z(^P7XZ;9Vs2@BOXDGPAWGs&86gu|91-qI4u!vN%3{$}o6RBegqVoEAcm-i)#-m6&q z{LDmq!@5W^jADkR6~@%6uJzd{0pp{+JkF4LV@`l~^Jj5{%FbnLpPCB+D@X!`aSfH6 zbN?c%ft18zN%ov=5oE@3kdq}2PVQkF&h^qwgmM(`i}OR7We1lC>Y8(~Tl-_g@f6*~ zw^D?@C|OGAgh@;iXC@}m))Qs(Y&MA#074;)gF#!7^KjZzerfs!nDd!j2DN6Nim`!^ zocmu+f3u&;at~$9IDob;^UZuVcg?f`dTC?M*Tm+RAZ=r@6Y|O|(%nfyEpLR?q+Krgx_EV~wH-;o83mhl;*^E_fvLWc@g13)Y z&L~pJ2DIiaqrIehT|e==71-Eig0E0S#~{^Lxe|#D7W@fVC=L46e0qp_d|wxxR%i z_mE103To++Q4;-=wFvm2!OA#0WKP?6AYTDO@y@X7rbv6~Bf1H?4&jD1tY|`@wf|sn z^=Q`DM3w?UcM8;~O(wM9<5&7I$=zMLQ~7XmKa(ll;Sr?~A4i?ionhrEHZk?5KZNg2 zJe7rvg{wXz1ROE!@91j-U{;MYzhy%bEF;W|?!HfhCQoI4gB8&yfspjG=|%dF=k10H zzz2nu{Ic?XEpa0)@7>{W-y8(=@#9|lk8fVLug|*)Xd7t#Tq3&j)6%i+OAq`__TFs1 zggZ)na0cMCcV3SV{vZ4d=$Pyt@%L)~%auDfcVr)@Z*%0p>pU2Mb5;1wu!L05XD%it zFq#$MQQ*NJ#F%4vWlp6|?NbsY!_7B+ZWyE8EA~A12gp|cOR11=$8P{mdS^tt$LGQS z-+n%1iR1SrR@nn|r25^px02j+~R72jYHyaDYQXS2I@vAmM z(lTO^kkYF-|DeJ*L+uV*ze#MIG5Gq&4>g6VQ*lqR6}Gt9s#@G#ZtQ8t4KV9b?2g_Z z-1!wMRq~yG67-e#4QWHAEB1y#6K#$Q(UJ4Ip|cHlOHtuOt9r|H8&<}x?@Bn!n!hVS zVJc(|EN_WZE5o`>Ui?8~^-|TH*&Ddo_6fbh9#f*ZGjm)r`#nB&a7XO-N;`J4N`tg~ zNj;M~UUz_g&0{8V-eUqkY>gZFh<#+!Zj+KCk)ljxkJ^m!Ywk#*15_&fK5kRQ*t@>s ztip_)hR*99dIas0=ep!7ft$~Z!Is)?i=HK=m6~gNv9+>VtMnsOUX^Bem}{RMn!UIM zT^T=#_Qbihqs#KUZ?1B|701F(eh^hRz(HCAu~r<~AnJXP55Sn?tx0I) zypDpvUTT)mLIO$b7Tc@57QsH1E_jOBnzvMrudKbFuq}AMqf6rWGE1=In{FQ&lzSpjJ9+o zp|N$g9bOyJLJCV6yjsxF-ATI<3;!H(pP|$7o2`$51b;)RpU!rjq!oq5=9>ftOQb_{cx_CL0a9nQeyE`ev1mJO03f8nieQ|u#~@jw&vw)gU8se z45!xtUt&{eD{UTjwA)cdQI7^Ffs8UvxP+(OOMK;kml!Hzg&x)B*>f^kuQ4j0mK4&> zwVjI+-x3SSo#xRuHuV+Q}_G7*md+CBvp0mqH0KNrQtr zC`2QCS!fG>3iD;G3-%l`nmj#j;OM>G8;zQq0;fj)M5ZfPfyB+G@4t~;BzC{=jh zl8y3y>Btayl*Q|glBvGknlTwhu0TCqwB{EH4M!JRL}GyDkNcYG9}9LPfcB$;#;cYE z2Zy|-HSH2)#B!tWq(e!__*>rzE_9~ZUzcb+Iatv5-Jq8Sf_z^1NR;PFC`?E&t<6)e z&GBNQt*aDjl2&P2dD1gygXem}tmpvdCG(UqxP&^{n=WU5_G~bkx2nxYTAH>ReNP`uU8nNipO)ndfhfb7uQ#@O8#DT#MDwV8c34wgoet=Z}LX zU$OlD55ZEqXS7i-qfqlW)6Yf%LIfNr=jc3*7yeLcft(25Gh_O;+@QBAHVzdqC#6(B z){VwNYcjM7&0foHvKd_P#kW z%kSc-<0kHM;$Zl(uK1uF8FHQVvW&7y6oBG@&XDLSm&GtYWnt$%nUQK?HBFQSU|R zTO;zcQEoBIyfs~DIwa}oOb?@{7u`d&3S#hKb_}_x0!YTE76V7;-)ACRXF~59D$s7c z;e#iw?|&O$$^f}uY9MQHa$b)YYeCj7YONo(;ZuenKQCYg3Lxmv?iTxxi`da+44q3Sv>m958@zSUqL4;*^usCuSnY5g@X77Xb}2|l9BVmY*8qPS#_*LNl^1?c_$J0N zq01eF>JCeXtm#Hip0d;1UmxOM2_=bX#f4%BkxnVbmsOrhhuoamhRM^Z5|YYNW}nIBZTZD)#5(V(_Ik z+_jIx^I$0khO^wpT7IPteRX7L9(UKK(rGFzCE^B8<@M_6#KHY3oJn%%YE+J=MZYL2 z8NFsX?j}9|2KvflvrWE#ch&AMn(i322)T(<7X!G<^GQZg(nSe3q@PI&o7Q>Mn?>FO zzX?mXIoelqBKdrVMRS{{#{%p`&mXY^I-YULQw4!ZwJAf*)>Ax=Oa3hz+UC6m1@AGSE zT%H9?zqX*0@<|)D@wN3oS zj!eT=DJL0BH}ZD@#-821f_Eyh|H@kA?%TiPBh{)r8Ovp{7ZO#CpFOlZxnpf|s{T?sV9xz{I) zaoAw#y+A~iv07<8uUSRJLm>vNl4TEHG;6jF>77>U(9fAg2wM7#nCOr4rSc4$2r!qY zGeqTi6ZRP67_&`e@FWFR+X6j?i(3QjZa{A8?6~eB-Zc7syfjlu`P-AbLCkvop^zu&u^cn)>*D#n(&1Yqp$NqG-Hgzg5uJttIt1c>_&k)(#1p&T?e zmR6z>(BGH@J8%;H->oILTVS;}m}R6{L-*7>TZ@dP*_ND=M$i^sBcM1!cwJCk;U7CE zSQdmC)EYXr@t&jjsr260jAWADe$Uj=jn_wrj38J<@S(HCso})HtP2uWz-G~%XX9(& zD&`4eh0zUBT+^`Fkw$Wvp`Vy%d)sD56I>M_gC+;XFSf7dF-coG0WM*&Lz>zph6XWn zs~aPRW=anO|#cA;(70#7B!UoG})7 zP*^p&$^#3W?u_vTPOl-i0H>3go&?_H5B!CP@Z(}T#Y(?i$;)Xs!)nie-6F;uZoLm~ zhW+*Ykw0F-&iN?mREEP(roDzZb#>5AYG~X8$3EBc__m3^{yL8Qgnz~y8z{*nk{qFMeqGbt; z3;ddBKoU0b(7)U)j}(J-{Y12KiNU-fG8T9@gKPDv=2)~vj$>FimlD_-=l}vJ$dTgr zvA`}kD66|SxYpD^@Jgv5uq0XT+g+C~h0-gal&=>&%@zz8Sj7X{GX*9d9+{BdF;88E zn)bCP$)Vj)DXOpv*tyF`Kr@9@helGACx;0>D-O_VJ+T1yPg++TWlgpBJpT!J16~hi zi5KY0rGxVs6BieVMfdHrbT@#1p`*O9LDH!OzG*PTM6glJ>~O4Z7XF|%#zkEX46U-+n+XI>bFBUurZ zCMsql!D4h_!-uB;z5Qn+Gf#vjU~e~$mrx!*0fVz4Ksd`U&g?01i<`3~7XSpA6TlOl zKmLs@>PM>*Sq4ws&tGm1Y$GE`jDpv%lH#XBu;U>XdW`a7;-C8^z=|fPgNqD#ek%L zd{KA!Bny!UA+|@_FsKaufWgUeSO%hoEUhuEF6B4&EErCFYYR$KC2H2sYA#CAC$X9+tMUINw z*FXDycZoOG-t*;WYFm5L*xjlIfN3rDMZvWA%h(PpRT$ku@jJ3IKO17W)??$UnIUzV z7X0SvJeaelD;y;ADZKlnZwU<*-+mXRKn90eX_C+A0DL*=ha>v$6}FejOWJ=kf^ST; zF{(XHKvtQC1$E$4cu*fcgTDyaC2kL zP7oq|S545}YL|<;F7XZU-CX^$%xzSk3jAe~zWk*a(R7!>84n$_iY`Mwaud=O@r(^- z`pw>_qjTUWUwHuPc|svsXLmSO2Eb*Ndsj69sD)I! zb#LbK!|*Aqc!C@%wtl4Qf(%_&dIF4ENhF_}knwXVTa9)J(aY;=t~N;?)Oj*{ZHL_) z21*)Cf7o_25Rc6}l>kMWkDX1k6SR^vm*&kmCmBR}dZ^5;O)7p155- z0R8gviQ^EVv@niG2i(9Y6{1>ttldq~yy>)=1dzq-k?NBiQB3O@Ej+xP*EPT1#R*#y zi*y198&~=I0+o9GG9h%~uIIMynLsgOezu<;+^gZSPr*>zPM~@az1!nMXw26?_g5|W z5k)V+gsdlRp=&caJ9%GpCk5Nit!d~}X|?|tfWf;q;y;xlA(f*{VHR!J&ZY(qBnDoy zVS3ae?TJ&KIVk>|uRoAus^HFW-=BUARMyni_B5~0x5{lizocziVw@;VKSpl!%UBII z9C0~J*IAB;HlsV4BXnn<3;1+(+#O3iH!R7H<$&6MQ1;NyqP&a?_O!(rzbuVwxv4m^ z9fBWJDjVabimn+hv}0u7LTjzT_TInv0If<#t&Xq~ce{jwk< zNZ@0@Ndf#VXS5sk%LWsk_95*ujDlM>U5yZ<@QI`5+9B$4p#SyEi zFE${~%?ukB+FZOfYKEHuCO>rS2aSwd>5%9Yw?9tgc)52H11lq55A2m5857VhUhYZQ zNzELR+wJcRb#@SiQo%0~9qGnP0j0S0+diq8^?OKlB^h42GJgusWCJ7&<5*Yr>|(*) zYeaKSi^oj*Dp5aJtbfET(qE#^p!RITV~X-&PA8tgQC~ZC{=x}adiYG1ljGnU9MQ^4 zrFE{}vH#1%``hL?VYQnC$bsOHRxpIY@(1X6Fu`HbO3*Zf0fh4auB)z6uZC}?hitFn z(Joh&kJ9xa8fAMQ_+#OW5CKPq@6=edz3z>%e}K5FN8yw-Pxbc_bBwq3t-Z{)1TEJA~~<9CaPp{F22!_?QH;0 z<$`e)h|we%ekC?ArEjpR;Jjjw=7_(g5wlzO*6f4b{mhBL-4OtlHusU%(pc$N`R5v? zQQ)9Y-fL*TY9B`xTmoa99Ad#Qet%c)jfPgoE30XJlpr~@fL9Ls>Eg?#^qYP11y9Z; z>O4RBZU1u(S!jkG!EO$l`=%U$RqAM;eUmL~j`jEnD=YQqNQyMlT1hBWdMo6+0~hx1 zLa{&mNEhf$1AmHC$Hv`{3x-^ItZbDDNjE5#9e-Bh*2aPS@}(9V*Jt?L;~P#Xwi-lJ zmI>|!px`M@2d%;wvoVIR)3Y2-?CSHXREW1UX-VS7o(H`DmdFCzD$aKgXv*B2q-&RC zK-w@4u4%?c^g;LOr59u1plFXeEACy@My{T#o2Q58U8xI(rrHla{ji%{IY0Ie;0W%0 ztRHKiT`y7Q$3DG63}IsOqs2_&qW zy~thT&=6#Q%eLeJzA|-~9GQQNCRArdeCHQO4mjKxRn3%|pVm?bpDY1xcvw=w|F|p1 zdoWLhDr?%)s?wC>qWKzo84P$mabE3SEAX68y;*sH<$YpbBoh<6AEmBl0cLmytBz;V5a{QaEH>HJ|4{}RfglL zdw(=+KZobPx9IK@9mGrH*HV)SeMs?5N>(~V7?OPlV6Ph#%&?InAH9iIkye;8lPpaW zAurp88KUL-{TAtAqwyzi*Hb_JkDW)2JuVfZ8zo_;D&_A3@bIQ{lV&)&&cI1DN{|xr zr^pa5+fRmG8YSg=apCHJ2>9O+_rLcYe^U=P6~6grp2Z*lJl*1>(XV)mnX|IqAO5Wy zgA7PQ>ilp2(${vt`{iHg`+qk3cPjgQ-Z)Sgq84dcQhR%4`~Umf z&|082O98F5|8LLT2{gc*87h$`3iUL>mOqx6%dsEt#vwnIZ{zvH?}nhiFr=MSlM#7! zH@U1K@;98+=#gnJOPHppP}()BOGqC?G@_V1w=RcSTF>HpN!aN}(T^ zH=>u8>lUXY%AoHp?09zwTNQgA$p1f{&k47o&)T^?ni-G2W^PEiue9epE7Qoe;J>Yo zrc}FB9cK!hRlRea$a*k2p4F5aK}slB3E?RpDU$$44JPD=aIV&j=Jsr7(`WmUWQ7`i zl7Z*I!+i&dl~uK43RVSKxn()D@+j?(-WiVm#?dg?elxR@k!>#qUclDb9v8TZL74_k za{g%8bfj39PXgI>*?u`-w;E9mHhR+E)8%&@mgrHKrnY+}39_uyy+ zs@5h})rlBbY%{0zvR(S)qU95)Tl4)}d4}r#I4O(n9Z9nGLX zXlJ7XG~WWtvi8iMk6FxxvVO3!9}0G-%T}aYXj$kO3}~c6NC%05jK7VUQ)rHD)Q&TW zCWT)_H%@Gf3UEaOmuAggeflvd|9q*PbW^snn`5;^1Av>5hi>3%)-z@>$-mWB7EPx^ zTmKp`sX}Bb=+!Dx$EYK+A8)sKoo!fXU$;&T4xN2h(VH|=5bQ~Cp6U!AIhoZcF(^{Z zHS&j~B!&qa>%nPdocpMAm{@qQdyUs3KXSmj-g3Ipmu2i_nNJ|AqvSl?$W=C{nar2d zbdU*fcanCz>PxjXajEvh=3s2aKRuzZGeYsCjPCo+15~T&uoU&JrCEldhXTDx zT|zbMOkKXhSzXDOx6=fT6XDa&7Ls0PrJ-~6U2xhB-K4XaleW6c{@U25F~0)B1Y|#5J^&X$(?==s30l9Xe_Pg3juB%6uaUzu zX$y~2zVp3In&b9b0K6W|-iTWnE0W&N@a`X+M~&*PK5W>gEUtU<4rBGHab<38Z>;WN zS;>ZD?`xoa1bGEQbPD#+D;*9_(2G4?qtF^O{u`kMMPRDI$RUXWZ#0x!~_Ye#6GD>QN1eGL-7I zUaIRjdz$-&JM1}}1u5+73@`Yt7j2-G_Ro+DFi>vHJ`+b`V2%5cyz8Fn_o8rV5MeJB zT13_eaI>9Hoi%?Nob=vHrAH{s;K^v+_2ihR56@rY2J_FcjM}x;V*!AmC?iRi?a3}X zY@1|MbWgq~FE$_UAg7_Vj0hb`(?N$_>{o8vKfRV2tMX|R-DvNK7o_bdS%o@`Ex4b= z`5f`6E6SXvCPq79`_T)~@nVbpLX;#>4CtO`{?ABKy=pCUJ^hEUB1Cqrn(v@S0AJgt zT$O+SN+Bf9^sI<~x}ELgFh#1;f{~D~%&=_m9r;r$s@$I812UHEM^` zoG2OYH*f<+`j=M&LBpd=x(bk!YN}0mXo0XTngJ3eDQqevxS;vT23)Jm9R{R5D+=Dt z0pYPAn*YXsUS`GmZ6X;od{=C`cU|0u6Xd3KHiyb_u4;^tc9*ufEh*z7Wl!4_ln(~q z%eYzhjP5w)_l$5+oD6|{3a%y9(S(EVoz~7xz2S)&o?kRU53Yb&#==Eik(na`uh0Me|TmL>F~ue>_XJdv9F%t-zHNAtPClgg?6xPld2Y_&u+>N z$eRhXSAl9Ouh63>R{jFX0=e#|qhA0sgZ%+BFH4j_`HQqo&R{8&@GukZY=E{n;um&8 z30EcGQFZXz!4gX6e=UbLXo?$%-38n>5VbyNU9M`@q)*+9mOuMud+|NEY)Rj&vg3Vx zEDvL@+|XOa>{l3HL>!bH-$U0@`#TU2?dVzoTuq|hLadEzU?cgz{qxtR<;c1-OL)*ku%Nuc2m!Xt#~;lAjaB%1yDk z2X|J~o4jusr9C_7DI?ATyWw=_(cbVo#m6=@yRLLgwrF|78qw)jmi z*)gws+Z4tFwSNLJMhTF0cUBVUlRV~QV+6bH4jL#@FfaUISnJ>BXtWOJ+&#=q3^5B^ zTl1gSQ85Wzi<)8Pj0%P=pJXbXo{p+An7>R2em2`rIKVnPnZxVW6C*IExXQwu&0vXb zl@;l3*{3y#?r=lU!oTj^T)#f$Tjbsp<>x>I#*6-^c2rUEgnTNVZiX0fg^kxXEi=tn33k#0ClfiDqplxe-Rul^lb8I@Ay(xo z7z9kST>~=(8Y^aQ%YGBs;q74T9ZVWV6r$g`d?&<3`f>(v`=jxqu7%&v$ueqU>U}Xc zP+-%UPF<wlU11*o}QF}E~Y$wBM+fOt&pY%U$Ze)?E-J*2E z4b)U{J;`)xRVrOZ!{heb)%gY@H5jO?4Tx!5ruYY@p|H94YV_9abxi`IsgT-fdo*%+ z2!di=-&)n)R>ujSe;F32sO(?#Q-3L&n6Yy7lv_IJT42l4(75bRlGAEOxyhVxL5EWq zOWFDao?p#5T7&gz3Zw?IlItlo2@@v2TYsv9fOEoa$J{2IX4(*AeCe??W+wksu z@4v^;fkq~n7ZTmCx4cU$Neq!qAIzDQ$--3CvkRP~@T19IA)hiARHt;OV_EM{Gs;3a zu4_@@u2mG6YlAL2K%9~lDb`pP3z^=Wh^PqCHeb3zvc{Sx zPrbHpvc^HsO*b2YFUt6R8=ya!{hJnkCcA?Puym86DysV};E@E|ZG74LFE2SmEtR0o zjItuqcS+?J$NpvIhfpL)8q0{2JGqOW^>zzM)1m7uDgwQYOYfkdURfwGEGT;Zk%()} zp7RC&jLth-E?`oz{wyoBlF(%tf12Gv@Z$fQ^8)`ALsXv`&%}7fnpExIV=4DfSfhTP z-qc-9cHAko1&Uc)ztj?vh^%#|2`XbKeo4$?8;b+g%k7*Bil_EmLOaG^VRAoOHELlQ zcdx!jmQ?Tb<>&usaG8AsUvFNKl4@&_0rKFbOit)tZf1RDB8kQk24f60&5eCCaHH!$ zY>Gx~ic>dTz2My=Yca2lwhi!in#kuSk<}@&u~nHlcHm^A2K|YUIE>fAARGg2A>9Y2FKoX33XUsYLiy`oEAX$TSZv@D1Ioc{0jUOy?ezH}R~-E?cD*pN zwAXGBG+8NjwFAu1n#rXxx6=Jps3HF7=}0+n=DjH03C*&oXP>ix=LNj@V4{|1;&nSF z->oy4K?c}ceXq&L2|dJz(kP)%x{XH~>H3cb$v!W2-Iw!&sm1ldgx2apbe! z_H1_0=SC`l-SU~)$^T5F>#cC3i?93>^Bgk5u{L|+O$Xh%GzTb{L9$w=1Wk!a0`;k) zL8Q9t5?MJa?ygzX+NX~WiZ~KXBn;R;H}%fbS}Z}=0wOn`cF%AsQQZS5!w!cF-Qg6- z#dh(NcwnS3UNw?nXwB=NoNzJI3Ei+dV!hf-Z#<$ubeAq}mUj+SbRhkHVo{eQ;fu4N zNx7#=_|gd(d%2>Gp}IZ~QfSp_8mVqYaaeoccp`UA{}he+7eB@-Sq|%Fo;6@lh@r7H zpOKBrCt}FYS0l~?H52%7=|ZP=5lrAz)PWkxygv5?W~kiupSxTjE$hh}HY{-)T zjNbAEH_g~0dF4U=@Ec0hqBF_dbPr+gsL6BhV&|ILFtc?^ia<_uOo(e2pJM@ATodq5 zhuXp0<~J)hM@`~#07N&G&{ScrohU3PqAqMCYkr37)SBLRGVzRn4A-GU&oAG{R$t2l zEB$D=qX3%h*S6x|di8sS-f|t^vXF5*@Ebp|njDFMX9y|Au`OAJ7*831bmAzuBR`mU zdBr!5@F_nZ`=@UcMd+omw+0ML`w)C4kbJGxo5uim8Ep`zK7{+rVWh{TJ7TqSnU7@h z#gz%e+;~6ARmMT3~+qS@N;B2_b4l63glpzx=Gu352Ny>Za z3eDueIn5|>cQQQoaGyjIk#y)w~{{&lGkbeFCMB8Qnai0ml|EJfi zdaq5rz_wfbh45Ex>ehsY#Ri)L&2GMtuuY2H8KPm!-@UQye>peczO^z!1A%G z#02j*`fqNqGx+$vl~U!2S1x07?hBR+vFjAq!PJQi3vHQ>H~qQ5joaq6&DC0o#ip5; z7**k2Q>DiQbF+SRpZ8M^N}CNFwLa=vzYhy~|mHA1hAFI09Dba8)&g z`#r>A)jwqjrLEM_>h#fz-0{aw6MfN;h@Lf;10hX3|q3wzZ$H z$cd=h-mU=kz7S-%32L7bApu1)glWfXxU7L+pLtxf!U-gb z^@$jm59=JwlJT;fqqPg_)v0*)`AN(dYc}qG$p&dLro%K*1bN1!_35rqc3tB7u!r;u zp>2U@&fyrQKp}f)%XGWZX|Kh);5JMZ@IZkOu<9ZwDL|368UE&9+cC@7vTWP*9~3ic zv|=1&KEhXLC((`M$iS?t9h>o0IF@fmy-ulJ$gP?B1*7D!&2ts#+&=is^F%=`wz7O+@kvSYyz^#gGz0++s|y;v&Yif6HRe4Twh#DJkoLdQM3S- zAF;lEoFQ;B3kYF+!b}7j1CESw8dA;s{*R^WKr{g;q2*O_M}|0+RaJ8%CLBnIG;D3I zW6iykTpP+OcXDVzN?ZX*iO&E{eD}xAZC|11Kx00WJWUa>>6npeDT(!a(Xk6b4hU8% z7-63kyy*D;?p#Ev>E`@gK=f17~;xHzLvo3fx9!h*kugsP#wK3e~Mq z^{0zi|NODAe2Z3{C*3=G(Ev&tcWwl_sUiPO{7)5===IHD*3vM>+RR3^`5bkfjIkb6 zjVT$yNv7XQ_jx^~lmj_xrWr5f00SO3FsCsQScwHEdl|L1?10(rCr5ol%AW;mAUyun zpQwUpq;&3H7ZvIh^`32YS6$_+2glOyw}-00`)NCZ`0Z2&05tF}L{&dlg8b_4pA8WX zmYryHH2Fq&?%eSN6qoEun=4Q3f30k;4Vy$>?}*HC#S*Q!pdH%I@85t|{eMySrh#nc zZQpR`nyc+}I&G&YZEaJv*U|;C>!x<9s&+b-wu&f;UFfvBprrPl7O|63gdpjH6h$RS zD^9fuS>hM=;ZNHHSM=2*Ad}hdexlG09UFc+0(AUkwYxnu9!qX1%lhjz@$B+@n#r48 zf~xW1DKm_{Vbt?**>|21gQP$k^_3YP;jtesM+$C>Zy4Gy+tv~ProItlHUlY1&3OnA z3leLZ$WD)AIzq^1PcKES{mhN~1@`^2T|O%KO?LrI0SRb1H5NpWBoU03>UVIf1G)2! z0~nj?2LAT2{5NH|qOY9_#B+f$oVdu-cXe*3LMgA?eD!xiW4urETB@Zj+wyPB#x4DR zr6*O+H^cstnZWNhqOpe+Fktd-deX zTRh3?xXGg|ngzgT=tIn4VIRsI%S z+)!{Dxr)>><#%`7L2OBnd|u=AnIYE3l}}9~%N3n%`d42Rpuxj))bSzo%`U-Vfqo~E zHq|^32@d)>Yn1mq!uZ|vS?07(v}WYceFi1*+>Ulg|(q3y=Y~r<* zNxLt8SWNL)*Fa1`FerxmwOuu4ddxBiAre*#X^WxHLS9#e6%<3BpAM{YEB7kVT)j&T zZ0EGn^R73w;*;0pDV;&rA*>nLq+tpCO?XpRXwlOzO~HAd#qd5*+)W=dB)51hFFO(# zADWV8yo`kJ#8D<`#nkoRjWk^J4wd9!Ny2_{G^kMk7)`mColFs zY1nK!`vHqdeKSuzZ?+pQZh>ji8dvP#^ch+D)!IJ+9eHYFsh|>lhCjVE3WY9g ziyd1TUn71_6Gi=qZa-qM3?SUhJ_#@86r@|Z8XX__=nlUN(ls+1DoLp4ji2^mzdYUJ zD7%9j56g*;%`Ktk@f9C?8W~)dWK(AOyIQA_D+vsV<$cRXfTwkmM>m8^^aZuQk-!;7 zc=Q*bS%@jdugJ$I1e+ERh3qhho#a$X@?Z^W&MyZMwFf+e_ZkeYttNGn0h@!B7hLhD zr?96W@KtgEScg0io>7*Rg?GXqL%h#&07BkKq=sLTCQ+c%bB@A$SbaTohqXMeE*aVt zz|0nxHl4*y1<+xgI-HM0)~6B%p#F8W>fOC$STn$AXEY)IxSZ!UTF#K1MnlVni0oh13-kl}vx(Wej+*oFR1q8tCQP=J0|?2Ahj(jGaR7;|_7o#t({{ zpoK>Wx<=&-8*cY~r2V2L^xnl-=3CGoBpY0f-lZKB(?1)T6wjE^Y{Jc^hZ`zK)$T!j zIx|xEV00*2a0$W%58V-5_0Q=aPhxsdd7O!tF=<+F`Ch(FBMvrCM_rE~ZV(1b0}F#9 z(YYIU6Nkjhjxf5=vgc`lU6U_N)yV2MeB-|^fs!QE!7k0hOtX2BUC89{W={! z`67g5BUYF(Gcj8RiI>|^0?Flxdvpvfioad$Z=yZw{<_N-cg`2ukrEQy)YZBsKUUH2 zm;H76T@U$aQ2S&EUFtz^l^DtoH9uA~vvyZ+&cSaAmb==I7)KMt(bTylg#RF+dzKk0 zXC#(w3QGST%M|Fm4vJwO?cT|7UT;4>!)DYK&dxW9_su(^mGE`JlhG;K@^i+7pt`qZ zFpg`aO6>u3yodDs!OG8mLgWOpRfS=p9$3#`c9)97&+t4js(_=R059>tUFnHq9bPMg zmJb803-qWmw!c)W{;aq|)&kXzmiaxC79vW3AFdrQyWbL1gPbB_6Gr=In+lg}>2KY0 zulg)W6VCl$5}76sbiNy3u4cGWJ`8h+_iS3LB0E4z%Wd^SJL+2K=NEg*8GbRH7XuGk zEEoq$aUi(){%h;gy11bJ9Evcos*ICai0YZ;!1e{PW$^MmUU!oP`83NJ1FxOb*}qIWoG#cubK=9WOr4Z+D#seuiX<>L z+I|(Rk0yQsHr8=v@@q?0cuoC;-GeV)*6s4SMjN!7E#&CkEE3s7VDC72;v{(5(>Fri z0%8c;?+utp$uJA{`Xbbr+5Ef>BT;m?2qHi6hukS|v(oe%ZS0?ggQC?ki?eeroqg4z zq}6(fS&Q`2yy>M0^AKK48Ehk>&2vHX)gsVP1ibtaePFPXY~*SHg@4gxp14}5tu`(K z90tg&fwoC1(}5g=(!@qBo*N20csP=igP$J}x5n#?h?BmSIZdthSM`wJjOX@+v~D3{ zV_>S|g(Z>rp%HiUz+u?1`kWoz?UKv*J=!yIZjxUo?2-ptj)tD0^R5-F@@KR&>4tIB zU$_ag3E^|#rfY7&4dn;z`s>DuRAcJME-c-bZn%P^`$d{Fn)MKXuk8>dC>zq8hjvja{xe`As!reQ+3;Sif zKGxr%gZA;~ezf6fu2dS5lsEBKlySe)@E{%12ewvvxK%XtG78%#94pA1q0-PP;Ypr49u}XRKe?Lw0ms*~%YwVA z=Bs+!$#0vf=BNvvFNIMYPv3=Ev$VxZE>QAF&a;6>4NVLKK^~`UstQu}fjLnT$+y57 z|G?1tz>|ACS>SPyZ00;Q>vG@MQjK6f)Ox@D^V&yZfX}k}Wb_L!FO9N`HkZ__QlXPM z3=h+L?lAV(S#Eh2v>@5z>i~SjK&&ZJL%QR`0PP~=57+%4KlN({)|lJ_HI;zah(BDu z7>#7vzN++JbPkg4-|I107v^Gyb%H9{-q_L6Jt<+njiO*}JjFjlw-D0#5!B)OEGJybn@`kW%EJki!}usQw+J?!xnv@3KB&L! zU+r_{Cj55JNpLyMgi+#_Mts_?K0)fiwQZ{_KVctVC9-Zmy;|-T`c5tY_pXgpGve+s zQd_^ijVT!=nSQ0{##)>n6}Rw|MA{Z8xibInDGVl zbyPetpzQXahuT35&`_kV2|A?Pcm6DlmBjHrC5ucOgfe2)OB(c1b9!1fvpB!L#t%_f z%Q1uNSDL)>lB6{X@6&XE$Py1r$O( zKq0h$6Da?N(|)Vt|A+qTTYItD^8D2nGRyyr))oHo=z;GbaiaRSMtTeP|AcPnf0rKV zf9dZ6<6r=At}xF?^Ur6vg;6yngI^u8H{@2@pnxX+c*lHFpd}+8W?c>0V!8dF;Nzwc z$rq3Z9VDpl64F{}8nL}7eWHo+2a{p65%J&tJ>V&u=OWP-?6vou!<9$+&0ZZX+W~Ih zHg=wXU!b%zZkD7#w~#FVi4+1Qk6B?sX#aKL3*~hhpb^?%N*Q(x8L=mUq!Nnq53lwA z?%}rdZ{4&uekNK4`-8?W=b@nga{l4m-;FGgd>y#HS=(#GuCLBGB9$44pL|cBJB3vR zX&pd&KN&EPuTv}Z4O{q24Rr<>oJpIa8WU_v&O_UD=;=w-QG(uNw1TVQCBk*Fh&W>zM+VA=S@IsG`@pkPR)Q>qJeFdfyJCLM9&48B zRA=_U>AtE{L#fd3kr&`!zovZKGyAWEMg9UEP} z)%-J9dEbq-<-iQ^df0M#7`TB|CqBp*`&S*N#`TFh7Fu|z_D|*?yN=|}Fpg18F^X?g z#>eX#lHJ%b&Mz6I^Vh?~bDR!v^{B)0KV&LQY%Mo5Z_n%JnCDLstsQHo<_m&@+&+r` za!c@Tk1>br(28$1Rlk!Om*HL_IO3KQ83$CUDg@VoJB})|L${P|)Lw%=DS;ec+&5%c ztO3(6qv%n`GmA`WkZY4zo`m-m9}!gEoou89@wF+ASErq*y_dmt)nUyX&J5hLCJ=Fz z7JX-$I*L~l7L?iIhYw|1poQx3U!t8MS=SRD?w>t}?P?ny#=yzB z1T@?2Q=0&yI&hkd%ePer5G2DSHr`go4K!F~n ziXxD)eZD$WY)oGZ)@Ai^n8zl0!zQfL8!q$+*Q=f#(+kn_uEw9mmf11T#1(Sjx2Ek=%S0EO)RG zzAtw{ECZwI;tsERO(ABr*+K<;(?a7)`C_>6486WS5Qmd3?C!#bzS#39OF#)O+F_?W zm1WFCC~_$(8Nu@+eIU>vCoU~6>%2=e!sS-HYlAPV}wMrAG0mqHsU z4hr=d7LX5X+M<4e-1wkV3)Q)MQ@S4YF1{)(>qxeOK;3wLmG2SnF%*s%d3? z=(XUKo`IMB-qTp1Ir)a#9tZ(C_3qrbIU=dhT>6YaOK@}O$M4v2@bYBthV<0RkK2~% zQC3rIQPkx=0k-_?(|4%k_@aDtVUvio+`b7{9<^1IGsp^XY zM04bA58tXgUk65PkKj0?;w!BD6m>VR+J6VeT?Q_8-v=gYkTO;48LjdTGgDQ4iIDa< zW@e9Iy^jJ_?zhmaO)(BA`f6|*(%&=y+cqPrfVTCUhz00On2r}}7LVB=?WHlGgm0f6 zAMC`jO7qAMh?WGY>Q8f+3k7e+>qzTo^^uBmjoZnDwoOIJ7W2)6fG$Z(|4Z(CK9H3O z_rla$Aps#@i+8ZL9PJRiH#DrU#-~v0qQ3*VSjm$1Ic{c16~_DN(IddgzohM87-l`5 z#*aXc-JZ&OlPlyOVIcVZ&%77yu^r3;wzc%c%8Ktwk;lVkow=&1R+)g<#Ry*tTOvg( zk{p!)1iyFw6#RA{?h);aDvXJWeCME^_h$DNl7*nMCV`N1V--vqbUyhw;a@6ZS6FlK z#BjqQXmU$tJkjcw>XNMr1@5o3nHX^N0HBTi@V~D$u8rG~KBqxz7E&qua#A7&6|Dz<}A@-=5d~^!>Dnd^)m?|Gcqp2gdrEwB}+N zz9_8elNEmAwMozqFRcWNV1&wZ-#O3yjVIFOnC>f+S@OO?L^x)^CER$@@A;ZJQ8hNn z{E=02T~|(g)QFe=k;jJf&Ie2bsG0*cz`1P5YMs7SAC0%$NuQ|J)2Ko#ln8h%r zoD{UKnMlHJ1f6q^qR|CpfD!=Ak6yV5?6QH8^a6#KC)EzlJQEMwq{hJeCK6|Es*|$X z^lM)|`cBpc=9v#6?4fVX>@dXqsp;qPPQwZG^n*2xEUBb1`=!|-9~0R0Z2H+lf+agy zT25pg-Y(&cfG@tb5G#8+Xs8ElN?x@&b|4BgCOuhu^8O_yRC!U1E;XE<@+Y@V-3=i!R>?rjyH{Rb+ahq;=={ zGi{W(I7$=Q-9<9d6-x!oMMk)A5pNz%1d#?unFPx$AjjrHEV9&8mu5qNmV)+2t<~Mb zRvA$B4Spc$2tMQ#KN0QvaCd9?RB7JXJod#NyT~ULpw@x1=m#nl5AS7y8nlklB!p|OrmRPoZCtrJauJL!( zv?VrRg<4Y8Hm=sr?;iCR?m@YHTon(rn3d2rs;yrWOMvO6LWSJbETUCiTW*{#iUDGz zOImj-ENR3QrrBq)Km;H3H< zA6Ngx7nX!8z@SU#p7uL{9Li16zWWdgo`f&*7v~eWSJry0 z^reX?_TnmM5Oclzjrh5wcMBA}>1_1I_~f`X(;8xSRMH~GIHwE;O}mj8P;c0)^MQ9~ zTCs_FY{0~ns==b8T#DKiB_pV7%v6fB*+yt@2=mjOFw~u8gTiFD#gooARyvU(VYg-N z(yynUw#}UqvNlz83U-puQ7jLh)PCGL0zuxt)epc^0K63NKN3}&cNVy~wh0v6!FzKs zg!b_Urh>*KY}h3w$?r*2+g+d zm^ga<^_7*4V*Wl7aXeYpQTAu;rT`49nLZURmVZAW`MyuQ9<5b=a)1AnR;jqtJFo%t z8#231QbNY@qJAB6rUeNyqxS)p$F-q+$Kw**2ct-L<`O|EU)Em zykEw@bMHqv6lt3no@0nkIq5qjB~GfCH?><;P#Fd}@DnrSIp^o^|7It*Xk-Di9t)|j zfLIh0!(34=k+#w1eM~zt50a_d>m7_HAMr?Wi)wBy#sa&{`3ZkU=irPbg|kY?5wRw{bgcFS9-7Y*{QcuTV1WyI)Hj7 z59ts~_S>~XIk8&Xp$!aRNkOwY54`ByH<48i1uN$k>d*OItG&;^mb@CR`qSfxf64&` z#;$I5L@f6%0#havnQ(;Yg*3P%8N0fjVDB-G<9FwYGD3sF)A^9E z*1S4^$!_mou$+8Nh&ZbN(gUKBf%gSKV|9r4@r|>sm}CnKZG7D{a+(KZh$IWO;$W3D z{vV_N`qrxcrHDSkEc<+;(}{UoVIq6FAmkMGQ*mN9%6UgqLD)nDbIgTZ6La$S+QFD= z?W+K<6p25vQ&ZB!llE40YyR{D$Hp!%IdhD5T|wjFQaiw_cEZ%#9}2{Ni(l9?8=k(B zxiZv7Jt40z~al=!oX?|)ksG@+BS+oUk9xhTq6 zZd8lMxAo-&N)C$-I z#Nq(F2TId$38nE4DgW#7*MC*pe_aR%Fd0D!*x|lyp8rw3|LxKH=Y8{k->0CPWwp?g zeL=fjdx0&G#*o%m*tE?6i7l`7zt_;&$1n8Euyq08fdDMCCdF1+ z{IJc(0J&k4y}SabE!KBH@x2Bbw9wvpwjO2zx@^nMQ=3|0Kx0$BJoD>@&2abFC=Paf zoM;^RkSO2y@fsOhF{pP6)Rxm)8*@RvtR$Lk{_Tgq@BUAp=sQWUa2o(5=^MB|Qup3IR34d$u$S|5xz_G(SVru^K?(vLX#OB=k{ zx{{(mQoyhFPrAna;s?$`U{4Ge9bbJFw-DrDlpU(`VRQ$pq+34EKNZY5BPnwWr|tILtp!;FHjEu!n`np&Hp5U3LGg?C@Mf+$=)DCH*SYZZaEW z1a7u&`$a%^%#L7ZG6`Z6M?XNDk@?NqRNh+>GyZC!%^;wc{7@IEn;PRXJ7|pJ+Sdd< z;+S|xtw0)*ZOgNU?5F{8;Z61jcgs$fDfeht)3?X3%TL^1*Gl!YHoD{GHi^6XP3xBa zEh70m5(sT>58~QWui0V|>kv%Yz1U@czdPwt(KGJ=?Fm%h`j3kYlbv&6j!y+4bYw3O zu&dgf_FX40z4>u{KxDh8sC4|h;uLYp@#v}AEebwz= zi$cqc5`y+_7=Rt2{qCER<6h_y+*DmI*CVlFoU-|nh5u~=P(YUTU~0e0{&N;Yli z;)T3+^YzAp9a_GA+BJwkrxV(Wuo{Sk&e_clqOc#?qT&`#88-5~?t3`6Qs$6wSAvwi zmTq!Gw@~@tMYeI$-$gcOT?QziEoS}POCt2kyCc+JL}}Ke&wCVMW=6;`0&J*#zJ2Vm zp`pX$V12QECfA&?4{hIG$J2LLpk*b~o4%|%lej3(V&TH9rto*QQ!@LKfOz*`U-Ph+ zKF~i@5I8^3yr-x^(d`DKB;&r`mxMObDTfJzbz8fB+g!bK3l@OVww+XMu8aXyxqZhF zL7O_)z=SX7ev*AR>2?FW<5Wb|-P>Or94jeCd<@t-SZQ!}vT%&`hlUYoJosNLIEhE3 z_Z#89G@Fp}JwQkTTX&TRUk}*h5LPiNmvuN*-u7&ze~PE<4Hc+{mbpZ~*p$^-`sBE7 zvd@Q(3iMQR{cl3`20s^@j+eh855_a~ojdqG|G%-Zq;#$zuw9v-7y?d})Zj?P-Cl33oq;eO(KH{;j3&5~Huh!--pLDX|O z(eWyf9pmrX(v^>(B)%{;YWQt_X`(3KHeXL(fRe|hz7`S<5cP+O*}|P}wo1@j%>%}6 ze=JN^?P}k%*XPk_;-rlaw;z>ve^RBegit-;)o8^2<8}N0QqiA?9)ZRSojc2uQMn|O zsv>HbX;votz*0g1qWl=KSLLV&sYO>+!mxE<`W~m{YZ!7R(Y8o?0(o6nO*{iJ@cl9<;l=%cE|+?R z!!B|})M#AoX(&%1S{ar7I%Hyp(VtwM98cbw)z=!XY^Jf}sKt@JTeGJVCyyXyv+g6z zy&A8Wr7DdTEtf{_)RBymN=hmoutmfe9xO3PAINx}4|)S$NG0;7dgP~`8Ttpp_}7ye zKyDtN;##MeX%F0g$Dj^0#Y@HQJv~zoLEy1g&@N%J@(zu?%XNl9$*K7X3o3=x-Ie$u zY_7}40GVFJ+7t|fY*yjHbna@hWJYGMw(Gh%JYLT|QYYDm;v|N*11^1#a1FPbe^uze z^m9v8I#FT3_4v90bb8T6s_-|81mfexu$CQ3PB#dLnP=`5*yU5=|78(BX>RKhvz+C9 zNyJm=60}dwz0DC z-4C`N&_}c-(>99XF6(18XVlyu#3kKHkSf{IYn(Dx+>#pZs}QYn^}*=iRO*mKkYq+l zOE2Si6}jCo8K}O~{ZDh+@;&lg<0@5)C|>sHVA(lpg)4UbG`Ak#YlzPOcFKtBmaA=3 zjOuyTC?_0UX_Aygz5pM4Y5C}%Z*N_ki&D=Y&=ToWZ^T2_OmE5N&fOq|onkspvJRIg zAp%`qo5UjQxIgZ^o)^{M!OUW~U&yDVlqw(k1GNO)p8GM0ik-o}WTiqq^TrDcnj*9S zF+x``Gr8HC3&tA=I0p~}P%Fv5*5Z*RNUh+9GyD5I`asS&JmKcpFx+)=#zBg*uF*6QHMQS>q@iQJafqi69to?m) zEo6|}Ca}8jTqmPj;eBW;e>;znN~B`H19sbqr2o=kxdc9~C7L96)Z|{Bf9WNyG-5KM zhMgU%vi=uzg1hV>?Xt4y1VQ4TBj3IMks`vlG`8b-k9?w%!Os{o=dNQi<~7z0%2{r? zgs{pky1q;n6#SMh9S&!26{)`4FUBAt8mFHtEg&fV$DSrWtj=1j^?q;s0*ZXC*7i`= zW$*)k%J=$3u#iS3fOI=3DEX z675Pa&E%i;^w>**)r*!io8m&PYX;?VmakQoeI)iH9<0n9E1rE6X^^YEC+C1K;B>Gj zxI^Vd)$S!70*~ZMHZ#n6#HXx~^JKO5pWGu#0Iw zX32BAQyY3ia|7p^cp)|PvPWxLzD9!b^^FI}RYvStmWD-S^vB)^j$~xTW{*-lr;M!4 zUs-~Q$=`SlU>YLT%ck*_Y_%e4_$}1amUK5pjQmo70=4~0`W4+JN8>Pj#!TFFXo2y? zztMD=d5IAM`H+!KkwuLiEp$bMRcDVYd*YZFH9F2(+Pe}lIK%csNA4vnuWiEP?~O05 zioqYrZU+;XK|0-^j!q4x=++Fr#n{)koyRhD0R&-Kg&D5De_bJBT#C`l7>JL$V8kA2 z=)GnYU(=3@Tbf(j*)QHFPjeZ!NTxK#R2EGm$;MH`t8?sT*Dc*JTbJ~xnQ}v1UEvud ziX(u7ZyslDWMJ}BC4P&ujQx+q`%i?OP+#vZWD;;p^m6ObzNrg41{KEo_mYph+nE}6 z876GaNY*ne*G9gQ%K*S&P@uAj`O53A>>>Snba6;XQb*w4z6h7`tdgb$zlsJ5IMkNQ z!Sd@z)Io6kcc*B>%-kurcUBgvLOQErF`VsKWT8}|Qz9ZX1Id94&t>gAJYwP`vMe%K zQZRlzx2+&NVKzQIMpv3`Bsj}9L^H)N(SQ$}gZjKRd3d8)#knW+#!%%!|s#;d`9fbN$8y(rd|y)?Pj zr8%t7TcbEI#(GM{4IAON$eK5&ia4kH^BJ4X)oRKN$NX- z5cK_mZ~P;)=ARYg-%&RI-+d}T{4__M)N3(a+Z~j1OEd%lU8l@3ty$xF0$xgaUR!;3 z|357c0=))6+!nrYfV>;(TG_t*geJlOF-6BsnavC)rW}|cPGyAGd`3)<2#;;~r~P3Q zV1$@GJpn~%tFBOCcD+3oQdQH6{Z_A@pnQoeBUZHg*jg!zr~~oO*V^crNXmqAru!`f zL|;g|cTZ|#uX1(i)}5jkrzPoO^3&`ug#3-OuWK67^`jzbiQL8IN0qPiL-!qE5rqNOEO3A#gJ!?b;C_DK7E-e zkm@ByDLdhO6^Mvbo4LmU7OyUtS@O2*C$3V{E}5}eIJ?{rx1_^KUR3aC_g=NaEo4f&=GEa= z;xD0($)`buwB_E(R36@&M$RgZ5rp8@11E$sm4BcB{|Rmo1^f|-g@>tz(%W)?I>Tsf z(sx7>D&)_1^i7AV(crY_wYJ<82~q)6&a8|N;2lg=*5ZXple2E2F6}OA0}N!=xnVNB z5p;vI<1{bH!Ah^D9Bq&BdrRdFOY~557Tth==)~cqatHlBuGkehvS+6j3Zv$9evf^@ zISu>H5(vi3cC=<))}wd(0H#va4E>eAk16J0_;9!G#@r%y4Sk(^(CavJ)VgkDuO7&e zwSbT>gLqFtA53T8^?O0Ehzhu*3FLIC>rHrr+#dl^DO?dRV`H7TAr z$Y|=gI+#&(rqfex0M#()0qYDg_7|HUpVWKeQY{Kl%T#}9?XslE)@qZ$zzh$15=9LvX|p1<5F^Y z*A%F-5&Bs^&T5VN13^aYp5sab2Yk+tw9Sn0(O681W$R4B@r< zRv7cxZSs_V;mGLDK($o{OqEvGEHJP(Jl3d0lxQN`r} z7BiKGp7@Q*f0lT?ZK&A%DsmUpBkP^u2m$DnPE~Zjyrp-%+F;-%yU3edA3|K-~d{2(r6>xDK%r(yCJolbf*GS`MP;|S~2l8kb< z)y3@Rvd-O@TJvl-@<@wrUz%C#2Hxf**aFPo86fKLcJ7BZhho+(IhS&iBJ81pwrfup zpIXqeV{WYPaXvhASV#6yI^kA@EVcN_1rIYh;X8JEOwl`bXV*$L1qPqR4+UvH5u~9mG7X&?f)DEHdyM`OlDnO@@93_G> zPz;KlLGu<26Zl>DKYI5LJ~pAR5!P*SymsB%>P`c#W4F_x82ztpGf`9#>7t02u@?cw z9M)EVSc@V#K_o6US=~u}Ae3q@Y8Lt6f})t$^+ckXXU_%YpKDyze0MjAC-vNMP^$uS zO0yl4XMbk(tW+JLYM?3Yf#?z)x~|NU;9=Ea9y4OJ*jy9+u4N5lUQui34mEyVFDI@c zjYw5FrWlZtVT!bUsAQ`#^@6~%odH*qIxI>cw9-MYi4Zki6Ehv;B65Aq1h?FdJRH4y zUYq8X4yFF2#2(>veI#G%3{_Z~g&Rm_SipP>zlP1XT<|E*gGqRUeP^ux(*(PW2f-Oo z*Ipf{&8Zr-JAFL;WX7km{GA$-ZJ)X0Ww?WRNZYuv1#oxTf}hu0 z_FrD`pu)f)=5IDmePGmPA6BpMc7DA*Cc?g+O$nvqwZjS(D?E!I#=jt>r0<<6Zzz3W zTk!AXuB7gR{recngkyzhO2hOqXT3r@SC1Aoqrl%=MUWhWnfBsG#GtdVs91UPn*IHG zVCWw`v#J3@DX+tNNqiTM5Zov^# zj1}%pf$QOZD>ZzsNDn6T#S^7d*hADZ@z~dm9>IIc1SSJB5QXCHxLOe`$&~+Lbk1vl zB=>Y07>ZF2-`Ji%9WckBmlNEbB+r}QOw5rxg$8$24 ze$(wo8ML}Hq!}FU(fS&lNo`>?hOcPrjND%z1 zCO%~fITT;w)g@#~sDs!peLSt0Wez2k^q!XKC$P>-TRaO_39g*-dK^I||Es?u(Ck7N zPbP?J4aTj0jSM+Vl?mDd6ePzoEjF8{ld;Sdg2j$QM?m=LH_i;;LS}qZ=Su&a65Sn- zP19{{YvQ#%{7EIoE{5IL^8cMi*PM)eaZ7oQ)EN?-lbK~z6NtGN3d>mfb)|dukf&2r zlAo{TIDVenyFA^=6V&OCmcNsI^*k!gd-A^CH1(&{vk9hWos_5kr8*!+DYon-{IpX} z*w{s1v>+~Pnj2HHDiraqlQ+hCOm#g-=NzvOj6t}K8qy~W6Fk4eF zOY+a3e*x>X&1tSe6`!Y^2|KWl;RklchG1J1kPtH8zOdk!?9H&KZNW{4=+Io;wd&Z7 z_pD*e$gldE_j-YV)mGZ0E!-fE5Z*3zhY-HL8ohY}8{eP6Z=fGKpRmv~NvPi{xsv*#PsV&GNbT8pS@?rjD^otgvlP~2lIuMG(<-IOcP<APe6@3M;jzcq!Mrj=3#q2a%=nlJnE zhn*nh1q@p1T=Zw=rJY+WA%E>JH=jSlBOi%J0(!^oDyxwKotvO*V7^swGef5bwu#&T{HX=|8aZm2F}+Oud-x@`;d+&OtLC3DZl+`GnQ zV+=?H&9f7;M~`d;@{sEVx~YDf6Ii!A`@7lM_Tz@(E$F8<_$GZ~zicq%dmx{Ub`~UxTJ4>&yMDOeJT1obgMc(- zsJLZXJFa8fSwpm@>khy)c~TJjkyd5~=@@kYFAoQwfeoV@MKD$I_X-J0T}Drg|!$Hjls zgmoMSdgC%O-ONHtVsOnypLG2IJv~p_oS+ivQDP(tlW6AA-|=&kQfB9 z7nM~nt>0U$EU{)+!qZljJDiXgtW1-6YK_qoiUY%$v5i)UlDEQu)$-zK-E_ATkpCd{ z!UGv;a3p5ePDw%}e!9f850$%eMN$iMKE~C+|!~gJs{HOoGlF z!@vpGrBkvhm{Zv&>fvq!CPYsQ7oO<^NP1<>y6(X8?T-1zH&!g7i8^N;TbRcbGTD@!4>o6 z1O8F-b0ofQ6)PIa;yHo=H;cOccd(4b4Xug-@-c9B>2HB)En%|%g%yhm>~>I~b69zc z4y>9BehOdtw9tUcGrV$eCG;>=wy0Qz7?kc&FJXMaA9>oWGdLX@F*(k|CWE>;(@XY6 zG{Nbg{mpsVakXKcnWv_FuqH|Fx&f#qEQ< zz+;ZcfcBOTV0G4l=Wo+DBhv~(b7C#{?|Xz-K&2tlC)*<@nKcGiiT|kA=Glfym+{mA zWX0jhR@mdIYidIQPO!5oxL~De%PhE^OEQBiW$e4hJ5s<^oT?*#u)#HZIWIn@Sy)^_ zr-p9Kj~B+4yNI~O99A>S#4AB~?cfqm-vGji$Q>P(CfHM(&&z>r%uHI77LegZx>cjc z7M3o=f**bJepen2Ut4zr1kr)#*#dG^@PaH>%?hyot2qflxwt2T4l5`+Ur&nQ=on%* zI-W}iu&wZw=1p+^(_sS1OL9+K?(z)p)nb`1F`$hk>VSS(vh?+`+;}n93 zAcV-}7~$Hp%nT%1{5lyZz3XH1Exf$)XxY^^HQPUJ8rx2|0ep(5sknG>Y1W1XO(g8? zfNGjdOw6{5cPTGfY7lpG%!7|Ly*K;nVB};F03)YxUzIgWM6F$I~=(G7|TrYr%PE=+6bfRA3 zP8b9PNG%h(U_F^xg4$$ks9!xJQ()T#DrHtHxsdtfCDN3p_hp4?gj?y@!JAv&?RqFe zdWvSa@@{fGqWDole6bm;6`tlV1ez1M?DKBzBrC6^=>!;ozJHJ$;KbQqD>OT0i9FTQ~1PBd#N#TI+C;u;nO1u)_iRwpQhp zKtYtPBzRwda$#&LX%>G7$YG(v$D@`mb>ZS3m;=~u%Oi~73%}&SNdMm1bWL5lo2OYWPq16(!cfL829jTl!E!55gDUyz|LZH)fFY})`cLEisC zv)L}Wc-s@Vv39%4aD}~5`<=qA=LvPw?RnI0Gvx-5Ig}Xhyv2FsAXAwVOcp=z{6EG1 zx7_$Y{ileHaHT`uVEp6%;7$GBOlkvEaEIc4JuScO^6~FPW2GM7O2sXIjrsXA&v6rG z|8sEVe^b@{odEw=B?b~9|L^Q7?9lXZC*MES|~3>7$P{Buri&i zDrPIHu@?_)i97?E$({!fp^SXHf$`S9jeQNPMTcU)Y*z}vMVLo@P9av7mo0vIS^LN4 z-#U?pGsS3LD#|eAFm1|T?l#aUh@JOk_KL@5sI~vTp{#4TTr5mXF_E(6+R}&uJtKga z<|0>_i?^%i>f-WX?=!;6U8H$#e+5t`r9*|TQ~cA2MXNp87iZJ6I{YoMUAjptUkQ6} z2#$G_?zE1kEP)gcVBah3C+&iaKOAVEKf{c>gSIGld+L1bx*n(d9dp`66!p#p@kmQvU}G_-VHd4C#ZT_p+@Rv%Y2K8pMd@J9mGRILmW*or zaCfQ#Z{$9sN0XhM3^xgTQMoqP=90KMepRDrY9lqk#%%X+zfH2y5o+sa2y@ua-J_=| zO%x|D8P#=!IH(>Qu}`w7<;w_tfmOB=TbOLXDgGvBQ>8BGjSo2UPdbMavWT}RY%^({ zfS_CiG08W!Q~X(@pd28A66V7l^`DPZlP+QN0$5FUy6`FWrG z*^qMD*Ld>*x3Ix9U(Q;rTYloh-}`?Cd%h`WR3%zTe@Kre zQm?1lVt=&qoZW}cR%{zHMrdAwEzZy%5hNkcONM(Db;1!Hr8pQNg6 zWL=2@A}8qjM~~g+)zM408bAC&E=8~Mm*Cw=eMIiEVGcCnOBgfR$EI9vkAB7gergE* z<<4?xz_hCM#*@J*=Dwn}h@d)oA8Z{G2Ey%$M3hBhfcICyNHifR{~B`u--^1v@p;vS zh*57lg7D4l%#$4klEMq=uD34>{8~$PLVt|;rC$(*A0#EGUc9&=->X{cazb#!I4Lnh z`xeNphU>ob!%A3+Ww6yeQp(iYH#w%HjRjXISbA?T6+xkNk4dftCZElPy*cH@6?(vT zZzbx?woMP;)B7N}pD!n+N<>!pbZX0dqk73)9*(R|-?qLEe@7fFE1!^Bxud({ix%j39Kidk53dai0wiB;cA9t$K(b}g1cT_8v1bq zk{)vRxd4Jb?gQ+;!;AVU;kS?TQ?^rgh{d|uO9zfL3A6-TKHU(1B^i9D_hl@(=gMVjiy&#J&vcwwCmuh-C!G(Sgs*R34J(ZfCuyB-U-JL^pZ^SJ> z9IW;lnId$rka8pPmW9v`yNR_21pBz96PBs>dNJRKJAA3@<7a`A8o-ZqTb%{~I+m|3 z)A07!wIUJM@bZOOLKNjXe2nEYX08%e@4U3_)WGHz_~}_^SPqyHaUPNhMN>Aa;Lif* z%w5(OyQ^y0fqH=c?8XyV1TkvfD1Yfi9T=((JOg?v2~GO8jUbg@7CjlA=?bQadFd>{ z+K}}2%pi~^O3NsZ+^K7E0_hjFt2q6l^q{J~nD4o5yfZ&@0;5W?Pp!gQSRu~?e3>P9 z3lIF)uSAUQjv1lCGK$y9Jdph4Xw+}RjI?1pgPW|jD-I=~6?F(jp*4LkTS~fDl3pA%qeLxhG8d zwg1QaIc`TcYe+}cH1LE7G%ZEqbL)~D{Ub-qN&_T9w(uxDvwB&i+rvkR%e(s7 zP7B8MiS#x(wqkWb(yXLTz9Mn;K?rYZH{)J{M*wtkYx?4Aj~-TcY8hgU8ZO^_V$yep zfY0eGv*IP}e9JD}DThuZ=%RgGc;34R7G~de^;1ilC#b34sFNr2{_~5eKkQvtxh;8SxjNV`9dl3$JXf#&G&uQQ- zA7Sl$MI$mtZg+DUgWxabs}wEm*PAgFywyQ7Z=F70Rv?8}wLXofkKmh)74P>;!%HzC z(SW$wT+i_`kT7pPpDM?ZO5Y@XU|eExMfPd`(eC&37uLNcN*G@u6cr(o=8vPS^vqLr z8`s}ewU2=U0Xw5KFu^-5WHWo7H_sprG;NRPL*^oL4PX6ibS-ODDd3hzx=3s{`BwK# z%C=CFSNUQ&Xc##;D^Iw0((Z~f+8-8}uKi(4VKqRV+4nO^prdVch+Qb%bD?4_@rXxR z*l1b))v++Uha{NDk$2=^qWpmEMb3rn4 z<56hvJTQFb$K$kyfB*a ziqHI{*E~;mDpo}lDxxV>a$+VGC~0Rx>Q#_pp&NiBS6XtPH7jQ%yT}R_tfm8D*e6-EL*@IZ_n*q(6j8i0mI)}y1c*QXWkq(0CyaCy369Ou&zAB?Lj^+ zN2Jw57eGP~7LlDd;IJ)3F!(=ZhZ$ICte>r}wlDzLM*r#2{5Dk`<7h`Jp1xRfas=jp1gy zfj9f4v0T@iEgp+-O&(nJ@2uwFC!OZ}Wg_7_LP))y@I~2!kk(Q@!(LSo7(m8n4fX>E~y!wRh{&mFDUs_w(?)?7jnTNE&-bLUbjwAQ~vo9 zn_sW2?If)BRNUKykNq^<^eL07bC2o+T(+p>^cn2knYYi6oA36Iy?+CIdW=H)dB7t+ zdM(Q2vH?Nq)8B3|{&~*6D}-0gL>(LV@x}zDT?&_~{>7-;=f}?}*5x$(kOr^?jzb}2mQt10FIi*QC+q0U2e zy;;?G;PiAuCvL!)n~>gW4`0dOgH{t2)UvsC{)|GmA-uLqavWSGBjI(VNkQ6E59Z2hzlt z+g082eJwJ37@Un7$(u`!yA?c}GWaw({iki=h4O z+NbN7oFG`O^-le3@D{hIvkOpWApfv~7&_@;ge;o5jm{dh+VzW*84cL-8B|k8n1yC& zQGL=ZU?9?b{AoYu;LfL2qDO)F<*?T&7jn37#euZ-e< zvb%wl*amJZW*Svj2h-78X=n|PpwUs`I)QTE)|0=?))6x;VzBQ6sBY!$rxSc8M*{d;xq8Z>t9nH45AhF1(pRGjRptz z>GTl+B6Vu^Wt6KhhemEwOrW=53iFyrU|$-Jxyi${{pE9>Ty+5!-@IP!J!Bmj12yTa z^Cx8BCI(vE^6v2@57aSH%=HNzoJnRXGAKMu&p?tEGlBiqeOC6lp?2+aYP@_i))$`kP8>Dhz~EfAmN8 zWnz>hGCk6F9t3Vi5nZFDpLYJIi+Rdb0^eJZ6`oM+tVa63(cIl&?oK5BENovPK1X(3 zb4}$CdATl7x0-}%%tgl33&7VM;Zoxp%8Y!bVaUCkU5)xX1joJ%YOlQbg@7M%1Lu%0 za~p=%u0ISpGztk?GU_y7giw|20<#-tyfJ?O3kq{ah-R6%%Hgv!{c$ijs!vR$A5B(X zE)*NzC-9$^G7dl>{b|@b@`CUk10-8t25YJFptrUC?96mEE(IvRycajW0o3~xIksa1 z3)gtruB+;6=xc9b;5(RkgT- zLyA`t%YyA`7fi}ay`@iGzNxtT#VMh-e&M0JctEEXj%Rn0r&PkhJ{zB@lwI6#Nvli` zJbH(=QGJFNA>R3OjGpdhsB~gh$a47nGD=2@{gd6^ZE;YTkXd)LX=TmbN{s<5z4zd$ z3hzEEJ^r(A#alNpiaw|z?d>PQDh}q}j(L3kBp=Pgms=A+_*UNyOi}_UirAd|Op@r| zvkEkA>pMuXJ}(nSz>?GXeZ>ka$P%cY=~Ek$&7Kbk>8=q`#ayh2Z&b* z?K%m}-Y&jMK~NF{P__rbO&ls;KHAmirY-x&KVIMi-){RB=N?0oR`=MQyG9br8s{xx z{_fsaEID{}o!4#%z$^m4ueUr`%j3#yEZi0)mwE2{xVi^MKdfH@dU;SJ3VTgLC}ox) zijqcF=j4xRm6?GlO43GoZ@0H2j2KFO{S0?ymtQ5$9*Bo6xYrobKZ%!Vf~Jt33Kkbb zA?6!a7N@s4Z+t!b!8>T}t8syRKfFJ853Abq%=vn~~ZQ-5{p4U{OE|qtdlC3>2Cv406!O1jl71Mwd+*{&%B>ovuKzsbxMmE+*a?fv+i2a zCwi>R1@gz%Yq#F?n@Ks)YoQW~oBfp}miCubm-n7R zM&h`*L`ymnW?hogO_jBACD8AZoN`$G2HX*nlo=?jYd=XCaR^NH%^&U4Q_i>;IQQ<< zmAoqiV=JG5nrGUpIvzh=;c!VZhxr)}2AB7$N3_#7J+%`h^GChYlSi$cKR7K}*6ZNw zJukH5wGrm{+8%~?lji0~d-F(o-zuK$Mt4lK;w;WPh z`F%b%f79~u?g~5eBmHpZ_}ap>ziVlo1OI-_5J#5?ifyu;e>M->79Ed&0_F37+5;`GnYfc2 z507h{F+0eXW5JjkxBG~`dKF73^%S=Fsm`)xyHec?O7@V$$LMQPO{)r^*9e*% z*DygF^sTxo3oK)7*=sc;s=Vx5`TRWv!GU(y1E3c?K%U!p^kqOgTtxh2w-pKBjMV%Y zbX&SCl4a?JCh2$-UR2(hWaol5g#C7-4|tUd=!Yf`?^zw2dyL35Egjj;FNsChTqP)Z z&LX|B)7YH5rOiT&6(l0h1N!7)AaIr~k|#K1`QHs3U7pZz_1K98xOA1fa3$ zx6x90nL`drsIL1{`5E3+E1Ql5RMEyrs(~n^(f^l%9AAx(mNL$6ZC>Ow8s|L2!=L<$~I#c{z8Rw5=zPJ~|uX|GN1P#>T7^3moi|-|Ew(abBzbBBPGqq?)agkg# zThA$e6Q%px<-bL@$!a&�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

TW)XF6AnNsW@225?vf-Xx-xNip4|8&Pje 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

b?`qI!>qzw8Vmye(mAlxMrC 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.1.2,<1.2.0 diff --git a/tutorials/regression-part2-automated-ml.yml b/tutorials/regression-part2-automated-ml.yml new file mode 100644 index 00000000..f6e23d59 --- /dev/null +++ b/tutorials/regression-part2-automated-ml.yml @@ -0,0 +1,10 @@ +name: regression-part2-automated-ml +dependencies: +- pip: + - azureml-sdk + - azureml-train-automl + - azureml-widgets + - azureml-explain-model + - matplotlib + - pandas_ml + - seaborn diff --git a/work-with-data/README.md b/work-with-data/README.md new file mode 100644 index 00000000..cfc265b2 --- /dev/null +++ b/work-with-data/README.md @@ -0,0 +1,9 @@ +# Work With Data Using Azure Machine Learning Service + +Azure Machine Learning Datasets (preview) make it easier to access and work with your data. Datasets manage data in various scenarios such as model training and pipeline creation. Using the Azure Machine Learning SDK, you can access underlying storage, explore and prepare data, manage the life cycle of different Dataset definitions, and compare between Datasets used in training and in production. + +- For an example of using Datasets, see the [sample](datasets). +- For advanced data preparation examples, see [dataprep](dataprep). + + +![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/README..png) \ No newline at end of file diff --git a/work-with-data/dataprep/README.md b/work-with-data/dataprep/README.md new file mode 100644 index 00000000..4346cd20 --- /dev/null +++ b/work-with-data/dataprep/README.md @@ -0,0 +1,255 @@ +# Azure Machine Learning Data Prep SDK + +The Azure Machine Learning Data Prep SDK helps data scientists explore, cleanse and transform data for machine learning workflows in any Python environment. + +Key benefits to the SDK: +- Cross-platform functionality. Write with a single SDK and run it on Windows, macOS, or Linux. +- Intelligent transformations powered by AI, including grouping similar values to their canonical form and deriving columns by examples without custom code. +- Capability to work with large, multiple files of different schema. +- Scalability on a single machine by streaming data during processing rather than loading into memory. +- Seamless integration with other Azure Machine Learning services. You can simply pass your prepared data file into `AutoMLConfig` object for automated machine learning training. + +You will find in this repo: +- [Getting Started Tutorial](tutorials/getting-started/getting-started.ipynb) for a quick introduction to the main features of Data Prep SDK. +- [Case Study Notebooks](case-studies/new-york-taxi) that present an end-to-end data preparation tutorial where users start with small dataset, profile data with statistics summary, cleanse and perform feature engineering. All transformation steps are saved in a dataflow object. Users can easily reapply the same steps on the full dataset, and run it on Spark. +- [How-To Guide Notebooks](how-to-guides) for more in-depth sample code at feature level. + +## Installation +Here are the [SDK installation steps](https://aka.ms/aml-data-prep-installation). + +## Documentation +Here is more information on how to use the new Data Prep SDK: +- [SDK overview and API reference docs](http://aka.ms/data-prep-sdk) that show different classes, methods, and function parameters for the SDK. +- [Tutorial: Prep NYC taxi data](https://docs.microsoft.com/azure/machine-learning/service/tutorial-data-prep) for regression modeling and then run automated machine learning to build the model. +- [How to load data](https://docs.microsoft.com/azure/machine-learning/service/how-to-load-data) is an overview guide on how to load data using the Data Prep SDK. +- [How to transform data](https://docs.microsoft.com/azure/machine-learning/service/how-to-transform-data) is an overview guide on how to transform data. +- [How to write data](https://docs.microsoft.com/azure/machine-learning/service/how-to-write-data) is an overview guide on how to write data to different storage locations. + +## Support + +If you have any questions or feedback, send us an email at: [askamldataprep@microsoft.com](mailto:askamldataprep@microsoft.com). + +## Release Notes + +### 2019-05-28 (version 1.1.4) + +New features +- You can now use the following expression language functions to extract and parse datetime values into new columns. + - `RegEx.extract_record()` extracts datetime elements into a new column. + - `create_datetime()` creates datetime objects from separate datetime elements. +- When calling `get_profile()`, you can now see that quantile columns are labeled as (est.) to clearly indicate that the values are approximations. +- You can now use ** globbing when reading from Azure Blob Storage. + - e.g. `dprep.read_csv(path='https://yourblob.blob.core.windows.net/yourcontainer/**/data/*.csv')` + +Bug fixes +- Fixed a bug related to reading a Parquet file from a remote source (Azure Blob). + +### 2019-05-08 (version 1.1.3) + +New features +- Added support to read from a PostgresSQL database, either by calling `read_postgresql` or using a Datastore. + - See examples in how-to guides: + - [Data Ingestion notebook](https://aka.ms/aml-data-prep-ingestion-nb) + - [Datastore notebook](https://aka.ms/aml-data-prep-datastore-nb) + +Bug fixes and improvements +- Fixed issues with column type conversion: + - Now correctly converts a boolean or numeric column to a boolean column. + - Now does not fail when attempting to set a date column to be date type. +- Improved JoinType types and accompanying reference documentation. When joining two dataflows, you can now specify one of these types of join: + - NONE, MATCH, INNER, UNMATCHLEFT, LEFTANTI, LEFTOUTER, UNMATCHRIGHT, RIGHTANTI, RIGHTOUTER, FULLANTI, FULL. +- Improved data type inference to recognize more date formats. + +### 2019-04-17 (version 1.1.2) + +Note: Data Prep Python SDK will no longer install `numpy` and `pandas` packages. See [updated installation instructions](https://aka.ms/aml-data-prep-installation). + +New features +- You can now use the Pivot transform. + - How-to guide: [Pivot notebook](https://aka.ms/aml-data-prep-pivot-nb) +- You can now use regular expressions in native functions. + - Examples: + - `dflow.filter(dprep.RegEx('pattern').is_match(dflow['column_name']))` + - `dflow.assert_value('column_name', dprep.RegEx('pattern').is_match(dprep.value))` +- You can now use `to_upper` and `to_lower` functions in expression language. +- You can now see the number of unique values of each column in a data profile. +- For some of the commonly used reader steps, you can now pass in the `infer_column_types` argument. If it is set to `True`, Data Prep will attempt to detect and automatically convert column types. + - `inference_arguments` is now deprecated. +- You can now call `Dataflow.shape`. + +Bug fixes and improvements +- `keep_columns` now accepts an additional optional argument `validate_column_exists`, which checks if the result of `keep_columns` will contain any columns. +- All reader steps (which read from a file) now accept an additional optional argument `verify_exists`. +- Improved performance of reading from pandas dataframe and getting data profiles. +- Fixed a bug where slicing a single step from a Dataflow failed with a single index. + +### 2019-04-08 (version 1.1.1) + +New features +- You can read multiple Datastore/DataPath/DataReference sources using read_* transforms. +- You can perform the following operations on columns to create a new column: division, floor, modulo, power, length. +- Data Prep is now part of the Azure ML diagnostics suite and will log diagnostic information by default. + - To turn this off, set this environment variable to true: DISABLE_DPREP_LOGGER + +Bug fixes and improvements +- Improved code documentation for commonly used classes and functions. +- Fixed a bug in auto_read_file that failed to read Excel files. +- Added option to overwrite the folder in read_pandas_dataframe. +- Improved performance of dotnetcore2 dependency installation, and added support for Fedora 27/28 and Ubuntu 1804. +- Improved the performance of reading from Azure Blobs. +- Column type detection now supports columns of type Long. +- Fixed a bug where some date values were being displayed as timestamps instead of Python datetime objects. +- Fixed a bug where some type counts were being displayed as doubles instead of integers. + +### 2019-03-25 (version 1.1.0) + +Breaking changes +- The concept of the Data Prep Package has been deprecated and is no longer supported. Instead of persisting multiple Dataflows in one Package, you can persist Dataflows individually. + - How-to guide: [Opening and Saving Dataflows notebook](https://aka.ms/aml-data-prep-open-save-dataflows-nb) + +New features +- Data Prep can now recognize columns that match a particular Semantic Type, and split accordingly. The STypes currently supported include: email address, geographic coordinates (latitude & longitude), IPv4 and IPv6 addresses, US phone number, and US zip code. + - How-to guide: [Semantic Types notebook](https://aka.ms/aml-data-prep-semantic-types-nb) +- Data Prep now supports the following operations to generate a resultant column from two numeric columns: subtract, multiply, divide, and modulo. +- You can call `verify_has_data()` on a Dataflow to check whether the Dataflow would produce records if executed. + +Bug fixes and improvements +- You can now specify the number of bins to use in a histogram for numeric column profiles. +- The `read_pandas_dataframe` transform now requires the DataFrame to have string- or byte- typed column names. +- Fixed a bug in the `fill_nulls` transform, where values were not correctly filled in if the column was missing. + +### 2019-03-11 (version 1.0.17) + +New features +- Now supports adding two numeric columns to generate a resultant column using the expression language. + +Bug fixes and improvements +- Improved the documentation and parameter checking for random_split. + +### 2019-02-27 (version 1.0.16) + +Bug fix +- Fixed a Service Principal authentication issue that was caused by an API change. + +### 2019-02-25 (version 1.0.15) + +New features +- Data Prep now supports writing file streams from a dataflow. Also provides the ability to manipulate the file stream names to create new file names. + - How-to guide: [Working With File Streams notebook](https://aka.ms/aml-data-prep-file-stream-nb) + +Bug fixes and improvements +- Improved performance of t-Digest on large data sets. +- Data Prep now supports reading data from a DataPath. +- One hot encoding now works on boolean and numeric columns. +- Other miscellaneous bug fixes. + +### 2019-02-11 (version 1.0.12) + +New features +- Data Prep now supports reading from an Azure SQL database using Datastore. + +Changes +- Significantly improved the memory performance of certain operations on large data. +- `read_pandas_dataframe()` now requires `temp_folder` to be specified. +- The `name` property on `ColumnProfile` has been deprecated - use `column_name` instead. + +### 2019-01-28 (version 1.0.8) + +Bug fixes +- Significantly improved the performance of getting data profiles. +- Fixed minor bugs related to error reporting. + +### 2019-01-14 (version 1.0.7) + +New features +- Datastore improvements (documented in [Datastore how-to-guide](https://aka.ms/aml-data-prep-datastore-nb)) + - Added ability to read from and write to Azure File Share and ADLS Datastores in scale-up. + - When using Datastores, Data Prep now supports using service principal authentication instead of interactive authentication. + - Added support for wasb and wasbs urls. + +### 2019-01-09 (version 1.0.6) + +Bug fixes +- Fixed bug with reading from public readable Azure Blob containers on Spark. + +### 2018-12-19 (version 1.0.4) + +New features +- `to_bool` function now allows mismatched values to be converted to Error values. This is the new default mismatch behavior for `to_bool` and `set_column_types`, whereas the previous default behavior was to convert mismatched values to False. +- When calling `to_pandas_dataframe`, there is a new option to interpret null/missing values in numeric columns as NaN. +- Added ability to check the return type of some expressions to ensure type consistency and fail early. +- You can now call `parse_json` to parse values in a column as JSON objects and expand them into multiple columns. + +Bug fixes +- Fixed a bug that crashed `set_column_types` in Python 3.5.2. +- Fixed a bug that crashed when connecting to Datastore using an AML image. + +### 2018-12-07 (version 0.5.3) + +Fixed missing dependency issue for .NET Core2 on Ubuntu 16. + +### 2018-12-03 (version 0.5.2) + +Breaking changes +- `SummaryFunction.N` was renamed to `SummaryFunction.Count`. + +Bug fixes +- Use latest AML Run Token when reading from and writing to datastores on remote runs. Previously, if the AML Run Token is updated in Python, the Data Prep runtime will not be updated with the updated AML Run Token. +- Additional clearer error messages +- to_spark_dataframe() will no longer crash when Spark uses Kryo serialization +- Value Count Inspector can now show more than 1000 unique values +- Random Split no longer fails if the original Dataflow doesn’t have a name + +### 2018-11-19 (version 0.5.0) + +New features +- Created a new DataPrep CLI to execute DataPrep packages and view the data profile for a dataset or dataflow +- Redesigned SetColumnType API to improve usability +- Renamed smart_read_file to auto_read_file +- Now includes skew and kurtosis in the Data Profile +- Can sample with stratified sampling +- Can read from zip files that contain CSV files +- Can split datasets row-wise with Random Split (e.g. into test-train sets) +- Can get all the column data types from a dataflow or a data profile by calling .dtypes +- Can get the row count from a dataflow or a data profile by calling .row_count + +Bug fixes +- Fixed long to double conversion +- Fixed assert after any add column +- Fixed an issue with FuzzyGrouping, where it would not detect groups in some cases +- Fixed sort function to respect multi-column sort order +- Fixed and/or expressions to be similar to how Pandas handles them +- Fixed reading from dbfs path. +- Made error messages more understandable +- Now no longer fails when reading on remote compute target using AML token +- Now no longer fails on Linux DSVM +- Now no longer crashes when non-string values are in string predicates +- Now handles assertion errors when Dataflow should fail correctly +- Now supports dbutils mounted storage locations on Azure Databricks + +### 2018-11-05 (version 0.4.0) + +New features +- Type Count added to Data Profile +- Value Count and Histogram is now available +- More percentiles in Data Profile +- The Median is available in Summarize +- Python 3.7 is now supported +- When you save a dataflow that contains datastores to a Data Prep package, the datastore information will be persisted as part of the Data Prep package +- Writing to datastore is now supported + +Bug fixes +- 64bit unsigned integer overflows are now handled properly on Linux +- Fixed incorrect text label for plain text files in smart_read +- String column type now shows up in metrics view +- Type count now is fixed to show ValueKinds mapped to single FieldType instead of individual ones +- Write_to_csv no longer fails when path is provided as a string +- When using Replace, leaving “find” blank will no longer fail + +## Datasets License Information + +IMPORTANT: Please read the notice and find out more about this NYC Taxi and Limousine Commission dataset here: http://www.nyc.gov/html/tlc/html/about/trip_record_data.shtml + +IMPORTANT: Please read the notice and find out more about this Chicago Police Department dataset here: https://catalog.data.gov/dataset/crimes-2001-to-present-398a4 + +![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/README.png) diff --git a/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi.ipynb b/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi.ipynb new file mode 100644 index 00000000..cdfb6d70 --- /dev/null +++ b/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi.ipynb @@ -0,0 +1,514 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Cleaning up New York Taxi Cab data\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's use DataPrep to clean and featurize the data which can then be used to predict taxi trip duration. We will not use the For Hire Vehicle (FHV) datasets as they are not really taxi rides and they don't provide drop-off time and geo-coordinates." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import display\n", + "from os import path\n", + "from tempfile import mkdtemp\n", + "\n", + "import pandas as pd\n", + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's take a quick peek at yellow cab data and green cab data to see what the data looks like. DataPrep supports globing, so you will notice below that we have added a `*` in the path.\n", + "\n", + "*We are using a small sample of the taxi data for this demo. You can find a bigger sample ~6GB by changing \"green-small\" to \"green-sample\" and \"yellow-small\" to \"yellow-sample\" in the paths below.*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pd.set_option('display.max_columns', None)\n", + "\n", + "cache_location = mkdtemp()\n", + "dataset_root = \"https://dprepdata.blob.core.windows.net/demo\"\n", + "\n", + "green_path = \"/\".join([dataset_root, \"green-small/*\"])\n", + "yellow_path = \"/\".join([dataset_root, \"yellow-small/*\"])\n", + "\n", + "print(\"Retrieving data from the following two sources:\")\n", + "print(green_path)\n", + "print(yellow_path)\n", + "\n", + "green_df = dprep.read_csv(path=green_path, header=dprep.PromoteHeadersMode.GROUPED)\n", + "yellow_df = dprep.auto_read_file(path=yellow_path)\n", + "\n", + "display(green_df.head(5))\n", + "display(yellow_df.head(5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data Cleanup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's define some shortcut transforms that will apply to all Dataflows." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "all_columns = dprep.ColumnSelector(term=\".*\", use_regex=True)\n", + "drop_if_all_null = [all_columns, dprep.ColumnRelationship(dprep.ColumnRelationship.ALL)]\n", + "useful_columns = [\n", + " \"cost\", \"distance\"\"distance\", \"dropoff_datetime\", \"dropoff_latitude\", \"dropoff_longitude\",\n", + " \"passengers\", \"pickup_datetime\", \"pickup_latitude\", \"pickup_longitude\", \"store_forward\", \"vendor\"\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's first work with the green taxi data and get it into a good shape that then can be combined with the yellow taxi data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tmp_df = (green_df\n", + " .replace_na(columns=all_columns)\n", + " .drop_nulls(*drop_if_all_null)\n", + " .rename_columns(column_pairs={\n", + " \"VendorID\": \"vendor\",\n", + " \"lpep_pickup_datetime\": \"pickup_datetime\",\n", + " \"Lpep_dropoff_datetime\": \"dropoff_datetime\",\n", + " \"lpep_dropoff_datetime\": \"dropoff_datetime\",\n", + " \"Store_and_fwd_flag\": \"store_forward\",\n", + " \"store_and_fwd_flag\": \"store_forward\",\n", + " \"Pickup_longitude\": \"pickup_longitude\",\n", + " \"Pickup_latitude\": \"pickup_latitude\",\n", + " \"Dropoff_longitude\": \"dropoff_longitude\",\n", + " \"Dropoff_latitude\": \"dropoff_latitude\",\n", + " \"Passenger_count\": \"passengers\",\n", + " \"Fare_amount\": \"cost\",\n", + " \"Trip_distance\": \"distance\"\n", + " })\n", + " .keep_columns(columns=useful_columns))\n", + "tmp_df.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "green_df = tmp_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's do the same thing to yellow taxi data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tmp_df = (yellow_df\n", + " .replace_na(columns=all_columns)\n", + " .drop_nulls(*drop_if_all_null)\n", + " .rename_columns(column_pairs={\n", + " \"vendor_name\": \"vendor\",\n", + " \"VendorID\": \"vendor\",\n", + " \"vendor_id\": \"vendor\",\n", + " \"Trip_Pickup_DateTime\": \"pickup_datetime\",\n", + " \"tpep_pickup_datetime\": \"pickup_datetime\",\n", + " \"Trip_Dropoff_DateTime\": \"dropoff_datetime\",\n", + " \"tpep_dropoff_datetime\": \"dropoff_datetime\",\n", + " \"store_and_forward\": \"store_forward\",\n", + " \"store_and_fwd_flag\": \"store_forward\",\n", + " \"Start_Lon\": \"pickup_longitude\",\n", + " \"Start_Lat\": \"pickup_latitude\",\n", + " \"End_Lon\": \"dropoff_longitude\",\n", + " \"End_Lat\": \"dropoff_latitude\",\n", + " \"Passenger_Count\": \"passengers\",\n", + " \"passenger_count\": \"passengers\",\n", + " \"Fare_Amt\": \"cost\",\n", + " \"fare_amount\": \"cost\",\n", + " \"Trip_Distance\": \"distance\",\n", + " \"trip_distance\": \"distance\"\n", + " })\n", + " .keep_columns(columns=useful_columns))\n", + "tmp_df.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "yellow_df = tmp_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now append the rows from the `yellow_df` to `green_df`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df = green_df.append_rows(dataflows=[yellow_df])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's take a look at the pickup and drop-off coordinates' data profile to see how the data is distributed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "decimal_type = dprep.TypeConverter(data_type=dprep.FieldType.DECIMAL)\n", + "combined_df = combined_df.set_column_types(type_conversions={\n", + " \"pickup_longitude\": decimal_type,\n", + " \"pickup_latitude\": decimal_type,\n", + " \"dropoff_longitude\": decimal_type,\n", + " \"dropoff_latitude\": decimal_type\n", + "})\n", + "combined_df.keep_columns(columns=[\n", + " \"pickup_longitude\", \"pickup_latitude\", \n", + " \"dropoff_longitude\", \"dropoff_latitude\"\n", + "]).get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the data profile, we can see that there are coordinates that are missing and coordinates that are not in New York. Let's filter out coordinates not in the [city border](https://mapmakerapp.com?map=5b60a055a191245990310739f658)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tmp_df = (combined_df\n", + " .drop_nulls(\n", + " columns=[\"pickup_longitude\", \"pickup_latitude\", \"dropoff_longitude\", \"dropoff_latitude\"],\n", + " column_relationship=dprep.ColumnRelationship(dprep.ColumnRelationship.ANY)\n", + " ) \n", + " .filter(dprep.f_and(\n", + " dprep.col(\"pickup_longitude\") <= -73.72,\n", + " dprep.col(\"pickup_longitude\") >= -74.09,\n", + " dprep.col(\"pickup_latitude\") <= 40.88,\n", + " dprep.col(\"pickup_latitude\") >= 40.53,\n", + " dprep.col(\"dropoff_longitude\") <= -73.72,\n", + " dprep.col(\"dropoff_longitude\") >= -74.09,\n", + " dprep.col(\"dropoff_latitude\") <= 40.88,\n", + " dprep.col(\"dropoff_latitude\") >= 40.53\n", + " )))\n", + "tmp_df.keep_columns(columns=[\n", + " \"pickup_longitude\", \"pickup_latitude\", \n", + " \"dropoff_longitude\", \"dropoff_latitude\"\n", + "]).get_profile()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df = tmp_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's take a look at the data profile for the `store_forward` column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df.keep_columns(columns='store_forward').get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the data profile of `store_forward` above, we can see that the data is inconsistent and there are missing values. Let's fix them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df = combined_df.replace(columns=\"store_forward\", find=\"0\", replace_with=\"N\").fill_nulls(\"store_forward\", \"N\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now split the pick up and drop off datetimes into a date column and a time column. We will use `split_column_by_example` to perform the split. If the `example` parameter of `split_column_by_example` is omitted, we will automatically try to figure out where to split based on the data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tmp_df = (combined_df\n", + " .split_column_by_example(source_column=\"pickup_datetime\")\n", + " .split_column_by_example(source_column=\"dropoff_datetime\"))\n", + "tmp_df.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df = tmp_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's rename the columns generated by `split_column_by_example` into meaningful names." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tmp_df = (combined_df\n", + " .rename_columns(column_pairs={\n", + " \"pickup_datetime_1\": \"pickup_date\",\n", + " \"pickup_datetime_2\": \"pickup_time\",\n", + " \"dropoff_datetime_1\": \"dropoff_date\",\n", + " \"dropoff_datetime_2\": \"dropoff_time\"\n", + " }))\n", + "tmp_df.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df = tmp_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Feature Engineering" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Datetime features" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's split the pickup and drop-off date further into day of week, day of month, and month. For pickup and drop-off time columns, we will split it into hour, minute, and second." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tmp_df = (combined_df\n", + " .derive_column_by_example(\n", + " source_columns=\"pickup_date\", \n", + " new_column_name=\"pickup_weekday\", \n", + " example_data=[(\"2009-01-04\", \"Sunday\"), (\"2013-08-22\", \"Thursday\")]\n", + " )\n", + " .derive_column_by_example(\n", + " source_columns=\"dropoff_date\",\n", + " new_column_name=\"dropoff_weekday\",\n", + " example_data=[(\"2013-08-22\", \"Thursday\"), (\"2013-11-03\", \"Sunday\")]\n", + " )\n", + " .split_column_by_example(source_column=\"pickup_date\")\n", + " .split_column_by_example(source_column=\"pickup_time\")\n", + " .split_column_by_example(source_column=\"dropoff_date\")\n", + " .split_column_by_example(source_column=\"dropoff_time\")\n", + " .split_column_by_example(source_column=\"pickup_time_1\")\n", + " .split_column_by_example(source_column=\"dropoff_time_1\")\n", + " .drop_columns(columns=[\n", + " \"pickup_date\", \"pickup_time\", \"dropoff_date\", \"dropoff_time\", \n", + " \"pickup_date_1\", \"dropoff_date_1\", \"pickup_time_1\", \"dropoff_time_1\"\n", + " ])\n", + " .rename_columns(column_pairs={\n", + " \"pickup_date_2\": \"pickup_month\",\n", + " \"pickup_date_3\": \"pickup_monthday\",\n", + " \"pickup_time_1_1\": \"pickup_hour\",\n", + " \"pickup_time_1_2\": \"pickup_minute\",\n", + " \"pickup_time_2\": \"pickup_second\",\n", + " \"dropoff_date_2\": \"dropoff_month\",\n", + " \"dropoff_date_3\": \"dropoff_monthday\",\n", + " \"dropoff_time_1_1\": \"dropoff_hour\",\n", + " \"dropoff_time_1_2\": \"dropoff_minute\",\n", + " \"dropoff_time_2\": \"dropoff_second\"\n", + " }))\n", + "tmp_df.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df = tmp_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the data above, we can see that the pickup and drop-off date and time components produced from the transforms above looks good. Let's drop the `pickup_datetime` and `dropoff_datetime` columns as they are no longer needed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tmp_df = combined_df.drop_columns(columns=[\"pickup_datetime\", \"dropoff_datetime\"])\n", + "tmp_df.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "combined_df = tmp_df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now save the transformation steps into a DataPrep package so we can use it to to run on spark." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_path = path.join(mkdtemp(), \"new_york_taxi.dprep\")\n", + "combined_df.save(file_path=dflow_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi.png)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi_scale-out.ipynb b/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi_scale-out.ipynb new file mode 100644 index 00000000..fd69f736 --- /dev/null +++ b/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi_scale-out.ipynb @@ -0,0 +1,135 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Scale-Out Data Preparation\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once we are done with preparing and featurizing the data locally, we can run the same steps on the full dataset in scale-out mode. The new york taxi cab data is about 300GB in total, which is perfect for scale-out. Let's start by downloading the package we saved earlier to disk. Feel free to run the `new_york_taxi_cab.ipynb` notebook to generate the package yourself, in which case you may comment out the download code and set the `package_path` to where the package is saved." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from tempfile import mkdtemp\n", + "from os import path\n", + "from urllib.request import urlretrieve\n", + "\n", + "dflow_root = mkdtemp()\n", + "dflow_path = path.join(dflow_root, \"new_york_taxi.dprep\")\n", + "print(\"Downloading Dataflow to: {}\".format(dflow_path))\n", + "urlretrieve(\"https://dprepdata.blob.core.windows.net/demo/new_york_taxi_v2.dprep\", dflow_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's load the package we just downloaded." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "\n", + "df = dprep.Dataflow.open(dflow_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's replace the datasources with the full dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from uuid import uuid4\n", + "\n", + "other_step = df._get_steps()[7].arguments['dataflows'][0]['anonymousSteps'][0]\n", + "other_step['id'] = str(uuid4())\n", + "other_step['arguments']['path']['target'] = 1\n", + "other_step['arguments']['path']['resourceDetails'][0]['path'] = 'https://wranglewestus.blob.core.windows.net/nyctaxi/yellow_tripdata*'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "green_dsource = dprep.BlobDataSource(\"https://wranglewestus.blob.core.windows.net/nyctaxi/green_tripdata*\")\n", + "df = df.replace_datasource(green_dsource)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once we have replaced the datasource, we can now run the same steps on the full dataset. We will print the first 5 rows of the spark DataFrame. Since we are running on the full dataset, this might take a little while depending on your spark cluster's size." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "spark_df = df.to_spark_dataframe()\n", + "spark_df.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/case-studies/new-york-taxi/new-york-taxi_scale-out.png)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License.", + "skip_execute_as_test": true + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/data/adls-dpreptestfiles.crt b/work-with-data/dataprep/data/adls-dpreptestfiles.crt new file mode 100644 index 00000000..98498f95 --- /dev/null +++ b/work-with-data/dataprep/data/adls-dpreptestfiles.crt @@ -0,0 +1,45 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDmkkyF0BwipZow +Wd1AMkRkySx0y079JPxpsYhv4i1xXKdoa9bpFqwoXmJpeQM1JWnU4UeZzFeM86qK +AhQvL4KV4kibcP2ENvu2NKFEdotO3uxPJ+6GlcYwMYzy+tUj008KnnRZfTrR78sJ +tIl3C6lnVL0ICihksG59P1sskRq3PvOjXLAdEZalwDjZ4ZPoNDZdj6nUjB2l8zqu +pKAt5mR+bJ9Sox4yrDuNhMmFt5QsRDRe3wUqdV+C9OCWHmjlmsjrYw7p9YmjBDvC +5U7mF0Mk/XeYFzj0pkXKQVqBL6xqig+q5ob0szYfg19iDeFhS3iIsRcJGEnRVW/A +NpsBZyKrAgMBAAECggEBANlvP8C1F8NInhZYuIAwpzTQTh86Fxw8g9h8dijkh2wv +LyQXBk07d1B+aZoDZ5X32UzKwcX04N9obfvFqBkzWZdVFJmZvUmwvEEActBoZkkT +io+/HX5HweVy5PPCvbsSK6jc8uXtZcnSs4tMeJIOKkvqqnTpd1w00Y1FcQqfMC16 +4p7o8wbt6OFoFAYqcxeVYVwDzCTLZD3+iJaqmntkBkoDndJy52yXQmMq5z1wbQVp +BL6+L9nTvmouy64jiHVSKOx8nnWThYfHsXoPv+rYywjeuK/v3hyaTAwogs36ooEn +SnuTBRvJcumN9Q0XIVlxKMVBcGyyAP+0yNKGz5NQgdECgYEA/I/Uq1E3epPJgEWR +Bub+LpCgwtrw/lgKncb/Q/AiE9qoXobUe4KNU8aGaNMb7uVNLckY7cOluLS6SQb3 +Mzwk2Jl0G3vk8rW46tZWvSYB8+zAR2Rz7seUOT9SE5OmvwpnHrnp3nRr1vvVd2bp +Q/ypwMLrwWQN51Kr+oTS74bUbrkCgYEA6bXVIUyao7z2Q3qAr6h+6JEWDbkJA7hJ +BjHIOXvxd1tMoJJX+X9+IE/2XoJaUkGCb0vrM/hi1cyQFmS4Or/J6IWSZu8oBpDr +EBmIK3PF1nrzNvWD28wM46c6ScehyWSm/u4bJWSm9liTX3dv5Kpa6ym7yLKc3c0B +ECpSJM+5SoMCgYEAq585Tukzn/IJPUcIk/4nv5C8DW0l0lAVdr2g/JOTNJajTwik +HwHJ86G1+Elsc9wRpAlBDWCjnm4BIFrBZGl8SEuOoJaCL4PZEotwCbxoG09IIbtb +JGkuifBDX9Y3ux3gkPqYt3e5SC99EVQ3MuHgoIJUHehVolmFUAkuJWIjvNECgYEA +5pU0VspRuELzZdgzpxvDOooLDDcHodfslGQBfFXBA1Xc4IACtHMJaa/7D3vkyUtA ++bYZtQjX2sEdWDq/WZdoCjXfIBfNkczhXt0R8G0lQFvGIu9QzUchYGrZo3mHMkBQ +Uy1xMw9/e4YgwQwCJcW+Nk7Sq00uX9enuN9IdHFOCykCgYAqAGMK6CH1tlpjvHrf +k+ZhigYxTXBlsVVvK1BIGGaiwzDpn65zeQp4aLOjSZkI1LuRi3tfTiZ321jRd64J +4lGk5Jurqv5grDmxROX/U50wEYbI9ncu/thU7syUdxDiqxHPI2RMG50mRcm3a55p +ZCNSqkMlcXyA0U1z8C1ILNUsbA== +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICoTCCAYkCAgPoMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNVBAMMCUNMSS1Mb2dp +bjAiGA8yMDE4MDcxMzIzMjA0N1oYDzIwMTkwNzEzMjMyMDQ5WjAUMRIwEAYDVQQD +DAlDTEktTG9naW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDmkkyF +0BwipZowWd1AMkRkySx0y079JPxpsYhv4i1xXKdoa9bpFqwoXmJpeQM1JWnU4UeZ +zFeM86qKAhQvL4KV4kibcP2ENvu2NKFEdotO3uxPJ+6GlcYwMYzy+tUj008KnnRZ +fTrR78sJtIl3C6lnVL0ICihksG59P1sskRq3PvOjXLAdEZalwDjZ4ZPoNDZdj6nU +jB2l8zqupKAt5mR+bJ9Sox4yrDuNhMmFt5QsRDRe3wUqdV+C9OCWHmjlmsjrYw7p +9YmjBDvC5U7mF0Mk/XeYFzj0pkXKQVqBL6xqig+q5ob0szYfg19iDeFhS3iIsRcJ +GEnRVW/ANpsBZyKrAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAI4VlaFb9NsXMLdT +Cw5/pk0Xo2Qi6483RGTy8vzrw88IE7f3juB/JWG+rayjtW5bBRx2fae4/ZIdZ4zg +N2FDKn2PQPAc9m9pcKyUKUvWOC8ixSkrUmeQew0l1AXU0hsPSlJ7/7ZK4efoyB47 +hj71fsyKdyKbisZDcUFBq/S8PazdPF0YOD1W/4A2tW0cSMg+jmFWynuUTdWt3SU8 +CwBGqdiSKT5faJuYwIWnRXDEQS3ObRn1OFEfFdd4d2sxjxydWKRgnINnGlBdiFAT +KzCozVr+75cO2ErH6x5C0hLQGG5BxXbaijyxyvaRNokTMVVv6OaDEnjzCGfJ72Yf +2wgitNc= +-----END CERTIFICATE----- diff --git a/work-with-data/dataprep/data/chicago-aldermen-2015.csv b/work-with-data/dataprep/data/chicago-aldermen-2015.csv new file mode 100644 index 00000000..a0cae0ba --- /dev/null +++ b/work-with-data/dataprep/data/chicago-aldermen-2015.csv @@ -0,0 +1,54 @@ +"Retrieved from https://en.wikipedia.org/wiki/Chicago_City_Council on November 6, 2018" + + +Ward,Name,Took Office,Party +1,Proco Joe Moreno,2010*,Dem +2,Brian Hopkins,2015,Dem +3,Pat Dowell,2007,Dem +4,Sophia King,2016*,Dem +5,Leslie Hairston,1999,Dem +6,Roderick Sawyer,2011,Dem +7,Gregory Mitchell,2015,Dem +8,Michelle A. Harris,2006*,Dem +9,Anthony Beale,1999,Dem +10,Susie Sadlowski Garza,2015,Dem +11,Patrick Daley Thompson,2015,Dem +12,George Cardenas,2003,Dem +13,Marty Quinn,2011,Dem +14,Edward M. Burke,1969,Dem +15,Raymond Lopez,2015,Dem +16,Toni Foulkes,2007,Dem +17,David H. Moore,2015,Dem +18,Derrick Curtis,2015,Dem +19,Matthew O'Shea,2011,Dem +20,Willie Cochran,2007,Dem +21,Howard Brookins Jr.,2003,Dem +22,Ricardo Muñoz,1993*,Dem +23,Silvana Tabares,2018*,Dem +24,"Michael Scott, Jr.",2015,Dem +25,Daniel Solis,1996*,Dem +26,Roberto Maldonado,2009*,Dem +27,"Walter Burnett, Jr.",1995,Dem +28,Jason Ervin,2011*,Dem +29,Chris Taliaferro,2015,Dem +30,Ariel Reboyras,2003,Dem +31,Milly Santiago,2015,Dem +32,Scott Waguespack,2007,Dem +33,Deb Mell,2013*,Dem +34,Carrie Austin,1994*,Dem +35,Carlos Ramirez-Rosa,2015,Dem +36,Gilbert Villegas,2015,Dem +37,Emma Mitts,2000*,Dem +38,Nicholas Sposato,2011,Ind +39,Margaret Laurino,1994*,Dem +40,Patrick J. O'Connor,1983,Dem +41,Anthony Napolitano,2015,Rep +42,Brendan Reilly,2007,Dem +43,Michele Smith,2011,Dem +44,Thomas M. Tunney,2002*,Dem +45,John Arena,2011,Dem +46,James Cappleman,2011,Dem +47,Ameya Pawar,2011,Dem +48,Harry Osterman,2011,Dem +49,Joe Moore,1991,Dem +50,Debra Silverstein,2011,Dem diff --git a/work-with-data/dataprep/data/crime-dirty.csv b/work-with-data/dataprep/data/crime-dirty.csv new file mode 100644 index 00000000..ef7beb0b --- /dev/null +++ b/work-with-data/dataprep/data/crime-dirty.csv @@ -0,0 +1,15 @@ +File updated 11/2/2018 + + + +ID|Case Number|Date|Block|IUCR|Primary Type|Description|Location Description|Arrest|Domestic|Beat|District|Ward|Community Area|FBI Code|X Coordinate|Y Coordinate|Year|Updated On|Latitude|Longitude|Location +10140490|HY329907|07/05/2015 11:50:00 PM|050XX N NEWLAND AVE|0820|THEFT|$500 AND UNDER|STREET|false|false|1613|016|41|10|06|1129230|1933315|2015|07/12/2015 12:42:46 PM|41.973309466|-87.800174996|(41.973309466, -87.800174996) +10139776|HY329265|07/05/2015 11:30:00 PM|011XX W MORSE AVE|0460|BATTERY|SIMPLE|STREET|false|true|2431|024|49|1|08B|1167370|1946271|2015|07/12/2015 12:42:46 PM|42.008124017|-87.65955018|(42.008124017, -87.65955018) +10140270|HY329253|07/05/2015 11:20:00 PM|121XX S FRONT AVE|0486|BATTERY|DOMESTIC BATTERY SIMPLE|STREET|false|true|0532||9|53|08B|||2015|07/12/2015 12:42:46 PM||| +10139885|HY329308|07/05/2015 11:19:00 PM|051XX W DIVISION ST|0610|BURGLARY|FORCIBLE ENTRY|SMALL RETAIL STORE|false|false|1531|015|37|25|05|1141721|1907465|2015|07/12/2015 12:42:46 PM|41.902152027|-87.754883404|(41.902152027, -87.754883404) +10140379|HY329556|07/05/2015 11:00:00 PM|012XX W LAKE ST|0930|MOTOR VEHICLE THEFT|THEFT/RECOVERY: AUTOMOBILE|STREET|false|false|1215|012|27|28|07|1168413|1901632|2015|07/12/2015 12:42:46 PM|41.885610142|-87.657008701|(41.885610142, -87.657008701) +10140868|HY330421|07/05/2015 10:54:00 PM|118XX S PEORIA ST|1320|CRIMINAL DAMAGE|TO VEHICLE|VEHICLE NON-COMMERCIAL|false|false|0524|005|34|53|14|1172409|1826485|2015|07/12/2015 12:42:46 PM|41.6793109|-87.644545209|(41.6793109, -87.644545209) +10139762|HY329232|07/05/2015 10:42:00 PM|026XX W 37TH PL|1020|ARSON|BY FIRE|VACANT LOT/LAND|false|false|0911|009|12|58|09|1159436|1879658|2015|07/12/2015 12:42:46 PM|41.825500607|-87.690578042|(41.825500607, -87.690578042) +10139722|HY329228|07/05/2015 10:30:00 PM|016XX S CENTRAL PARK AVE|1811|NARCOTICS|POSS: CANNABIS 30GMS OR LESS|ALLEY|true|false|1021|010|24|29|18|1152687|1891389|2015|07/12/2015 12:42:46 PM|41.857827814|-87.715028789|(41.857827814, -87.715028789) +10139774|HY329209|07/05/2015 10:15:00 PM|048XX N ASHLAND AVE|1310|CRIMINAL DAMAGE|TO PROPERTY|APARTMENT|false|false|2032|020|46|3|14|1164821|1932394|2015|07/12/2015 12:42:46 PM|41.970099796|-87.669324377|(41.970099796, -87.669324377) +10139697|HY329177|07/05/2015 10:10:00 PM|058XX S ARTESIAN AVE|1320|CRIMINAL DAMAGE|TO VEHICLE|ALLEY|false|false|0824|008|16|63|14|1160997|1865851|2015|07/12/2015 12:42:46 PM|41.787580282|-87.685233078|(41.787580282, -87.685233078) diff --git a/work-with-data/dataprep/data/crime-full.csv b/work-with-data/dataprep/data/crime-full.csv new file mode 100644 index 00000000..e46bda18 --- /dev/null +++ b/work-with-data/dataprep/data/crime-full.csv @@ -0,0 +1,1001 @@ +ID,Case Number,Date,Block,IUCR,Primary Type,Description,Location Description,Damage Cost,Arrest,Domestic,Beat,District,Ward,Community Area,FBI Code,X Coordinate,Y Coordinate,Year,Updated On,Latitude,Longitude,Location +2705685,HJ329710,2003-04-26 00:00:00,105XX S WOOD ST,0460,BATTERY,SIMPLE,RESIDENCE,4615,False,False,2212,NA,19,72,08B,1166261,1834693,2003,ERROR,41.701967722,-87.666817049,"(41.701967722, -87.666817049)" +8367900,HT600798,2011-11-22 15:15:00,063XX S PULASKI RD,0890,THEFT,FROM BUILDING,LIBRARY,3882,False,False,813,8,13,65,06,1150746,1862490,2011,ERROR,41.778563069,-87.722907063,"(41.778563069, -87.722907063)" +8726660,HV402430,2012-07-26 19:30:00,019XX W BELMONT AVE,0610,BURGLARY,FORCIBLE ENTRY,SMALL RETAIL STORE,4566,False,False,1931,19,32,5,05,1162975,1921223,2012,ERROR,41.939485082,-87.676427052,"(41.939485082, -87.676427052)" +9103375,HW247768,2013-04-25 14:20:00,047XX S ASHLAND AVE,0460,BATTERY,SIMPLE,SIDEWALK,2469,False,False,931,9,20,61,08B,1166425,1873469,2013,ERROR,41.808370972,-87.665113707,"(41.808370972, -87.665113707)" +3807148,HL176623,2005-02-12 01:24:03,078XX S EAST END AVE,0920,MOTOR VEHICLE THEFT,ATT: AUTOMOBILE,ALLEY,989,False,False,414,4,8,43,07,1188944,1853066,2005,ERROR,41.751873397,-87.583173082,"(41.751873397, -87.583173082)" +4241820,HL534761,2005-08-07 21:12:26,068XX S MARSHFIELD AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3807,False,False,725,7,17,67,08B,1166478,1859475,2005,ERROR,41.769968592,-87.665318117,"(41.769968592, -87.665318117)" +4824796,HM433465,2006-06-23 22:15:00,105XX S STATE ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1504,False,False,512,5,34,49,14,1178075,1835093,2006,ERROR,41.702806335,-87.623545729,"(41.702806335, -87.623545729)" +8172753,HT407165,2011-07-20 17:50:00,021XX W 21ST PL,0820,THEFT,$500 AND UNDER,RESIDENCE,262,False,False,1223,12,25,31,06,1162541,1889761,2011,2011-03-08 09:56:44,41.853160001,-87.678904133,"(41.853160001, -87.678904133)" +8154927,HT390078,2011-07-10 15:50:00,046XX W NORTH AVE,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,2972,True,False,2533,25,37,25,06,1144852,1910260,2011,2011-11-07 10:34:01,41.909763298,-87.743312036,"(41.909763298, -87.743312036)" +6944181,HR349635,2009-05-30 18:13:00,015XX W 63RD ST,031A,ROBBERY,ARMED: HANDGUN,ALLEY,1296,False,False,713,7,16,67,03,1167116,1862990,2009,2009-12-06 18:29:17,41.779600581,-87.662879029,"(41.779600581, -87.662879029)" +5836733,HN643782,2007-10-12 14:00:00,027XX W SUMMERDALE AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE-GARAGE,3105,False,False,2011,20,40,4,14,1157009,1935459,2007,ERROR,41.978672864,-87.697965904,"(41.978672864, -87.697965904)" +7740163,HS548309,2010-10-04 21:30:00,057XX N WASHTENAW AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,APARTMENT,594,True,False,2011,20,40,2,18,1157257,1938221,2010,2010-04-10 23:26:24,41.986246871,-87.696978408,"(41.986246871, -87.696978408)" +4984650,HM597309,2006-09-11 18:30:00,052XX S STATE ST,0890,THEFT,FROM BUILDING,"SCHOOL, PUBLIC, BUILDING",1591,False,False,232,2,3,40,06,1177212,1869954,2006,ERROR,41.798488523,-87.625655865,"(41.798488523, -87.625655865)" +3941717,HL312501,2005-04-22 08:00:00,069XX S EBERHART AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,1397,False,True,322,3,6,69,08B,1180740,1858964,2005,ERROR,41.76825049,-87.613055702,"(41.76825049, -87.613055702)" +5084563,HM687682,2006-10-28 14:30:00,022XX S PULASKI RD,0560,ASSAULT,SIMPLE,STREET,2224,False,False,1013,10,22,29,08A,1150013,1889063,2006,ERROR,41.851497405,-87.724904562,"(41.851497405, -87.724904562)" +8136835,HT371007,2011-06-28 23:20:00,011XX N CHRISTIANA AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,538,True,False,1121,11,26,23,18,1153742,1907328,2011,ERROR,41.901545171,-87.710731851,"(41.901545171, -87.710731851)" +1815636,G631826,2001-10-20 18:30:00,046XX S HALSTED ST,5002,OTHER OFFENSE,OTHER VEHICLE OFFENSE,STREET,2501,True,False,921,NA,NA,NA,26,1171705,1874218,2001,ERROR,41.810312019,-87.645725963,"(41.810312019, -87.645725963)" +9276577,HW420770,2013-08-23 16:40:00,007XX S ALBANY AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,658,True,False,1134,11,24,27,18,1155766,1896834,2013,ERROR,41.872708075,-87.703580324,"(41.872708075, -87.703580324)" +1603336,G368431,2001-06-24 19:52:14,001XX W CHICAGO AV,5001,OTHER OFFENSE,OTHER CRIME INVOLVING PROPERTY,PARKING LOT/GARAGE(NON.RESID.),3948,True,False,1832,NA,NA,NA,26,1175257,1905662,2001,ERROR,41.896517917,-87.631755414,"(41.896517917, -87.631755414)" +7210353,HR608426,2009-10-26 08:40:00,015XX S PULASKI RD,2024,NARCOTICS,POSS: HEROIN(WHITE),ABANDONED BUILDING,1124,True,False,1012,10,24,29,18,1149936,1891869,2009,2009-04-11 09:53:16,41.859198911,-87.725114223,"(41.859198911, -87.725114223)" +4471848,HL301576,2005-04-17 20:50:03,006XX E GRAND AVE,1330,CRIMINAL TRESPASS,TO LAND,OTHER,3257,False,False,1834,18,42,8,26,1180772,1904096,2005,2007-11-06 15:52:33,41.892095212,-87.611548469,"(41.892095212, -87.611548469)" +7637700,HS441827,2010-08-02 12:45:00,028XX W 22ND PL,1570,SEX OFFENSE,PUBLIC INDECENCY,SIDEWALK,2297,False,False,1033,10,12,30,17,1157857,1888970,2010,ERROR,41.851086084,-87.696117577,"(41.851086084, -87.696117577)" +4703044,HM309199,2006-04-24 00:00:00,012XX N CLARK ST,0820,THEFT,$500 AND UNDER,STREET,20,False,False,1821,18,42,8,06,1175274,1908508,2006,2014-04-12 12:43:35,41.904327102,-87.631607477,"(41.904327102, -87.631607477)" +3961270,HL324634,2005-04-29 08:30:00,005XX W DIVISION ST,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,CHA HALLWAY/STAIRWELL/ELEVATOR,4726,False,False,1821,18,27,8,04B,1172300,1908305,2005,ERROR,41.903836297,-87.642537685,"(41.903836297, -87.642537685)" +2281333,HH546276,2002-07-30 13:30:00,069XX S EMERALD AVE,2014,NARCOTICS,MANU/DELIVER: HEROIN (WHITE),SIDEWALK,864,True,False,732,NA,6,68,18,1172538,1859044,2002,ERROR,41.76865457,-87.643117449,"(41.76865457, -87.643117449)" +1837655,G672065,2001-11-07 21:45:40,062XX S ST LAWRENCE AV,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,4001,True,False,313,NA,NA,NA,14,1181266,1863792,2001,ERROR,41.78148689,-87.610979035,"(41.78148689, -87.610979035)" +1842160,G679483,2001-11-11 03:30:00,021XX N CAMPBELL AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,1986,False,False,1431,NA,NA,NA,07,1159571,1914405,2001,ERROR,41.920846924,-87.689126006,"(41.920846924, -87.689126006)" +2636288,HJ216645,2003-03-03 20:48:00,045XX W WASHINGTON BLVD,1506,PROSTITUTION,SOLICIT ON PUBLIC WAY,STREET,1393,True,False,1113,NA,28,26,16,1146373,1900156,2003,ERROR,41.882007967,-87.737982025,"(41.882007967, -87.737982025)" +1496183,G233892,2001-04-24 09:00:00,004XX E MC FETRIDGE DR,0820,THEFT,$500 AND UNDER,PARK PROPERTY,187,False,False,133,NA,NA,NA,06,1179031,1894246,2001,2014-04-12 12:43:35,41.865106307,-87.618243723,"(41.865106307, -87.618243723)" +3931945,HL305935,2005-04-19 16:00:00,015XX N ARTESIAN AVE,0810,THEFT,OVER $500,RESIDENTIAL YARD (FRONT/BACK),1386,False,False,1423,14,1,24,06,1159834,1910187,2005,2014-04-12 12:43:35,41.909266991,-87.688276249,"(41.909266991, -87.688276249)" +5621453,HN427508,2007-06-25 13:25:00,040XX W DICKENS AVE,2022,NARCOTICS,POSS: COCAINE,STREET,2783,True,False,2525,25,30,20,18,1149318,1913614,2007,2007-01-07 02:00:46,41.918881522,-87.726818542,"(41.918881522, -87.726818542)" +1709847,G508926,2001-08-26 02:00:00,010XX N MONTICELLO AV,0460,BATTERY,SIMPLE,RESIDENCE,3761,False,True,1112,NA,NA,NA,08B,1151889,1906869,2001,ERROR,41.900322332,-87.717550266999993,"(41.900322332, -87.717550267)" +2306701,HH595350,2002-08-21 05:45:00,001XX W POLK ST,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,1890,False,False,131,NA,2,32,07,1175097,1896746,2002,ERROR,41.872055484,-87.63261045,"(41.872055484, -87.63261045)" +6581196,HP653741,2008-10-07 03:00:00,034XX W SCHOOL ST,0820,THEFT,$500 AND UNDER,RESIDENTIAL YARD (FRONT/BACK),312,False,False,1732,17,35,21,06,1152747,1921690,2008,ERROR,41.940975485,-87.714005626,"(41.940975485, -87.714005626)" +5443942,HN272087,2007-04-08 15:44:18,059XX S LAFLIN ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1938,False,False,713,7,16,67,14,1167399,1865013,2007,ERROR,41.785145883,-87.661783592,"(41.785145883, -87.661783592)" +3792137,HK836619,2004-12-30 18:30:00,131XX S VERNON AVE,2027,NARCOTICS,POSS: CRACK,RESIDENCE PORCH/HALLWAY,4455,True,False,533,5,9,54,18,1181519,1818462,2004,ERROR,41.657089903,-87.61144499,"(41.657089903, -87.61144499)" +9206054,HW352076,2013-07-07 15:00:00,016XX W 38TH ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,975,True,True,912,9,11,59,08B,1165971,1879568,2013,2013-08-07 12:07:35,41.825116974,-87.666605306,"(41.825116974, -87.666605306)" +4720801,HM327230,2006-05-03 05:03:58,026XX E 73RD ST,0334,ROBBERY,ATTEMPT: ARMED-KNIFE/CUT INSTR,SIDEWALK,4270,False,False,334,3,7,43,03,1194873,1857481,2006,2006-07-05 03:57:33,41.763844577,-87.561301231,"(41.763844577, -87.561301231)" +7034576,HR441456,2009-07-21 21:05:00,107XX S HALSTED ST,0326,ROBBERY,AGGRAVATED VEHICULAR HIJACKING,GAS STATION,1442,True,False,2233,22,34,75,03,1172840,1833829,2009,ERROR,41.699454527,-87.642752065,"(41.699454527, -87.642752065)" +8348862,HT582100,2011-11-09 15:00:00,108XX S BENSLEY AVE,0560,ASSAULT,SIMPLE,SIDEWALK,844,False,False,434,4,10,51,08A,1194612,1833847,2011,ERROR,41.698997218,-87.563033068,"(41.698997218, -87.563033068)" +2882234,HJ552553,2003-08-08 22:00:00,109XX S WABASH AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE-GARAGE,664,False,False,513,5,9,49,14,1178490,1832061,2003,ERROR,41.694476704,-87.622117791,"(41.694476704, -87.622117791)" +8571720,HV245210,2011-12-26 09:00:00,038XX W NORTH AVE,0842,THEFT,AGG: FINANCIAL ID THEFT,BANK,4830,False,False,2535,25,30,23,06,1150240,1910394,2011,ERROR,41.910027597,-87.723515111,"(41.910027597, -87.723515111)" +3479397,HK551539,2004-08-11 11:00:00,014XX S CHRISTIANA AVE,0810,THEFT,OVER $500,STREET,3767,False,False,1021,10,24,29,06,1154306,1892825,2004,2014-04-12 12:43:35,41.861736212,-87.709047718,"(41.861736212, -87.709047718)" +6571624,HP644036,2008-10-23 18:10:00,003XX S PLYMOUTH CT,0340,ROBBERY,ATTEMPT: STRONGARM-NO WEAPON,STREET,3266,False,False,123,1,2,32,03,1176091,1898936,2008,2008-06-11 15:42:26,41.878042652,-87.628895109,"(41.878042652, -87.628895109)" +3425217,HK489162,2004-07-11 19:46:59,060XX S MICHIGAN AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,SIDEWALK,1195,False,True,311,3,20,40,08B,1178229,1865098,2004,ERROR,41.785140156,-87.62207368,"(41.785140156, -87.62207368)" +9297900,HW443063,2013-09-08 14:30:00,073XX S MICHIGAN AVE,0320,ROBBERY,STRONGARM - NO WEAPON,STREET,3308,False,False,323,3,6,69,03,1178393,1856302,2013,2013-02-10 11:54:38,41.760999284,-87.621739229,"(41.760999284, -87.621739229)" +5343960,HM744635,2006-11-28 07:45:00,042XX S CALUMET AVE,1506,PROSTITUTION,SOLICIT ON PUBLIC WAY,STREET,2763,True,False,214,2,3,38,16,1179097,1876982,2006,ERROR,41.81773117,-87.61852889,"(41.81773117, -87.61852889)" +7231348,HR644530,2009-11-15 13:15:00,048XX N KENMORE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,2613,True,True,2024,20,46,3,08B,1168339,1932383,2009,ERROR,41.969994063,-87.656388912,"(41.969994063, -87.656388912)" +8276922,HT511056,2011-09-23 18:15:00,076XX S CICERO AVE,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,3184,True,False,833,8,13,65,06,1145766,1853738,2011,ERROR,41.75464162,-87.741385158,"(41.75464162, -87.741385158)" +7096630,HR505081,2009-08-26 18:00:00,081XX S PAULINA ST,0460,BATTERY,SIMPLE,RESIDENCE PORCH/HALLWAY,2842,False,False,614,6,18,71,08B,1166381,1850921,2009,ERROR,41.746497306,-87.66591694,"(41.746497306, -87.66591694)" +3509426,HK586658,2004-08-28 00:00:00,083XX S KINGSTON AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,606,False,False,423,4,7,46,07,1194581,1850155,2004,ERROR,41.743748652,-87.562611996,"(41.743748652, -87.562611996)" +9478072,HX130921,2014-01-21 17:00:00,068XX S WOLCOTT AVE,4387,OTHER OFFENSE,VIOLATE ORDER OF PROTECTION,RESIDENCE,1761,False,True,726,7,17,67,26,1164830,1859219,2014,ERROR,41.769301059,-87.671366222,"(41.769301059, -87.671366222)" +9320325,HW464485,2013-09-23 21:30:00,016XX E 77TH ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1715,False,True,414,4,8,43,14,1188749,1854298,2013,ERROR,41.755258781,-87.583848327,"(41.755258781, -87.583848327)" +8528788,HV205948,2012-03-20 09:50:00,111XX S EWING AVE,1305,CRIMINAL DAMAGE,CRIMINAL DEFACEMENT,RESIDENCE-GARAGE,1676,False,False,433,4,10,52,14,1202171,1831712,2012,ERROR,41.692949738,-87.535428598,"(41.692949738, -87.535428598)" +4290691,HL605721,2005-09-11 14:00:00,001XX N DEARBORN ST,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),2621,False,False,122,1,42,32,06,1175948,1901601,2005,2014-04-12 12:43:35,41.885358785,-87.629339896,"(41.885358785, -87.629339896)" +9191309,HW336113,2013-06-25 11:28:00,013XX N ASHLAND AVE,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,3808,True,False,1433,14,1,24,06,1165434,1909298,2013,ERROR,41.906710108,-87.66772974,"(41.906710108, -87.66772974)" +6943981,HR348351,2009-05-29 22:00:00,057XX N MC VICKER AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,1332,False,False,1622,16,45,10,14,1134948,1938056,2009,ERROR,41.986219618,-87.779035389,"(41.986219618, -87.779035389)" +1432501,G153916,2001-03-18 01:15:59,023XX N HAMLIN AV,0460,BATTERY,SIMPLE,RESIDENCE,2320,True,False,2525,NA,NA,NA,08B,1150616,1915362,2001,ERROR,41.923652901,-87.722003735,"(41.923652901, -87.722003735)" +8288964,HT523191,2011-10-01 16:10:00,021XX E 70TH ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,4671,True,False,331,3,5,43,18,1191556,1859009,2011,2011-01-10 19:38:17,41.768118581,-87.573409074,"(41.768118581, -87.573409074)" +7910721,HT140388,2011-01-28 23:30:00,029XX W ADAMS ST,0530,ASSAULT,AGGRAVATED: OTHER DANG WEAPON,APARTMENT,4639,False,False,1124,11,2,27,04A,1156613,1898917,2011,ERROR,41.878406942,-87.700414194,"(41.878406942, -87.700414194)" +2089112,HH309698,2002-04-15 20:15:00,010XX N LAWLER AV,2027,NARCOTICS,POSS: CRACK,SIDEWALK,1200,True,False,1531,NA,NA,NA,18,1142456,1906817,2002,ERROR,41.900360209,-87.752199731,"(41.900360209, -87.752199731)" +9419206,HW562896,2013-12-08 01:39:00,011XX S CANAL ST,0810,THEFT,OVER $500,RESTAURANT,4414,False,False,124,1,2,28,06,1173358,1895046,2013,ERROR,41.867429345,-87.639045471,"(41.867429345, -87.639045471)" +8846059,HV519322,2012-10-14 20:00:00,018XX W 103RD ST,0610,BURGLARY,FORCIBLE ENTRY,OTHER,4604,False,False,2213,22,19,72,05,1165953,1836420,2012,ERROR,41.70671343,-87.667895994,"(41.70671343, -87.667895994)" +6454875,HP535283,2008-08-26 03:22:00,019XX W 19TH ST,0560,ASSAULT,SIMPLE,APARTMENT,1503,False,False,1223,12,25,31,08A,1163581,1890708,2008,ERROR,41.855736847,-87.675060338,"(41.855736847, -87.675060338)" +8223778,HT457480,2011-08-20 15:15:00,007XX E 43RD ST,0560,ASSAULT,SIMPLE,CHA PARKING LOT/GROUNDS,4240,False,False,213,2,4,38,08A,1182085,1876707,2011,ERROR,41.816907818,-87.607576648,"(41.816907818, -87.607576648)" +1622655,G400595,2001-07-09 14:55:00,035XX N HERMITAGE AV,0560,ASSAULT,SIMPLE,ALLEY,2256,False,False,1923,NA,NA,NA,08A,1164066,1923605,2001,ERROR,41.945998404,-87.672349817,"(41.945998404, -87.672349817)" +7843346,HS655355,2010-12-10 22:15:00,028XX S SPAULDING AVE,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,SIDEWALK,2971,True,False,1032,10,22,30,15,1154782,1885145,2010,ERROR,41.840651893,-87.707505856,"(41.840651893, -87.707505856)" +6043466,HP146623,2008-01-28 00:00:07,091XX S STONY ISLAND AVE,031A,ROBBERY,ARMED: HANDGUN,SMALL RETAIL STORE,3209,False,False,413,4,8,48,03,1188345,1844791,2008,2008-08-02 08:13:34,41.729180293,-87.585631578,"(41.729180293, -87.585631578)" +9633417,HX284237,2014-05-31 15:00:00,083XX S INGLESIDE AVE,0820,THEFT,$500 AND UNDER,APARTMENT,380,False,False,632,6,8,44,06,1183965,1849926,2014,2014-07-06 12:40:43,41.743374586,-87.601516477,"(41.743374586, -87.601516477)" +9050651,HW196019,2013-03-16 14:30:00,037XX N SEMINARY AVE,0810,THEFT,OVER $500,OTHER,3835,False,False,1923,19,44,6,06,1168275,1925255,2013,ERROR,41.950435964,-87.656831163,"(41.950435964, -87.656831163)" +1724326,G530529,2001-08-24 09:00:00,068XX N FRANCISCO AV,0810,THEFT,OVER $500,CONSTRUCTION SITE,4601,False,False,2411,NA,NA,NA,06,1155826,1945498,2001,2014-04-12 12:43:35,42.006244328,-87.70204429,"(42.006244328, -87.70204429)" +7313924,HS117962,2009-11-23 09:18:00,001XX W JACKSON BLVD,1110,DECEPTIVE PRACTICE,BOGUS CHECK,BANK,2873,True,False,112,1,2,32,11,1175229,1898922,2009,2011-03-01 19:34:38,41.878023606,-87.632060555,"(41.878023606, -87.632060555)" +8348182,HT581427,2011-11-08 17:45:00,049XX S WABASH AVE,0820,THEFT,$500 AND UNDER,"SCHOOL, PUBLIC, BUILDING",404,True,False,231,2,3,38,06,1177513,1872176,2011,2011-09-11 12:54:43,41.804579093,-87.624484847,"(41.804579093, -87.624484847)" +8457513,HV134404,2012-01-27 10:40:00,095XX S LAFAYETTE AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4775,False,False,511,5,21,49,07,1177575,1841925,2012,ERROR,41.721565569,-87.625170844,"(41.721565569, -87.625170844)" +3991779,HL277064,2005-04-05 22:00:00,004XX S CICERO AVE,1506,PROSTITUTION,SOLICIT ON PUBLIC WAY,STREET,4442,True,False,1533,15,24,25,16,1144422,1897554,2005,ERROR,41.874904679,-87.745211632,"(41.874904679, -87.745211632)" +8231576,HT460583,2011-08-03 11:00:00,025XX W 63RD ST,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,OTHER,2479,False,False,825,8,15,66,26,1160154,1862826,2011,ERROR,41.779296661,-87.688407212,"(41.779296661, -87.688407212)" +4496745,HL799743,2005-12-20 09:05:00,073XX S SACRAMENTO AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,4808,False,False,835,8,18,66,05,1157607,1855700,2005,ERROR,41.75979389,-87.697937855,"(41.75979389, -87.697937855)" +5033246,HM641247,2006-10-04 20:00:00,080XX S LAFLIN ST,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,4181,False,False,612,6,21,71,05,1167772,1851438,2006,ERROR,41.747886306,-87.660805171,"(41.747886306, -87.660805171)" +5208608,HM778355,2006-12-16 19:10:00,068XX S HALSTED ST,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,TAVERN/LIQUOR STORE,3007,True,False,723,7,6,68,15,1172116,1859172,2006,2007-01-01 07:32:02,41.7690151,-87.644660529,"(41.7690151, -87.644660529)" +2528826,HJ107508,2003-01-04 22:02:46,030XX W 47TH ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,4398,False,False,912,NA,14,58,14,1157054,1873373,2003,ERROR,41.808302333,-87.699487137,"(41.808302333, -87.699487137)" +1613083,G380569,2001-06-30 03:34:50,010XX E 133 ST,0560,ASSAULT,SIMPLE,CHA APARTMENT,4421,True,False,533,NA,NA,NA,08A,1185848,1817236,2001,ERROR,41.653625,-87.595643042,"(41.653625, -87.595643042)" +5350380,HN205637,2007-03-03 10:00:00,032XX W 63RD ST,0460,BATTERY,SIMPLE,APARTMENT,1902,False,False,823,8,15,66,08B,1155671,1862624,2007,2007-08-03 21:51:06,41.77883343,-87.704847919,"(41.77883343, -87.704847919)" +5570467,HN377874,2007-04-02 12:00:00,064XX S LANGLEY AVE,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,4605,False,True,312,3,20,42,26,1181963,1862332,2007,ERROR,41.777464417,-87.608468819,"(41.777464417, -87.608468819)" +3864729,HL237399,2005-03-16 10:00:00,046XX S HALSTED ST,0860,THEFT,RETAIL THEFT,GROCERY FOOD STORE,3954,True,False,921,9,11,61,06,1171713,1873921,2005,ERROR,41.809496845,-87.645705337,"(41.809496845, -87.645705337)" +4208971,HL527767,2005-07-04 22:30:00,056XX S MICHIGAN AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,1863,False,True,233,2,20,40,08B,1178081,1867582,2005,ERROR,41.791959854,-87.622541015,"(41.791959854, -87.622541015)" +4616724,HM210940,2006-03-03 10:26:14,0000X W RANDOLPH ST,1330,CRIMINAL TRESPASS,TO LAND,RESTAURANT,1416,True,False,122,1,42,32,26,1175995,1901321,2006,2006-07-03 03:46:57,41.884589391,-87.629175742,"(41.884589391, -87.629175742)" +9574311,HX225021,2014-04-15 20:00:00,088XX S LOOMIS ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4758,False,False,2222,22,21,71,07,1168581,1846266,2014,2014-06-05 00:39:53,41.733676216,-87.657989464,"(41.733676216, -87.657989464)" +3138687,HK129812,2004-01-16 21:30:00,115XX S YALE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4990,False,True,522,5,34,53,08B,1176719,1828319,2004,ERROR,41.684248043,-87.628713951,"(41.684248043, -87.628713951)" +8344443,HT528932,2011-10-05 05:00:00,052XX W SCHOOL ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,2954,False,False,1634,16,38,15,14,1140574,1921391,2011,2011-08-11 10:43:52,41.940387619,-87.758753823,"(41.940387619, -87.758753823)" +1796827,G622864,2001-10-15 20:30:00,030XX S GRATTEN AV,0810,THEFT,OVER $500,OTHER,2409,False,False,923,NA,NA,NA,06,1169291,1884678,2001,2014-04-12 12:43:35,41.839067958,-87.654276965,"(41.839067958, -87.654276965)" +6321465,HP407211,2008-06-21 01:00:00,013XX S KEDZIE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,SIDEWALK,1039,False,False,1022,10,24,29,08B,1155195,1893805,2008,ERROR,41.864407654,-87.705758038,"(41.864407654, -87.705758038)" +4048269,HL397796,2005-06-04 01:00:00,106XX S HALSTED ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,VEHICLE NON-COMMERCIAL,1253,False,True,2233,22,34,73,08B,1172820,1834491,2005,ERROR,41.701271596,-87.642805861,"(41.701271596, -87.642805861)" +3192419,HK199140,2004-02-23 08:15:00,068XX S STEWART AVE,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, BUILDING",2632,True,False,722,7,6,68,08B,1174671,1859859,2004,ERROR,41.770843786,-87.635274724,"(41.770843786, -87.635274724)" +2000440,HH198576,2002-02-20 16:32:03,064XX W DIVERSEY AV,0620,BURGLARY,UNLAWFUL ENTRY,APPLIANCE STORE,4375,False,False,2512,NA,NA,NA,05,NA,NA,2002,ERROR,NA,NA, +9584763,HX234897,2014-04-23 21:50:00,026XX N RUTHERFORD AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,1333,False,False,2512,25,36,18,08B,1130931,1916809,2014,ERROR,41.927985963,-87.794301643,"(41.927985963, -87.794301643)" +6633395,HP703001,2008-11-26 10:25:55,001XX N STATE ST,0850,THEFT,ATTEMPT THEFT,SIDEWALK,1180,True,False,122,1,42,32,06,1176393,1900887,2008,2008-01-12 07:38:55,41.8833895,-87.627727352,"(41.8833895, -87.627727352)" +10025851,HY215334,2015-04-09 08:09:00,037XX N KILPATRICK AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4106,True,True,1731,17,38,15,08B,1144437,1924607,2015,ERROR,41.949140682,-87.744474617,"(41.949140682, -87.744474617)" +4257303,HL576564,2005-08-28 00:01:00,018XX N KEDVALE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,1447,False,False,2534,25,30,20,14,1148380,1912201,2005,ERROR,41.915022267,-87.730301385,"(41.915022267, -87.730301385)" +5994905,HP103223,2008-01-02 20:45:00,029XX W WARREN BLVD,2027,NARCOTICS,POSS: CRACK,SIDEWALK,2969,True,False,1331,12,2,27,18,1156699,1900170,2008,2008-03-01 08:43:16,41.881843556,-87.700064472,"(41.881843556, -87.700064472)" +6422343,HP506170,2008-08-05 23:00:00,008XX E 101ST ST,0820,THEFT,$500 AND UNDER,VEHICLE NON-COMMERCIAL,111,False,False,511,5,8,50,06,1183373,1838023,2008,2008-11-08 08:34:38,41.710725164,-87.604055073,"(41.710725164, -87.604055073)" +20991,HW370238,2013-07-19 21:43:00,038XX W HIRSCH ST,0110,HOMICIDE,FIRST DEGREE MURDER,STREET,3308,False,False,2535,25,30,23,01A,1150202,1908997,2013,2013-03-10 07:24:17,41.906194835,-87.723691183,"(41.906194835, -87.723691183)" +6084521,HP178194,2008-02-16 03:00:00,066XX S ASHLAND AVE,0313,ROBBERY,ARMED: OTHER DANGEROUS WEAPON,SIDEWALK,3004,False,False,725,7,17,67,03,1166856,1860593,2008,ERROR,41.773028465,-87.663900629,"(41.773028465, -87.663900629)" +10080403,HY268040,2015-05-19 11:00:00,049XX W IRVING PARK RD,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,3481,False,True,1624,16,45,15,08B,1142440,1926175,2015,ERROR,41.95348083,-87.751776252,"(41.95348083, -87.751776252)" +6924204,HR316222,2009-05-12 09:00:00,0000X W 95TH ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,CTA GARAGE / OTHER PROPERTY,3480,True,False,634,6,21,49,18,1177743,1841988,2009,ERROR,41.721734655,-87.624553594,"(41.721734655, -87.624553594)" +6386680,HP461038,2008-07-18 22:43:31,045XX W HARRISON ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,2401,True,False,1131,11,24,26,18,1146348,1896935,2008,ERROR,41.873169637,-87.738155845,"(41.873169637, -87.738155845)" +6784849,HR199128,2009-03-04 20:12:00,066XX N CLARK ST,2170,NARCOTICS,POSSESSION OF DRUG EQUIPMENT,ALLEY,3928,True,False,2432,24,40,1,18,1164013,1944171,2009,2009-04-03 20:55:02,42.002433404,-87.671961019,"(42.002433404, -87.671961019)" +7712756,HS519770,2010-09-17 12:11:00,060XX S WESTERN AVE,0820,THEFT,$500 AND UNDER,SMALL RETAIL STORE,37,False,False,825,8,15,66,06,1161368,1864419,2010,ERROR,41.783642999,-87.683912427,"(41.783642999, -87.683912427)" +7906876,HT136770,2011-01-26 15:55:00,0000X S KOSTNER AVE,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,SIDEWALK,4097,True,False,1113,11,28,26,26,1147028,1899356,2011,ERROR,41.87980018,-87.735597307,"(41.87980018, -87.735597307)" +7991866,HT223888,2011-03-28 19:15:00,110XX S PRINCETON AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,544,True,False,513,5,34,49,18,1176291,1831621,2011,ERROR,41.693318823,-87.630182058,"(41.693318823, -87.630182058)" +7123089,HR531884,2009-09-11 18:13:00,013XX S MILLARD AVE,051A,ASSAULT,AGGRAVATED: HANDGUN,STREET,4647,True,False,1011,10,24,29,04A,1152210,1893660,2009,ERROR,41.864069113,-87.71671981,"(41.864069113, -87.71671981)" +2000324,HH200986,2002-02-21 18:47:45,014XX N AVERS AV,0460,BATTERY,SIMPLE,RESIDENCE,3701,False,True,2535,NA,NA,NA,08B,1150481,1909461,2002,ERROR,41.907462651,-87.722654169,"(41.907462651, -87.722654169)" +8240399,HT466105,2011-08-25 22:55:00,021XX N CICERO AVE,1513,PROSTITUTION,SOLICIT FOR BUSINESS,STREET,2564,True,False,2522,25,31,19,16,1144003,1913897,2011,2011-02-09 09:05:11,41.919759591,-87.746339498,"(41.919759591, -87.746339498)" +1514753,G260660,2001-05-06 18:53:39,028XX W 19 ST,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,STREET,3078,True,False,1022,NA,NA,NA,15,1157943,1890640,2001,2010-02-06 10:34:17,41.855666993,-87.695756427,"(41.855666993, -87.695756427)" +3724031,HK768759,2004-11-12 13:00:00,012XX S ASHLAND AVE,1120,DECEPTIVE PRACTICE,FORGERY,RESIDENCE,1595,False,False,1224,12,2,28,10,1165874,1894646,2004,ERROR,41.866494521,-87.666531674,"(41.866494521, -87.666531674)" +9095092,HW222941,2013-04-07 10:05:00,039XX S CALUMET AVE,2014,NARCOTICS,MANU/DELIVER: HEROIN (WHITE),SIDEWALK,3181,True,False,213,2,3,38,18,1179129,1878622,2013,2013-03-05 09:26:15,41.822230731,-87.618361446,"(41.822230731, -87.618361446)" +9606372,HX256322,2014-05-10 14:15:00,017XX S ASHLAND AVE,0860,THEFT,RETAIL THEFT,OTHER,824,False,False,1233,12,25,31,06,1166044,1891531,2014,ERROR,41.857943062,-87.665996477,"(41.857943062, -87.665996477)" +2080168,HH302067,2002-04-11 17:30:00,005XX S CENTRAL AV,0460,BATTERY,SIMPLE,RESIDENCE,830,False,False,1522,NA,NA,NA,08B,1139180,1897038,2002,ERROR,41.873585657,-87.764470903,"(41.873585657, -87.764470903)" +2538562,HJ119047,2003-01-10 17:30:00,030XX N MOBILE AVE,0890,THEFT,FROM BUILDING,"SCHOOL, PUBLIC, BUILDING",1800,False,False,2511,NA,36,19,06,1133838,1919613,2003,ERROR,41.935629815,-87.783553233,"(41.935629815, -87.783553233)" +5728743,HN535876,2007-08-18 03:00:00,055XX W DAKIN ST,0460,BATTERY,SIMPLE,ALLEY,3956,False,False,1633,16,38,15,08B,1138581,1925659,2007,ERROR,41.952135858,-87.765975048,"(41.952135858, -87.765975048)" +4533362,HM121306,2006-01-05 13:00:00,112XX S PERRY AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,STREET,4073,False,True,522,5,34,49,08B,1177464,1830657,2006,ERROR,41.690647109,-87.62591646,"(41.690647109, -87.62591646)" +2940341,HJ623964,2003-09-11 22:52:01,079XX S JUSTINE ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3976,False,False,612,6,21,71,14,1167342,1852128,2003,ERROR,41.749788975,-87.662361113,"(41.749788975, -87.662361113)" +4022229,HL375934,2005-05-23 16:30:00,043XX W IRVING PARK RD,1320,CRIMINAL DAMAGE,TO VEHICLE,OTHER,547,False,False,1722,17,38,16,14,1146478,1926264,2005,ERROR,41.953648902,-87.736929761,"(41.953648902, -87.736929761)" +8856428,HV530199,2012-10-22 19:10:00,001XX N LA CROSSE AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,RESIDENCE-GARAGE,3046,False,False,1532,15,28,25,07,1144050,1900929,2012,ERROR,41.884173077,-87.746492747,"(41.884173077, -87.746492747)" +10126995,HY315210,2015-04-01 09:00:00,099XX S WINSTON AVE,1153,DECEPTIVE PRACTICE,FINANCIAL IDENTITY THEFT OVER $ 300,RESIDENCE,3601,False,False,2213,22,21,73,11,1168775,1838907,2015,ERROR,41.713477841,-87.6574904,"(41.713477841, -87.6574904)" +7059731,HR465421,2009-08-04 10:00:00,050XX W ADDISON ST,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE,3296,False,False,1634,16,38,15,05,1142074,1923423,2009,2009-03-09 12:45:23,41.945935898,-87.753190197,"(41.945935898, -87.753190197)" +4369628,HL653099,2005-10-04 14:15:00,0000X E 68TH ST,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,4938,False,False,322,3,20,69,05,1178251,1859995,2005,ERROR,41.7711365,-87.622147753,"(41.7711365, -87.622147753)" +2085336,HH300310,2002-04-11 14:22:32,052XX S ASHLAND AV,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,2024,True,False,932,NA,NA,NA,18,1166514,1870200,2002,ERROR,41.79939856,-87.664880524,"(41.79939856, -87.664880524)" +2837739,HJ500794,2003-07-17 12:00:00,064XX S ASHLAND AVE,0820,THEFT,$500 AND UNDER,PARKING LOT/GARAGE(NON.RESID.),327,False,False,725,7,15,67,06,1166729,1862274,2003,2014-04-12 12:43:35,41.777644057,-87.664318242,"(41.777644057, -87.664318242)" +9359551,HW502824,2013-10-16 13:00:00,087XX S CONSTANCE AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,SIDEWALK,4794,False,False,412,4,8,48,14,1189994,1847553,2013,ERROR,41.736720026,-87.579502355,"(41.736720026, -87.579502355)" +4269836,HL586435,2005-09-01 00:00:00,099XX S BEVERLY AVE,0890,THEFT,FROM BUILDING,RESIDENCE,2317,False,True,2213,22,21,72,06,1168186,1838983,2005,ERROR,41.713699063,-87.659645355,"(41.713699063, -87.659645355)" +3579880,HK667872,2004-10-05 09:30:00,053XX S COTTAGE GROVE AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,3907,False,False,2131,2,5,41,05,1182540,1869405,2004,ERROR,41.796859993,-87.606134249,"(41.796859993, -87.606134249)" +1396555,G081867,2001-02-09 10:00:00,070XX S SANGAMON ST,1812,NARCOTICS,POSS: CANNABIS MORE THAN 30GMS,STREET,2543,True,False,733,NA,NA,NA,18,1171146,1858301,2001,ERROR,41.766646226,-87.648241507,"(41.766646226, -87.648241507)" +6210516,HP298503,2008-04-23 19:00:00,088XX S COLFAX AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,3830,False,False,423,4,7,46,05,1195065,1846888,2008,2008-08-05 07:57:25,41.734771822,-87.560946053,"(41.734771822, -87.560946053)" +8335396,HT565225,2011-10-29 12:22:19,069XX S LAFAYETTE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESTAURANT,4794,False,True,731,7,6,69,08B,1176984,1859255,2011,2011-07-11 12:45:02,41.769134515,-87.626814372,"(41.769134515, -87.626814372)" +4575638,HL791694,2005-12-15 20:55:00,006XX N TRUMBULL AVE,2024,NARCOTICS,POSS: HEROIN(WHITE),SIDEWALK,1930,True,False,1121,11,27,23,18,1153311,1903852,2005,ERROR,41.892015265,-87.712407374,"(41.892015265, -87.712407374)" +9527333,HX181612,2014-03-12 10:50:00,081XX S VINCENNES AVE,0545,ASSAULT,PRO EMP HANDS NO/MIN INJURY,"SCHOOL, PUBLIC, BUILDING",1878,True,False,622,6,21,44,08A,1174722,1850745,2014,ERROR,41.745832739,-87.635358835,"(41.745832739, -87.635358835)" +2802818,HJ444885,2003-06-21 19:30:00,029XX S STATE ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,CHA PARKING LOT/GROUNDS,4371,False,True,2113,1,3,35,08B,1176715,1885559,2003,ERROR,41.841321197,-87.627008027,"(41.841321197, -87.627008027)" +8240826,HT474337,2011-08-31 10:04:00,070XX S CLYDE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,ALLEY,3112,True,False,331,3,5,43,08B,1191389,1858691,2011,2011-01-09 06:32:18,41.767250011,-87.574031486,"(41.767250011, -87.574031486)" +8766702,HV441335,2012-08-21 22:30:00,023XX N MILWAUKEE AVE,0620,BURGLARY,UNLAWFUL ENTRY,OTHER,4672,False,False,1414,14,35,22,05,1157125,1915354,2012,2012-03-09 18:32:27,41.923501123,-87.698087331,"(41.923501123, -87.698087331)" +9497756,HX152161,2014-02-15 11:52:00,071XX S CONSTANCE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,4239,False,True,324,3,5,43,08B,1189655,1857726,2014,ERROR,41.764643801,-87.580418177,"(41.764643801, -87.580418177)" +7234599,HR643869,2009-11-15 00:30:00,002XX W NORTH AVE,0890,THEFT,FROM BUILDING,OTHER,3514,False,False,1814,18,43,7,06,1174055,1911024,2009,2010-05-01 11:53:51,41.911258405,-87.636010015,"(41.911258405, -87.636010015)" +3179722,HK182024,2004-02-13 23:00:00,013XX W IRVING PARK RD,0810,THEFT,OVER $500,STREET,4624,False,False,1923,19,47,6,06,1166363,1926630,2004,2014-04-12 12:43:35,41.954250222,-87.663819997,"(41.954250222, -87.663819997)" +4222202,HL539526,2005-08-10 01:50:00,043XX S LAMON AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,CHA PARKING LOT/GROUNDS,4720,False,False,814,8,23,56,14,1144637,1875298,2005,ERROR,41.813827102,-87.744982032,"(41.813827102, -87.744982032)" +7219347,HR634999,2009-11-09 19:00:00,052XX S RACINE AVE,0560,ASSAULT,SIMPLE,APARTMENT,2784,False,True,934,9,16,61,08A,1169250,1870146,2009,ERROR,41.799191555,-87.654848486,"(41.799191555, -87.654848486)" +7793169,HS594082,2010-11-01 18:00:00,019XX W 95TH ST,1350,CRIMINAL TRESPASS,TO STATE SUP LAND,LIBRARY,553,False,False,2221,22,19,72,26,1164769,1841686,2010,2010-07-11 08:22:48,41.721189215,-87.672083692,"(41.721189215, -87.672083692)" +2103641,HH335780,2002-04-28 01:50:00,014XX W MARQUETTE RD,0560,ASSAULT,SIMPLE,STREET,4679,False,False,725,NA,17,67,08A,1167596,1860349,2002,ERROR,41.772343059,-87.661194963,"(41.772343059, -87.661194963)" +8223696,HT457644,2011-08-20 17:25:00,098XX S HALSTED ST,0320,ROBBERY,STRONGARM - NO WEAPON,STREET,1911,True,False,2223,22,21,73,03,1172676,1839503,2011,ERROR,41.715028437,-87.643186054,"(41.715028437, -87.643186054)" +7585036,HS388608,2010-07-01 06:25:00,003XX N OAKLEY BLVD,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,2283,False,False,1332,12,27,28,07,1160973,1902387,2010,2010-02-07 11:19:23,41.887839582,-87.684308839,"(41.887839582, -87.684308839)" +5931509,HN704677,2007-11-12 23:30:00,079XX S STATE ST,0320,ROBBERY,STRONGARM - NO WEAPON,CTA PLATFORM,3105,False,False,623,6,6,44,03,1177618,1852608,2007,2007-12-12 01:05:15,41.750880081,-87.624691153,"(41.750880081, -87.624691153)" +5710926,HN519237,2007-08-09 16:00:00,012XX N ROCKWELL ST,0890,THEFT,FROM BUILDING,RESIDENCE,3673,False,False,1423,14,26,24,06,1158874,1908481,2007,ERROR,41.904605346,-87.691849732,"(41.904605346, -87.691849732)" +3611691,HK705389,2004-10-23 13:30:00,010XX S WELLS ST,0810,THEFT,OVER $500,STREET,2968,False,False,131,1,2,32,06,1174825,1895931,2004,2014-04-12 12:43:35,41.86982516,-87.633633456,"(41.86982516, -87.633633456)" +4713760,HM209421,2006-03-02 13:48:00,072XX S PAULINA ST,2095,NARCOTICS,ATTEMPT POSSESSION NARCOTICS,STREET,2117,True,False,735,7,17,67,18,1166303,1856763,2006,ERROR,41.762530229,-87.666036716,"(41.762530229, -87.666036716)" +2607516,HJ206193,2003-02-26 14:20:00,034XX S DR MARTIN LUTHER KING JR DR,1330,CRIMINAL TRESPASS,TO LAND,DRUG STORE,3921,False,False,2122,NA,4,35,26,1179511,1882404,2003,ERROR,41.832600096,-87.616844359,"(41.832600096, -87.616844359)" +6611578,HP684171,2008-11-15 03:25:00,009XX W BELMONT AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESTAURANT,1170,True,False,1924,19,44,6,14,1169428,1921475,2008,ERROR,41.940038439,-87.652703194,"(41.940038439, -87.652703194)" +8020502,HT251222,2011-04-14 22:00:00,066XX S TALMAN AVE,0820,THEFT,$500 AND UNDER,STREET,490,False,False,831,8,15,66,06,1159896,1860350,2011,ERROR,41.772507471,-87.689421041,"(41.772507471, -87.689421041)" +3795590,HL160354,2005-01-28 23:00:00,036XX N MARSHFIELD AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2815,False,False,1923,19,47,6,14,1164641,1923969,2005,ERROR,41.946985052,-87.670225966,"(41.946985052, -87.670225966)" +4925940,HM538019,2006-08-13 16:09:24,060XX S VERNON AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,OTHER,3128,True,True,313,3,20,42,08B,1180344,1865263,2006,ERROR,41.785544659,-87.61431417,"(41.785544659, -87.61431417)" +8500347,HV177396,2012-02-29 11:20:00,001XX W LAKE ST,1210,DECEPTIVE PRACTICE,THEFT OF LABOR/SERVICES,CTA GARAGE / OTHER PROPERTY,4130,True,False,113,1,42,32,11,1175354,1901695,2012,2012-01-03 08:18:16,41.885630077,-87.631518326,"(41.885630077, -87.631518326)" +2617371,HJ216694,2003-03-03 18:00:00,073XX S SOUTH SHORE DR,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,1366,False,False,334,NA,7,43,26,1195387,1857193,2003,ERROR,41.76304161,-87.559426843,"(41.76304161, -87.559426843)" +7970463,HT202960,2011-02-15 17:00:00,077XX S NORMAL AVE,1110,DECEPTIVE PRACTICE,BOGUS CHECK,APARTMENT,4925,False,False,621,6,17,69,11,1174337,1853795,2011,ERROR,41.754210881,-87.636679059,"(41.754210881, -87.636679059)" +6529891,HP602686,2008-10-01 02:46:00,002XX E 35TH ST,0820,THEFT,$500 AND UNDER,SIDEWALK,496,False,False,2112,2,2,35,06,1178613,1881880,2008,ERROR,41.831182706,-87.620155187,"(41.831182706, -87.620155187)" +7957427,HT189220,2011-03-05 02:00:00,044XX N MILWAUKEE AVE,0460,BATTERY,SIMPLE,SIDEWALK,4218,False,False,1623,16,45,15,08B,1141633,1928786,2011,2011-08-03 11:36:40,41.960660635,-87.754678083,"(41.960660635, -87.754678083)" +8157874,HT373208,2011-06-30 09:40:00,038XX W MADISON ST,2027,NARCOTICS,POSS: CRACK,GROCERY FOOD STORE,4416,True,False,1122,11,28,26,18,1150456,1899690,2011,ERROR,41.880650508,-87.723001331,"(41.880650508, -87.723001331)" +3216861,HK232167,2004-03-10 18:30:00,0000X W 111TH ST,0890,THEFT,FROM BUILDING,HOSPITAL BUILDING/GROUNDS,2419,False,False,522,5,34,49,06,1177728,1831317,2004,ERROR,41.692452292,-87.624930074,"(41.692452292, -87.624930074)" +2177171,HH431013,2002-06-10 05:51:58,046XX W 82ND ST,0460,BATTERY,SIMPLE,APARTMENT,4946,False,False,834,NA,13,70,08B,1146910,1849636,2002,ERROR,41.743363318,-87.737296807,"(41.743363318, -87.737296807)" +8393994,HT626631,2011-12-10 20:40:00,076XX S NORMAL AVE,0460,BATTERY,SIMPLE,STREET,3091,False,False,621,6,17,69,08B,1174322,1854368,2011,ERROR,41.755783597,-87.636717026,"(41.755783597, -87.636717026)" +3445477,HK002362,2004-07-23 21:30:00,021XX W SUPERIOR ST,0820,THEFT,$500 AND UNDER,RESIDENCE,26,False,False,1313,12,1,24,06,1162023,1904942,2004,2014-04-12 12:43:35,41.894828854,-87.680381499,"(41.894828854, -87.680381499)" +1393385,G083313,2001-02-09 22:06:39,063XX S ALBANY AV,2027,NARCOTICS,POSS: CRACK,STREET,1258,True,False,823,NA,NA,NA,18,1156765,1862558,2001,ERROR,41.778630305,-87.700838972,"(41.778630305, -87.700838972)" +8144026,HT378531,2011-07-02 23:00:00,033XX W 63RD ST,1310,CRIMINAL DAMAGE,TO PROPERTY,OTHER,2973,False,False,823,8,15,66,14,1155411,1862617,2011,2011-05-07 14:12:04,41.778819432,-87.705801296,"(41.778819432, -87.705801296)" +1971985,HH155823,2002-01-29 15:25:27,033XX W FLOURNOY ST,0460,BATTERY,SIMPLE,STREET,4324,False,False,1134,NA,NA,NA,08B,1154134,1896793,2002,ERROR,41.872628267,-87.709573266,"(41.872628267, -87.709573266)" +8586946,HV261160,2012-04-25 19:30:00,103XX S TORRENCE AVE,0810,THEFT,OVER $500,RESIDENCE,3718,False,False,434,4,10,51,06,1195470,1836864,2012,2012-10-05 12:01:25,41.707255067,-87.559792308,"(41.707255067, -87.559792308)" +4342220,HL629648,2005-09-22 17:00:00,021XX N LAWLER AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3886,True,False,2522,25,31,19,08B,1142418,1913633,2005,ERROR,41.919064771,-87.752169679,"(41.919064771, -87.752169679)" +9280006,HW424844,2013-08-25 21:30:00,001XX W 95TH ST,0890,THEFT,FROM BUILDING,RESTAURANT,4034,False,False,634,6,21,49,06,1176654,1841992,2013,ERROR,41.721770166,-87.628542273,"(41.721770166, -87.628542273)" +2813480,HJ464372,2003-06-30 18:40:01,005XX S COLUMBUS DR,0820,THEFT,$500 AND UNDER,PARK PROPERTY,143,True,False,124,1,2,32,06,1178319,1898125,2003,2014-04-12 12:43:35,41.875766752,-87.620739245,"(41.875766752, -87.620739245)" +3561152,HK648531,2004-09-26 04:30:45,110XX S HALSTED ST,0560,ASSAULT,SIMPLE,OTHER,4272,True,False,2233,22,34,75,08A,1172920,1831338,2004,ERROR,41.692617079,-87.642532293,"(41.692617079, -87.642532293)" +10050076,HY238761,2015-04-28 07:55:00,082XX S MARYLAND AVE,031A,ROBBERY,ARMED: HANDGUN,ALLEY,3860,False,False,631,6,8,44,03,1183383,1850376,2015,2015-05-05 12:47:16,41.744622997,-87.603634954,"(41.744622997, -87.603634954)" +8001148,HT233085,2011-04-03 23:15:00,107XX S BENSLEY AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,638,False,True,434,4,10,51,26,1194521,1834420,2011,2011-06-04 13:32:13,41.700571822,-87.563347504,"(41.700571822, -87.563347504)" +6228780,HP311138,2008-05-01 15:18:59,0000X E GARFIELD BLVD,0560,ASSAULT,SIMPLE,RESTAURANT,1289,False,False,233,2,20,40,08A,1177260,1868390,2008,ERROR,41.794195672,-87.625527066,"(41.794195672, -87.625527066)" +6779900,HR184949,2009-02-23 16:00:00,010XX W GARFIELD BLVD,0820,THEFT,$500 AND UNDER,RESIDENCE,477,False,False,712,7,16,68,06,1170399,1868173,2009,2009-06-04 15:49:19,41.79375246,-87.65069229,"(41.79375246, -87.65069229)" +7426826,HS229492,2010-03-28 01:59:00,066XX N OLMSTED AVE,0460,BATTERY,SIMPLE,PARKING LOT/GARAGE(NON.RESID.),643,True,False,1612,16,41,9,08B,1124889,1943652,2010,ERROR,42.001748276,-87.815908566,"(42.001748276, -87.815908566)" +2466429,HH796219,2002-11-22 20:00:00,074XX S INDIANA AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,4674,False,True,323,NA,6,69,26,1178843,1855836,2002,ERROR,41.759710302,-87.620104123,"(41.759710302, -87.620104123)" +4607990,HM202545,2006-02-26 12:00:00,069XX S CORNELL AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,1838,False,False,332,3,5,43,05,1188596,1859060,2006,2006-05-03 04:52:17,41.768329782,-87.584257056,"(41.768329782, -87.584257056)" +4704001,HM309457,2006-04-22 00:00:00,043XX W 59TH ST,1305,CRIMINAL DAMAGE,CRIMINAL DEFACEMENT,RESIDENTIAL YARD (FRONT/BACK),3755,False,False,813,8,13,62,14,1148566,1865172,2006,ERROR,41.785965095,-87.730830299,"(41.785965095, -87.730830299)" +4742590,HM348904,2006-05-13 16:30:00,029XX E 80TH ST,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,STREET,693,True,False,422,4,7,46,15,1196921,1852523,2006,ERROR,41.75018879,-87.553959645,"(41.75018879, -87.553959645)" +6917090,HR321572,2009-05-14 22:30:00,022XX N NAGLE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3547,True,True,2512,25,36,19,08B,1133001,1914445,2009,2009-03-06 14:53:51,41.921462904,-87.786750394,"(41.921462904, -87.786750394)" +6072784,HP171029,2008-02-11 20:52:00,013XX S CANAL ST,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,GROCERY FOOD STORE,3576,False,False,131,1,2,28,11,1173296,1893975,2008,ERROR,41.864491821,-87.639304867,"(41.864491821, -87.639304867)" +8412665,HT645859,2011-12-24 07:30:00,021XX N CALIFORNIA AVE,0340,ROBBERY,ATTEMPT: STRONGARM-NO WEAPON,SIDEWALK,1132,False,False,1431,14,1,22,03,1157384,1914494,2011,2012-12-01 19:07:21,41.921135948,-87.69715911,"(41.921135948, -87.69715911)" +3341445,HK378381,2004-05-20 22:30:00,002XX E OHIO ST,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),3433,False,False,1834,18,42,8,06,1178331,1904297,2004,2014-04-12 12:43:35,41.892702762,-87.620506953,"(41.892702762, -87.620506953)" +6468782,HP546433,2008-09-01 02:01:30,037XX N HALSTED ST,0460,BATTERY,SIMPLE,SIDEWALK,4456,False,False,2324,19,46,6,08B,1170291,1925093,2008,2008-09-10 20:44:50,41.949947525,-87.649425319,"(41.949947525, -87.649425319)" +8339679,HT573293,2011-11-03 13:20:00,007XX E 60TH ST,1330,CRIMINAL TRESPASS,TO LAND,RESIDENCE,4190,False,False,313,3,20,42,26,1182232,1865350,2011,2011-04-11 10:08:19,41.785739862,-87.607389268,"(41.785739862, -87.607389268)" +1950234,HH139194,2002-01-18 17:00:00,009XX W HURON ST,0810,THEFT,OVER $500,OTHER,4875,False,False,1323,NA,NA,NA,06,1169707,1905109,2002,2014-04-12 12:43:35,41.895123178,-87.652155546,"(41.895123178, -87.652155546)" +7041908,HR449221,2009-07-26 01:00:00,015XX N OAKLEY BLVD,0810,THEFT,OVER $500,STREET,2985,False,False,1424,14,1,24,06,1160755,1910100,2009,ERROR,41.9090092,-87.684895322,"(41.9090092, -87.684895322)" +6768049,HR182077,2009-02-21 17:10:00,119XX S MICHIGAN AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1367,False,False,532,5,9,53,14,1178941,1825981,2009,ERROR,41.677782065,-87.620650719,"(41.677782065, -87.620650719)" +3758036,HL123864,2005-01-14 07:45:00,068XX S CARPENTER ST,031A,ROBBERY,ARMED: HANDGUN,STREET,3785,False,False,724,7,17,68,03,1170445,1859718,2005,ERROR,41.770549943,-87.65076972,"(41.770549943, -87.65076972)" +9421792,HW565556,2013-12-10 12:10:00,065XX S SANGAMON ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,3539,False,True,723,7,17,68,08B,1171144,1861336,2013,ERROR,41.774974679,-87.648160214,"(41.774974679, -87.648160214)" +7746209,HS553647,2010-10-08 08:30:00,075XX S KINGSTON AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,APARTMENT,1033,False,True,421,4,7,43,14,1194540,1855586,2010,ERROR,41.758652752,-87.562583957,"(41.758652752, -87.562583957)" +3024764,HJ733776,2003-11-01 00:00:00,086XX S HERMITAGE AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,4585,False,False,614,6,18,71,05,1166139,1847570,2003,ERROR,41.73730682,-87.666898779,"(41.73730682, -87.666898779)" +6155450,HP244652,2008-03-26 09:00:00,047XX S KEDVALE AVE,0820,THEFT,$500 AND UNDER,VEHICLE NON-COMMERCIAL,11,False,False,815,8,23,57,06,1149453,1872819,2008,ERROR,41.806932525,-87.727380443,"(41.806932525, -87.727380443)" +2806099,HJ459080,2003-06-28 11:01:33,052XX S MARSHFIELD AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3964,False,False,932,9,16,61,08B,1166277,1869726,2003,ERROR,41.798102902,-87.665763163,"(41.798102902, -87.665763163)" +1454319,G168841,2001-03-24 21:20:00,014XX S LOOMIS ST,2024,NARCOTICS,POSS: HEROIN(WHITE),CHA PARKING LOT/GROUNDS,4558,True,False,1231,NA,NA,NA,18,1167225,1893379,2001,ERROR,41.862988863,-87.661608429,"(41.862988863, -87.661608429)" +5328640,HN178376,2007-02-15 17:43:21,0000X W 47TH ST,1330,CRIMINAL TRESPASS,TO LAND,PARK PROPERTY,1763,True,False,231,2,3,38,26,1176599,1873813,2007,ERROR,41.809091808,-87.627787677,"(41.809091808, -87.627787677)" +5030418,HM631435,2006-09-30 04:01:00,012XX S SAWYER AVE,0560,ASSAULT,SIMPLE,RESIDENCE,1035,True,False,1022,10,24,29,08A,1154859,1893942,2006,2006-06-10 04:52:45,41.86479033,-87.706987824,"(41.86479033, -87.706987824)" +9694653,HX345023,2014-07-14 16:10:00,038XX W CHICAGO AVE,2028,NARCOTICS,POSS: SYNTHETIC DRUGS,OTHER,1780,True,False,1112,11,27,23,18,1150812,1905031,2014,ERROR,41.89529982,-87.7215543,"(41.89529982, -87.7215543)" +2506466,HH847658,2002-12-19 09:26:25,010XX S LOOMIS ST,0460,BATTERY,SIMPLE,SIDEWALK,2190,False,False,1231,NA,2,28,08B,1167166,1895406,2002,ERROR,41.868552388,-87.661766802,"(41.868552388, -87.661766802)" +3995692,HL357532,2005-05-14 23:00:00,106XX S MACKINAW AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,4448,False,False,432,4,10,52,14,1200233,1834857,2005,ERROR,41.70162895,-87.542418081,"(41.70162895, -87.542418081)" +1406698,G119938,2001-03-01 16:31:26,023XX W MADISON ST,0320,ROBBERY,STRONGARM - NO WEAPON,STREET,1768,False,False,1332,NA,NA,NA,03,1161051,1900007,2001,ERROR,41.881307035,-87.684088522,"(41.881307035, -87.684088522)" +2063190,HH283739,2002-04-03 19:45:00,040XX N LINCOLN AV,1330,CRIMINAL TRESPASS,TO LAND,DRUG STORE,622,True,False,1912,NA,NA,NA,26,1162045,1927127,2002,ERROR,41.955705485,-87.679679583,"(41.955705485, -87.679679583)" +2187834,HH444731,2002-06-15 21:22:16,004XX E 75TH ST,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,1461,False,False,323,NA,6,69,07,1180456,1855421,2002,ERROR,41.758534647,-87.61420526,"(41.758534647, -87.61420526)" +4856312,HM458067,2006-07-06 11:34:00,037XX S WESTERN AVE,0460,BATTERY,SIMPLE,CTA BUS,2800,False,False,913,9,12,59,08B,1161018,1879907,2006,ERROR,41.826151267,-87.684767164,"(41.826151267, -87.684767164)" +4948851,HM562638,2006-08-25 20:00:00,069XX S LOOMIS BLVD,0890,THEFT,FROM BUILDING,RESIDENCE,3312,False,False,734,7,17,67,06,1168156,1858673,2006,ERROR,41.767731872,-87.65919032,"(41.767731872, -87.65919032)" +6687849,HR102460,2009-01-02 15:29:00,105XX S EGGLESTON AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,1562,False,False,2233,22,34,49,18,1175109,1835065,2009,2009-02-01 16:25:12,41.702796067,-87.634407275,"(41.702796067, -87.634407275)" +7550137,HS346624,2010-06-07 05:46:00,007XX W GARFIELD BLVD,0610,BURGLARY,FORCIBLE ENTRY,SMALL RETAIL STORE,4436,False,False,934,9,3,61,05,1172153,1868433,2010,ERROR,41.794427528,-87.644252869,"(41.794427528, -87.644252869)" +8453368,HV131074,2012-01-23 22:00:00,068XX N SHERIDAN RD,0820,THEFT,$500 AND UNDER,STREET,44,False,False,2431,24,49,1,06,1167006,1945516,2012,ERROR,42.006060138,-87.660911233,"(42.006060138, -87.660911233)" +1754134,G565506,2001-09-20 22:05:00,007XX E 111 ST,0460,BATTERY,SIMPLE,POLICE FACILITY/VEH PARKING LOT,715,False,False,531,NA,NA,NA,08B,1183305,1831462,2001,ERROR,41.692722515,-87.604507439,"(41.692722515, -87.604507439)" +2522777,HJ100697,2002-12-31 23:30:00,116XX S VINCENNES AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2478,False,False,2234,NA,34,75,14,1165846,1827777,2002,ERROR,41.682997842,-87.668532113,"(41.682997842, -87.668532113)" +1864439,G708189,2001-11-25 13:30:00,007XX N LARAMIE AV,0325,ROBBERY,VEHICULAR HIJACKING,ALLEY,1350,False,False,1524,NA,NA,NA,03,1141513,1904551,2001,ERROR,41.894159515,-87.755719492,"(41.894159515, -87.755719492)" +6934825,HR340739,2009-05-25 21:45:00,061XX S RACINE AVE,2027,NARCOTICS,POSS: CRACK,SIDEWALK,1582,True,False,713,7,16,67,18,1169326,1864268,2009,ERROR,41.783059997,-87.654739922,"(41.783059997, -87.654739922)" +7981176,HT213299,2011-03-21 13:59:00,001XX W 35TH ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,CTA TRAIN,4539,True,False,924,9,11,34,18,1175731,1881812,2011,ERROR,41.831061278,-87.630731407,"(41.831061278, -87.630731407)" +4065862,HL408235,2005-06-08 22:48:24,002XX W 87TH ST,0840,THEFT,FINANCIAL ID THEFT: OVER $300,WAREHOUSE,3963,False,False,622,6,21,44,06,1176403,1847255,2005,ERROR,41.736218161,-87.629303966,"(41.736218161, -87.629303966)" +2034759,HH247819,2002-03-16 10:00:00,018XX N DRAKE AV,0930,MOTOR VEHICLE THEFT,THEFT/RECOVERY: AUTOMOBILE,STREET,776,False,False,1422,NA,NA,NA,07,1152570,1911949,2002,ERROR,41.914248882,-87.714914388,"(41.914248882, -87.714914388)" +5544920,HN356358,2007-05-21 17:00:00,090XX S LAFLIN ST,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,3133,False,False,2222,22,21,73,26,1167966,1844587,2007,ERROR,41.729082013,-87.660290623,"(41.729082013, -87.660290623)" +4959301,HM571754,2006-08-29 22:30:00,015XX E 87TH ST,0610,BURGLARY,FORCIBLE ENTRY,SMALL RETAIL STORE,1634,True,False,412,4,8,48,05,1187761,1847553,2006,2008-02-05 01:04:48,41.736773407,-87.587683232,"(41.736773407, -87.587683232)" +7521660,HS325090,2010-05-25 00:00:00,030XX N PARKSIDE AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3579,False,False,2514,25,31,19,14,1138129,1919810,2010,ERROR,41.93609381,-87.767778496,"(41.93609381, -87.767778496)" +4982342,HM596076,2006-09-12 00:00:00,014XX N DAMEN AVE,0820,THEFT,$500 AND UNDER,VEHICLE NON-COMMERCIAL,35,False,False,1424,14,1,24,06,1162762,1909616,2006,2014-04-12 12:43:35,41.907639198,-87.677536132,"(41.907639198, -87.677536132)" +7403488,HS205438,2010-03-13 04:25:00,080XX S MAY ST,0810,THEFT,OVER $500,STREET,2170,False,False,612,6,21,71,06,1170015,1851280,2010,ERROR,41.74740433,-87.652590689,"(41.74740433, -87.652590689)" +3140084,HK132250,2004-01-18 11:06:46,024XX N RACINE AVE,0460,BATTERY,SIMPLE,STREET,2504,False,False,1933,19,32,7,08B,1167856,1916386,2004,ERROR,41.926108075,-87.658627954,"(41.926108075, -87.658627954)" +5902358,HN700127,2007-11-10 11:00:00,003XX S WACKER DR,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),749,False,False,112,1,2,32,06,1174015,1898799,2007,2014-04-12 12:43:35,41.877713219,-87.636521697,"(41.877713219, -87.636521697)" +4572931,HM163096,2006-02-04 14:00:00,045XX S WESTERN AVE,0820,THEFT,$500 AND UNDER,PARKING LOT/GARAGE(NON.RESID.),42,False,False,914,9,12,61,06,1161174,1874190,2006,2014-04-12 12:43:35,41.810459905,-87.684353246,"(41.810459905, -87.684353246)" +10012442,HY202116,2015-03-28 21:12:00,050XX N FRANCISCO AVE,2093,NARCOTICS,FOUND SUSPECT NARCOTICS,APARTMENT,1022,True,False,2031,20,40,4,26,1156167,1933292,2015,2015-04-04 12:43:24,41.972743598,-87.701121216,"(41.972743598, -87.701121216)" +6554328,HN700877,2007-11-11 04:00:00,076XX S RHODES AVE,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,STREET,4899,False,False,624,6,6,69,04B,1181189,1854379,2007,ERROR,41.755658442,-87.611550936,"(41.755658442, -87.611550936)" +4398984,HL638310,2005-10-05 11:25:00,011XX N MONTICELLO AVE,2095,NARCOTICS,ATTEMPT POSSESSION NARCOTICS,STREET,3766,True,False,1112,11,27,23,18,1151799,1907151,2005,ERROR,41.901097939,-87.717873412,"(41.901097939, -87.717873412)" +2797787,HJ448611,2003-06-23 07:00:00,057XX S ABERDEEN ST,0890,THEFT,FROM BUILDING,STREET,1540,False,False,712,7,16,68,06,1169915,1867003,2003,ERROR,41.79055238,-87.652501059,"(41.79055238, -87.652501059)" +4471342,HL761655,2005-11-29 09:57:17,071XX S LAFAYETTE AVE,1750,OFFENSE INVOLVING CHILDREN,CHILD ABUSE,RESIDENCE,3232,True,False,731,7,6,69,20,1177037,1857432,2005,ERROR,41.764130805,-87.626675,"(41.764130805, -87.626675)" +2751617,HJ389272,2003-05-27 08:16:40,028XX W 24TH BLVD,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, BUILDING",2758,False,False,1033,NA,12,30,08B,1157546,1887831,2003,ERROR,41.847966869,-87.697289982,"(41.847966869, -87.697289982)" +8965579,HW113481,2013-01-11 03:00:00,023XX N LOWELL AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,3640,False,False,2522,25,31,20,26,1146957,1915184,2013,2013-05-02 14:37:42,41.923235246,-87.735452966,"(41.923235246, -87.735452966)" +5171598,HM762872,2006-12-08 13:45:00,044XX W GLADYS AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,1177,False,True,1131,11,24,26,08B,1146659,1898021,2006,ERROR,41.876143827,-87.736986299,"(41.876143827, -87.736986299)" +5510052,HN329067,2007-05-08 00:30:00,049XX W BELDEN AVE,0890,THEFT,FROM BUILDING,APARTMENT,2656,False,True,2522,25,31,19,06,1142729,1914878,2007,ERROR,41.922475392,-87.750995953,"(41.922475392, -87.750995953)" +6808651,HR218704,2009-03-16 14:17:54,004XX N CENTRAL AVE,0460,BATTERY,SIMPLE,SIDEWALK,2744,False,False,1512,15,29,25,08B,1138919,1902473,2009,ERROR,41.888504748,-87.765297105,"(41.888504748, -87.765297105)" +4991519,HM449098,2006-07-01 22:02:41,071XX S MERRILL AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,4231,True,False,333,3,5,43,18,1191746,1858246,2006,ERROR,41.766020241,-87.572737379,"(41.766020241, -87.572737379)" +4052767,HL403567,2005-06-06 19:30:00,025XX S WASHTENAW AVE,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,STREET,3538,False,False,1034,10,12,30,04B,1158738,1886739,2005,ERROR,41.844945991,-87.692945166,"(41.844945991, -87.692945166)" +8032674,HT264580,2011-04-21 13:30:00,098XX S ELLIS AVE,0890,THEFT,FROM BUILDING,RESIDENCE,4298,False,False,511,5,8,50,06,1184583,1839875,2011,ERROR,41.715779068,-87.599566057,"(41.715779068, -87.599566057)" +6241707,HP331800,2008-05-12 08:00:00,047XX W HARRISON ST,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,APARTMENT,3322,False,False,1131,11,24,25,26,1144898,1896892,2008,ERROR,41.873079106,-87.743480642,"(41.873079106, -87.743480642)" +3134450,HK125197,2004-01-14 12:00:00,064XX S WINCHESTER AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,3991,True,False,726,7,15,67,07,1164415,1862214,2004,ERROR,41.777528496,-87.672803106,"(41.777528496, -87.672803106)" +1329888,G024689,2001-01-12 11:00:00,109XX S EDBROOKE AV,4650,OTHER OFFENSE,SEX OFFENDER: FAIL TO REGISTER,RESIDENCE,3264,True,False,513,NA,NA,NA,26,1179220,1832688,2001,ERROR,41.696180708,-87.619426047,"(41.696180708, -87.619426047)" +5756722,HN564951,2007-09-02 06:30:00,014XX W 77TH ST,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2012,False,False,612,6,17,71,14,1167956,1853723,2007,2007-06-09 01:56:11,41.754152713,-87.660065416,"(41.754152713, -87.660065416)" +8280908,HT515268,2011-09-26 15:06:00,057XX S SACRAMENTO AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,4992,False,False,824,8,14,63,05,1157396,1866517,2011,2011-01-10 09:51:22,41.789481609,-87.69841849,"(41.789481609, -87.69841849)" +7835980,HS637015,2010-11-28 22:35:00,103XX S ALBANY AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4609,False,True,2211,22,19,74,08B,1157431,1835903,2010,2010-08-12 16:32:38,41.705471163,-87.699117536,"(41.705471163, -87.699117536)" +3607193,HK688263,2004-10-15 11:56:00,081XX S WOODLAWN AVE,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,756,False,False,411,4,8,45,07,1185584,1851347,2004,ERROR,41.74723602,-87.595539795,"(41.74723602, -87.595539795)" +9468000,HX121120,2014-01-18 23:00:00,015XX S KEDZIE AVE,0820,THEFT,$500 AND UNDER,RESIDENCE,155,False,False,1022,10,24,29,06,1155311,1892547,2014,ERROR,41.860953237,-87.705365981,"(41.860953237, -87.705365981)" +6576153,HP648649,2008-10-25 10:30:00,041XX W POTOMAC AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,4740,False,False,2534,25,37,23,14,1148653,1908301,2008,ERROR,41.904315009,-87.729399290999993,"(41.904315009, -87.729399291)" +3864612,HL239886,2005-01-21 00:01:00,100XX W OHARE ST,0890,THEFT,FROM BUILDING,AIRPORT/AIRCRAFT,4122,False,False,1651,16,41,76,06,1100635,1934208,2005,ERROR,41.976200173,-87.905312411,"(41.976200173, -87.905312411)" +6466118,HP543667,2008-08-30 13:24:00,001XX N STATE ST,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,3056,True,False,122,1,42,32,06,1176390,1900949,2008,2008-02-09 08:07:10,41.883559699,-87.627736496,"(41.883559699, -87.627736496)" +5519425,HL290196,2005-04-12 11:30:00,010XX N RIDGEWAY AVE,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,ALLEY,2680,True,False,1112,NA,27,23,26,NA,NA,2005,2007-08-08 02:59:32,NA,NA, +2939383,HJ625230,2003-09-11 10:00:00,021XX N DAMEN AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,3838,False,False,1432,14,32,22,26,1162713,1914372,2003,ERROR,41.92069102,-87.677582552,"(41.92069102, -87.677582552)" +2424495,HH744202,2001-10-19 13:00:00,073XX S HALSTED ST,1120,DECEPTIVE PRACTICE,FORGERY,CURRENCY EXCHANGE,4149,False,False,733,NA,17,68,10,1172207,1855826,2001,ERROR,41.759831266,-87.644425197,"(41.759831266, -87.644425197)" +9212260,HW358008,2013-07-11 15:00:00,052XX W LE MOYNE ST,0810,THEFT,OVER $500,APARTMENT,4849,False,True,2532,25,37,25,06,1141354,1909518,2013,2013-12-07 09:06:47,41.907792476,-87.756180729,"(41.907792476, -87.756180729)" +4506402,HL736000,2005-11-15 03:42:50,0000X E BURTON PL,2022,NARCOTICS,POSS: COCAINE,STREET,2650,True,False,1824,18,43,8,18,1176556,1910448,2005,ERROR,41.909621673,-87.626839707,"(41.909621673, -87.626839707)" +1344183,G043772,2001-01-21 16:50:00,042XX S WENTWORTH AV,1330,CRIMINAL TRESPASS,TO LAND,GAS STATION,3960,False,False,935,NA,NA,NA,26,1175608,1876581,2001,ERROR,41.816709722,-87.631339516,"(41.816709722, -87.631339516)" +1563501,G320369,2001-06-03 04:40:00,035XX S FEDERAL ST,0460,BATTERY,SIMPLE,CHA APARTMENT,791,False,True,211,NA,NA,NA,08B,1176255,1881268,2001,ERROR,41.829556721,-87.628825201,"(41.829556721, -87.628825201)" +3340028,HK377408,2004-05-20 20:54:54,007XX S WESTERN AVE,1330,CRIMINAL TRESPASS,TO LAND,GROCERY FOOD STORE,555,True,False,1224,12,2,28,26,1160541,1896784,2004,ERROR,41.872473399,-87.68605047,"(41.872473399, -87.68605047)" +9985418,HY175099,2015-03-06 23:40:00,015XX N WELLS ST,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,RESTAURANT,3210,False,False,1821,18,43,8,11,1174460,1910637,2015,ERROR,41.910187414,-87.634533777,"(41.910187414, -87.634533777)" +5212650,HM793429,2006-12-24 11:45:00,041XX W VAN BUREN ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1567,False,False,1132,11,24,26,14,1148616,1897735,2006,2007-02-01 06:08:05,41.875321473,-87.729808188,"(41.875321473, -87.729808188)" +10002890,HY192231,2015-03-20 16:27:00,033XX W 60TH ST,2826,OTHER OFFENSE,HARASSMENT BY ELECTRONIC MEANS,RESIDENCE,509,False,False,822,8,16,66,26,1154876,1864671,2015,ERROR,41.784466611,-87.70770788,"(41.784466611, -87.70770788)" +6237879,HP323989,2008-05-08 10:35:00,053XX S WESTERN BLVD,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,PARKING LOT/GARAGE(NON.RESID.),2025,False,False,915,9,16,63,07,1161471,1869210,2008,2008-11-05 13:01:30,41.796788001,-87.683402001,"(41.796788001, -87.683402001)" +9198183,HW343691,2013-07-01 12:30:00,078XX S LAFLIN ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1113,False,False,612,6,17,71,14,1167515,1852777,2013,2013-02-07 08:31:12,41.751566217,-87.661708602,"(41.751566217, -87.661708602)" +9914993,HY104705,2015-01-05 05:00:00,045XX S DREXEL BLVD,0820,THEFT,$500 AND UNDER,APARTMENT,127,False,False,221,2,4,39,06,1182916,1875077,2015,2015-12-01 12:48:04,41.812415676,-87.604579073,"(41.812415676, -87.604579073)" +5812687,HN606869,2007-06-14 15:00:00,056XX S DORCHESTER AVE,0890,THEFT,FROM BUILDING,GAS STATION,2970,False,False,2133,2,5,41,06,1186553,1867773,2007,2007-10-10 01:49:09,41.792287561,-87.591469992,"(41.792287561, -87.591469992)" +1758189,G568662,2001-09-22 11:17:43,031XX W 26 ST,5001,OTHER OFFENSE,OTHER CRIME INVOLVING PROPERTY,PARKING LOT/GARAGE(NON.RESID.),992,True,False,1033,NA,NA,NA,26,1156100,1886534,2001,ERROR,41.844437017,-87.70263184,"(41.844437017, -87.70263184)" +6402809,HP474280,2008-07-23 16:00:00,089XX S YATES BLVD,0842,THEFT,AGG: FINANCIAL ID THEFT,RESIDENCE,1990,False,False,413,4,7,48,06,1193691,1845865,2008,2008-08-08 18:07:56,41.731998345,-87.566013112,"(41.731998345, -87.566013112)" +5475427,HN298043,2007-04-22 08:35:00,053XX S KEDZIE AVE,1513,PROSTITUTION,SOLICIT FOR BUSINESS,STREET,1380,True,False,911,9,14,63,16,1155993,1869052,2007,ERROR,41.796466324,-87.703494798,"(41.796466324, -87.703494798)" +1523992,G255343,2001-05-04 09:55:00,056XX S ROCKWELL ST,141C,WEAPONS VIOLATION,UNLAWFUL USE OTHER DANG WEAPON,"SCHOOL, PUBLIC, BUILDING",3922,True,False,824,NA,NA,NA,15,1159962,1867094,2001,ERROR,41.791012589,-87.688993852,"(41.791012589, -87.688993852)" +2921705,HJ600586,2003-09-01 01:00:00,023XX N KARLOV AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3929,False,True,2525,25,31,20,08B,1148703,1915194,2003,ERROR,41.923229102,-87.729037228,"(41.923229102, -87.729037228)" +1854395,G695770,2001-11-19 10:10:00,066XX S PULASKI RD,0810,THEFT,OVER $500,COMMERCIAL / BUSINESS OFFICE,3532,False,False,833,NA,NA,NA,06,1150890,1860070,2001,2014-04-12 12:43:35,41.771919396,-87.722442165,"(41.771919396, -87.722442165)" +1908796,G765379,2001-12-23 04:20:00,055XX W VAN BUREN ST,0460,BATTERY,SIMPLE,RESIDENCE,1688,False,False,1522,NA,NA,NA,08B,1139450,1897421,2001,ERROR,41.874631745,-87.763470245,"(41.874631745, -87.763470245)" +8164922,HT399379,2011-07-15 23:40:00,076XX S CICERO AVE,0560,ASSAULT,SIMPLE,STREET,1389,False,False,833,8,13,65,08A,1145766,1853738,2011,ERROR,41.75464162,-87.741385158,"(41.75464162, -87.741385158)" +3254937,HK279665,2004-04-03 10:00:00,065XX N BOSWORTH AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,3491,False,False,2432,24,40,1,05,1164802,1943333,2004,ERROR,42.000117152,-87.669082287,"(42.000117152, -87.669082287)" +5390304,HN233940,2007-03-18 19:50:00,006XX N ALBANY AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,VEHICLE NON-COMMERCIAL,1362,False,True,1313,12,27,23,08B,1155550,1904304,2007,ERROR,41.893210839,-87.704172295,"(41.893210839, -87.704172295)" +8413638,HT647149,2011-12-25 17:30:00,069XX S CAMPBELL AVE,0330,ROBBERY,AGGRAVATED,SIDEWALK,1377,False,False,832,8,15,66,03,1160874,1858299,2011,ERROR,41.766859084,-87.685892542,"(41.766859084, -87.685892542)" +9527581,HX181802,2014-03-12 12:15:00,054XX S WINCHESTER AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,1147,False,True,932,9,16,61,08B,1164316,1868765,2014,ERROR,41.795507366,-87.672981591,"(41.795507366, -87.672981591)" +5133690,HM731231,2006-11-20 14:00:00,073XX N HARLEM AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,2303,False,False,1611,16,41,9,26,1127349,1948389,2006,ERROR,42.014705871,-87.806751157,"(42.014705871, -87.806751157)" +1995039,HH153002,2002-01-28 07:00:00,074XX N DAMEN AV,0460,BATTERY,SIMPLE,RESIDENCE,794,False,True,2424,NA,NA,NA,08B,1161699,1949553,2002,ERROR,42.01725048,-87.680322966,"(42.01725048, -87.680322966)" +4296516,HL531043,2005-08-05 23:09:43,006XX N CENTRAL PARK AVE,2024,NARCOTICS,POSS: HEROIN(WHITE),STREET,2630,True,False,1122,11,27,23,18,1152231,1903962,2005,ERROR,41.892338494,-87.716370862,"(41.892338494, -87.716370862)" +5458813,HN287100,2007-04-16 17:45:00,051XX S ASHLAND AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,PARKING LOT/GARAGE(NON.RESID.),4687,True,True,932,9,16,61,08B,1166579,1870797,2007,ERROR,41.801035411,-87.66462512,"(41.801035411, -87.66462512)" +1556046,G312776,2001-05-30 16:45:00,014XX E 69 ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE PORCH/HALLWAY,1587,False,False,332,NA,NA,NA,14,1187298,1859575,2001,ERROR,41.769773909,-87.588998442,"(41.769773909, -87.588998442)" +6713807,HR129777,2009-01-19 21:00:00,020XX N MOZART ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,4049,False,False,1414,14,35,22,14,1157024,1913723,2009,ERROR,41.919027585,-87.698502808,"(41.919027585, -87.698502808)" +1940740,HH119927,2002-01-11 12:00:00,047XX S UNION AV,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, BUILDING",3229,True,False,935,NA,NA,NA,08B,1172475,1873203,2002,ERROR,41.807509816,-87.642931616,"(41.807509816, -87.642931616)" +3362955,HK409648,2004-06-04 17:00:00,055XX W QUINCY ST,051A,ASSAULT,AGGRAVATED: HANDGUN,STREET,3449,False,False,1522,15,29,25,04A,1139492,1898492,2004,ERROR,41.877569945,-87.763289921,"(41.877569945, -87.763289921)" +1892993,G744161,2001-12-12 11:40:00,077XX W HOWARD ST,0840,THEFT,FINANCIAL ID THEFT: OVER $300,RESIDENCE,2824,False,False,1611,NA,NA,NA,06,1123943,1949779,2001,ERROR,42.018576967,-87.819253498,"(42.018576967, -87.819253498)" +4987543,HM599912,2006-09-13 23:41:57,032XX S LITUANICA AVE,1305,CRIMINAL DAMAGE,CRIMINAL DEFACEMENT,PARKING LOT/GARAGE(NON.RESID.),2748,True,False,924,9,11,60,14,1170787,1883697,2006,ERROR,41.836343419,-87.648816066,"(41.836343419, -87.648816066)" +8978243,HW122178,2013-01-04 11:00:00,017XX N LOTUS AVE,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,2463,False,False,2532,25,37,25,26,1139764,1910834,2013,2013-12-02 12:25:23,41.91143294,-87.761989425,"(41.91143294, -87.761989425)" +9287934,HW432629,2013-09-01 03:30:00,023XX S HOMAN AVE,0460,BATTERY,SIMPLE,SIDEWALK,2341,False,False,1024,10,22,30,08B,1154021,1888439,2013,2013-02-09 12:27:03,41.849706206,-87.710210751,"(41.849706206, -87.710210751)" +3601383,HK694936,2004-09-18 00:00:00,002XX N KILBOURN AVE,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,RESIDENCE,3628,False,False,1113,11,28,26,11,1146400,1900624,2004,ERROR,41.883291699,-87.737870956,"(41.883291699, -87.737870956)" +7719567,HS527233,2010-09-21 20:50:00,051XX W WELLINGTON AVE,4387,OTHER OFFENSE,VIOLATE ORDER OF PROTECTION,RESIDENCE,3751,False,True,2521,25,31,19,26,1141674,1919503,2010,2010-11-10 15:54:50,41.935186459,-87.754757729,"(41.935186459, -87.754757729)" +6550412,HP624139,2008-10-13 00:00:37,052XX S MARSHFIELD AVE,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,STREET,2524,True,False,932,9,16,61,15,1166269,1870015,2008,ERROR,41.798896123,-87.66578427,"(41.798896123, -87.66578427)" +2902949,HJ579061,2003-07-16 15:58:00,046XX W 55TH ST,1206,DECEPTIVE PRACTICE,"THEFT BY LESSEE,MOTOR VEH",OTHER,4441,True,False,813,8,23,56,11,1146337,1867704,2003,ERROR,41.792955883,-87.738938839,"(41.792955883, -87.738938839)" +3505136,HK581743,2004-08-25 18:04:00,071XX W 64TH PL,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,4733,False,False,812,8,23,64,26,1129743,1860965,2004,ERROR,41.774761494,-87.799941841,"(41.774761494, -87.799941841)" +8081212,HT310002,2011-05-23 11:30:00,046XX N SHERIDAN RD,0460,BATTERY,SIMPLE,SIDEWALK,3485,False,False,2312,19,46,3,08B,1168819,1931361,2011,ERROR,41.967179243,-87.654653712,"(41.967179243, -87.654653712)" +1652291,G430211,2001-07-22 12:15:00,012XX N PARKSIDE AV,2230,LIQUOR LAW VIOLATION,ILLEGAL CONSUMPTION BY MINOR,STREET,1369,True,False,2531,NA,NA,NA,22,1138416,1907506,2001,ERROR,41.902325047,-87.767022333,"(41.902325047, -87.767022333)" +7784284,HS576050,2010-10-21 19:27:56,008XX E 89TH ST,2027,NARCOTICS,POSS: CRACK,APARTMENT,4100,True,False,632,6,8,44,18,1183562,1846188,2010,2010-10-11 15:04:15,41.733126496,-87.603109289,"(41.733126496, -87.603109289)" +4392495,HL685535,2005-10-18 07:00:00,008XX N LATROBE AVE,0810,THEFT,OVER $500,STREET,4453,False,False,1524,15,37,25,06,1141162,1905112,2005,2014-04-12 12:43:35,41.895705443,-87.756994782,"(41.895705443, -87.756994782)" +8322510,HT548001,2011-10-17 17:30:00,067XX S CLYDE AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,4763,False,False,331,3,5,43,26,1191419,1860438,2011,ERROR,41.772043187,-87.573864982,"(41.772043187, -87.573864982)" +5784220,HN581658,2007-06-08 23:40:00,003XX S WASHTENAW AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,SIDEWALK,2376,False,True,1125,11,2,27,08B,1158395,1898272,2007,ERROR,41.876600753,-87.693888703,"(41.876600753, -87.693888703)" +1473650,G192572,2001-04-05 10:10:00,028XX W ARTHINGTON ST,2024,NARCOTICS,POSS: HEROIN(WHITE),STREET,1925,True,False,1135,NA,NA,NA,18,1157618,1895874,2001,ERROR,41.870036252,-87.6968069,"(41.870036252, -87.6968069)" +7157534,HR565848,2009-10-01 15:40:00,079XX S DAMEN AVE,0320,ROBBERY,STRONGARM - NO WEAPON,SIDEWALK,531,False,False,611,6,18,71,03,1164362,1852231,2009,2009-10-10 16:21:59,41.750134873,-87.673278237,"(41.750134873, -87.673278237)" +3712271,HK684159,2004-10-13 13:15:00,072XX S SOUTH CHICAGO AVE,5011,OTHER OFFENSE,LICENSE VIOLATION,RESIDENCE-GARAGE,824,False,False,323,3,5,69,26,1183382,1857501,2004,2007-11-06 15:52:33,41.764174754,-87.603417126,"(41.764174754, -87.603417126)" +1741181,G550640,2001-09-12 14:00:00,021XX W RANDOLPH ST,1750,OFFENSE INVOLVING CHILDREN,CHILD ABUSE,RESIDENCE,4543,False,True,1332,NA,NA,NA,20,1162226,1901155,2001,ERROR,41.884432772,-87.679741878,"(41.884432772, -87.679741878)" +6882076,HR288440,2009-04-25 18:00:00,046XX S RICHMOND ST,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,4485,False,False,912,9,14,58,14,1157528,1873541,2009,ERROR,41.80875374,-87.697744048,"(41.80875374, -87.697744048)" +8303183,HT520944,2011-09-30 03:15:00,002XX E 121ST PL,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,2521,False,False,532,5,9,53,08B,1180264,1824414,2011,ERROR,41.673451854,-87.615855891,"(41.673451854, -87.615855891)" +9010374,HW157593,2013-02-15 12:30:00,051XX S CALIFORNIA AVE,0820,THEFT,$500 AND UNDER,PARKING LOT/GARAGE(NON.RESID.),94,False,False,923,9,14,63,06,1158620,1870342,2013,ERROR,41.799953035,-87.693826098,"(41.799953035, -87.693826098)" +7689122,HS495203,2010-08-31 19:00:00,003XX E OHIO ST,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,RESIDENCE,4329,False,False,1834,18,42,8,11,1178952,1904236,2010,ERROR,41.892521195,-87.618228164,"(41.892521195, -87.618228164)" +1438982,G153765,2001-03-17 23:38:07,062XX S ST LAWRENCE AV,1821,NARCOTICS,MANU/DEL:CANNABIS 10GM OR LESS,STREET,4128,True,False,313,NA,NA,NA,18,1181276,1863401,2001,ERROR,41.780413719,-87.610954415,"(41.780413719, -87.610954415)" +9340354,HW484281,2013-10-07 23:00:00,096XX S WENTWORTH AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,2824,False,False,511,5,21,49,05,1176693,1840729,2013,ERROR,41.718303448,-87.628437301,"(41.718303448, -87.628437301)" +3044554,HJ746479,2003-11-08 10:25:00,018XX W 51ST ST,2017,NARCOTICS,MANU/DELIVER:CRACK,SIDEWALK,1194,True,False,932,9,16,61,18,1165175,1870833,2003,ERROR,41.801164057,-87.669773064,"(41.801164057, -87.669773064)" +6080834,HP159980,2008-02-05 10:10:00,010XX N HUDSON AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,964,True,False,1823,18,27,8,18,1172947,1907120,2008,ERROR,41.900570272,-87.640196289,"(41.900570272, -87.640196289)" +4594393,HM187557,2006-02-18 03:50:00,027XX N MILWAUKEE AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,OTHER,4685,False,False,1412,14,35,22,14,1153678,1918136,2006,2014-04-12 12:43:35,41.931204528,-87.710678708,"(41.931204528, -87.710678708)" +9681055,HX331130,2014-07-04 15:00:00,021XX W PETERSON AVE,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,4484,True,False,2413,24,40,2,06,1161031,1939882,2014,2014-11-07 12:39:31,41.990726931,-87.683051337,"(41.990726931, -87.683051337)" +6696343,HR110836,2009-01-07 19:30:00,021XX W SHAKESPEARE AVE,0320,ROBBERY,STRONGARM - NO WEAPON,STREET,3344,False,False,1432,14,32,22,03,1161425,1914413,2009,2009-03-02 19:02:28,41.920830456,-87.682313785,"(41.920830456, -87.682313785)" +7313029,HS117588,2010-01-13 00:21:00,032XX N CLARK ST,2022,NARCOTICS,POSS: COCAINE,STREET,4814,True,False,1924,19,44,6,18,1169902,1921495,2010,ERROR,41.94008298,-87.650960519,"(41.94008298, -87.650960519)" +8914447,HV587762,2012-12-03 19:40:00,077XX S ASHLAND AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,4861,True,False,612,6,17,71,18,1167067,1853091,2012,2012-03-12 20:28:11,41.752437462,-87.663341352,"(41.752437462, -87.663341352)" +8813683,HV486788,2012-09-22 09:00:00,017XX E 56TH ST,0890,THEFT,FROM BUILDING,APARTMENT,3006,False,False,235,2,5,41,06,1188630,1868244,2012,ERROR,41.793530583,-87.583839014,"(41.793530583, -87.583839014)" +2915956,HJ592169,2003-08-28 08:00:00,078XX S GREENWOOD AVE,0560,ASSAULT,SIMPLE,RESIDENCE,3119,False,False,624,6,8,69,08A,1184870,1853297,2003,2007-11-06 15:52:33,41.752603788,-87.598095,"(41.752603788, -87.598095)" +5426156,HN256370,2007-03-29 18:30:00,0000X W CERMAK RD,0810,THEFT,OVER $500,STREET,1448,False,False,134,1,3,33,06,1176378,1889737,2007,2014-04-12 12:43:35,41.852793534,-87.628118788,"(41.852793534, -87.628118788)" +1331639,G028143,2001-01-12 21:00:00,034XX S KING DR,0820,THEFT,$500 AND UNDER,DRUG STORE,282,True,False,2122,NA,NA,NA,06,1179512,1882402,2001,2014-04-12 12:43:35,41.832594585,-87.616840751,"(41.832594585, -87.616840751)" +1947050,HH132691,2002-01-17 18:49:57,005XX E 44 PL,0460,BATTERY,SIMPLE,RESIDENCE,4529,False,False,222,NA,NA,NA,08B,1180563,1875665,2002,ERROR,41.814083626,-87.613191703,"(41.814083626, -87.613191703)" +2490949,HH828905,2002-12-07 18:00:00,008XX W 53RD ST,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,RESIDENCE,2258,False,False,934,NA,3,61,11,1171449,1869703,2002,ERROR,41.797928002,-87.646797215,"(41.797928002, -87.646797215)" +5985175,HN780490,2007-12-27 17:30:00,019XX E 79TH ST,031A,ROBBERY,ARMED: HANDGUN,OTHER,2491,True,False,414,4,8,43,03,1190626,1853018,2007,ERROR,41.751701272,-87.577010969,"(41.751701272, -87.577010969)" +2817048,HJ473101,2003-07-04 23:55:30,050XX N WESTERN AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,1995,False,False,2031,20,47,4,05,1159496,1933143,2003,ERROR,41.972266669,-87.688883926,"(41.972266669, -87.688883926)" +9456119,HX108965,2014-01-10 01:04:00,075XX S LAFAYETTE AVE,2028,NARCOTICS,POSS: SYNTHETIC DRUGS,STREET,1608,True,False,623,6,6,69,18,1177099,1855191,2014,2014-12-01 00:40:28,41.757979849,-87.626515246,"(41.757979849, -87.626515246)" +2798720,HJ444156,2003-06-21 15:58:37,001XX W LAKE ST,0312,ROBBERY,ARMED:KNIFE/CUTTING INSTRUMENT,CTA PLATFORM,1268,False,False,113,1,42,32,03,1175497,1901776,2003,ERROR,41.885849135,-87.630990773,"(41.885849135, -87.630990773)" +7499449,HS302738,2010-05-11 17:56:00,045XX N BROADWAY,0486,BATTERY,DOMESTIC BATTERY SIMPLE,STREET,2688,False,False,2311,19,46,3,08B,1168079,1930569,2010,2010-12-05 14:32:35,41.965022021,-87.65739756,"(41.965022021, -87.65739756)" +3967996,HL332690,2005-05-03 13:20:00,014XX S CHRISTIANA AVE,0545,ASSAULT,PRO EMP HANDS NO/MIN INJURY,"SCHOOL, PUBLIC, BUILDING",2513,True,False,1021,10,24,29,08A,1154231,1892646,2005,ERROR,41.861246512,-87.709327807,"(41.861246512, -87.709327807)" +8209042,HT442926,2011-08-11 14:41:00,068XX S WOLCOTT AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,1871,True,False,726,7,17,67,18,1164911,1859184,2011,2011-11-08 15:42:15,41.769203304,-87.6710703,"(41.769203304, -87.6710703)" +4973670,HM585353,2006-09-06 07:50:00,094XX S LAFAYETTE AVE,0810,THEFT,OVER $500,STREET,4120,False,False,634,6,21,49,06,1177555,1842613,2006,2014-04-12 12:43:35,41.723453982,-87.625223371,"(41.723453982, -87.625223371)" +1393322,G107984,2001-02-22 22:15:00,087XX S PEORIA ST,0271,CRIM SEXUAL ASSAULT,ATTEMPT AGG: HANDGUN,STREET,1517,False,False,2222,NA,NA,NA,02,1171783,1847121,2001,ERROR,41.735952909,-87.646233916,"(41.735952909, -87.646233916)" +9297259,HW442278,2013-09-07 15:30:00,059XX S ELIZABETH ST,0560,ASSAULT,SIMPLE,RESIDENCE,1658,False,False,713,7,16,67,08A,1168974,1865056,2013,2013-10-09 09:12:36,41.785229979,-87.656007708,"(41.785229979, -87.656007708)" +8764812,HV435925,2012-08-17 15:00:00,053XX S MONITOR AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,2173,False,False,811,8,23,56,07,1138309,1868694,2012,ERROR,41.795821159,-87.768353304,"(41.795821159, -87.768353304)" +6872272,HR278952,2009-04-21 02:08:00,083XX S KEDZIE AVE,0610,BURGLARY,FORCIBLE ENTRY,BARBERSHOP,2575,False,False,834,8,18,70,05,1156456,1849198,2009,ERROR,41.741974607,-87.702331067,"(41.741974607, -87.702331067)" +3886460,HL262763,2005-03-29 08:00:00,062XX W DICKENS AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,926,False,False,2512,25,29,19,07,1134702,1913339,2005,ERROR,41.918397987,-87.780526574,"(41.918397987, -87.780526574)" +9247581,HW393661,2013-08-04 23:33:00,083XX S DORCHESTER AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,STREET,2059,True,True,412,4,8,45,08B,1187024,1850166,2013,2013-05-08 05:38:09,41.743961235,-87.590300673,"(41.743961235, -87.590300673)" +6240467,HP328601,2008-05-09 00:00:00,038XX N PACIFIC AVE,0890,THEFT,FROM BUILDING,RESIDENCE,957,False,False,1631,16,36,17,06,1122068,1924371,2008,2008-11-05 08:02:48,41.948885353,-87.82670641,"(41.948885353, -87.82670641)" +2325815,HH620831,2002-08-29 18:30:00,009XX E 82ND ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1157,False,False,631,NA,8,44,14,1184144,1850866,2002,ERROR,41.745949866,-87.600831299,"(41.745949866, -87.600831299)" +1481452,G218910,2001-04-14 10:15:00,017XX W 43 ST,0820,THEFT,$500 AND UNDER,STREET,248,False,False,914,NA,NA,NA,06,1165712,1876148,2001,2014-04-12 12:43:35,41.815737633,-87.667652714,"(41.815737633, -87.667652714)" +2193379,HH452636,2002-06-19 11:09:24,027XX E 76TH ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,509,False,False,421,NA,7,43,14,1195835,1855198,2002,ERROR,41.757556115,-87.557850794,"(41.757556115, -87.557850794)" +6970254,HR375030,2009-06-14 14:15:00,052XX W WOLFRAM ST,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE,683,False,False,2514,25,31,19,05,1140877,1918484,2009,ERROR,41.932404942,-87.757711919,"(41.932404942, -87.757711919)" +7626585,HS431129,2010-07-26 19:16:00,012XX W WASHBURNE AVE,2220,LIQUOR LAW VIOLATION,ILLEGAL POSSESSION BY MINOR,STREET,4211,True,False,1231,12,2,28,22,1168368,1894493,2010,ERROR,41.866021153,-87.65738041,"(41.866021153, -87.65738041)" +8990632,HW137651,2013-01-30 12:12:00,060XX S TALMAN AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,1914,True,False,825,8,15,66,05,1159702,1864538,2013,2013-05-02 08:59:06,41.784003919,-87.690017346,"(41.784003919, -87.690017346)" +3647659,HK742033,2004-11-10 09:15:00,062XX S COTTAGE GROVE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,STREET,3824,False,True,313,3,20,42,08B,1182595,1863841,2004,ERROR,41.781590611,-87.606105147,"(41.781590611, -87.606105147)" +6003770,HP103458,2008-01-03 00:06:00,056XX S TRIPP AVE,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,SIDEWALK,2815,True,False,813,8,13,62,15,1148949,1867064,2008,2008-09-01 10:07:35,41.791149667,-87.729377285,"(41.791149667, -87.729377285)" +6025842,HP129074,2008-01-02 16:00:00,069XX S KIMBARK AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3731,False,False,321,3,5,69,14,1185895,1859153,2008,ERROR,41.768649107,-87.59415445,"(41.768649107, -87.59415445)" +4800609,HM411215,2006-05-17 20:00:00,114XX S DR MARTIN LUTHER KING JR DR,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,3162,False,True,531,5,9,49,26,1180987,1829335,2006,ERROR,41.686939238,-87.613059096,"(41.686939238, -87.613059096)" +1666539,G443084,2001-07-27 21:11:26,048XX W FLETCHER ST,0460,BATTERY,SIMPLE,RESIDENCE,2067,True,False,2521,NA,NA,NA,08B,1143266,1920538,2001,ERROR,41.937996967,-87.748881061,"(41.937996967, -87.748881061)" +5182497,HM771133,2006-12-13 02:45:33,037XX W ROOSEVELT RD,0460,BATTERY,SIMPLE,STREET,1387,False,False,1011,10,24,29,08B,1151547,1894410,2006,2007-11-06 15:52:33,41.866140241,-87.719133978,"(41.866140241, -87.719133978)" +2653415,HJ264288,2003-03-27 17:55:45,035XX S RHODES AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,1255,False,False,212,NA,4,35,08B,1180131,1881336,2003,ERROR,41.829655213,-87.614602295,"(41.829655213, -87.614602295)" +1351172,G053225,2001-01-25 20:00:00,023XX W CULLERTON ST,0820,THEFT,$500 AND UNDER,STREET,16,False,False,1223,NA,NA,NA,06,1161315,1890317,2001,2014-04-12 12:43:35,41.854711274,-87.683388522,"(41.854711274, -87.683388522)" +10107538,HY295865,2015-06-10 18:55:00,031XX W VAN BUREN ST,2024,NARCOTICS,POSS: HEROIN(WHITE),PARK PROPERTY,515,True,False,1134,11,28,27,18,1155706,1897956,2015,ERROR,41.875788166,-87.703770398,"(41.875788166, -87.703770398)" +2379764,HH688374,2002-10-02 22:17:34,012XX E 93RD ST,0560,ASSAULT,SIMPLE,OTHER,3430,False,True,413,NA,8,47,08A,1186314,1843608,2002,ERROR,41.725982183,-87.593108809,"(41.725982183, -87.593108809)" +2267732,HH544517,2002-07-29 18:20:00,024XX N MONTICELLO AVE,0460,BATTERY,SIMPLE,APARTMENT,1972,False,False,2524,NA,35,22,08B,1151599,1915885,2002,ERROR,41.925068771,-87.718378015,"(41.925068771, -87.718378015)" +9401609,HW544488,2013-11-22 15:00:00,022XX W 47TH ST,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,1599,False,True,931,9,12,61,26,1161952,1873419,2013,ERROR,41.80832803,-87.681521068,"(41.80832803, -87.681521068)" +1724872,G526711,2001-09-02 23:30:00,008XX W BARRY AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,1898,False,False,1932,NA,NA,NA,07,1170165,1920835,2001,ERROR,41.938266163,-87.650013252,"(41.938266163, -87.650013252)" +7236503,HR651141,2009-11-19 12:30:00,045XX N KEDZIE AVE,0843,THEFT,ATTEMPT FINANCIAL IDENTITY THEFT,RESIDENCE,1452,False,False,1724,17,33,14,06,1154268,1930085,2009,ERROR,41.963981641,-87.708190292,"(41.963981641, -87.708190292)" +4531738,HM116986,2006-01-10 17:40:00,018XX W ADDISON ST,0460,BATTERY,SIMPLE,STREET,1107,False,False,1923,19,47,5,08B,1163300,1923889,2006,ERROR,41.94679389,-87.675157351,"(41.94679389, -87.675157351)" +6323346,HP407292,2008-06-21 01:08:00,027XX W 79TH ST,1220,DECEPTIVE PRACTICE,THEFT OF LOST/MISLAID PROP,ALLEY,1728,True,False,835,8,18,70,11,1159359,1852062,2008,ERROR,41.749774969,-87.691616189,"(41.749774969, -87.691616189)" +2811252,HJ435689,2003-06-17 19:01:00,007XX N HARDING AVE,2024,NARCOTICS,POSS: HEROIN(WHITE),SIDEWALK,3835,True,False,1112,11,27,23,18,1149954,1904935,2003,ERROR,41.895053131,-87.724708046,"(41.895053131, -87.724708046)" +10109799,HY298102,2015-06-12 13:35:00,055XX W BELMONT AVE,0560,ASSAULT,SIMPLE,ALLEY,4032,False,False,2514,25,30,19,08A,1138524,1920677,2015,ERROR,41.938465788,-87.766305737,"(41.938465788, -87.766305737)" +2122047,HH360853,2002-05-08 03:00:00,035XX W 115TH PL,0810,THEFT,OVER $500,STREET,586,False,False,2211,NA,19,74,06,1155019,1827742,2002,2014-04-12 12:43:35,41.683124226,-87.70816705,"(41.683124226, -87.70816705)" +5459179,HN286810,2007-04-14 11:00:00,017XX N SEDGWICK ST,0610,BURGLARY,FORCIBLE ENTRY,CONSTRUCTION SITE,4496,False,False,1813,18,43,7,05,1173287,1911789,2007,2007-01-05 07:13:54,41.913374701,-87.638808607,"(41.913374701, -87.638808607)" +9427739,HW571708,2013-12-15 12:19:00,038XX W 65TH PL,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,RESIDENCE,2456,True,False,833,8,13,65,15,1151877,1860935,2013,ERROR,41.774273798,-87.718801431,"(41.774273798, -87.718801431)" +9549159,HX200896,2014-03-23 14:00:00,0000X W WASHINGTON ST,1152,DECEPTIVE PRACTICE,ILLEGAL USE CASH CARD,BANK,506,False,False,111,1,42,32,11,1175824,1900858,2014,ERROR,41.883322741,-87.629817609,"(41.883322741, -87.629817609)" +3085284,HJ808984,2003-12-10 01:30:00,015XX W MADISON ST,031A,ROBBERY,ARMED: HANDGUN,STREET,1775,False,False,1211,12,2,28,03,1165905,1900059,2003,ERROR,41.881347579,-87.666263434,"(41.881347579, -87.666263434)" +4372149,HL665453,2005-10-10 19:30:00,037XX N RECREATION DR,0820,THEFT,$500 AND UNDER,PARK PROPERTY,375,False,False,2323,19,46,6,06,1171814,1925403,2005,2014-04-12 12:43:35,41.950764691,-87.643817819,"(41.950764691, -87.643817819)" +8955831,HW104841,2013-01-01 20:21:00,027XX W 56TH ST,2826,OTHER OFFENSE,HARASSMENT BY ELECTRONIC MEANS,RESIDENCE,4110,False,False,824,8,16,63,26,1159094,1867451,2013,2013-10-01 14:22:16,41.792010053,-87.692166871,"(41.792010053, -87.692166871)" +7846213,HS658466,2010-12-13 10:50:00,032XX W 26TH ST,031A,ROBBERY,ARMED: HANDGUN,SMALL RETAIL STORE,4184,True,False,1024,10,22,30,03,1155078,1886589,2010,2012-06-02 11:35:31,41.844608478,-87.706380961,"(41.844608478, -87.706380961)" +7357351,HS158950,2010-02-10 08:10:00,003XX S SACRAMENTO BLVD,033A,ROBBERY,ATTEMPT: ARMED-HANDGUN,STREET,3954,False,False,1124,11,28,27,03,1156394,1898275,2010,2010-06-07 20:35:42,41.876649659,-87.701235678,"(41.876649659, -87.701235678)" +8459205,HV136620,2012-01-28 22:45:00,008XX N TRIPP AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2553,False,False,1111,11,37,23,14,1147864,1905093,2012,ERROR,41.895527127,-87.732380072,"(41.895527127, -87.732380072)" +1389813,G101962,2001-02-19 18:05:00,016XX E 74 PL,0560,ASSAULT,SIMPLE,STREET,681,False,True,324,NA,NA,NA,08A,1188468,1855950,2001,ERROR,41.75979873,-87.584825427,"(41.75979873, -87.584825427)" +7865849,HS679407,2010-12-28 12:40:00,031XX W MADISON ST,0560,ASSAULT,SIMPLE,APARTMENT,1924,False,False,1124,11,28,27,08A,1155534,1899811,2010,ERROR,41.880881934,-87.704352007,"(41.880881934, -87.704352007)" +4757789,HM369541,2006-05-23 16:00:00,017XX N WOLCOTT AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,2770,False,True,1434,14,32,24,26,1163450,1911711,2006,ERROR,41.913373563,-87.674949723,"(41.913373563, -87.674949723)" +3189601,HK196709,2004-02-21 18:00:00,025XX N HARLEM AVE,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),1669,False,False,2512,25,36,18,06,1127702,1916103,2004,2014-04-12 12:43:35,41.9261037,-87.806183216,"(41.9261037, -87.806183216)" +4409636,HL704892,2005-10-30 15:03:19,083XX S BOND AVE,0454,BATTERY,AGG PO HANDS NO/MIN INJURY,RESIDENCE,4370,True,False,424,4,10,46,08B,1198647,1849847,2005,ERROR,41.742802579,-87.547724486,"(41.742802579, -87.547724486)" +1505171,G248949,2001-05-01 12:00:00,002XX S MICHIGAN AV,0560,ASSAULT,SIMPLE,STREET,861,False,False,123,NA,NA,NA,08A,1177284,1899473,2001,ERROR,41.879489257,-87.624498463,"(41.879489257, -87.624498463)" +4586711,HM178303,2006-02-13 02:00:53,003XX N LARAMIE AVE,0460,BATTERY,SIMPLE,RESTAURANT,1167,False,False,1532,15,28,25,08B,1141691,1901663,2006,ERROR,41.886231203,-87.755137217,"(41.886231203, -87.755137217)" +1989552,HH189521,2002-02-15 19:50:00,075XX S SAGINAW AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,1219,True,False,421,NA,NA,NA,07,1195277,1855344,2002,ERROR,41.757970535,-87.559890935,"(41.757970535, -87.559890935)" +3644751,HK740805,2004-11-08 17:00:00,018XX S KARLOV AVE,0810,THEFT,OVER $500,STREET,788,False,False,1012,10,24,29,06,1149319,1890548,2004,2014-04-12 12:43:35,41.855585896,-87.727413274,"(41.855585896, -87.727413274)" +8573990,HV248559,2012-04-18 23:00:00,004XX N CENTRAL PARK BLVD,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,602,False,False,1122,11,27,23,07,1152144,1902675,2012,ERROR,41.888808551,-87.716724345,"(41.888808551, -87.716724345)" +3524215,HK604674,2004-09-05 19:15:00,036XX W SHAKESPEARE AVE,2027,NARCOTICS,POSS: CRACK,SIDEWALK,4206,True,False,2525,25,26,22,18,1151518,1913999,2004,2007-11-06 15:52:33,41.919895016,-87.718725318,"(41.919895016, -87.718725318)" +1847188,G685045,2001-11-14 09:00:00,050XX S LAWNDALE AV,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE-GARAGE,3981,False,False,821,NA,NA,NA,05,1152515,1871081,2001,ERROR,41.802103414,-87.716195637,"(41.802103414, -87.716195637)" +7342528,HS143733,2010-01-30 11:35:00,010XX W FOSTER AVE,0340,ROBBERY,ATTEMPT: STRONGARM-NO WEAPON,ALLEY,1187,False,False,2023,20,48,77,03,1168536,1934754,2010,2010-11-02 08:18:49,41.976495882,-87.655595568,"(41.976495882, -87.655595568)" +7778066,HS583693,2010-10-26 13:20:00,003XX W 117TH ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4154,False,True,522,5,34,53,08B,1176304,1827385,2010,ERROR,41.681694308,-87.630261037,"(41.681694308, -87.630261037)" +1348067,G033281,2001-01-16 13:30:00,011XX N WASHTENAW AV,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,783,True,False,1311,NA,NA,NA,18,1158142,1907777,2001,ERROR,41.902688501,-87.694557839,"(41.902688501, -87.694557839)" +9251968,HW395546,2013-05-19 00:01:00,005XX E 32ND ST,0840,THEFT,FINANCIAL ID THEFT: OVER $300,RESIDENCE,2608,False,False,211,2,4,35,06,1180472,1883635,2013,2013-08-08 11:12:43,41.835955998,-87.613280481,"(41.835955998, -87.613280481)" +1866081,G710432,2001-08-30 04:15:00,055XX W NORTH AV,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,3533,False,False,2532,NA,NA,NA,05,1138844,1910120,2001,ERROR,41.909490409,-87.765386627,"(41.909490409, -87.765386627)" +3462034,HK533284,2004-08-01 00:00:00,004XX N OAKLEY BLVD,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),2427,False,False,1313,12,27,24,06,1161044,1902666,2004,2014-04-12 12:43:35,41.888603708,-87.684040351,"(41.888603708, -87.684040351)" +8200080,HT434131,2011-08-05 01:00:00,043XX S HONORE ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,3797,False,False,914,9,12,61,07,1164697,1876102,2011,2011-06-08 10:43:36,41.81563292,-87.67137723,"(41.81563292, -87.67137723)" +1532170,G282968,2001-05-15 17:00:00,008XX W CUYLER AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,589,False,False,2322,NA,NA,NA,07,1169953,1927283,2001,ERROR,41.955964358,-87.650603629,"(41.955964358, -87.650603629)" +8748519,HV423660,2012-08-09 23:43:00,003XX N DEARBORN ST,0460,BATTERY,SIMPLE,BAR OR TAVERN,4895,True,False,1831,18,42,8,08B,1175921,1902673,2012,2012-10-08 09:09:49,41.888301019,-87.629406752,"(41.888301019, -87.629406752)" +3064212,HJ783363,2003-11-26 12:45:00,003XX N MICHIGAN AVE,0890,THEFT,FROM BUILDING,RESTAURANT,4952,False,False,122,1,42,32,06,1177207,1902277,2003,ERROR,41.887185329,-87.624696174,"(41.887185329, -87.624696174)" +9266858,HW411605,2013-08-16 21:00:00,026XX N MOZART ST,0820,THEFT,$500 AND UNDER,STREET,2,False,False,1411,14,35,22,06,1156956,1917741,2013,ERROR,41.930054663,-87.69864338,"(41.930054663, -87.69864338)" +2563813,HJ151075,2003-01-24 17:00:00,037XX S DR MARTIN LUTHER KING JR DR,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE-GARAGE,995,False,False,211,NA,3,35,05,1179437,1879984,2003,ERROR,41.825961127,-87.617189893,"(41.825961127, -87.617189893)" +6272130,HP360251,2008-05-27 23:19:23,078XX S CLYDE AVE,0495,BATTERY,AGGRAVATED OF A SENIOR CITIZEN,RESIDENCE,1708,True,False,414,4,8,43,04B,1191603,1853399,2008,ERROR,41.752723145,-87.573418451,"(41.752723145, -87.573418451)" +1694291,G483134,2001-08-14 15:00:00,063XX S ELLIS AV,041A,BATTERY,AGGRAVATED: HANDGUN,STREET,1918,False,False,314,NA,NA,NA,04B,1184186,1863155,2001,ERROR,41.779671095,-87.600293674,"(41.779671095, -87.600293674)" +9180052,HW325256,2013-06-19 00:05:00,063XX S DR MARTIN LUTHER KING JR DR,1210,DECEPTIVE PRACTICE,THEFT OF LABOR/SERVICES,CTA PLATFORM,2418,True,False,312,3,20,69,11,1179964,1863307,2013,ERROR,41.780185918,-87.615767262,"(41.780185918, -87.615767262)" +9280642,HW425506,2013-08-26 22:00:00,018XX N LARRABEE ST,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE-GARAGE,2483,False,False,1813,18,43,7,05,1171935,1912782,2013,ERROR,41.916129478,-87.643746195,"(41.916129478, -87.643746195)" +4593336,HM184287,2006-02-16 10:20:00,008XX E 103RD ST,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, BUILDING",3892,False,False,512,5,9,50,08B,1183714,1836812,2006,2006-06-03 03:53:14,41.707394098,-87.602843897,"(41.707394098, -87.602843897)" +8204247,HT438156,2011-08-08 14:10:00,092XX S CLYDE AVE,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,845,False,False,413,4,8,48,07,1191741,1844312,2011,2011-09-08 09:44:36,41.727784247,-87.573206878,"(41.727784247, -87.573206878)" +7787558,HS589500,2010-10-27 10:00:00,070XX S PERRY AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,3600,False,False,731,7,6,69,07,1176581,1858104,2010,2010-05-11 08:26:29,41.765985117,-87.628326152,"(41.765985117, -87.628326152)" +5607127,HN408670,2007-06-16 08:15:00,002XX W 106TH PL,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,4526,False,True,512,5,34,49,14,1176238,1834270,2007,ERROR,41.700589255,-87.630296938,"(41.700589255, -87.630296938)" +8287357,HT521174,2011-09-30 11:00:00,052XX W JACKSON BLVD,1310,CRIMINAL DAMAGE,TO PROPERTY,APARTMENT,2963,False,False,1522,15,29,25,14,1141368,1898125,2011,2011-01-10 10:34:37,41.876528452,-87.756410705,"(41.876528452, -87.756410705)" +2090773,HH320445,2002-04-20 19:30:00,054XX W JACKSON BL,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,3689,False,False,1522,NA,NA,NA,26,1140018,1898171,2002,ERROR,41.876679476,-87.761366414,"(41.876679476, -87.761366414)" +9718774,HX368460,2014-07-31 11:30:00,042XX S CALIFORNIA AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,4785,False,False,921,9,12,58,05,1158367,1876251,2014,2014-07-08 12:40:12,41.816173258,-87.694592874,"(41.816173258, -87.694592874)" +5349618,HN204723,2007-03-02 20:20:00,036XX W CORTLAND ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,3550,True,False,2535,25,26,22,08B,1151444,1912332,2007,2007-08-03 21:51:06,41.915322073,-87.719041079,"(41.915322073, -87.719041079)" +9883576,HX534835,2014-12-08 06:30:00,026XX N OAK PARK AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,1739,False,False,2512,25,36,18,14,1130669,1917134,2014,ERROR,41.928882318,-87.79525692,"(41.928882318, -87.79525692)" +2130685,HH371576,2002-05-14 14:00:00,034XX N NARRAGANSETT AVE,0810,THEFT,OVER $500,"SCHOOL, PUBLIC, BUILDING",2624,False,False,1632,NA,36,17,06,1133059,1921942,2002,2014-04-12 12:43:35,41.942034525,-87.786361549,"(41.942034525, -87.786361549)" +1450362,G178877,2001-03-29 17:50:47,003XX W 52 PL,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,STREET,3979,False,False,934,NA,NA,NA,04B,1174942,1870105,2001,ERROR,41.798953863,-87.633975858,"(41.798953863, -87.633975858)" +3802924,HL107794,2005-01-05 11:00:00,051XX S CICERO AVE,1513,PROSTITUTION,SOLICIT FOR BUSINESS,STREET,4516,True,False,814,8,23,56,16,1145182,1870371,2005,ERROR,41.800296394,-87.743107012,"(41.800296394, -87.743107012)" +6423969,HP506680,2008-08-08 17:00:00,105XX S VINCENNES AVE,0890,THEFT,FROM BUILDING,CONSTRUCTION SITE,3187,False,False,2212,22,19,72,06,1168786,1835112,2008,2008-06-09 10:53:12,41.703063528,-87.657559187,"(41.703063528, -87.657559187)" +4619517,HM216542,2006-03-06 13:00:00,032XX S KEDZIE AVE,0890,THEFT,FROM BUILDING,RESTAURANT,4455,False,False,1033,10,12,30,06,1155585,1883125,2006,2006-08-03 04:01:50,41.835092676,-87.704613393,"(41.835092676, -87.704613393)" +3896698,HL272683,2005-04-03 19:00:00,028XX N NARRAGANSETT AVE,0820,THEFT,$500 AND UNDER,PARKING LOT/GARAGE(NON.RESID.),40,False,False,2511,25,36,19,06,1133225,1917988,2005,2014-04-12 12:43:35,41.931181393,-87.785844226,"(41.931181393, -87.785844226)" +8760257,HV434693,2012-08-17 15:30:00,051XX N MILWAUKEE AVE,1345,CRIMINAL DAMAGE,TO CITY OF CHICAGO PROPERTY,POLICE FACILITY/VEH PARKING LOT,653,True,False,1623,16,45,11,14,1138481,1933660,2012,ERROR,41.974093149,-87.766147965,"(41.974093149, -87.766147965)" +5284889,HN142213,2007-01-24 07:45:00,003XX W 83RD ST,0810,THEFT,OVER $500,CONSTRUCTION SITE,3850,False,False,622,6,21,44,06,1175483,1849872,2007,2014-04-12 12:43:35,41.743420145,-87.632596441,"(41.743420145, -87.632596441)" +6146157,HP237155,2008-03-21 16:00:00,055XX W NORTH AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,PARKING LOT/GARAGE(NON.RESID.),2698,False,False,2532,25,37,25,14,1138898,1910041,2008,ERROR,41.909272642,-87.765190174,"(41.909272642, -87.765190174)" +5828373,HN638214,2007-10-09 16:00:00,013XX E 62ND ST,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,STREET,4548,True,False,314,3,20,42,26,1185883,1864191,2007,ERROR,41.782474101,-87.594039684,"(41.782474101, -87.594039684)" +9962492,HY151390,2015-02-13 20:00:00,019XX W MORSE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,OTHER,581,False,False,2424,24,49,1,14,1161994,1946107,2015,ERROR,42.007788395,-87.679334315,"(42.007788395, -87.679334315)" +6576639,HP649030,2008-10-26 14:44:24,026XX W CHICAGO AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,1628,False,False,1313,12,26,24,05,1158778,1905190,2008,2009-01-08 20:33:55,41.895576534999996,-87.692292699,"(41.895576535, -87.692292699)" +2773697,HJ394838,2003-05-29 17:10:00,008XX N AVERS AVE,2024,NARCOTICS,POSS: HEROIN(WHITE),SIDEWALK,1426,True,False,1112,NA,27,23,18,1150597,1905710,2003,ERROR,41.897167269,-87.722326187,"(41.897167269, -87.722326187)" +3455350,HK523596,2004-07-29 00:00:00,068XX W SHAKESPEARE AVE,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,876,False,False,2512,25,36,25,07,1130189,1913566,2004,ERROR,41.919099535,-87.797102792,"(41.919099535, -87.797102792)" +1510568,G238792,2001-04-26 20:58:13,047XX W LAKE ST,2024,NARCOTICS,POSS: HEROIN(WHITE),ALLEY,1889,True,False,1113,NA,NA,NA,18,1144354,1901858,2001,ERROR,41.886716653,-87.745353037,"(41.886716653, -87.745353037)" +10002833,HY192117,2015-03-20 16:50:00,109XX S MICHIGAN AVE,0560,ASSAULT,SIMPLE,STREET,2360,False,False,513,5,9,49,08A,1178806,1832645,2015,ERROR,41.696072117,-87.620943149,"(41.696072117, -87.620943149)" +9628747,HX278675,2014-05-27 15:00:00,027XX N CLYBOURN AVE,0890,THEFT,FROM BUILDING,GROCERY FOOD STORE,2623,False,False,1931,19,32,7,06,1162878,1918140,2014,2014-03-06 12:44:29,41.931027188,-87.676870318,"(41.931027188, -87.676870318)" +9380277,HW523315,2013-11-06 16:00:00,072XX S MAPLEWOOD AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3549,False,False,832,8,18,66,08B,1160671,1856428,2013,ERROR,41.761728977,-87.686688178,"(41.761728977, -87.686688178)" +3098367,HJ826189,2003-12-19 03:20:00,087XX S SAGINAW AVE,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,RESIDENCE,1560,True,False,423,4,7,46,15,1195358,1847409,2003,ERROR,41.736194269,-87.559855496,"(41.736194269, -87.559855496)" +8700475,HV376946,2012-07-11 02:30:00,013XX W LOYOLA AVE,0820,THEFT,$500 AND UNDER,RESIDENCE,179,False,False,2432,24,40,1,06,1165965,1943761,2012,ERROR,42.001266756,-87.664791595,"(42.001266756, -87.664791595)" +6309518,HP399117,2008-06-16 06:00:00,047XX S WOLCOTT AVE,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE,2208,False,False,931,9,20,61,05,1164528,1873118,2008,ERROR,41.80744805,-87.672081377,"(41.80744805, -87.672081377)" +2246591,HH520136,2002-07-19 00:30:00,032XX W BELLE PLAINE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,1923,False,False,1724,NA,33,16,14,1153950,1927112,2002,ERROR,41.955829898,-87.709439072,"(41.955829898, -87.709439072)" +1703748,G500124,2001-08-22 02:45:00,059XX W FULTON ST,0460,BATTERY,SIMPLE,APARTMENT,1254,False,True,1512,NA,NA,NA,08B,1136489,1901353,2001,ERROR,41.885475115,-87.774247889,"(41.885475115, -87.774247889)" +2097320,HH313066,2002-04-17 12:39:00,044XX S FEDERAL ST,1350,CRIMINAL TRESPASS,TO STATE SUP LAND,CHA PARKING LOT/GROUNDS,4334,True,False,221,NA,NA,NA,26,1176474,1875517,2002,ERROR,41.813770553,-87.628194868,"(41.813770553, -87.628194868)" +2304088,HH586111,2002-08-17 09:18:40,014XX S SPRINGFIELD AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,APARTMENT,3059,False,False,1011,NA,24,29,14,1150654,1892710,2002,ERROR,41.861492727,-87.722456686,"(41.861492727, -87.722456686)" +2503528,HH844467,2002-12-17 17:20:00,086XX S COMMERCIAL AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,VEHICLE NON-COMMERCIAL,2687,False,False,424,NA,10,46,08B,1197693,1847869,2002,ERROR,41.737398641,-87.551285747,"(41.737398641, -87.551285747)" +7702717,HS509807,2010-09-11 03:30:00,057XX S DR MARTIN LUTHER KING JR DR,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2786,False,False,234,2,20,40,14,1179868,1866840,2010,2010-11-09 10:33:16,41.789883007,-87.616011151,"(41.789883007, -87.616011151)" +4497359,HL799972,2005-12-20 14:10:00,0000X N STATE ST,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,687,True,False,122,1,42,32,06,1176321,1900593,2005,ERROR,41.882584372,-87.628000611,"(41.882584372, -87.628000611)" +1913830,G774488,2001-11-21 17:00:00,004XX S DEARBORN ST,0810,THEFT,OVER $500,COMMERCIAL / BUSINESS OFFICE,4896,False,False,132,NA,NA,NA,06,1176036,1898446,2001,2014-04-12 12:43:35,41.876699301,-87.629111816,"(41.876699301, -87.629111816)" +7752781,HS561321,2010-10-12 19:00:00,023XX N ORCHARD ST,0810,THEFT,OVER $500,STREET,3795,False,False,1812,18,43,7,06,1171165,1916071,2010,ERROR,41.925171608,-87.646478328,"(41.925171608, -87.646478328)" +1495352,G236482,2001-04-25 08:00:00,054XX N SHERIDAN RD,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,APARTMENT,1411,False,False,2023,NA,NA,NA,26,1168688,1936590,2001,ERROR,41.981530612,-87.654983135,"(41.981530612, -87.654983135)" +4269646,HL579828,2005-08-29 19:47:25,029XX N MONITOR AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,1280,False,True,2514,25,30,19,08B,1136910,1919097,2005,ERROR,41.934159241,-87.772275653,"(41.934159241, -87.772275653)" +8734967,HV410309,2012-06-06 09:00:00,030XX N ELSTON AVE,5002,OTHER OFFENSE,OTHER VEHICLE OFFENSE,OTHER,2621,False,False,1411,14,1,21,26,1158467,1919885,2012,2014-03-02 14:06:09,41.935907119,-87.693031988,"(41.935907119, -87.693031988)" +6183611,HP273306,2008-04-10 19:00:00,031XX N MELVINA AVE,0810,THEFT,OVER $500,STREET,2739,False,False,2511,25,36,19,06,1134469,1920529,2008,2008-12-04 08:34:56,41.938132293,-87.781212545,"(41.938132293, -87.781212545)" +7078825,HR486686,2009-08-16 11:45:00,049XX W AUGUSTA BLVD,0470,PUBLIC PEACE VIOLATION,RECKLESS CONDUCT,RESIDENTIAL YARD (FRONT/BACK),3558,True,False,1531,15,37,25,24,1143223,1906157,2009,ERROR,41.898534802,-87.749398973,"(41.898534802, -87.749398973)" +5779480,HN586714,2007-09-13 10:30:14,006XX W 61ST ST,2095,NARCOTICS,ATTEMPT POSSESSION NARCOTICS,SIDEWALK,2183,True,False,711,7,16,68,18,1172737,1864389,2007,ERROR,41.783317484,-87.642230568,"(41.783317484, -87.642230568)" +9870258,HX520700,2014-11-25 22:12:00,062XX W 64TH ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,990,False,False,812,8,23,64,07,1135733,1861466,2014,2014-02-12 12:51:55,41.776032284,-87.777971032,"(41.776032284, -87.777971032)" +8288909,HT523203,2011-10-01 17:20:00,048XX W FLOURNOY ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,2283,True,False,1533,15,24,25,18,1144395,1896558,2011,2011-01-10 18:31:03,41.872172039,-87.745335817,"(41.872172039, -87.745335817)" +8102316,HT336736,2011-06-08 11:03:00,042XX S ASHLAND AVE,0850,THEFT,ATTEMPT THEFT,PARKING LOT/GARAGE(NON.RESID.),4936,True,False,914,9,12,61,06,1166338,1876850,2011,2011-09-06 11:16:04,41.817650668,-87.665336409,"(41.817650668, -87.665336409)" +7465353,HS267327,2010-04-20 07:15:00,059XX S INDIANA AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,4064,False,False,233,2,20,40,05,1178571,1865818,2010,2010-03-07 11:45:21,41.787108136,-87.620797891,"(41.787108136, -87.620797891)" +2448483,HH774056,2002-11-12 03:30:00,027XX S EMERALD AVE,0820,THEFT,$500 AND UNDER,STREET,467,False,False,923,NA,11,60,06,1171708,1886483,2002,2014-04-12 12:43:35,41.843968255,-87.645354708,"(41.843968255, -87.645354708)" +7555079,HS359022,2010-06-14 11:45:00,021XX N HUMBOLDT BLVD,0810,THEFT,OVER $500,SIDEWALK,576,False,False,1414,14,35,22,06,1156190,1914398,2010,ERROR,41.920896739,-87.701548757,"(41.920896739, -87.701548757)" +10102657,HY291732,2015-04-15 12:30:00,098XX S CHARLES ST,1153,DECEPTIVE PRACTICE,FINANCIAL IDENTITY THEFT OVER $ 300,RESIDENCE,1478,False,False,2213,22,19,72,11,1167555,1839283,2015,ERROR,41.714535835,-87.661947749,"(41.714535835, -87.661947749)" +9023020,HW170629,2013-02-26 04:45:00,119XX S PERRY AVE,031A,ROBBERY,ARMED: HANDGUN,STREET,1991,False,False,522,5,9,53,03,1177683,1825981,2013,2013-02-04 12:18:39,41.677810527,-87.625255393,"(41.677810527, -87.625255393)" +4650295,HM247617,2006-03-23 06:22:00,027XX N RUTHERFORD AVE,0930,MOTOR VEHICLE THEFT,THEFT/RECOVERY: AUTOMOBILE,STREET,3249,True,False,2512,25,36,18,07,1130917,1917271,2006,ERROR,41.929253988,-87.794342417,"(41.929253988, -87.794342417)" +7862271,HS676968,2010-12-26 15:15:00,072XX S ROCKWELL ST,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,3255,False,True,831,8,18,66,26,1160255,1856471,2010,2011-01-01 13:13:56,41.761855547,-87.688211689,"(41.761855547, -87.688211689)" +1549513,G304867,2001-05-26 17:00:00,064XX S COTTAGE GROVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,"SCHOOL, PUBLIC, GROUNDS",4371,False,False,312,NA,NA,NA,07,1182638,1862119,2001,ERROR,41.776864286,-87.606000899,"(41.776864286, -87.606000899)" +1455026,G184846,2001-04-01 17:12:49,083XX S VERNON AV,0560,ASSAULT,SIMPLE,RESIDENCE,544,False,False,632,NA,NA,NA,08A,1180775,1849537,2001,ERROR,41.742380976,-87.613216643,"(41.742380976, -87.613216643)" +4427823,HL724219,2005-11-08 00:00:00,014XX E 54TH ST,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE-GARAGE,2419,False,False,2132,2,4,41,05,1186925,1869842,2005,2006-04-02 03:37:59,41.797956235,-87.590040347,"(41.797956235, -87.590040347)" +5679423,HN487410,2007-07-25 02:40:00,059XX S CAMPBELL AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,2902,True,False,824,8,16,66,14,1160764,1865105,2007,ERROR,41.785537974,-87.686107985,"(41.785537974, -87.686107985)" +7495732,HS275457,2010-04-25 14:45:00,043XX S DR MARTIN LUTHER KING JR DR,0320,ROBBERY,STRONGARM - NO WEAPON,STREET,1747,False,False,222,2,3,38,03,1179615,1876521,2010,2010-04-06 18:55:38,41.816454308,-87.616642833,"(41.816454308, -87.616642833)" +7803721,HS613635,2010-11-13 12:10:00,005XX S PULASKI RD,031A,ROBBERY,ARMED: HANDGUN,STREET,2845,False,False,1132,11,24,26,03,1149769,1897321,2010,ERROR,41.874163085,-87.725585552,"(41.874163085, -87.725585552)" +1710750,G511505,2001-08-25 23:00:00,007XX W WAVELAND AV,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),949,False,False,2323,NA,NA,NA,06,1170739,1924926,2001,2014-04-12 12:43:35,41.94947945,-87.647783443,"(41.94947945, -87.647783443)" +2224263,HH493721,2002-07-07 18:09:00,104XX S WABASH AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1931,False,False,512,NA,9,49,14,1178385,1835579,2002,ERROR,41.70413297,-87.622395901,"(41.70413297, -87.622395901)" +6935102,HR333609,2009-05-21 15:30:00,100XX W OHARE ST,0560,ASSAULT,SIMPLE,AIRPORT/AIRCRAFT,3283,False,False,1651,16,41,76,08A,1100635,1934208,2009,ERROR,41.976200173,-87.905312411,"(41.976200173, -87.905312411)" +5382077,HN231591,2007-03-08 10:10:00,048XX S CHICAGO BEACH DR,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,APARTMENT,1299,False,False,2132,2,4,39,26,1187822,1873221,2007,ERROR,41.807207115,-87.5866433,"(41.807207115, -87.5866433)" +2472765,HH804536,2002-11-26 22:00:00,045XX N MILWAUKEE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,OTHER,4091,False,False,1623,NA,45,15,14,1140801,1930053,2002,ERROR,41.96415277,-87.757705682,"(41.96415277, -87.757705682)" +8663325,HV338303,2012-06-16 12:00:00,079XX S MAY ST,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,4672,False,False,612,6,17,71,26,1169994,1852029,2012,ERROR,41.749460143,-87.652645927,"(41.749460143, -87.652645927)" +3682557,HK717624,2004-10-29 10:00:00,018XX S KARLOV AVE,2095,NARCOTICS,ATTEMPT POSSESSION NARCOTICS,CHA PARKING LOT/GROUNDS,594,True,False,1012,10,24,29,18,1149325,1890347,2004,ERROR,41.855034211,-87.727396457,"(41.855034211, -87.727396457)" +8506092,HV183131,2012-03-04 16:20:00,085XX S UNIVERSITY AVE,0560,ASSAULT,SIMPLE,RESIDENCE PORCH/HALLWAY,2189,False,False,412,4,8,45,08A,1185352,1848596,2012,2012-06-03 14:26:12,41.739692446,-87.596476215,"(41.739692446, -87.596476215)" +1536639,G279063,2001-05-15 01:15:00,069XX S MICHIGAN AV,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,APARTMENT,3491,False,False,322,NA,NA,NA,04B,1178326,1858850,2001,ERROR,41.767992795,-87.621907549,"(41.767992795, -87.621907549)" +8590655,HV265015,2012-04-30 16:07:00,020XX W 63RD ST,1330,CRIMINAL TRESPASS,TO LAND,RESTAURANT,3738,True,False,726,7,15,67,26,1163970,1862819,2012,2012-01-05 05:05:59,41.779198065,-87.674417489,"(41.779198065, -87.674417489)" +3957643,HL229706,2005-03-12 02:50:00,038XX W 45TH PL,2022,NARCOTICS,POSS: COCAINE,SIDEWALK,1543,True,False,821,8,14,57,18,1151741,1874241,2005,ERROR,41.810790121,-87.718951399,"(41.810790121, -87.718951399)" +5944517,HN743446,2007-12-01 08:00:00,052XX W HUTCHINSON ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,1515,False,False,1624,16,38,15,07,1140768,1927717,2007,2007-10-12 01:03:40,41.957743188,-87.757884694,"(41.957743188, -87.757884694)" +8314963,HT548064,2011-10-18 11:20:00,039XX W WILCOX ST,0520,ASSAULT,AGGRAVATED:KNIFE/CUTTING INSTR,STREET,2006,False,False,1122,11,28,26,04A,1150350,1899010,2011,ERROR,41.878786581,-87.723408302,"(41.878786581, -87.723408302)" +2262015,HH538299,2002-07-27 00:01:00,070XX S PULASKI RD,1310,CRIMINAL DAMAGE,TO PROPERTY,SMALL RETAIL STORE,4192,False,False,833,NA,13,65,14,1150947,1857820,2002,ERROR,41.765743919,-87.722291814,"(41.765743919, -87.722291814)" +3238230,HK259979,2004-03-24 11:30:00,019XX W PRYOR AVE,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,692,False,False,2212,22,19,75,26,1165739,1831480,2004,ERROR,41.69316178,-87.668819238,"(41.69316178, -87.668819238)" +8307337,HT541535,2011-10-13 19:10:00,016XX S DRAKE AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,RESIDENCE PORCH/HALLWAY,3686,True,False,1021,10,24,29,18,1153013,1891601,2011,ERROR,41.858403118,-87.713826549,"(41.858403118, -87.713826549)" +8075720,HT308470,2011-05-22 12:18:00,030XX N LECLAIRE AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,2281,True,False,2521,25,31,19,18,1141796,1919925,2011,ERROR,41.936342208,-87.754298893,"(41.936342208, -87.754298893)" +1831471,G661456,2001-11-02 23:30:30,006XX E 46 ST,0810,THEFT,OVER $500,APARTMENT,3210,False,False,222,NA,NA,NA,06,1181656,1874705,2001,2014-04-12 12:43:35,41.811424106,-87.609212194,"(41.811424106, -87.609212194)" +1577474,G323442,2001-06-04 17:25:00,005XX S DESPLAINES ST,2024,NARCOTICS,POSS: HEROIN(WHITE),STREET,4460,True,False,131,NA,NA,NA,18,1172055,1897694,2001,ERROR,41.874724464,-87.643750852,"(41.874724464, -87.643750852)" +6763360,HR180663,2009-02-20 17:00:00,028XX N CLARK ST,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),2537,False,False,1932,19,44,6,06,1171377,1919102,2009,ERROR,41.933484144,-87.645610016,"(41.933484144, -87.645610016)" +6307441,HP396661,2008-06-14 20:00:00,105XX S PERRY AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,4564,False,False,512,5,34,49,14,1177515,1834741,2008,ERROR,41.701853045,-87.625606891,"(41.701853045, -87.625606891)" +1963559,HH143797,2002-01-23 15:00:00,005XX W HARRISON ST,2111,NARCOTICS,SALE/DEL HYPODERMIC NEEDLE,HOTEL/MOTEL,4004,True,False,131,NA,NA,NA,26,1173114,1897625,2002,ERROR,41.874511711,-87.639864736,"(41.874511711, -87.639864736)" +4056395,HL405242,2005-06-07 01:00:00,060XX S MARSHFIELD AVE,0820,THEFT,$500 AND UNDER,CHURCH/SYNAGOGUE/PLACE OF WORSHIP,154,False,False,714,7,15,67,06,1166409,1864855,2005,2014-04-12 12:43:35,41.784733473,-87.665417872,"(41.784733473, -87.665417872)" +6587922,HP660944,2008-11-01 23:35:00,069XX N GLENWOOD AVE,0460,BATTERY,SIMPLE,SIDEWALK,4341,False,False,2431,24,49,1,08B,1165536,1945973,2008,2008-03-11 08:27:31,42.007345709,-87.666306413,"(42.007345709, -87.666306413)" +7794546,HS604138,2010-11-07 23:30:00,025XX S CALIFORNIA AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,4457,False,True,1033,10,12,30,08B,1158063,1887206,2010,ERROR,41.846241276,-87.695409603,"(41.846241276, -87.695409603)" +9556953,HX208548,2014-04-02 10:30:00,023XX N KEDZIE BLVD,0890,THEFT,FROM BUILDING,RESIDENCE,1548,False,False,1413,14,26,22,06,1154602,1915578,2014,2014-05-04 00:39:46,41.924166711,-87.707351793,"(41.924166711, -87.707351793)" +4911586,HM527657,2006-08-08 09:00:00,013XX W HASTINGS ST,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,RESIDENCE,615,False,False,1231,12,2,28,11,1167818,1893895,2006,2006-09-09 04:19:56,41.864392058,-87.659416725,"(41.864392058, -87.659416725)" +8530103,HV207352,2012-03-21 03:28:00,037XX N DRAKE AVE,041A,BATTERY,AGGRAVATED: HANDGUN,ALLEY,1354,False,False,1732,17,35,16,04B,1152101,1924527,2012,ERROR,41.948773209,-87.716304871,"(41.948773209, -87.716304871)" +3202279,HK212812,2004-03-01 04:50:00,119XX S MICHIGAN AVE,031A,ROBBERY,ARMED: HANDGUN,STREET,4345,False,False,532,5,9,53,03,1178938,1826078,2004,ERROR,41.678048316,-87.620658763,"(41.678048316, -87.620658763)" +1535129,G277552,2001-05-14 13:00:20,070XX S RHODES AV,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, BUILDING",895,False,False,322,NA,NA,NA,08B,1181167,1858324,2001,ERROR,41.766484442,-87.611510246,"(41.766484442, -87.611510246)" +9439829,HW583592,2013-12-22 09:00:00,010XX W COLUMBIA AVE,0890,THEFT,FROM BUILDING,RESIDENCE PORCH/HALLWAY,4348,True,False,2432,24,49,1,06,1167737,1945049,2013,ERROR,42.004762897,-87.658235361,"(42.004762897, -87.658235361)" +1486467,G222730,2001-04-19 14:30:00,007XX N MICHIGAN AV,0820,THEFT,$500 AND UNDER,DEPARTMENT STORE,255,True,False,1834,NA,NA,NA,06,1177338,1905329,2001,2014-04-12 12:43:35,41.8955572,-87.624122473,"(41.8955572, -87.624122473)" +3473591,HK542000,2004-08-07 02:10:00,0000X W MAPLE ST,0460,BATTERY,SIMPLE,STREET,4558,False,False,1824,18,42,8,08B,1176112,1907663,2004,ERROR,41.90198953,-87.628554812,"(41.90198953, -87.628554812)" +6110065,HP205470,2008-03-01 12:00:00,051XX S HYDE PARK BLVD,0810,THEFT,OVER $500,STREET,2074,False,False,2132,2,4,41,06,1188191,1871483,2008,2008-05-03 12:11:15,41.80242912,-87.585345416,"(41.80242912, -87.585345416)" +2531598,HJ111717,2002-12-27 14:00:00,005XX N STATE ST,0890,THEFT,FROM BUILDING,COMMERCIAL / BUSINESS OFFICE,3490,False,False,1834,NA,42,8,06,1176317,1903817,2002,ERROR,41.891431292,-87.62791798,"(41.891431292, -87.62791798)" +8908679,HV581869,2012-11-29 17:00:00,029XX S DR MARTIN LUTHER KING JR DR,0880,THEFT,PURSE-SNATCHING,RESIDENCE PORCH/HALLWAY,2582,False,False,133,1,4,35,06,1179425,1885823,2012,2012-02-12 14:35:58,41.841984044,-87.617055292,"(41.841984044, -87.617055292)" +8613270,HV287141,2012-02-24 09:00:00,039XX W 79TH ST,0890,THEFT,FROM BUILDING,"SCHOOL, PUBLIC, BUILDING",4666,False,False,834,8,18,70,06,1151305,1851848,2012,ERROR,41.749348763,-87.721135378,"(41.749348763, -87.721135378)" +7961115,HT185399,2011-03-02 11:00:00,017XX N WHIPPLE ST,0460,BATTERY,SIMPLE,STREET,631,False,False,1421,14,26,23,08B,1155646,1911652,2011,ERROR,41.91337247,-87.703621623,"(41.91337247, -87.703621623)" +7919740,HT148765,2011-02-05 14:25:00,075XX S YALE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,STREET,3762,False,True,623,6,17,69,08B,1175854,1854934,2011,2011-08-02 15:08:26,41.75730259,-87.631085699,"(41.75730259, -87.631085699)" +2663371,HJ278006,2003-04-01 19:15:00,067XX S WOLCOTT AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,1220,False,False,726,NA,15,67,26,1164809,1859980,2003,ERROR,41.77138979,-87.67142173,"(41.77138979, -87.67142173)" +6550942,HP624443,2008-10-12 17:30:00,071XX S LAWNDALE AVE,0810,THEFT,OVER $500,RESIDENTIAL YARD (FRONT/BACK),1714,False,False,833,8,13,65,06,1152981,1856803,2008,ERROR,41.762913214,-87.714863238,"(41.762913214, -87.714863238)" +9052143,HW197487,2013-03-18 16:14:00,006XX W CHICAGO AVE,0460,BATTERY,SIMPLE,COMMERCIAL / BUSINESS OFFICE,1207,False,True,1822,18,27,8,08B,1172179,1905661,2013,ERROR,41.896583685,-87.643060288,"(41.896583685, -87.643060288)" +2128982,HH359279,2002-05-09 08:20:00,040XX S FEDERAL ST,2095,NARCOTICS,ATTEMPT POSSESSION NARCOTICS,CHA PARKING LOT/GROUNDS,2837,True,False,214,NA,3,38,18,1176399,1878070,2002,ERROR,41.820777898,-87.628393146,"(41.820777898, -87.628393146)" +9466436,HX119098,2014-01-18 12:15:00,016XX N MAPLEWOOD AVE,0560,ASSAULT,SIMPLE,RESIDENCE,3224,True,False,1434,14,1,24,08A,1159155,1910783,2014,ERROR,41.910916447,-87.690754192,"(41.910916447, -87.690754192)" +6833293,HR212717,2009-03-12 23:27:00,067XX S PARNELL AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,APARTMENT,4242,False,False,722,7,6,68,14,1173780,1859903,2009,2009-02-04 16:12:49,41.770984325,-87.638539479,"(41.770984325, -87.638539479)" +5816963,HN621314,2007-09-30 22:18:00,033XX W 65TH ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3009,True,False,823,8,15,66,14,1155444,1861367,2007,ERROR,41.775388583,-87.705713759,"(41.775388583, -87.705713759)" +8683873,HV358753,2012-06-29 14:40:00,062XX S EBERHART AVE,2024,NARCOTICS,POSS: HEROIN(WHITE),SIDEWALK,2697,True,False,313,3,20,42,18,1180607,1863977,2012,ERROR,41.782009714,-87.613389379,"(41.782009714, -87.613389379)" +9332406,HW476255,2013-10-02 09:15:00,003XX E 51ST ST,0860,THEFT,RETAIL THEFT,SMALL RETAIL STORE,1444,True,False,231,2,3,40,06,1179083,1871232,2013,2013-02-10 12:40:29,41.801952997,-87.618755627,"(41.801952997, -87.618755627)" +6106681,HP199670,2008-02-27 19:00:00,003XX W 45TH ST,0890,THEFT,FROM BUILDING,PARK PROPERTY,1877,False,False,935,9,3,37,06,1174657,1874998,2008,2008-01-03 09:26:24,41.812387089,-87.634875192,"(41.812387089, -87.634875192)" +8322735,HT556921,2011-10-24 09:45:00,016XX W MONROE ST,1320,CRIMINAL DAMAGE,TO VEHICLE,VEHICLE NON-COMMERCIAL,2411,False,True,1211,12,2,28,14,1165749,1899667,2011,2011-10-11 16:19:44,41.880275226,-87.666847435,"(41.880275226, -87.666847435)" +8806688,HV480499,2012-09-17 23:00:00,066XX S GREENWOOD AVE,0560,ASSAULT,SIMPLE,APARTMENT,661,True,True,321,3,5,42,08A,1184405,1861060,2012,ERROR,41.773917101,-87.599556337,"(41.773917101, -87.599556337)" +5649250,HN423753,2007-06-23 12:55:00,033XX W FULLERTON AVE,0820,THEFT,$500 AND UNDER,OTHER,203,False,False,1413,14,35,22,06,1153552,1915794,2007,2014-04-12 12:43:35,41.924780401,-87.711204182,"(41.924780401, -87.711204182)" +9481419,HX134548,2014-01-31 16:30:00,008XX N RIDGEWAY AVE,2024,NARCOTICS,POSS: HEROIN(WHITE),STREET,535,True,False,1112,11,27,23,18,1151276,1905276,2014,2014-05-02 00:38:42,41.895963033,-87.719843697,"(41.895963033, -87.719843697)" +7452945,HS254403,2010-04-12 18:00:00,011XX S DELANO CT E,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,4917,False,True,131,1,2,32,26,1175238,1895318,2010,ERROR,41.8681338,-87.632135611,"(41.8681338, -87.632135611)" +9863463,HX513290,2014-11-20 01:26:00,028XX W LAWRENCE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,SIDEWALK,3692,True,False,1911,19,47,4,08B,1156822,1931722,2014,ERROR,41.968422144,-87.698755406,"(41.968422144, -87.698755406)" +3888951,HL158249,2005-02-02 13:25:00,035XX W CHICAGO AVE,2027,NARCOTICS,POSS: CRACK,RESIDENCE,2282,True,False,1121,11,27,23,18,1152516,1905145,2005,ERROR,41.895579135,-87.715292874,"(41.895579135, -87.715292874)" +5869284,HN668807,2007-10-24 23:00:00,049XX W WEST END AVE,0326,ROBBERY,AGGRAVATED VEHICULAR HIJACKING,STREET,934,False,False,1532,15,28,25,03,1143315,1900566,2007,ERROR,41.883190721,-87.749200857,"(41.883190721, -87.749200857)" +2703495,HJ328167,2003-04-27 16:00:00,053XX S MAY ST,0610,BURGLARY,FORCIBLE ENTRY,ABANDONED BUILDING,3328,False,False,934,NA,16,61,05,1169605,1869148,2003,ERROR,41.796445235,-87.653575559,"(41.796445235, -87.653575559)" +4257280,HL574185,2005-08-27 01:30:00,084XX S ELIZABETH ST,0620,BURGLARY,UNLAWFUL ENTRY,APARTMENT,2999,False,False,613,6,21,71,05,1169506,1848666,2005,ERROR,41.740242188,-87.65453137,"(41.740242188, -87.65453137)" +5932502,HN731979,2007-11-28 09:30:00,077XX S BURNHAM AVE,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, BUILDING",3095,False,False,421,4,7,43,08B,1196020,1854375,2007,2007-11-12 01:04:21,41.755293165,-87.557200019,"(41.755293165, -87.557200019)" +6238887,HP326210,2008-05-09 08:00:00,011XX N CLARK ST,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),1292,False,False,1824,18,42,8,06,1175376,1907750,2008,2008-12-05 11:05:25,41.902244823,-87.631255595,"(41.902244823, -87.631255595)" +1875426,G723872,2001-12-03 03:00:00,043XX S SAWYER AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4250,False,False,821,NA,NA,NA,07,1155459,1875390,2001,ERROR,41.813869382,-87.705283202,"(41.813869382, -87.705283202)" +7725847,HS533443,2010-09-25 13:18:00,023XX N LONG AVE,0810,THEFT,OVER $500,PARK PROPERTY,2755,False,False,2515,25,37,19,06,1139957,1914995,2010,ERROR,41.922847656,-87.761178414,"(41.922847656, -87.761178414)" +3451995,HK003019,2004-07-24 01:30:00,013XX N HALSTED ST,2027,NARCOTICS,POSS: CRACK,STREET,687,True,False,1822,18,27,8,18,1170807,1909089,2004,ERROR,41.906020499,-87.647998785,"(41.906020499, -87.647998785)" +4626034,HM223084,2006-03-09 20:19:52,060XX S HARPER AVE,0890,THEFT,FROM BUILDING,APARTMENT,985,False,True,314,3,5,42,06,1187444,1865041,2006,ERROR,41.784769597,-87.588289691,"(41.784769597, -87.588289691)" +2635210,HJ244826,2003-03-17 22:30:00,041XX W NORTH AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4737,False,False,2534,25,30,23,07,1148753,1910358,2003,ERROR,41.90995769,-87.728978726,"(41.90995769, -87.728978726)" +4038854,HL311429,2005-04-22 14:30:00,055XX N LINCOLN AVE,1513,PROSTITUTION,SOLICIT FOR BUSINESS,HOTEL/MOTEL,2883,True,False,2011,20,40,4,16,1158216,1936790,2005,ERROR,41.982300536,-87.693490542,"(41.982300536, -87.693490542)" +4031728,HL384570,2005-05-28 07:00:00,003XX N LATROBE AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4941,False,False,1523,15,28,25,07,1141271,1901864,2005,2007-11-06 15:52:33,41.886790527,-87.756674616,"(41.886790527, -87.756674616)" +9537378,HX190485,2014-03-19 09:02:00,118XX S PERRY AVE,0497,BATTERY,AGGRAVATED DOMESTIC BATTERY: OTHER DANG WEAPON,SIDEWALK,2791,False,True,522,5,34,53,04B,1177670,1826389,2014,2014-04-04 00:38:54,41.678930434,-87.625290705,"(41.678930434, -87.625290705)" +7080558,HR488689,2009-08-17 13:55:00,012XX W ARTHUR AVE,0850,THEFT,ATTEMPT THEFT,SIDEWALK,2422,False,False,2432,24,40,1,06,1166693,1943419,2009,ERROR,42.000312672,-87.662123274,"(42.000312672, -87.662123274)" +6119164,HP210946,2008-03-06 13:17:00,020XX N SEDGWICK ST,0460,BATTERY,SIMPLE,STREET,3739,True,False,1814,18,43,7,08B,1173293,1914116,2008,2008-10-03 08:28:03,41.919759963,-87.638717325,"(41.919759963, -87.638717325)" +5259619,HN135255,2007-01-20 17:00:00,041XX N CLARENDON AVE,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE,2690,False,False,2322,19,46,3,05,1170144,1927369,2007,2007-12-02 06:20:13,41.956196167,-87.649898952,"(41.956196167, -87.649898952)" +9177023,HW321951,2013-06-16 23:00:00,002XX W 103RD ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,4706,True,False,511,5,9,49,18,1176620,1836684,2013,ERROR,41.707205049,-87.628825892,"(41.707205049, -87.628825892)" +2827497,HJ477324,2003-07-07 01:35:00,046XX N CLIFTON AVE,0460,BATTERY,SIMPLE,STREET,1471,False,False,2311,19,46,3,08B,1167640,1930699,2003,ERROR,41.965388238,-87.659007885,"(41.965388238, -87.659007885)" +3012718,HJ716464,2003-10-25 02:42:00,042XX W KAMERLING AVE,0810,THEFT,OVER $500,VEHICLE NON-COMMERCIAL,1750,False,False,2534,25,37,23,06,1147558,1908601,2003,2014-04-12 12:43:35,41.905159324,-87.733413851,"(41.905159324, -87.733413851)" +3294036,HK326749,2004-04-25 21:00:00,051XX W BLOOMINGDALE AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,4527,False,False,2533,25,37,25,05,1141931,1911525,2004,ERROR,41.913289233,-87.754011314,"(41.913289233, -87.754011314)" +6803317,HR213727,2009-03-12 16:00:00,0000X N STATE ST,0890,THEFT,FROM BUILDING,SMALL RETAIL STORE,4883,False,False,122,1,42,32,06,1176328,1900431,2009,ERROR,41.882139677,-87.627979796,"(41.882139677, -87.627979796)" +9971989,HY161202,2015-02-23 14:56:00,004XX E 48TH ST,0820,THEFT,$500 AND UNDER,APARTMENT,404,False,True,223,2,3,38,06,1180243,1873338,2015,2015-06-03 12:42:07,41.807705499,-87.614436883,"(41.807705499, -87.614436883)" +6271204,HP353232,2008-05-23 19:45:00,094XX S BURNSIDE AVE,0560,ASSAULT,SIMPLE,RESIDENCE,3954,False,False,633,6,9,44,08A,1182714,1842560,2008,2008-02-06 06:39:38,41.72319054,-87.60632814,"(41.72319054, -87.60632814)" +2826618,HJ482716,2003-04-01 13:00:00,034XX W CARROLL AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,3974,False,False,1123,11,28,27,05,1153347,1902226,2003,ERROR,41.887552644,-87.712318379,"(41.887552644, -87.712318379)" +7953888,HT186007,2011-03-02 19:00:00,007XX N WABASH AVE,0820,THEFT,$500 AND UNDER,STREET,92,False,False,1834,18,42,8,06,1176565,1905227,2011,2011-03-03 06:43:40,41.895294805,-87.626964572,"(41.895294805, -87.626964572)" +3979852,HL343037,2005-05-07 19:00:00,016XX E 50TH ST,0810,THEFT,OVER $500,STREET,749,False,False,2132,2,4,39,06,1188465,1872158,2005,2014-04-12 12:43:35,41.804274819,-87.584318985,"(41.804274819, -87.584318985)" +4101174,HL441592,2005-06-24 16:00:00,061XX S WESTERN AVE,0820,THEFT,$500 AND UNDER,PARKING LOT/GARAGE(NON.RESID.),44,False,False,825,8,15,66,06,1161471,1863600,2005,2014-04-12 12:43:35,41.781393417,-87.683557482,"(41.781393417, -87.683557482)" +2613348,HJ215734,2003-01-16 16:00:00,077XX S WESTERN AVE,0820,THEFT,$500 AND UNDER,STREET,157,False,False,835,NA,18,70,06,1161675,1853495,2003,2014-04-12 12:43:35,41.753659623,-87.683089644,"(41.753659623, -87.683089644)" +5184536,HM766200,2006-12-09 17:00:00,031XX W 31ST ST,0820,THEFT,$500 AND UNDER,STREET,311,False,False,1033,10,12,30,06,1155985,1883878,2006,2014-04-12 12:43:35,41.837150955,-87.703125393,"(41.837150955, -87.703125393)" +8230238,HT464106,2011-08-24 19:15:00,113XX S STEWART AVE,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,RESIDENCE,3176,False,False,2233,22,34,49,04B,1175621,1829403,2011,ERROR,41.687247278,-87.632701111,"(41.687247278, -87.632701111)" +7374070,HS176750,2010-02-22 20:50:00,019XX S PULASKI RD,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,STREET,4263,True,False,1014,10,24,29,15,1150062,1890214,2010,ERROR,41.854654939,-87.724694776,"(41.854654939, -87.724694776)" +5274149,HN143339,2007-01-25 19:00:00,079XX S COTTAGE GROVE AVE,1330,CRIMINAL TRESPASS,TO LAND,TAVERN/LIQUOR STORE,1997,True,False,624,6,8,44,26,1182965,1852736,2007,ERROR,41.751108806,-87.605093358,"(41.751108806, -87.605093358)" +5487050,HN286402,2007-04-16 14:00:00,039XX N LAWNDALE AVE,2890,PUBLIC PEACE VIOLATION,OTHER VIOLATION,"SCHOOL, PUBLIC, BUILDING",640,True,False,1732,17,39,16,26,1150988,1925736,2007,ERROR,41.95211271,-87.720364302,"(41.95211271, -87.720364302)" +9176249,HW319713,2013-06-15 10:45:00,069XX S CRANDON AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE PORCH/HALLWAY,2794,False,True,331,3,5,43,08B,1192569,1859188,2013,ERROR,41.76858515,-87.569690199,"(41.76858515, -87.569690199)" +5970003,HN765590,2007-12-18 14:00:00,028XX N MILWAUKEE AVE,0820,THEFT,$500 AND UNDER,VEHICLE-COMMERCIAL,352,False,False,1412,14,35,21,06,1152714,1918810,2007,2014-04-12 12:43:35,41.933073195,-87.71420336,"(41.933073195, -87.71420336)" +4936885,HM544641,2006-08-16 20:30:00,095XX S INDIANA AVE,0320,ROBBERY,STRONGARM - NO WEAPON,STREET,4828,True,False,511,5,6,49,03,1179217,1841960,2006,ERROR,41.72162439,-87.619155472,"(41.72162439, -87.619155472)" +9474839,HX128512,2014-01-26 16:00:00,019XX W PETERSON AVE,0850,THEFT,ATTEMPT THEFT,RESTAURANT,3213,True,False,2413,24,40,2,06,1162040,1939914,2014,ERROR,41.990793656,-87.679339104,"(41.990793656, -87.679339104)" +7802326,HS611894,2010-11-11 18:45:00,004XX S KEDZIE AVE,0820,THEFT,$500 AND UNDER,SMALL RETAIL STORE,174,False,False,1134,11,28,27,06,1155148,1897810,2010,ERROR,41.87539874,-87.705823098,"(41.87539874, -87.705823098)" +7731223,HS539017,2010-09-28 22:26:00,007XX S CALIFORNIA AVE,2027,NARCOTICS,POSS: CRACK,SIDEWALK,4969,True,False,1135,11,2,27,18,1157851,1896880,2010,ERROR,41.872792072,-87.695924058,"(41.872792072, -87.695924058)" +5221374,HN105437,2007-01-04 02:13:58,088XX S MARSHFIELD AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,600,True,True,2221,22,21,71,08B,1166921,1846213,2007,2014-04-12 12:43:35,41.733566359,-87.664072408,"(41.733566359, -87.664072408)" +4542176,HL749208,2005-11-22 06:19:20,045XX W CERMAK RD,1506,PROSTITUTION,SOLICIT ON PUBLIC WAY,STREET,2808,True,False,1012,10,24,29,16,1146657,1889031,2005,ERROR,41.851474184,-87.737222838,"(41.851474184, -87.737222838)" +4562689,HM152355,2006-01-29 19:30:00,020XX N LAWLER AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,3832,False,False,2522,25,31,19,05,1142381,1913021,2006,ERROR,41.917386066,-87.752320857,"(41.917386066, -87.752320857)" +9707558,HX357398,2014-07-23 17:30:00,020XX E 71ST ST,0554,ASSAULT,AGG PO HANDS NO/MIN INJURY,SMALL RETAIL STORE,1641,True,False,331,3,5,43,08A,1191387,1858371,2014,ERROR,41.766371955,-87.574049171,"(41.766371955, -87.574049171)" +4714041,HM227293,2006-03-11 20:25:00,065XX S WOLCOTT AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,622,True,False,726,7,15,67,18,1164765,1861497,2006,ERROR,41.775553569,-87.671540228,"(41.775553569, -87.671540228)" +5921937,HN718848,2007-11-20 17:36:30,059XX S PAULINA ST,0560,ASSAULT,SIMPLE,RESIDENCE PORCH/HALLWAY,3639,False,False,714,7,15,67,08A,1165991,1865160,2007,2007-02-12 01:04:24,41.785579331,-87.66694177,"(41.785579331, -87.66694177)" +3218316,HK228080,2004-03-03 15:00:00,039XX W 79TH ST,0330,ROBBERY,AGGRAVATED,"SCHOOL, PUBLIC, GROUNDS",4389,False,False,834,8,18,70,03,1151161,1851841,2004,ERROR,41.749332365,-87.721663241,"(41.749332365, -87.721663241)" +3487515,HK557821,2004-08-14 06:20:00,006XX N LONG AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,4892,False,False,1524,15,37,25,14,1140252,1903605,2004,ERROR,41.891586777,-87.760374016,"(41.891586777, -87.760374016)" +2451451,HH777241,2002-11-13 04:00:00,084XX S DREXEL AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,3522,False,False,632,6,8,44,07,1183741,1848976,2002,ERROR,41.740772908,-87.602366784,"(41.740772908, -87.602366784)" +4502366,HL808084,2005-12-24 13:00:00,066XX N ASHLAND AVE,2826,OTHER OFFENSE,HARASSMENT BY ELECTRONIC MEANS,RESIDENCE,2954,False,False,2432,24,40,1,26,1164366,1944194,2005,ERROR,42.002489027,-87.670661713,"(42.002489027, -87.670661713)" +4931618,HM406308,2006-06-10 20:45:38,026XX W 47TH ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,3205,True,False,911,9,12,58,18,1159669,1873369,2006,ERROR,41.808238032,-87.689895989,"(41.808238032, -87.689895989)" +3614068,HK707958,2004-10-24 23:00:00,066XX S MAY ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3727,False,True,724,7,17,68,08B,1169839,1860579,2004,ERROR,41.77292581,-87.652966109,"(41.77292581, -87.652966109)" +3751122,HK756140,2004-11-17 11:30:00,071XX S VERNON AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,2489,True,False,323,3,6,69,18,1180514,1858040,2004,ERROR,41.765720129,-87.613912421,"(41.765720129, -87.613912421)" +8775540,HV450164,2012-08-28 03:10:00,040XX S CALUMET AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,3867,True,True,213,2,3,38,08B,1179057,1878335,2012,ERROR,41.821444824,-87.618634338,"(41.821444824, -87.618634338)" +3361951,HK409235,2004-06-04 00:01:00,022XX S WASHTENAW AVE,0820,THEFT,$500 AND UNDER,STREET,380,False,False,1034,10,28,30,06,1158665,1889279,2004,2014-04-12 12:43:35,41.851917522,-87.693143577,"(41.851917522, -87.693143577)" +6028483,HP130477,2008-01-18 08:00:00,054XX S BISHOP ST,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,4831,False,False,933,9,16,61,05,1167547,1868813,2008,2008-10-02 09:00:51,41.795570365,-87.661132024,"(41.795570365, -87.661132024)" +3491821,HK563513,2004-08-17 14:22:57,115XX S CHURCH ST,1320,CRIMINAL DAMAGE,TO VEHICLE,DRIVEWAY - RESIDENTIAL,1773,False,False,2212,22,34,75,14,1165666,1828209,2004,ERROR,41.684187135,-87.669178838,"(41.684187135, -87.669178838)" +3018372,HJ720023,2003-10-25 01:00:00,038XX W 31ST ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,2821,False,False,1031,10,22,30,08B,1151504,1883738,2003,ERROR,41.836855819,-87.719571859,"(41.836855819, -87.719571859)" +1354914,G057397,2001-01-28 06:00:00,105XX S TRUMBULL AV,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,1221,False,False,2211,NA,NA,NA,14,1155237,1834766,2001,ERROR,41.702395083,-87.707182113,"(41.702395083, -87.707182113)" +4569529,HM158752,2006-01-23 08:00:00,072XX S RHODES AVE,0560,ASSAULT,SIMPLE,APARTMENT,3176,False,True,323,3,6,69,08A,1181195,1857297,2006,ERROR,41.763665603,-87.611439209,"(41.763665603, -87.611439209)" +6806076,HR217090,2009-03-15 12:05:00,054XX N LINCOLN AVE,0820,THEFT,$500 AND UNDER,RESTAURANT,346,False,False,2011,20,40,4,06,1158464,1936018,2009,ERROR,41.980177046,-87.692599697,"(41.980177046, -87.692599697)" +6083955,HP180648,2008-02-17 16:45:01,049XX S LA CROSSE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,3195,False,False,814,8,23,56,14,1144901,1871417,2008,ERROR,41.803172073,-87.744111245,"(41.803172073, -87.744111245)" +8807854,HV481114,2012-09-18 08:00:00,006XX E GRAND AVE,0810,THEFT,OVER $500,OTHER,674,False,False,1834,18,42,8,06,1180766,1904096,2012,ERROR,41.89209535,-87.611570504,"(41.89209535, -87.611570504)" +2338089,HH593976,2002-08-20 19:30:00,029XX N CENTRAL PARK AVE,2210,LIQUOR LAW VIOLATION,SELL/GIVE/DEL LIQUOR TO MINOR,TAVERN/LIQUOR STORE,3797,True,False,2523,NA,35,21,22,1151842,1919206,2002,ERROR,41.934177086,-87.717397454,"(41.934177086, -87.717397454)" +8452974,HV130354,2012-01-24 16:11:00,048XX N SHERIDAN RD,0560,ASSAULT,SIMPLE,CURRENCY EXCHANGE,2398,False,True,2024,20,46,3,08A,1168724,1932205,2012,ERROR,41.96949727,-87.654978443,"(41.96949727, -87.654978443)" +5324749,HN183465,2007-02-18 09:00:00,020XX W 70TH PL,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,2289,True,False,735,7,17,67,26,1163866,1857860,2007,ERROR,41.765592081,-87.674937943,"(41.765592081, -87.674937943)" +1623626,G373657,2001-06-27 02:16:41,005XX W DIVISION ST,2027,NARCOTICS,POSS: CRACK,STREET,3021,True,False,1823,NA,NA,NA,18,1172307,1908305,2001,ERROR,41.903836143,-87.642511973,"(41.903836143, -87.642511973)" +6375121,HP457261,2008-07-17 01:28:39,042XX W VAN BUREN ST,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,SIDEWALK,4906,False,False,1132,11,24,26,04B,1148391,1897730,2008,2008-01-08 22:09:13,41.875312091,-87.730634436,"(41.875312091, -87.730634436)" +3783641,HK789089,2004-12-04 10:37:29,029XX S DEARBORN ST,1822,NARCOTICS,MANU/DEL:CANNABIS OVER 10 GMS,CHA APARTMENT,2874,True,False,2113,1,3,35,18,1176498,1885030,2004,ERROR,41.839874474,-87.627820281,"(41.839874474, -87.627820281)" +4549735,HM139445,2006-01-22 23:10:00,019XX S RACINE AVE,1330,CRIMINAL TRESPASS,TO LAND,RESIDENCE PORCH/HALLWAY,1515,True,False,1233,12,25,31,26,1168712,1890864,2006,ERROR,41.856055437,-87.656222636,"(41.856055437, -87.656222636)" +2457835,HH783440,2002-11-10 12:30:00,042XX W 25TH PL,0810,THEFT,OVER $500,STREET,4867,False,False,1013,NA,22,30,06,1148465,1886754,2002,2014-04-12 12:43:35,41.845191169,-87.730645627,"(41.845191169, -87.730645627)" +6612069,HP685482,2008-11-15 20:00:00,037XX W LEXINGTON ST,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,4044,False,False,1133,11,24,27,26,1151664,1896487,2008,ERROR,41.871837466,-87.718649859,"(41.871837466, -87.718649859)" +3543604,HK627013,2004-09-16 09:20:00,047XX N WINTHROP AVE,0560,ASSAULT,SIMPLE,SIDEWALK,3752,False,True,2312,19,46,3,08A,1168071,1931384,2004,ERROR,41.967258582,-87.657403334,"(41.967258582, -87.657403334)" +5036221,HM519102,2006-08-04 11:47:26,051XX W MAYPOLE AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,OTHER,1726,True,False,1532,15,28,25,18,1141785,1900930,2006,ERROR,41.884218022,-87.754810169,"(41.884218022, -87.754810169)" +8417213,HT650314,2011-12-28 12:50:00,023XX W MADISON ST,2170,NARCOTICS,POSSESSION OF DRUG EQUIPMENT,STREET,2061,True,False,1332,12,2,28,18,1160516,1899992,2011,ERROR,41.881276963,-87.686053431,"(41.881276963, -87.686053431)" +1406729,G971618,2001-02-23 11:00:00,0000X W HUBBARD ST,0820,THEFT,$500 AND UNDER,TAXICAB,182,False,False,1831,NA,NA,NA,06,1176066,1903352,2001,2014-04-12 12:43:35,41.890160967,-87.628853795,"(41.890160967, -87.628853795)" +5919963,HN717978,2007-11-20 10:45:00,011XX N KEYSTONE AVE,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,SIDEWALK,1751,True,False,1111,11,27,23,26,1149217,1907290,2007,ERROR,41.901529809,-87.727353784,"(41.901529809, -87.727353784)" +2685011,HJ285981,2003-04-07 17:30:00,002XX W 24TH PL,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,4426,True,False,2111,NA,25,34,18,1174709,1888096,2003,ERROR,41.848327956,-87.634293516,"(41.848327956, -87.634293516)" +7647393,HS452564,2010-08-08 11:11:00,044XX N BROADWAY,0870,THEFT,POCKET-PICKING,SMALL RETAIL STORE,644,False,False,2311,19,46,3,06,1168390,1929931,2010,2010-12-08 18:54:25,41.96326459,-87.656272632,"(41.96326459, -87.656272632)" +9876690,HX527338,2014-12-02 10:50:00,116XX S WALLACE ST,1365,CRIMINAL TRESPASS,TO RESIDENCE,RESIDENCE,4273,True,False,524,5,34,53,26,1174437,1827665,2014,2014-09-12 12:42:40,41.682504278,-87.637086989,"(41.682504278, -87.637086989)" +8735782,HV411396,2012-08-01 22:30:00,013XX W PRATT BLVD,1812,NARCOTICS,POSS: CANNABIS MORE THAN 30GMS,STREET,4850,True,False,2432,24,49,1,18,1166147,1945262,2012,2012-02-08 02:34:01,42.005381627,-87.664078885,"(42.005381627, -87.664078885)" +1815802,G640754,2001-10-24 17:35:00,072XX S RIDGELAND AV,0460,BATTERY,SIMPLE,SIDEWALK,3648,False,False,324,NA,NA,NA,08B,1189086,1857179,2001,ERROR,41.763156434,-87.582521182,"(41.763156434, -87.582521182)" +9048414,HW192963,2013-03-13 16:10:00,035XX E 114TH ST,0460,BATTERY,SIMPLE,STREET,1436,False,False,433,4,10,52,08B,1201622,1829943,2013,ERROR,41.688109378,-87.537498377,"(41.688109378, -87.537498377)" +4252619,HL568353,2005-08-24 10:51:19,027XX E 83RD ST,0610,BURGLARY,FORCIBLE ENTRY,SMALL RETAIL STORE,1402,False,False,423,4,7,46,05,1195865,1850505,2005,ERROR,41.744677425,-87.557895859,"(41.744677425, -87.557895859)" +7015773,HR422984,2009-07-11 07:45:00,060XX N BROADWAY,0860,THEFT,RETAIL THEFT,GROCERY FOOD STORE,3873,True,False,2433,24,48,77,06,1167253,1940103,2009,ERROR,41.991201436,-87.660159064,"(41.991201436, -87.660159064)" +7085175,HR493489,2009-08-20 03:00:00,042XX S COTTAGE GROVE AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,STREET,3060,False,False,213,2,4,38,14,1182265,1877306,2009,ERROR,41.818547345,-87.606897794,"(41.818547345, -87.606897794)" +3872852,HL239738,2005-03-11 19:30:00,007XX W 112TH ST,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,840,False,False,2233,22,34,49,07,1173427,1830631,2005,ERROR,41.690665788,-87.640696894,"(41.690665788, -87.640696894)" +4784156,HM394717,2006-06-05 08:00:00,058XX N LINCOLN AVE,0890,THEFT,FROM BUILDING,"SCHOOL, PUBLIC, BUILDING",1276,False,False,2011,20,40,2,06,1155783,1938688,2006,2006-09-06 04:07:44,41.987558265,-87.702387107,"(41.987558265, -87.702387107)" +6149991,HP239396,2008-03-23 02:31:00,017XX N CLARK ST,0460,BATTERY,SIMPLE,BAR OR TAVERN,2251,False,False,1814,18,43,7,08B,1174666,1912148,2008,2008-08-04 17:08:11,41.914329059,-87.633731746,"(41.914329059, -87.633731746)" +2706200,HJ317376,2003-04-22 23:30:09,058XX W CHICAGO AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,1400,True,False,1511,NA,29,25,18,1137385,1904788,2003,ERROR,41.894885131,-87.77087487,"(41.894885131, -87.77087487)" +3243981,HK261903,2004-03-25 07:00:00,041XX W BARRY AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,1844,False,False,2523,25,31,21,26,1148267,1920250,2004,ERROR,41.93711164,-87.730508643,"(41.93711164, -87.730508643)" +4670809,HM272274,2006-04-04 16:30:00,001XX W 110TH PL,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,RESIDENCE PORCH/HALLWAY,2999,False,False,513,5,34,49,04B,1177196,1831716,2006,2006-09-04 03:54:02,41.693559196,-87.626865823,"(41.693559196, -87.626865823)" +3551830,HK636854,2004-09-20 17:00:00,016XX W HOLLYWOOD AVE,0560,ASSAULT,SIMPLE,APARTMENT,2262,False,False,2012,20,40,77,08A,1164030,1937887,2004,ERROR,41.985189571,-87.672076994,"(41.985189571, -87.672076994)" +5555385,HN360342,2007-05-20 06:00:00,071XX S SOUTH SHORE DR,0560,ASSAULT,SIMPLE,STREET,4204,False,True,334,3,7,43,08A,1194449,1858304,2007,2007-11-06 15:52:33,41.766113381,-87.562828234,"(41.766113381, -87.562828234)" +8722256,HV398745,2012-04-24 09:00:00,080XX S DREXEL AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,1617,False,False,631,6,8,44,26,1183677,1851641,2012,ERROR,41.748087445,-87.602518345,"(41.748087445, -87.602518345)" +2870583,HJ537839,2003-08-03 14:50:00,044XX N HAZEL ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,VEHICLE NON-COMMERCIAL,3347,False,True,2313,19,46,3,08B,1169420,1929662,2003,ERROR,41.96250405,-87.652493562,"(41.96250405, -87.652493562)" +2202542,HH464935,2002-06-24 21:31:52,001XX W 87TH ST,1330,CRIMINAL TRESPASS,TO LAND,PARKING LOT/GARAGE(NON.RESID.),4936,True,False,622,NA,21,44,26,1176895,1847317,2002,ERROR,41.736377238,-87.627499596,"(41.736377238, -87.627499596)" +9078445,HW222090,2013-04-06 15:50:00,003XX E 60TH ST,0320,ROBBERY,STRONGARM - NO WEAPON,SIDEWALK,3449,False,False,232,2,20,40,03,1179110,1865332,2013,ERROR,41.785762231,-87.618836444,"(41.785762231, -87.618836444)" +6538835,HP612336,2008-10-06 14:30:00,012XX N MONTICELLO AVE,1563,SEX OFFENSE,CRIMINAL SEXUAL ABUSE,"SCHOOL, PUBLIC, BUILDING",1976,True,False,2535,25,26,23,17,1151767,1908135,2008,2009-03-07 13:40:43,41.90379876,-87.717965025,"(41.90379876, -87.717965025)" +1891896,G739646,2001-12-10 17:30:00,001XX W OAK ST,0460,BATTERY,SIMPLE,APARTMENT,3559,False,True,1824,NA,NA,NA,08B,1174581,1907163,2001,ERROR,41.900651882,-87.63419329,"(41.900651882, -87.63419329)" +6361408,HP448520,2008-07-12 15:30:00,038XX W POLK ST,0560,ASSAULT,SIMPLE,APARTMENT,1593,False,False,1133,11,24,26,08A,1150950,1896132,2008,ERROR,41.870877306,-87.721280557,"(41.870877306, -87.721280557)" +3347368,HK388596,2004-05-26 07:20:00,092XX S ABERDEEN ST,0460,BATTERY,SIMPLE,SIDEWALK,4671,False,False,2222,22,21,73,08B,1170580,1843176,2004,ERROR,41.725153538,-87.650755883,"(41.725153538, -87.650755883)" +9948680,HY137572,2015-02-02 10:40:00,052XX W CONGRESS PKWY,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,4151,False,False,1522,15,29,25,05,1141716,1897144,2015,2015-09-02 12:45:35,41.873830036,-87.755157207,"(41.873830036, -87.755157207)" +8157166,HT392395,2011-07-11 22:03:00,012XX N CLYBOURN AVE,0850,THEFT,ATTEMPT THEFT,SIDEWALK,2478,True,False,1821,18,27,8,06,1172815,1908509,2011,2011-12-07 09:18:42,41.904384688,-87.640639932,"(41.904384688, -87.640639932)" +5615641,HN424136,2007-06-23 16:58:43,080XX S ST LOUIS AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,754,False,False,834,8,18,70,05,1154418,1850741,2007,2007-03-08 02:38:51,41.746249624,-87.709757369,"(41.746249624, -87.709757369)" +6678841,HP753255,2008-12-27 17:05:00,052XX N SHERIDAN RD,0860,THEFT,RETAIL THEFT,GROCERY FOOD STORE,1653,True,False,2023,20,48,77,06,1168731,1935088,2008,ERROR,41.977408151,-87.654868748,"(41.977408151, -87.654868748)" +4652026,HM251430,2006-03-25 00:01:00,097XX S AVENUE L,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,APARTMENT,1778,False,False,432,4,10,52,26,1201832,1841147,2006,2006-04-04 03:37:55,41.718848775,-87.53635002,"(41.718848775, -87.53635002)" +7172268,HR582951,2009-10-11 12:15:00,003XX W NORTH AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,3748,False,False,1814,18,43,7,26,1173850,1911019,2009,ERROR,41.911249256,-87.636763258,"(41.911249256, -87.636763258)" +6920364,HR325590,2009-05-17 03:50:00,053XX S NORDICA AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE-GARAGE,1851,False,False,811,8,23,56,14,1130322,1868174,2009,ERROR,41.794534496,-87.797654615,"(41.794534496, -87.797654615)" +6413617,HP474865,2008-07-25 22:40:00,044XX W MADISON ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,4473,True,False,1113,11,28,26,18,1146975,1899609,2008,2008-07-08 12:18:35,41.880495455,-87.73578545,"(41.880495455, -87.73578545)" +5099538,HM702282,2006-11-05 05:30:00,039XX N SHERIDAN RD,0870,THEFT,POCKET-PICKING,CTA TRAIN,1993,False,False,2324,19,44,6,06,1168850,1926543,2006,2006-08-11 05:36:51,41.953957812,-87.654680033,"(41.953957812, -87.654680033)" +7256815,HR671119,2009-11-22 02:00:00,012XX N CLARK ST,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,ATM (AUTOMATIC TELLER MACHINE),2265,False,False,1821,18,42,8,11,1175274,1908508,2009,2009-04-12 14:38:56,41.904327102,-87.631607477,"(41.904327102, -87.631607477)" +5593784,HN398599,2007-06-11 15:11:00,005XX E 71ST ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,1097,True,False,322,3,6,69,18,1181308,1858110,2007,ERROR,41.765893956,-87.61100002,"(41.765893956, -87.61100002)" +6361346,HP448207,2008-07-11 08:30:00,051XX S HARPER AVE,0560,ASSAULT,SIMPLE,SIDEWALK,4863,False,True,2132,2,4,41,08A,1187200,1871163,2008,ERROR,41.801574625,-87.588989942,"(41.801574625, -87.588989942)" +9900148,HX550849,2014-12-21 21:00:00,033XX S INDIANA AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3486,False,False,211,2,3,35,14,1178111,1882842,2014,ERROR,41.833833929,-87.621967815,"(41.833833929, -87.621967815)" +9176806,HW321737,2013-06-16 07:30:00,111XX S GREEN ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENTIAL YARD (FRONT/BACK),1833,False,False,2233,22,34,75,14,1172607,1830711,2013,ERROR,41.690903375,-87.643696617,"(41.690903375, -87.643696617)" +9255503,HW400240,2013-08-09 09:00:00,016XX W WRIGHTWOOD AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,PARKING LOT/GARAGE(NON.RESID.),2272,False,False,1931,19,32,7,14,1164679,1917284,2013,ERROR,41.928640244,-87.670276344,"(41.928640244, -87.670276344)" +8879865,HV553462,2012-11-08 14:00:00,075XX S CHAMPLAIN AVE,0880,THEFT,PURSE-SNATCHING,SIDEWALK,937,False,False,624,6,6,69,06,1181824,1855392,2012,2012-09-11 10:05:24,41.758423578,-87.609192588,"(41.758423578, -87.609192588)" +8619862,HV293402,2012-05-19 09:00:00,051XX W HURON ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,3108,False,True,1532,15,28,25,08B,1142167,1904099,2012,ERROR,41.892907066,-87.753328745,"(41.892907066, -87.753328745)" +8939790,HV611318,2012-12-20 18:00:00,029XX W WASHINGTON BLVD,0610,BURGLARY,FORCIBLE ENTRY,"SCHOOL, PUBLIC, BUILDING",2123,False,False,1222,12,2,27,05,1156901,1900584,2012,2013-01-01 19:08:51,41.882975519,-87.699311498,"(41.882975519, -87.699311498)" +4471061,HL762489,2005-11-29 18:24:56,025XX S DRAKE AVE,502P,OTHER OFFENSE,FALSE/STOLEN/ALTERED TRP,ALLEY,3995,True,False,1024,10,22,30,26,1153071,1886821,2005,ERROR,41.845285083,-87.71374025,"(41.845285083, -87.71374025)" +6948596,HR354023,2009-05-08 09:00:00,002XX E GARFIELD BLVD,1152,DECEPTIVE PRACTICE,ILLEGAL USE CASH CARD,RESIDENCE,4044,False,False,232,2,3,40,11,1178909,1868644,2009,ERROR,41.794855256,-87.61947257,"(41.794855256, -87.61947257)" +3024446,HJ731807,2003-11-01 10:19:57,082XX S COMMERCIAL AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,2796,False,True,424,4,10,46,08B,1197665,1850765,2003,ERROR,41.745346184,-87.551291909,"(41.745346184, -87.551291909)" +7096237,HR504828,2009-08-26 16:56:00,019XX W 33RD ST,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,4912,True,False,922,9,11,59,06,1163768,1882841,2009,ERROR,41.834145069,-87.674595443,"(41.834145069, -87.674595443)" +5309441,HN169039,2007-02-09 18:50:00,061XX W 64TH PL,0560,ASSAULT,SIMPLE,APARTMENT,3080,False,False,812,8,13,64,08A,1136487,1861077,2007,ERROR,41.774951401,-87.775216121,"(41.774951401, -87.775216121)" +3332497,HK367263,2004-05-16 04:42:21,003XX N STATE ST,0460,BATTERY,SIMPLE,STREET,3474,True,False,1831,18,42,8,08B,1176258,1902572,2004,ERROR,41.888016277,-87.628172231,"(41.888016277, -87.628172231)" +5531805,HN344079,2007-05-15 14:45:00,080XX S COTTAGE GROVE AVE,033A,ROBBERY,ATTEMPT: ARMED-HANDGUN,SIDEWALK,4786,False,False,631,6,8,44,03,1182985,1852068,2007,2007-03-06 02:49:28,41.749275279,-87.605040789,"(41.749275279, -87.605040789)" +2251499,HH522712,2002-07-19 21:00:00,054XX N LINCOLN AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,OTHER,811,False,False,2011,NA,40,4,14,1158470,1935921,2002,ERROR,41.979910751,-87.6925803,"(41.979910751, -87.6925803)" +5009126,HM619632,2006-09-24 03:00:00,021XX N RICHMOND ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,1319,False,True,1414,14,35,22,08B,1156551,1914178,2006,ERROR,41.920285736,-87.700228327,"(41.920285736, -87.700228327)" +5710364,HN518757,2007-08-01 17:00:00,084XX W BRYN MAWR AVE,0890,THEFT,FROM BUILDING,OTHER,1004,False,False,1614,16,41,76,06,1119008,1936135,2007,ERROR,41.98121629,-87.837704596,"(41.98121629, -87.837704596)" +3722385,HK829243,2004-12-26 19:40:00,104XX S GREEN ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,STREET,933,False,True,2233,22,34,73,08B,1172542,1835330,2004,ERROR,41.703580052,-87.643799205,"(41.703580052, -87.643799205)" +3832461,HL203373,2005-02-26 12:35:31,002XX S HOYNE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,4153,False,False,1211,12,2,28,14,1162403,1898857,2005,ERROR,41.878123162,-87.679156235,"(41.878123162, -87.679156235)" +8764513,HV439482,2012-08-20 20:08:00,038XX S ELLIS AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE PORCH/HALLWAY,1953,True,True,212,2,4,36,08B,1182599,1879695,2012,ERROR,41.825095178,-87.605598354,"(41.825095178, -87.605598354)" +4299664,HL609145,2005-09-13 08:30:00,014XX E 73RD ST,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,1634,False,False,324,3,5,43,26,1186949,1856916,2005,ERROR,41.762485657,-87.590361895,"(41.762485657, -87.590361895)" +5945461,HN743993,2007-12-05 19:06:00,050XX W HURON ST,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,STREET,551,True,False,1531,15,28,25,26,1142830,1904220,2007,2007-09-12 01:04:39,41.89322678,-87.750890754,"(41.89322678, -87.750890754)" +3283363,HK312676,2004-04-20 00:11:42,046XX N CLIFTON AVE,1220,DECEPTIVE PRACTICE,THEFT OF LOST/MISLAID PROP,STREET,2918,True,False,2311,19,46,3,11,1167636,1931034,2004,ERROR,41.966307576,-87.659012898,"(41.966307576, -87.659012898)" +4021146,HL375918,2005-05-24 12:46:15,107XX S EBERHART AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,2789,False,False,513,5,9,49,14,1181446,1833717,2005,ERROR,41.698953504,-87.6112443,"(41.698953504, -87.6112443)" +6928511,HR332778,2009-05-20 17:30:00,007XX W BARRY AVE,0810,THEFT,OVER $500,STREET,4362,False,False,2332,19,44,6,06,1170898,1920733,2009,ERROR,41.937970207,-87.647322338,"(41.937970207, -87.647322338)" +9938186,HY126791,2015-01-24 00:10:00,026XX W EVERGREEN AVE,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,RESIDENCE,3583,False,False,1423,14,26,24,11,1158479,1908916,2015,ERROR,41.905807118,-87.69328876,"(41.905807118, -87.69328876)" +2981789,HJ644907,2003-09-21 22:04:00,062XX S MORGAN ST,2027,NARCOTICS,POSS: CRACK,STREET,4061,True,False,712,7,16,68,18,1170765,1863047,2003,ERROR,41.779678145,-87.649499695,"(41.779678145, -87.649499695)" +2677516,HJ285368,2003-04-07 13:20:00,001XX W 72ND ST,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,1047,False,True,731,NA,6,69,26,1176827,1857294,2003,ERROR,41.763756849,-87.627448846,"(41.763756849, -87.627448846)" +2878862,HJ547518,2003-08-07 12:00:00,048XX W BELLE PLAINE AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,2191,False,False,1624,16,45,15,05,1143180,1926855,2003,ERROR,41.955332998,-87.749038868,"(41.955332998, -87.749038868)" +6312467,HP396461,2008-06-13 07:20:00,018XX W LAWRENCE AVE,0460,BATTERY,SIMPLE,PARKING LOT/GARAGE(NON.RESID.),4648,False,False,2032,20,47,4,08B,1163122,1931948,2008,ERROR,41.968911917,-87.675584255,"(41.968911917, -87.675584255)" +2168387,HH420999,2002-06-02 19:00:00,005XX W WILSON DR,0610,BURGLARY,FORCIBLE ENTRY,OTHER,867,False,False,2313,NA,46,3,05,1171655,1930932,2002,ERROR,41.965939938,-87.644238851,"(41.965939938, -87.644238851)" +4428519,HL721751,2005-11-07 17:35:28,003XX S CENTRAL AVE,0460,BATTERY,SIMPLE,STREET,2098,True,False,1522,15,29,25,08B,1139164,1897488,2005,ERROR,41.874820808,-87.764518699,"(41.874820808, -87.764518699)" +6253386,HP340946,2008-05-17 00:30:00,021XX W DIVISION ST,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,1175,False,False,1424,14,32,24,05,1161971,1908008,2008,2008-02-06 10:11:07,41.903243283,-87.680486791,"(41.903243283, -87.680486791)" +2704055,HJ328889,2003-04-28 10:00:00,115XX S YALE AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,STREET,1601,False,True,522,NA,34,53,26,1176713,1828501,2003,ERROR,41.684747613,-87.628730466,"(41.684747613, -87.628730466)" +1882196,G720932,2001-12-01 13:10:00,020XX S STATE ST,2024,NARCOTICS,POSS: HEROIN(WHITE),SIDEWALK,800,True,False,2111,NA,NA,NA,18,1176680,1890478,2001,ERROR,41.854820078,-87.626987993,"(41.854820078, -87.626987993)" +7507209,HS310705,2010-05-16 14:00:00,022XX S KOLIN AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3702,False,True,1013,10,22,29,08B,1147967,1888437,2010,ERROR,41.849819116,-87.732430029,"(41.849819116, -87.732430029)" +5257400,HM683946,2006-10-26 19:50:02,002XX N KOSTNER AVE,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,SIDEWALK,3908,True,False,1113,11,28,26,26,1146987,1900681,2006,ERROR,41.883436917,-87.735713976,"(41.883436917, -87.735713976)" +4144821,HL474019,2005-07-10 03:55:00,0000X W 69TH ST,033A,ROBBERY,ATTEMPT: ARMED-HANDGUN,CTA GARAGE / OTHER PROPERTY,1874,False,False,731,7,6,69,03,1177312,1859236,2005,ERROR,41.769074977,-87.62561266,"(41.769074977, -87.62561266)" +6107802,HP202858,2008-03-01 03:30:00,001XX E PERSHING RD,0890,THEFT,FROM BUILDING,HOTEL/MOTEL,2798,False,False,211,2,3,35,06,1177861,1879207,2008,2008-02-03 09:27:45,41.823864879,-87.622995394,"(41.823864879, -87.622995394)" +9057825,HW202237,2013-03-22 08:00:00,039XX N LAMON AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2777,False,True,1634,16,45,15,14,1143003,1926071,2013,ERROR,41.953184943,-87.74970919,"(41.953184943, -87.74970919)" +10105129,HY293838,2015-06-09 11:30:00,034XX W 62ND ST,1330,CRIMINAL TRESPASS,TO LAND,RESIDENCE-GARAGE,3155,False,False,823,8,15,66,26,1154383,1863255,2015,ERROR,41.780590724,-87.709553096,"(41.780590724, -87.709553096)" +8759952,HV434029,2012-08-16 21:00:00,028XX E 128TH ST,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,3407,False,False,433,4,10,55,07,1197309,1820733,2012,2012-04-09 11:30:20,41.662944388,-87.553592675,"(41.662944388, -87.553592675)" +7959811,HT190199,2011-03-05 19:15:00,028XX S PULASKI RD,1320,CRIMINAL DAMAGE,TO VEHICLE,VEHICLE NON-COMMERCIAL,1473,False,False,1031,10,22,30,14,1150127,1885011,2011,2011-08-03 08:38:18,41.840375986,-87.724591567,"(41.840375986, -87.724591567)" +3048313,HJ763595,2003-11-16 19:10:00,0000X N LOCKWOOD AVE,031A,ROBBERY,ARMED: HANDGUN,STREET,1950,False,False,1522,15,28,25,03,1141098,1899565,2003,ERROR,41.880484972,-87.757366589,"(41.880484972, -87.757366589)" +5843787,HN653880,2007-10-17 06:15:00,005XX W DIVERSEY PKWY,0820,THEFT,$500 AND UNDER,SIDEWALK,154,False,False,2333,19,44,6,06,1172289,1918919,2007,2014-04-12 12:43:35,41.932961861,-87.642263918,"(41.932961861, -87.642263918)" +2978407,HJ675858,2003-10-06 08:00:00,047XX N ARTESIAN AVE,0810,THEFT,OVER $500,STREET,3029,False,False,1911,19,47,4,06,1159226,1931185,2003,2014-04-12 12:43:35,41.966899389,-87.689930834,"(41.966899389, -87.689930834)" +6332578,HP413557,2008-06-21 14:00:00,060XX S HERMITAGE AVE,1750,OFFENSE INVOLVING CHILDREN,CHILD ABUSE,RESIDENCE,524,False,True,714,7,15,67,20,1165681,1864379,2008,ERROR,41.783442756,-87.66810053,"(41.783442756, -87.66810053)" +5107772,HM708459,2006-11-08 09:00:00,093XX S VINCENNES AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,DRIVEWAY - RESIDENTIAL,568,False,False,2222,22,21,73,14,1170848,1842818,2006,2006-11-11 07:40:50,41.724165299,-87.649784606,"(41.724165299, -87.649784606)" +7053138,HR458882,2009-07-31 17:50:00,053XX W CHICAGO AVE,2027,NARCOTICS,POSS: CRACK,SIDEWALK,3429,True,False,1524,15,37,25,18,1140842,1904779,2009,ERROR,41.894797544,-87.758178285,"(41.894797544, -87.758178285)" +3752876,HK761921,2004-11-19 21:59:00,035XX W VAN BUREN ST,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,SIDEWALK,2319,True,False,1133,11,28,27,26,1152952,1897760,2004,ERROR,41.875305308,-87.713887327,"(41.875305308, -87.713887327)" +8246665,HT470888,2011-08-28 17:00:00,043XX N CALIFORNIA AVE,0610,BURGLARY,FORCIBLE ENTRY,CAR WASH,1992,False,False,1724,17,33,16,05,1156889,1928818,2011,ERROR,41.960452042,-87.698588153,"(41.960452042, -87.698588153)" +8588225,HV262806,2012-04-28 22:55:00,013XX W 15TH ST,1350,CRIMINAL TRESPASS,TO STATE SUP LAND,CHA PARKING LOT/GROUNDS,2791,True,False,1231,12,2,28,26,1167750,1892902,2012,ERROR,41.861668647,-87.659694953,"(41.861668647, -87.659694953)" +4546442,HM132623,2006-01-18 12:40:00,021XX N MELVINA AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,2714,False,False,2512,25,29,19,14,1134778,1913205,2006,ERROR,41.91802893,-87.780250515,"(41.91802893, -87.780250515)" +4972684,HM583449,2006-09-04 17:00:00,050XX S ELIZABETH ST,0460,BATTERY,SIMPLE,STREET,1534,False,False,933,9,16,61,08B,1168804,1871336,2006,2006-03-10 05:10:58,41.802466697,-87.656449709,"(41.802466697, -87.656449709)" +7723442,HS530831,2010-09-23 23:32:00,003XX W SCOTT ST,031A,ROBBERY,ARMED: HANDGUN,STREET,4018,True,False,1821,18,27,8,03,1173890,1908750,2010,2011-10-05 09:12:33,41.905022117,-87.636684012,"(41.905022117, -87.636684012)" +2773492,HJ393599,2003-05-29 03:10:00,008XX N SPRINGFIELD AVE,2027,NARCOTICS,POSS: CRACK,STREET,844,True,False,1112,NA,27,23,18,1150284,1905083,2003,ERROR,41.895452828,-87.72349217,"(41.895452828, -87.72349217)" +3692637,HK794797,2004-12-07 11:00:31,018XX W 34TH ST,0920,MOTOR VEHICLE THEFT,ATT: AUTOMOBILE,STREET,3810,False,False,922,9,11,59,07,1164352,1882114,2004,ERROR,41.832137795,-87.672473125,"(41.832137795, -87.672473125)" +4219807,HL460633,2005-07-03 19:58:47,010XX N LECLAIRE AVE,2027,NARCOTICS,POSS: CRACK,SIDEWALK,2203,True,False,1531,15,37,25,18,1142213,1906317,2005,ERROR,41.898992665,-87.753104713,"(41.898992665, -87.753104713)" +2573876,HJ163682,2003-02-03 19:00:00,038XX N PLAINFIELD AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,2418,False,False,1631,NA,36,17,14,1119989,1924864,2003,ERROR,41.950271676,-87.834338101,"(41.950271676, -87.834338101)" +4071798,HL321033,2005-04-27 11:45:00,008XX N HUDSON AVE,2027,NARCOTICS,POSS: CRACK,STREET,4486,True,False,1823,18,27,8,18,1172997,1905793,2005,ERROR,41.896927801,-87.640052025,"(41.896927801, -87.640052025)" +4818852,HM291602,2006-04-14 23:58:42,026XX W 23RD PL,2230,LIQUOR LAW VIOLATION,ILLEGAL CONSUMPTION BY MINOR,SIDEWALK,2628,False,False,1034,10,28,30,22,1158965,1888336,2006,ERROR,41.849323686,-87.69206834,"(41.849323686, -87.69206834)" +7027138,HR426417,2009-07-13 04:45:00,060XX S WABASH AVE,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE,3907,False,False,311,3,20,40,05,1177713,1864904,2009,ERROR,41.784619498,-87.623971417,"(41.784619498, -87.623971417)" +9581848,HX232365,2014-04-21 23:03:00,015XX W SUPERIOR ST,1310,CRIMINAL DAMAGE,TO PROPERTY,APARTMENT,4430,False,False,1215,12,27,24,14,1165974,1905059,2014,ERROR,41.895066484,-87.66586726,"(41.895066484, -87.66586726)" +9174144,HW318226,2013-06-11 00:01:00,014XX W ROSEMONT AVE,0841,THEFT,FINANCIAL ID THEFT:$300 &UNDER,APARTMENT,4653,False,False,2433,24,40,77,06,1165397,1941952,2013,ERROR,41.996314969,-87.666932963,"(41.996314969, -87.666932963)" +5992602,HP100090,2008-01-01 00:30:00,063XX S DR MARTIN LUTHER KING JR DR,051A,ASSAULT,AGGRAVATED: HANDGUN,SIDEWALK,3889,False,False,312,3,20,69,04A,1179974,1862865,2008,2008-06-01 06:49:00,41.778972797,-87.615744123,"(41.778972797, -87.615744123)" +2254466,HH524868,2002-07-21 05:00:00,006XX N ST CLAIR ST,0460,BATTERY,SIMPLE,STREET,3497,False,False,1834,NA,42,8,08B,1177765,1904573,2002,ERROR,41.893473003,-87.622577223,"(41.893473003, -87.622577223)" +7861681,HS675991,2010-12-20 07:00:00,016XX W WRIGHTWOOD AVE,0890,THEFT,FROM BUILDING,RESIDENCE-GARAGE,741,False,False,1931,19,32,7,06,1164728,1917284,2010,ERROR,41.928639204,-87.670096285,"(41.928639204, -87.670096285)" +4305016,HL615231,2005-09-15 19:00:00,014XX W ESTES AVE,0820,THEFT,$500 AND UNDER,STREET,331,False,False,2423,24,49,1,06,1165114,1947454,2005,2014-04-12 12:43:35,42.011418612,-87.667816666,"(42.011418612, -87.667816666)" +6978791,HR377687,2009-06-16 05:00:00,064XX S WINCHESTER AVE,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,4805,True,False,726,7,15,67,26,1164424,1861895,2009,ERROR,41.776652928,-87.672779095,"(41.776652928, -87.672779095)" +7951980,HT184140,2011-03-01 16:00:00,069XX S MAPLEWOOD AVE,0560,ASSAULT,SIMPLE,APARTMENT,4213,False,True,832,8,15,66,08A,1160614,1858473,2011,2011-03-03 11:09:39,41.767341932,-87.686840757,"(41.767341932, -87.686840757)" +9319944,HW463891,2013-09-23 15:24:00,007XX N MENARD AVE,0650,BURGLARY,HOME INVASION,APARTMENT,3017,False,False,1511,15,29,25,05,1137630,1904607,2013,2013-09-10 17:10:49,41.894384033,-87.769979402,"(41.894384033, -87.769979402)" +6757834,HR174805,2009-02-17 16:34:15,070XX S WENTWORTH AVE,1330,CRIMINAL TRESPASS,TO LAND,CURRENCY EXCHANGE,3968,True,False,731,7,6,69,26,1176225,1857985,2009,ERROR,41.765666567,-87.629634577,"(41.765666567, -87.629634577)" +4825337,HM438385,2006-04-20 09:00:00,023XX S LAKE SHORE DR E,0890,THEFT,FROM BUILDING,OTHER,2059,False,False,133,1,2,33,06,1180538,1889157,2006,ERROR,41.851107212,-87.612868333,"(41.851107212, -87.612868333)" +8933517,HV605679,2012-12-17 02:14:00,012XX S SPRINGFIELD AVE,0820,THEFT,$500 AND UNDER,STREET,31,False,False,1011,10,24,29,06,1150625,1893795,2012,ERROR,41.864470661,-87.722534804,"(41.864470661, -87.722534804)" +3080102,HJ805073,2003-12-04 19:00:00,043XX N KEDVALE AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,2862,False,True,1722,17,39,16,26,1148110,1928515,2003,ERROR,41.959794498,-87.730872135,"(41.959794498, -87.730872135)" +7734425,HS535056,2010-09-25 17:30:00,088XX S LOWE AVE,0810,THEFT,OVER $500,STREET,3573,False,False,2223,22,21,71,06,1173480,1846204,2010,2010-02-10 09:42:41,41.733399192,-87.640043801,"(41.733399192, -87.640043801)" +9596026,HX246438,2014-05-02 23:30:00,005XX N KEDZIE AVE,033A,ROBBERY,ATTEMPT: ARMED-HANDGUN,STREET,4495,False,False,1121,11,27,23,03,1154906,1903472,2014,2014-07-05 00:40:24,41.890940685,-87.706559801,"(41.890940685, -87.706559801)" +9870090,HX520069,2014-11-25 11:40:00,053XX N WESTERN AVE,0460,BATTERY,SIMPLE,NURSING HOME/RETIREMENT HOME,2515,False,False,2011,20,40,4,08B,1159335,1935518,2014,2014-02-12 12:51:55,41.978787101,-87.689410305,"(41.978787101, -87.689410305)" +8508277,HV185019,2012-03-06 08:40:00,072XX S COLES AVE,1360,CRIMINAL TRESPASS,TO VEHICLE,STREET,3879,True,False,334,3,7,43,26,1194431,1857617,2012,2012-06-03 15:34:55,41.764228646,-87.562916763,"(41.764228646, -87.562916763)" +2177946,HH431848,2002-06-03 16:00:00,041XX W 31ST ST,0840,THEFT,FINANCIAL ID THEFT: OVER $300,RESIDENCE,868,False,False,1031,NA,22,30,06,1149298,1883758,2002,ERROR,41.836953671,-87.727666121,"(41.836953671, -87.727666121)" +1986260,HH132885,2002-01-17 20:40:00,030XX W MADISON ST,1506,PROSTITUTION,SOLICIT ON PUBLIC WAY,STREET,1187,True,False,1124,NA,NA,NA,16,1156378,1899829,2002,ERROR,41.880914312,-87.701252404,"(41.880914312, -87.701252404)" +9234994,HW381728,2013-07-26 09:30:00,0000X W WACKER DR,0820,THEFT,$500 AND UNDER,SIDEWALK,318,False,False,111,1,42,32,06,1175775,1902091,2013,ERROR,41.886707265,-87.629960428,"(41.886707265, -87.629960428)" +7988307,HT219886,2011-03-25 20:58:00,009XX N MOZART ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,797,True,True,1311,12,26,24,08B,1157172,1906205,2011,ERROR,41.898394561,-87.69816358,"(41.898394561, -87.69816358)" +7278970,HR694241,2009-12-17 11:00:00,041XX N GREENVIEW AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,APARTMENT,4882,False,False,1923,19,47,6,14,1165286,1927513,2009,ERROR,41.956696233,-87.66775397,"(41.956696233, -87.66775397)" +8025601,HT256918,2011-04-19 13:00:00,076XX S KINGSTON AVE,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,APARTMENT,2051,True,False,421,4,7,43,15,1194477,1854868,2011,ERROR,41.756684054,-87.562838411,"(41.756684054, -87.562838411)" +2962454,HJ650469,2003-09-24 11:45:00,0000X W 79TH ST,1210,DECEPTIVE PRACTICE,THEFT OF LABOR/SERVICES,OTHER,2463,True,False,623,6,6,44,11,1177482,1852588,2003,ERROR,41.750828271,-87.625190123,"(41.750828271, -87.625190123)" +3532647,HK574001,2004-08-22 14:49:40,056XX S WINCHESTER AVE,2014,NARCOTICS,MANU/DELIVER: HEROIN (WHITE),SIDEWALK,1797,True,False,715,7,15,67,18,1164271,1867524,2004,ERROR,41.792102855,-87.673181553,"(41.792102855, -87.673181553)" +3038829,HJ750788,2003-11-10 15:25:00,020XX S KOSTNER AVE,0560,ASSAULT,SIMPLE,OTHER,4836,True,False,1012,10,24,29,08A,1147406,1890054,2003,ERROR,41.854267127,-87.734447612,"(41.854267127, -87.734447612)" +9806012,HX453692,2014-10-03 13:30:00,023XX N PULASKI RD,0810,THEFT,OVER $500,PARKING LOT/GARAGE(NON.RESID.),2254,False,False,2525,25,31,20,06,1149281,1915512,2014,2014-10-10 12:36:29,41.924090526,-87.72690517,"(41.924090526, -87.72690517)" +3511264,HK589583,2004-08-29 14:30:00,022XX N CANNON DR,0820,THEFT,$500 AND UNDER,STREET,219,False,False,1814,18,43,7,06,1175058,1914939,2004,2014-04-12 12:43:35,41.921978896,-87.632207786,"(41.921978896, -87.632207786)" +5053020,HM659802,2006-10-14 14:45:00,035XX S WESTERN BLVD,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,2470,False,False,913,9,11,59,08B,1161140,1881089,2006,ERROR,41.829392282,-87.684286808,"(41.829392282, -87.684286808)" +8357212,HT590698,2011-11-15 15:45:00,040XX W WEST END AVE,0470,PUBLIC PEACE VIOLATION,RECKLESS CONDUCT,STREET,2682,True,False,1114,11,28,26,24,1149062,1900618,2011,ERROR,41.883224133,-87.728095989,"(41.883224133, -87.728095989)" +2234104,HH504870,2002-07-12 13:00:00,016XX S SPRINGFIELD AVE,0420,BATTERY,AGGRAVATED:KNIFE/CUTTING INSTR,SIDEWALK,557,False,False,1014,NA,24,29,04B,1150624,1891627,2002,ERROR,41.858521432,-87.722595091,"(41.858521432, -87.722595091)" +5023389,HM632245,2006-09-29 19:00:00,012XX N LA SALLE DR,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,549,False,False,1821,18,43,8,14,1174869,1908585,2006,2006-03-10 05:10:58,41.904547475,-87.633092826,"(41.904547475, -87.633092826)" +4167710,HL497000,2005-07-18 14:07:00,039XX N RAVENSWOOD AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,1045,False,False,1923,19,47,6,26,1163702,1926130,2005,ERROR,41.952934826,-87.673616315,"(41.952934826, -87.673616315)" +2170727,HH421736,2002-06-05 21:00:00,100XX W OHARE ST,0460,BATTERY,SIMPLE,AIRPORT/AIRCRAFT,994,False,False,1651,NA,41,76,08B,1100635,1934208,2002,ERROR,41.976200173,-87.905312411,"(41.976200173, -87.905312411)" +2294558,HH580615,2002-08-14 08:30:00,025XX N BOSWORTH AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,3119,False,False,1931,NA,32,7,07,1165613,1916698,2002,ERROR,41.927012356,-87.666860956,"(41.927012356, -87.666860956)" +7991942,HT224010,2011-03-28 20:45:00,063XX S ARTESIAN AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,1474,True,False,825,8,15,66,18,1161174,1862364,2011,ERROR,41.778007816,-87.684680539,"(41.778007816, -87.684680539)" +3096983,HJ825511,2003-12-18 17:20:00,075XX N CLARK ST,0860,THEFT,RETAIL THEFT,DEPARTMENT STORE,706,True,False,2422,24,49,1,06,1162974,1949868,2003,ERROR,42.018088059,-87.675622371,"(42.018088059, -87.675622371)" +2315170,HH603150,2002-08-25 01:40:00,093XX S PARNELL AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE-GARAGE,3586,False,False,2223,NA,21,73,14,1174239,1842710,2002,ERROR,41.723794372,-87.637366715,"(41.723794372, -87.637366715)" +4440071,HL737554,2005-11-15 19:05:00,032XX W 63RD ST,031A,ROBBERY,ARMED: HANDGUN,GROCERY FOOD STORE,4578,False,False,823,8,15,66,03,1155598,1862622,2005,ERROR,41.778829406,-87.705115599,"(41.778829406, -87.705115599)" +7177547,HR586223,2009-10-09 10:00:00,081XX S PAULINA ST,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,2086,False,False,614,6,18,71,05,1166467,1850703,2009,2009-10-11 13:28:08,41.745897252,-87.665608013,"(41.745897252, -87.665608013)" +3254737,HK280639,2004-03-01 00:00:00,067XX S MERRILL AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,4572,False,False,331,3,5,43,26,1191738,1860941,2004,ERROR,41.773415721,-87.57267934,"(41.773415721, -87.57267934)" +3311197,HK345294,2004-05-05 21:35:00,008XX N CALIFORNIA AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,SIDEWALK,1400,True,True,1311,12,26,24,08B,1157536,1905231,2004,ERROR,41.895714416,-87.69685317,"(41.895714416, -87.69685317)" +2302106,HH588911,2002-08-17 21:00:00,041XX S CAMPBELL AVE,0620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE,1887,False,False,914,NA,12,58,05,1160346,1876870,2002,ERROR,41.817831272,-87.687316372,"(41.817831272, -87.687316372)" +6488303,HP564304,2008-09-10 18:17:00,001XX W BRAYTON ST,0560,ASSAULT,SIMPLE,RESIDENCE PORCH/HALLWAY,4667,False,False,523,5,9,53,08A,1177795,1821422,2008,ERROR,41.665297395,-87.62498261,"(41.665297395, -87.62498261)" +5443072,HN275682,2006-02-01 08:00:00,006XX E 89TH ST,0840,THEFT,FINANCIAL ID THEFT: OVER $300,RESIDENCE,2116,False,False,632,6,6,44,06,1182025,1846146,2006,ERROR,41.733046889,-87.608741291,"(41.733046889, -87.608741291)" +9210579,HW356745,2013-07-05 22:00:00,100XX S PROSPECT AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,594,False,False,2213,22,19,72,14,1167485,1837705,2013,ERROR,41.710207052,-87.662249162,"(41.710207052, -87.662249162)" +9287060,HW431457,2013-08-30 19:00:00,081XX S EUCLID AVE,0890,THEFT,FROM BUILDING,APARTMENT,1161,False,False,414,4,8,46,06,1190659,1851058,2013,2013-01-09 07:31:32,41.746322062,-87.576953181,"(41.746322062, -87.576953181)" +5794196,HN603823,2007-09-21 23:33:00,078XX S EAST END AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,1314,True,False,414,4,8,43,18,1188864,1853073,2007,ERROR,41.751894519,-87.583466017,"(41.751894519, -87.583466017)" +4344629,HL643695,2005-09-29 21:00:00,063XX S PULASKI RD,051A,ASSAULT,AGGRAVATED: HANDGUN,STREET,2707,False,False,813,8,13,65,04A,1150755,1862153,2005,ERROR,41.777638112,-87.722882838,"(41.777638112, -87.722882838)" +6191990,HP281294,2008-04-15 15:00:00,006XX W WAVELAND AVE,0810,THEFT,OVER $500,STREET,932,False,False,2323,19,46,6,06,1171539,1925299,2008,ERROR,41.950485378,-87.644831764,"(41.950485378, -87.644831764)" +8304880,HT539173,2011-10-12 03:00:00,056XX W WASHINGTON BLVD,1310,CRIMINAL DAMAGE,TO PROPERTY,APARTMENT,3476,False,True,1512,15,29,25,14,1138540,1900218,2011,ERROR,41.882323613,-87.766743629,"(41.882323613, -87.766743629)" +1587243,G356113,2001-06-18 20:30:00,045XX N CLARK ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4800,False,False,1922,NA,NA,NA,07,1165538,1930394,2001,ERROR,41.964596441,-87.66674516,"(41.964596441, -87.66674516)" +9232964,HW379118,2013-07-25 19:00:00,060XX N FRANCISCO AVE,0460,BATTERY,SIMPLE,APARTMENT,4723,False,False,2413,24,50,2,08B,1155879,1940034,2013,ERROR,41.991249811,-87.701997517,"(41.991249811, -87.701997517)" +5382099,HN230597,2007-03-15 20:30:00,016XX N PAULINA ST,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,562,False,False,1433,14,1,24,05,1164792,1911272,2007,2007-02-08 01:58:25,41.912140543,-87.670031984,"(41.912140543, -87.670031984)" +8703573,HV379876,2012-07-12 19:30:00,040XX W LAKE ST,1210,DECEPTIVE PRACTICE,THEFT OF LABOR/SERVICES,CTA GARAGE / OTHER PROPERTY,4140,True,False,1114,11,28,26,11,1149648,1901481,2012,ERROR,41.885580938,-87.725921713,"(41.885580938, -87.725921713)" +6026181,HP129984,2008-01-17 13:00:00,029XX S LOOMIS ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,1082,True,False,923,9,11,60,07,1168373,1885564,2008,ERROR,41.841519091,-87.657620002,"(41.841519091, -87.657620002)" +3088718,HJ812750,2003-12-11 23:20:00,019XX N KARLOV AVE,0650,BURGLARY,HOME INVASION,RESIDENCE,2638,False,False,2534,25,30,20,05,1148701,1912536,2003,ERROR,41.915935339,-87.729113388,"(41.915935339, -87.729113388)" +3998545,HL355767,2005-05-14 10:20:57,023XX E 75TH ST,0454,BATTERY,AGG PO HANDS NO/MIN INJURY,GAS STATION,1092,True,False,334,3,7,43,08B,1193470,1855738,2005,ERROR,41.759096092,-87.566500388,"(41.759096092, -87.566500388)" +7613298,HS418212,2010-07-19 00:30:00,056XX S NEENAH AVE,0820,THEFT,$500 AND UNDER,STREET,101,False,False,811,8,23,56,06,1133641,1866435,2010,ERROR,41.789704965,-87.785524264,"(41.789704965, -87.785524264)" +6980905,HR385311,2009-06-19 20:00:00,024XX N CICERO AVE,0610,BURGLARY,FORCIBLE ENTRY,OTHER,902,False,False,2521,25,31,19,05,1143947,1915964,2009,ERROR,41.9254327,-87.746493281,"(41.9254327, -87.746493281)" +4628112,HM225198,2006-03-10 20:00:00,089XX S RIDGELAND AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1600,False,False,413,4,8,48,14,1189473,1845824,2006,ERROR,41.731987986,-87.581466439,"(41.731987986, -87.581466439)" +9426414,HW569938,2013-12-12 11:45:00,012XX N ASHLAND AVE,031A,ROBBERY,ARMED: HANDGUN,CTA BUS STOP,2851,False,False,1424,14,1,24,03,1165544,1908172,2013,ERROR,41.903617945,-87.667357779,"(41.903617945, -87.667357779)" +5040428,HM649718,2006-10-09 02:00:00,005XX S CAMPBELL AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4722,False,False,1135,11,2,28,07,1159775,1897648,2006,ERROR,41.874860113,-87.68883898,"(41.874860113, -87.68883898)" +9176835,HW321707,2013-06-16 19:30:00,055XX W GRAND AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,POLICE FACILITY/VEH PARKING LOT,3141,True,False,2515,25,29,19,18,1138769,1913443,2013,ERROR,41.918610464,-87.765581339,"(41.918610464, -87.765581339)" +9817998,HX467737,2014-10-14 15:30:00,020XX N MILWAUKEE AVE,0820,THEFT,$500 AND UNDER,OTHER,287,False,False,1431,14,1,22,06,1159346,1913460,2014,ERROR,41.918258409,-87.689978754,"(41.918258409, -87.689978754)" +1398760,G114124,2001-02-22 23:55:00,082XX S JEFFERY BL,0820,THEFT,$500 AND UNDER,PARKING LOT/GARAGE(NON.RESID.),273,False,False,414,NA,NA,NA,06,1190990,1850969,2001,2014-04-12 12:43:35,41.746069847,-87.575743214,"(41.746069847, -87.575743214)" +4410684,HL706234,2005-10-29 09:00:00,044XX N RAVENSWOOD AVE,0810,THEFT,OVER $500,STREET,3661,False,False,1922,19,47,3,06,1163617,1929327,2005,2014-04-12 12:43:35,41.961709338,-87.673838335,"(41.961709338, -87.673838335)" +3516152,HK591240,2004-08-29 15:30:00,048XX N PAULINA ST,0820,THEFT,$500 AND UNDER,STREET,171,False,False,2032,20,47,3,06,1164401,1932012,2004,2014-04-12 12:43:35,41.969060495,-87.670879593,"(41.969060495, -87.670879593)" +4894707,HM510203,2006-07-30 11:00:00,047XX N SAWYER AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,RESIDENTIAL YARD (FRONT/BACK),4021,False,False,1713,17,33,14,14,1153814,1931419,2006,2006-02-08 04:53:53,41.967651297,-87.709823827,"(41.967651297, -87.709823827)" +9622145,HX271986,2014-05-22 13:00:00,017XX N HALSTED ST,0820,THEFT,$500 AND UNDER,STREET,139,False,False,1813,18,43,7,06,1170734,1911649,2014,ERROR,41.91304688,-87.648191824,"(41.91304688, -87.648191824)" +4015702,HL308318,2005-04-21 01:56:40,042XX S PRAIRIE AVE,1506,PROSTITUTION,SOLICIT ON PUBLIC WAY,STREET,2725,True,False,214,2,3,38,16,1178699,1877152,2005,ERROR,41.81820674,-87.619983684,"(41.81820674, -87.619983684)" +3849018,HL221418,2005-03-07 20:10:00,113XX S EDBROOKE AVE,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,STREET,1114,True,False,531,5,9,49,04B,1179176,1829999,2005,ERROR,41.68880271,-87.619668717,"(41.68880271, -87.619668717)" +6545175,HP615080,2008-10-08 08:55:39,073XX S PHILLIPS AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,1777,False,True,334,3,7,43,08B,1193960,1856732,2008,2008-10-10 05:55:47,41.761811703,-87.564672045,"(41.761811703, -87.564672045)" +2948741,HJ636339,2003-09-17 19:10:00,011XX N MAYFIELD AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,2818,False,True,1511,15,29,25,26,1136769,1907269,2003,ERROR,41.901704366,-87.773077802,"(41.901704366, -87.773077802)" +5845070,HN655038,2007-10-15 06:15:00,056XX W LAKE ST,0810,THEFT,OVER $500,STREET,1243,False,False,1512,15,29,25,06,1138390,1902170,2007,2014-04-12 12:43:35,41.88768287,-87.767247156,"(41.88768287, -87.767247156)" +5008839,HM618424,2006-09-23 13:20:00,017XX N HUMBOLDT BLVD,0320,ROBBERY,STRONGARM - NO WEAPON,SIDEWALK,4129,False,False,1421,14,35,23,03,1156056,1911677,2006,2006-01-10 07:25:21,41.913432802,-87.702114688,"(41.913432802, -87.702114688)" +8587110,HV261318,2012-04-27 21:40:00,084XX S MANISTEE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,4048,False,False,423,4,7,46,08B,1195995,1849774,2012,ERROR,41.742668288,-87.557443682,"(41.742668288, -87.557443682)" +7872477,HT102978,2011-01-03 10:30:00,034XX E 133RD ST,0810,THEFT,OVER $500,STREET,3405,False,False,433,4,10,55,06,1201126,1817424,2011,2011-04-01 07:20:15,41.653768523,-87.539736212,"(41.653768523, -87.539736212)" +9916472,HY105958,2014-11-26 12:00:00,030XX E 92ND ST,1110,DECEPTIVE PRACTICE,BOGUS CHECK,BANK,4969,False,False,424,4,10,46,11,1198084,1844583,2014,2015-08-01 12:39:18,41.728371838,-87.549962839,"(41.728371838, -87.549962839)" +9490762,HX144691,2014-02-07 22:00:00,055XX S ALBANY AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,3213,False,False,824,8,14,63,14,1156621,1867583,2014,2014-12-02 00:40:02,41.792422534,-87.701231458,"(41.792422534, -87.701231458)" +5857483,HN656362,2007-10-18 18:45:00,005XX E 107TH ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,2928,False,True,512,5,9,49,08B,1181507,1834155,2007,ERROR,41.700154031,-87.611007498,"(41.700154031, -87.611007498)" +2535237,HJ109917,2002-12-20 15:00:00,074XX S COLFAX AVE,1562,SEX OFFENSE,AGG CRIMINAL SEXUAL ABUSE,APARTMENT,4858,False,False,334,NA,7,43,17,1194702,1856124,2002,ERROR,41.76012508,-87.561972571,"(41.76012508, -87.561972571)" +8804120,HV477870,2012-09-16 03:10:00,047XX S LOOMIS BLVD,0460,BATTERY,SIMPLE,RESIDENTIAL YARD (FRONT/BACK),3213,False,False,933,9,3,61,08B,1167835,1873371,2012,ERROR,41.808071849,-87.659944973,"(41.808071849, -87.659944973)" +3273588,HK301044,2004-04-14 11:45:00,012XX S KOSTNER AVE,031A,ROBBERY,ARMED: HANDGUN,STREET,1578,False,False,1011,10,24,29,03,1147285,1893972,2004,ERROR,41.865020926,-87.734791482,"(41.865020926, -87.734791482)" +7072295,HR477075,2009-08-08 19:00:00,001XX E 47TH ST,0870,THEFT,POCKET-PICKING,BAR OR TAVERN,3597,False,False,224,2,3,38,06,1178126,1873860,2009,2009-12-08 13:56:14,41.809186244,-87.622185553,"(41.809186244, -87.622185553)" +9162055,HW306837,2013-04-09 11:00:00,068XX S CORNELL AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,3084,False,True,332,3,5,43,26,1188350,1859644,2013,2013-09-06 03:34:44,41.769938203,-87.585140129,"(41.769938203, -87.585140129)" +2688193,HJ309724,2003-04-18 19:30:00,022XX N STOCKTON DR,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2743,False,False,1814,NA,43,7,14,1174127,1915368,2003,ERROR,41.923176926,-87.635615683,"(41.923176926, -87.635615683)" +7691340,HS497006,2010-09-03 13:07:00,009XX E MARQUETTE RD,0486,BATTERY,DOMESTIC BATTERY SIMPLE,ALLEY,1215,False,True,321,3,5,42,08B,1183558,1861405,2010,2010-08-09 07:54:48,41.774883602,-87.602650487,"(41.774883602, -87.602650487)" +3282771,HK213978,2004-03-01 16:53:11,007XX W 61ST ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,1555,True,False,711,7,16,68,18,1172314,1864376,2004,ERROR,41.783291129,-87.643781806,"(41.783291129, -87.643781806)" +4751054,HM261185,2006-03-30 12:02:00,011XX N RIDGEWAY AVE,2095,NARCOTICS,ATTEMPT POSSESSION NARCOTICS,STREET,2426,True,False,1112,11,27,23,18,1151136,1907139,2006,ERROR,41.901078036,-87.720308999,"(41.901078036, -87.720308999)" +5962440,HN757326,2007-12-12 22:20:00,055XX W ADAMS ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,1914,False,False,1522,15,29,25,07,1139406,1898740,2007,2008-06-01 01:05:00,41.878252056,-87.76359965,"(41.878252056, -87.76359965)" +1834047,G670254,2001-11-06 18:00:00,013XX W ERIE ST,0560,ASSAULT,SIMPLE,STREET,3142,False,False,1324,NA,NA,NA,08A,1167244,1904514,2001,ERROR,41.89354376,-87.661218582,"(41.89354376, -87.661218582)" +3301460,HK334439,2004-04-30 13:30:00,022XX N AUSTIN AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4380,False,True,2515,25,37,19,08B,1136060,1914635,2004,ERROR,41.921930233,-87.77550612,"(41.921930233, -87.77550612)" +2669621,HJ286771,2003-04-06 01:00:00,030XX W 55TH ST,0460,BATTERY,SIMPLE,STREET,2322,False,False,824,NA,14,63,08B,1157258,1867977,2003,ERROR,41.793490848,-87.69888499,"(41.793490848, -87.69888499)" +6369079,HP455430,2008-07-16 06:10:00,076XX S PHILLIPS AVE,0560,ASSAULT,SIMPLE,STREET,4088,False,False,421,4,7,43,08A,1193825,1854450,2008,ERROR,41.755553029,-87.565241501,"(41.755553029, -87.565241501)" +6855131,HR261020,2009-04-10 00:00:00,014XX W ERIE ST,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,3068,False,False,1324,12,27,24,14,1166896,1904503,2009,ERROR,41.893521048,-87.662496975,"(41.893521048, -87.662496975)" +4274768,HL591848,2005-09-04 20:00:00,033XX W WRIGHTWOOD AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,2202,False,False,1412,14,35,22,07,1153390,1917123,2005,ERROR,41.928430509,-87.711764046,"(41.928430509, -87.711764046)" +6197251,HP285608,2008-04-17 18:30:00,030XX W FILLMORE ST,1320,CRIMINAL DAMAGE,TO VEHICLE,PARKING LOT/GARAGE(NON.RESID.),3447,True,False,1134,11,28,29,14,1155966,1895253,2008,ERROR,41.868365618,-87.702888661,"(41.868365618, -87.702888661)" +8433392,HV111791,2012-01-10 01:40:00,067XX S ST LAWRENCE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3287,False,True,321,3,20,42,08B,1181358,1860502,2012,ERROR,41.772456691,-87.610743096,"(41.772456691, -87.610743096)" +7505865,HS308821,2010-05-04 08:00:00,080XX S SANGAMON ST,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,3502,False,True,621,6,21,71,26,1171334,1851481,2010,ERROR,41.747927164,-87.647751629,"(41.747927164, -87.647751629)" +7557528,HS361664,2010-06-15 19:11:00,016XX S KARLOV AVE,2027,NARCOTICS,POSS: CRACK,RESIDENCE,863,True,False,1012,10,24,29,18,1149364,1891408,2010,ERROR,41.85794497,-87.727225818,"(41.85794497, -87.727225818)" +6702067,HR108720,2009-01-06 14:45:00,004XX S KEELER AVE,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, GROUNDS",4011,False,False,1132,11,24,26,08B,1148424,1897525,2009,ERROR,41.874748911,-87.730518558,"(41.874748911, -87.730518558)" +6400565,HP436822,2008-07-06 21:08:22,064XX S CALIFORNIA AVE,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,SIDEWALK,1031,True,False,825,8,15,66,26,1158854,1861997,2008,2008-03-08 06:46:11,41.777048422,-87.693195807,"(41.777048422, -87.693195807)" +3736578,HL102041,2005-01-02 00:30:00,065XX S LOOMIS BLVD,0810,THEFT,OVER $500,VEHICLE NON-COMMERCIAL,1274,False,False,725,7,17,67,06,1168076,1861511,2005,2014-04-12 12:43:35,41.775521427,-87.659402042,"(41.775521427, -87.659402042)" +9845718,HX495009,2014-11-04 15:30:00,013XX W 95TH ST,0810,THEFT,OVER $500,"SCHOOL, PUBLIC, BUILDING",2826,False,False,2213,22,21,73,06,1169214,1841722,2014,2014-11-11 12:41:33,41.72119316,-87.655801492,"(41.72119316, -87.655801492)" +5253960,HN130561,2007-01-16 00:00:00,024XX N LINCOLN AVE,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,2443,False,False,1933,19,43,7,26,1170249,1916386,2007,ERROR,41.926056068,-87.649834888,"(41.926056068, -87.649834888)" +3927220,HL299821,2005-04-16 22:55:00,009XX W 32ND PL,0460,BATTERY,SIMPLE,APARTMENT,951,False,False,924,9,11,60,08B,1170602,1883473,2005,ERROR,41.835732786,-87.64950144,"(41.835732786, -87.64950144)" +5904951,HN703395,2007-11-11 10:00:00,053XX S PULASKI RD,1152,DECEPTIVE PRACTICE,ILLEGAL USE CASH CARD,GROCERY FOOD STORE,2864,False,False,815,8,23,62,11,1150568,1868904,2007,ERROR,41.796167541,-87.723392818,"(41.796167541, -87.723392818)" +7616487,HS420429,2010-07-20 13:40:00,071XX S JEFFERY BLVD,0860,THEFT,RETAIL THEFT,SMALL RETAIL STORE,679,True,False,333,3,5,43,06,1190817,1858158,2010,ERROR,41.765801254,-87.576145268,"(41.765801254, -87.576145268)" +2377718,HH685782,2002-10-01 16:00:00,008XX S WESTERN AVE,0890,THEFT,FROM BUILDING,COMMERCIAL / BUSINESS OFFICE,3836,False,False,1224,NA,25,28,06,1160551,1896364,2002,ERROR,41.871320673,-87.686025384,"(41.871320673, -87.686025384)" +3401548,HK459731,2004-06-11 02:00:00,025XX N FRANCISCO AVE,1152,DECEPTIVE PRACTICE,ILLEGAL USE CASH CARD,BANK,3180,False,False,1414,14,35,22,11,1156577,1916550,2004,ERROR,41.92679416,-87.700068437,"(41.92679416, -87.700068437)" +4828468,HM412792,2006-06-14 02:11:58,0000X E GARFIELD BLVD,0320,ROBBERY,STRONGARM - NO WEAPON,SIDEWALK,3102,False,False,233,2,20,40,03,1177699,1868405,2006,2006-07-10 09:43:17,41.794226904,-87.62391683,"(41.794226904, -87.62391683)" +2097067,HH314898,2002-04-18 08:40:00,024XX W LAWRENCE AV,0560,ASSAULT,SIMPLE,OTHER,4703,True,False,2031,NA,NA,NA,08A,1159019,1931842,2002,ERROR,41.968706493,-87.690673825,"(41.968706493, -87.690673825)" +3603839,HK646357,2004-09-25 03:10:00,008XX N ASHLAND AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2836,False,False,1322,12,1,24,14,1165526,1906012,2004,ERROR,41.897691137,-87.667485477,"(41.897691137, -87.667485477)" +9512342,HX167043,2014-02-27 23:16:00,064XX S CARPENTER ST,0497,BATTERY,AGGRAVATED DOMESTIC BATTERY: OTHER DANG WEAPON,RESIDENCE,4856,False,False,724,7,16,68,04B,1170455,1862310,2014,2014-03-03 00:39:23,41.777662488,-87.650657643,"(41.777662488, -87.650657643)" +4261619,HL578454,2005-08-29 09:15:00,134XX S VERNON AVE,0610,BURGLARY,FORCIBLE ENTRY,BAR OR TAVERN,1530,False,False,533,5,9,54,05,1181807,1816319,2005,ERROR,41.651202555,-87.610456918,"(41.651202555, -87.610456918)" +6432144,HP512424,2008-08-14 09:50:00,069XX S MICHIGAN AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,APARTMENT,600,True,False,322,3,6,69,26,1178318,1859155,2008,ERROR,41.768829929,-87.621927626,"(41.768829929, -87.621927626)" +2698942,HJ315490,2003-04-22 05:50:00,051XX W CHICAGO AVE,0340,ROBBERY,ATTEMPT: STRONGARM-NO WEAPON,STREET,1909,False,False,1531,NA,37,25,03,1141640,1904877,2003,ERROR,41.89505175,-87.755244987,"(41.89505175, -87.755244987)" +9682603,HX333155,2014-07-06 03:15:00,037XX N PLAINFIELD AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4490,False,True,1631,16,36,17,08B,1120024,1923862,2014,ERROR,41.9475215,-87.83423089,"(41.9475215, -87.83423089)" +6546232,HP616924,2008-10-09 08:25:00,099XX S PEORIA ST,051A,ASSAULT,AGGRAVATED: HANDGUN,RESIDENTIAL YARD (FRONT/BACK),1859,False,False,2232,22,34,73,04A,1172031,1838751,2008,ERROR,41.712979002,-87.645570329,"(41.712979002, -87.645570329)" +1881357,G729957,2001-12-05 20:38:41,119XX S INDIANA AV,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,2478,False,False,532,NA,NA,NA,05,1179753,1826087,2001,ERROR,41.678054476,-87.617675326,"(41.678054476, -87.617675326)" +6182379,HP269231,2008-04-08 20:30:00,004XX E RANDOLPH ST,1506,PROSTITUTION,SOLICIT ON PUBLIC WAY,RESIDENCE,739,True,False,124,1,42,32,16,1179262,1901369,2008,2008-12-04 06:24:35,41.884646917,-87.617177553,"(41.884646917, -87.617177553)" +10144936,HY333549,2015-07-05 15:00:00,044XX N HARDING AVE,031A,ROBBERY,ARMED: HANDGUN,APARTMENT,1023,False,False,1723,17,39,14,03,1149292,1929511,2015,2015-12-07 12:42:46,41.962504708,-87.726500599,"(41.962504708, -87.726500599)" +7819756,HS629676,2010-11-22 19:30:00,047XX S PRINCETON AVE,0810,THEFT,OVER $500,PARK PROPERTY,1456,False,False,935,9,3,37,06,1175039,1873724,2010,ERROR,41.808882585,-87.633512079,"(41.808882585, -87.633512079)" +1863051,G706647,2001-11-24 19:41:00,019XX S SPRINGFIELD AV,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,ALLEY,1573,True,False,1014,NA,NA,NA,15,1150673,1889905,2001,ERROR,41.853795099,-87.722460197,"(41.853795099, -87.722460197)" +5895492,HN672912,2007-10-27 10:56:31,0000X E 103RD PL,2027,NARCOTICS,POSS: CRACK,APARTMENT,2190,False,False,512,5,9,49,18,1178109,1836390,2007,2007-09-11 09:43:39,41.706364714,-87.623382077,"(41.706364714, -87.623382077)" +9222506,HW369005,2013-07-19 01:00:00,062XX S EBERHART AVE,041A,BATTERY,AGGRAVATED: HANDGUN,SIDEWALK,2207,False,False,313,3,20,42,04B,1180607,1863944,2013,ERROR,41.781919159,-87.613390392,"(41.781919159, -87.613390392)" +9470449,HX123527,2014-01-22 11:30:00,064XX N SHERIDAN RD,0810,THEFT,OVER $500,CHA PARKING LOT/GROUNDS,3913,False,False,2432,24,40,1,06,1167086,1942670,2014,ERROR,41.998248939,-87.660699164,"(41.998248939, -87.660699164)" +4564532,HM151795,2006-01-29 12:30:00,005XX N STATE ST,0870,THEFT,POCKET-PICKING,CTA TRAIN,4085,False,False,1834,18,42,8,06,1176315,1903875,2006,2006-02-02 04:21:12,41.891590492,-87.627923574,"(41.891590492, -87.627923574)" +7288999,HR705891,2009-12-26 01:00:00,060XX S WOLCOTT AVE,0560,ASSAULT,SIMPLE,RESIDENCE,1934,True,True,714,7,15,67,08A,1164770,1864356,2009,ERROR,41.783398933,-87.671441226,"(41.783398933, -87.671441226)" +4264409,HL581667,2005-08-30 18:11:00,012XX N STATE PKWY,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,4438,True,False,1824,18,43,8,05,1176096,1908851,2005,ERROR,41.905249818,-87.628577732,"(41.905249818, -87.628577732)" +4500033,HL801967,2005-12-20 22:30:00,027XX S KARLOV AVE,0810,THEFT,OVER $500,STREET,3640,False,False,1031,10,22,30,06,1149446,1885667,2005,2014-04-12 12:43:35,41.842189353,-87.727073599,"(41.842189353, -87.727073599)" +4521721,HM109102,2006-01-05 22:30:00,072XX S KEDZIE AVE,0281,CRIM SEXUAL ASSAULT,NON-AGGRAVATED,APARTMENT,2123,False,False,831,8,18,66,02,1156259,1856141,2006,ERROR,41.761031279,-87.702866467,"(41.761031279, -87.702866467)" +5579203,HN387749,2007-06-05 23:00:00,028XX N LAKE SHORE DR,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,1495,False,False,2333,19,44,6,26,1173893,1918915,2007,2007-10-07 01:59:11,41.932915255,-87.636369534,"(41.932915255, -87.636369534)" +4112242,HL450369,2005-06-28 17:30:00,043XX S GREENWOOD AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3025,False,False,2123,2,4,39,14,1184277,1876379,2005,ERROR,41.815956678,-87.599546214,"(41.815956678, -87.599546214)" +3506201,HK555583,2004-08-13 16:42:00,044XX W IOWA ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4369,False,False,1111,11,37,23,08B,1146528,1905586,2004,ERROR,41.896905546,-87.737274355,"(41.896905546, -87.737274355)" +2409260,HH725869,2002-10-20 01:00:00,007XX W ROSCOE ST,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESTAURANT,2063,False,False,2331,NA,44,6,26,1170419,1922748,2002,ERROR,41.943509953,-87.649023628,"(41.943509953, -87.649023628)" +2203186,HH000417,2002-06-19 17:15:00,057XX S LOWE AVE,0313,ROBBERY,ARMED: OTHER DANGEROUS WEAPON,SIDEWALK,4447,False,False,711,NA,3,68,03,1172986,1866791,2002,ERROR,41.789903341,-87.641246766,"(41.789903341, -87.641246766)" +5011028,HM467142,2006-07-10 17:24:47,039XX W ARMITAGE AVE,1513,PROSTITUTION,SOLICIT FOR BUSINESS,STREET,4204,True,False,2535,25,30,20,16,1150000,1912964,2006,2006-07-10 09:43:17,41.917084599,-87.724329742,"(41.917084599, -87.724329742)" +2248732,HH525417,2002-06-08 09:00:00,014XX N WELLS ST,1120,DECEPTIVE PRACTICE,FORGERY,RESIDENCE,4807,False,False,1821,NA,27,8,10,1174403,1909875,2002,ERROR,41.908097724,-87.634765969,"(41.908097724, -87.634765969)" +5736007,HN543999,2007-08-22 14:00:45,072XX N CLARK ST,0820,THEFT,$500 AND UNDER,STREET,248,False,False,2423,24,49,1,06,1163247,1948255,2007,2014-04-12 12:43:35,42.013656195,-87.674663459,"(42.013656195, -87.674663459)" +1601570,G372788,2001-06-26 17:53:24,089XX S COTTAGE GROVE,0460,BATTERY,SIMPLE,RESIDENCE,3120,False,False,633,NA,NA,NA,08B,1183085,1845890,2001,ERROR,41.732319842,-87.604865988,"(41.732319842, -87.604865988)" +8530168,HV207435,2012-03-21 05:45:00,009XX E 130TH PL,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,4330,False,False,533,5,9,54,14,1184644,1819036,2012,ERROR,41.658592656,-87.599992356,"(41.658592656, -87.599992356)" +4862832,HM474817,2006-07-10 20:00:00,055XX S SHORE DR,2826,OTHER OFFENSE,HARASSMENT BY ELECTRONIC MEANS,RESTAURANT,2392,False,False,2132,2,5,41,26,1189509,1868821,2006,ERROR,41.795092837,-87.580597321,"(41.795092837, -87.580597321)" +4406900,HK742347,2004-11-10 11:59:00,078XX S CORNELL AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,4344,True,False,411,4,8,43,18,1188552,1853522,2004,ERROR,41.753134073,-87.584595019,"(41.753134073, -87.584595019)" +3010706,HJ715145,2003-10-17 11:00:00,062XX N SACRAMENTO AVE,1120,DECEPTIVE PRACTICE,FORGERY,RESIDENCE,1790,False,False,2413,24,50,2,10,1155185,1941017,2003,ERROR,41.993961219,-87.704523692,"(41.993961219, -87.704523692)" +5557879,HN367489,2007-05-27 01:15:00,020XX N STAVE ST,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,SIDEWALK,4000,True,False,1431,14,1,22,15,1158880,1913340,2007,2010-02-06 10:34:17,41.917938701,-87.691694171,"(41.917938701, -87.691694171)" +3066701,HJ786859,2003-11-22 23:30:00,105XX S MICHIGAN AVE,2825,OTHER OFFENSE,HARASSMENT BY TELEPHONE,RESIDENCE,1288,False,False,512,5,9,49,26,1178884,1835024,2003,ERROR,41.702598655,-87.62058548,"(41.702598655, -87.62058548)" +6668993,HP742526,2008-12-19 22:00:00,002XX W EVERGREEN AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2419,False,False,1821,NA,43,8,14,NA,NA,2008,1999-08-11 15:39:40,NA,NA, +6353756,HP432354,2008-07-04 05:00:00,013XX W 97TH ST,0320,ROBBERY,STRONGARM - NO WEAPON,RESIDENCE PORCH/HALLWAY,2082,False,False,2213,22,21,73,03,1168978,1840625,2008,ERROR,41.71818792,-87.656697493,"(41.71818792, -87.656697493)" +6669938,HP740347,2008-11-26 12:00:00,052XX W FOSTER AVE,0840,THEFT,FINANCIAL ID THEFT: OVER $300,RESIDENCE,1990,False,False,1623,16,45,11,06,1140701,1934124,2008,ERROR,41.97532578,-87.757972837,"(41.97532578, -87.757972837)" +4826288,HM332892,2006-05-05 20:37:41,042XX S COTTAGE GROVE AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,SIDEWALK,1058,True,False,2123,2,4,36,18,1182360,1876778,2006,ERROR,41.817096269,-87.606565687,"(41.817096269, -87.606565687)" +7886005,HT116106,2011-01-12 09:30:00,054XX S HERMITAGE AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,4182,False,False,932,9,16,61,05,1165565,1868597,2011,ERROR,41.795019934,-87.668406224,"(41.795019934, -87.668406224)" +6705302,HR108700,2005-12-31 09:00:00,029XX N KEDZIE AVE,0840,THEFT,FINANCIAL ID THEFT: OVER $300,RESIDENCE,889,False,False,1411,NA,35,21,06,NA,NA,2005,ERROR,NA,NA, +8964081,HW112182,2013-01-10 00:00:00,044XX S ARCHER AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2466,False,False,821,8,14,58,14,1155228,1874993,2013,2013-11-01 07:24:01,41.81278459,-87.706141165,"(41.81278459, -87.706141165)" +7699490,HS506358,2010-09-03 11:00:00,067XX S EAST END AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,776,False,False,332,3,5,43,26,1188736,1860587,2010,ERROR,41.77251665,-87.583695111,"(41.77251665, -87.583695111)" +9180878,HW325720,2013-06-19 11:15:00,110XX S WESTERN AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,STREET,4683,False,True,2212,22,19,75,08B,1162390,1831737,2013,ERROR,41.693937278,-87.681073558,"(41.693937278, -87.681073558)" +2297694,HH568797,2002-08-09 14:50:00,050XX W WASHINGTON BLVD,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,3902,True,False,1533,NA,28,25,18,1142677,1899991,2002,ERROR,41.881624743,-87.751557974,"(41.881624743, -87.751557974)" +7644446,HS448315,2010-08-06 05:23:00,008XX W ADDISON ST,0860,THEFT,RETAIL THEFT,GAS STATION,2267,False,False,2331,19,44,6,06,1170247,1924094,2010,2010-09-08 10:51:39,41.947207196,-87.64961635,"(41.947207196, -87.64961635)" +1423522,G144180,2001-03-11 14:00:00,016XX W GREENLEAF AV,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,4093,False,True,2423,NA,NA,NA,26,1164186,1947061,2001,ERROR,42.010359958,-87.671242346,"(42.010359958, -87.671242346)" +5125870,HM723860,2006-11-16 14:00:00,011XX W WILSON AVE,0460,BATTERY,SIMPLE,"SCHOOL, PUBLIC, BUILDING",4704,True,False,2311,19,46,3,08B,1167577,1930654,2006,ERROR,41.965266117,-87.659240822,"(41.965266117, -87.659240822)" +2492829,HH829410,2002-12-10 12:59:05,041XX W 16TH ST,502R,OTHER OFFENSE,VEHICLE TITLE/REG OFFENSE,STREET,2870,True,False,1012,NA,24,29,26,1148968,1891764,2002,ERROR,41.85892954,-87.728670191,"(41.85892954, -87.728670191)" +3807185,HL176495,2005-02-11 23:40:00,053XX W HIRSCH ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,3180,False,False,2532,25,37,25,07,1140742,1908843,2005,ERROR,41.905951477,-87.758445541,"(41.905951477, -87.758445541)" +5654829,HN465341,2007-07-14 01:00:00,053XX S WABASH AVE,0930,MOTOR VEHICLE THEFT,THEFT/RECOVERY: AUTOMOBILE,STREET,1188,False,False,232,2,3,40,07,1177577,1869771,2007,ERROR,41.7979781,-87.624322882,"(41.7979781, -87.624322882)" +7897069,HT126881,2011-01-19 20:50:00,016XX N KIMBALL AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,RESIDENCE,3699,True,False,1422,14,26,23,18,1153494,1910674,2011,ERROR,41.910731836,-87.7115537,"(41.910731836, -87.7115537)" +6053794,HP155338,2008-02-02 02:15:00,002XX N ASHLAND AVE,0560,ASSAULT,SIMPLE,TAVERN/LIQUOR STORE,4130,False,False,1333,12,27,28,08A,1165726,1901925,2008,2008-07-02 06:38:43,41.886471844,-87.666867499,"(41.886471844, -87.666867499)" +5565328,HN356933,2007-05-21 21:30:57,070XX S JEFFERY BLVD,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,VEHICLE NON-COMMERCIAL,4147,True,False,331,3,5,43,18,1190805,1858575,2007,ERROR,41.766945825,-87.576175796,"(41.766945825, -87.576175796)" +3248772,HK265125,2004-03-27 02:54:00,0000X W DIVISION ST,3710,INTERFERENCE WITH PUBLIC OFFICER,RESIST/OBSTRUCT/DISARM OFFICER,SIDEWALK,4310,True,False,1824,18,42,8,24,1175892,1908408,2004,2014-04-12 12:43:35,41.904038802,-87.629340436,"(41.904038802, -87.629340436)" +1757877,G572420,2001-09-24 01:00:00,009XX N HARDING AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,797,False,False,1112,NA,NA,NA,07,1149924,1905981,2001,ERROR,41.897924046,-87.72479098,"(41.897924046, -87.72479098)" +2774357,HJ418762,2003-06-09 22:26:00,017XX N TALMAN AVE,0320,ROBBERY,STRONGARM - NO WEAPON,STREET,826,False,False,1421,NA,1,24,03,1158398,1911205,2003,ERROR,41.912089977,-87.693523584,"(41.912089977, -87.693523584)" +8926173,HV598195,2012-12-11 15:00:00,001XX N KEDZIE AVE,2092,NARCOTICS,SOLICIT NARCOTICS ON PUBLICWAY,SIDEWALK,4028,True,False,1331,12,27,27,26,1155069,1900663,2012,2012-11-12 15:22:54,41.883229246,-87.706036593,"(41.883229246, -87.706036593)" +5741562,HN545599,2007-08-23 11:06:35,056XX W CORCORAN PL,0560,ASSAULT,SIMPLE,GROCERY FOOD STORE,2009,True,False,1512,15,29,25,08A,1138929,1901964,2007,ERROR,41.887107805,-87.765272755,"(41.887107805, -87.765272755)" +7388091,HS188642,2010-03-02 16:00:00,059XX W AUGUSTA BLVD,0460,BATTERY,SIMPLE,SIDEWALK,4552,False,False,1511,15,29,25,08B,1136861,1906101,2010,2010-04-03 11:52:03,41.898497579,-87.772767903,"(41.898497579, -87.772767903)" +4148394,HL472015,2005-07-09 03:00:00,004XX W 98TH ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,VEHICLE NON-COMMERCIAL,1031,False,True,2223,22,21,73,08B,1174873,1839956,2005,ERROR,41.716222926,-87.635126228,"(41.716222926, -87.635126228)" +7111964,HR520191,2009-09-04 19:15:00,040XX W LAKE ST,2024,NARCOTICS,POSS: HEROIN(WHITE),CTA PLATFORM,1207,True,False,1114,11,28,26,18,1149614,1901481,2009,2009-04-09 20:50:05,41.885581599,-87.726046569,"(41.885581599, -87.726046569)" +1498551,G234518,2001-04-24 20:15:00,005XX N COLUMBUS DR,0810,THEFT,OVER $500,STREET,1805,False,False,1834,NA,NA,NA,06,1178452,1903693,2001,2014-04-12 12:43:35,41.891042598,-87.620081013,"(41.891042598, -87.620081013)" +1470135,G198684,2001-03-30 19:30:00,049XX W WEST END AV,0460,BATTERY,SIMPLE,APARTMENT,883,False,True,1532,NA,NA,NA,08B,1143503,1900491,2001,ERROR,41.882981399,-87.748512379,"(41.882981399, -87.748512379)" +4380629,HL672144,2005-10-14 08:30:00,002XX W 87TH ST,1320,CRIMINAL DAMAGE,TO VEHICLE,PARKING LOT/GARAGE(NON.RESID.),2556,False,False,622,6,21,44,14,1176403,1847255,2005,ERROR,41.736218161,-87.629303966,"(41.736218161, -87.629303966)" +3828054,HL198936,2005-02-23 21:00:00,049XX N MILWAUKEE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,PARKING LOT/GARAGE(NON.RESID.),1349,False,False,1623,16,45,11,14,1139081,1932636,2005,ERROR,41.971272283,-87.763966593,"(41.971272283, -87.763966593)" +5649370,HN457869,2007-07-10 15:30:00,121XX S PRINCETON AVE,0530,ASSAULT,AGGRAVATED: OTHER DANG WEAPON,STREET,2266,False,False,523,5,34,53,04A,1176434,1824279,2007,ERROR,41.673168048,-87.629877991,"(41.673168048, -87.629877991)" +1926664,G777431,2001-12-30 12:22:03,075XX S COTTAGE GROVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,2792,True,False,624,NA,NA,NA,18,1182916,1854849,2001,ERROR,41.756908237,-87.605207385,"(41.756908237, -87.605207385)" +3349468,HK391905,2004-05-25 15:00:00,015XX E 93RD ST,0820,THEFT,$500 AND UNDER,SIDEWALK,483,False,False,413,4,8,48,06,1187948,1843656,2004,2014-04-12 12:43:35,41.726075187,-87.587121912,"(41.726075187, -87.587121912)" +9289687,HW434804,2013-09-02 18:45:00,057XX S PRAIRIE AVE,2023,NARCOTICS,POSS: HEROIN(BRN/TAN),VACANT LOT/LAND,3421,True,False,232,2,20,40,18,1178985,1866850,2013,2013-03-09 11:05:41,41.789930619,-87.619248524,"(41.789930619, -87.619248524)" +4657553,HM172738,2006-02-09 20:15:00,001XX E WACKER DR,1505,PROSTITUTION,CALL OPERATION,HOTEL/MOTEL,2254,True,False,124,1,42,32,16,1177757,1902582,2006,2006-01-04 03:33:39,41.888009783,-87.622667166,"(41.888009783, -87.622667166)" +1848637,G686275,2001-10-17 17:00:00,031XX W 103 ST,1130,DECEPTIVE PRACTICE,FRAUD OR CONFIDENCE GAME,BANK,2485,False,False,2211,NA,NA,NA,11,1157251,1836208,2001,ERROR,41.706311774,-87.699768468,"(41.706311774, -87.699768468)" +1714907,G518024,2001-08-30 00:38:59,122XX S PEORIA ST,0326,ROBBERY,AGGRAVATED VEHICULAR HIJACKING,STREET,2852,False,False,524,NA,NA,NA,03,1172502,1823677,2001,ERROR,41.671603244,-87.644286984,"(41.671603244, -87.644286984)" +8501862,HV177514,2012-02-19 16:00:00,027XX N MOBILE AVE,5002,OTHER OFFENSE,OTHER VEHICLE OFFENSE,STREET,3247,False,False,2512,25,29,19,26,1133987,1917369,2012,ERROR,41.9294694,-87.783058569,"(41.9294694, -87.783058569)" +2788429,HJ437145,2003-06-17 08:00:00,084XX W GREGORY ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3143,False,False,1614,NA,41,76,14,NA,NA,2003,ERROR,NA,NA, +7308921,HS113429,2010-01-10 10:28:00,130XX S DR MARTIN LUTHER KING JR DR,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,4532,True,True,533,5,9,54,08B,1180948,1818658,2010,2010-11-01 05:08:49,41.657640861,-87.613528366,"(41.657640861, -87.613528366)" +9411869,HW555535,2013-11-28 08:00:00,032XX W DOUGLAS BLVD,0890,THEFT,FROM BUILDING,COMMERCIAL / BUSINESS OFFICE,2178,False,False,1022,10,24,29,06,1154818,1893309,2013,2013-02-12 13:43:47,41.86305413,-87.707155285,"(41.86305413, -87.707155285)" +6863000,HR267765,2009-04-14 15:32:00,016XX E 53RD ST,1210,DECEPTIVE PRACTICE,THEFT OF LABOR/SERVICES,TAXICAB,1052,False,False,2132,2,4,41,11,1188194,1870489,2009,ERROR,41.799701444,-87.585366136,"(41.799701444, -87.585366136)" +8079527,HT312157,2011-05-17 14:00:00,063XX N CLAREMONT AVE,0620,BURGLARY,UNLAWFUL ENTRY,APARTMENT,4304,False,False,2413,24,50,2,05,1159464,1941832,2011,ERROR,41.99611031,-87.688761143,"(41.99611031, -87.688761143)" +5959995,HN755465,2007-11-06 08:00:00,076XX S PHILLIPS AVE,1150,DECEPTIVE PRACTICE,CREDIT CARD FRAUD,BANK,3955,False,True,421,4,7,43,11,1193897,1854798,2007,2008-12-01 01:05:03,41.756506203,-87.564966253,"(41.756506203, -87.564966253)" +4579723,HM166790,2006-02-06 17:30:00,084XX S INGLESIDE AVE,0460,BATTERY,SIMPLE,SIDEWALK,2807,False,False,632,6,8,44,08B,1183976,1849454,2006,ERROR,41.74207911,-87.601490881,"(41.74207911, -87.601490881)" +5789960,HN600058,2007-09-20 10:00:00,071XX W DICKENS AVE,0820,THEFT,$500 AND UNDER,STREET,270,False,False,2512,25,36,25,06,1127875,1913121,2007,2014-04-12 12:43:35,41.917917786,-87.805614941,"(41.917917786, -87.805614941)" +4775666,HM388940,2006-05-30 13:00:00,072XX S KIMBARK AVE,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,2394,False,False,324,3,5,69,07,1186082,1857490,2006,2006-04-06 04:24:23,41.764081271,-87.593521441,"(41.764081271, -87.593521441)" +7711055,HS518043,2010-09-16 12:05:00,026XX E 79TH ST,0460,BATTERY,SIMPLE,OTHER,3669,False,False,421,4,7,43,08B,1195505,1853147,2010,ERROR,41.751936174,-87.559127815,"(41.751936174, -87.559127815)" +8137536,HT371329,2011-06-29 00:08:00,038XX W FLOURNOY ST,041A,BATTERY,AGGRAVATED: HANDGUN,RESIDENTIAL YARD (FRONT/BACK),4277,False,False,1133,11,24,26,04B,1150802,1896712,2011,ERROR,41.872471787,-87.721808751,"(41.872471787, -87.721808751)" +7387445,HS189260,2010-01-24 20:37:00,016XX W 32ND ST,2826,OTHER OFFENSE,HARASSMENT BY ELECTRONIC MEANS,RESIDENCE,3922,False,False,922,9,11,59,26,1166016,1883413,2010,2010-05-03 08:02:26,41.835667093,-87.666330688,"(41.835667093, -87.666330688)" +1834918,G668790,2001-11-05 19:00:00,015XX N SPRINGFIELD AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,3471,False,False,2535,NA,NA,NA,07,1150124,1910319,2001,ERROR,41.909824052,-87.723943209,"(41.909824052, -87.723943209)" +6824043,HR227480,2009-03-21 10:35:00,124XX S EMERALD AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,896,False,False,523,5,34,53,14,1173548,1822149,2009,ERROR,41.667387143,-87.640503609,"(41.667387143, -87.640503609)" +3334126,HK375058,2004-05-18 19:25:00,018XX W FARWELL AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,1815,False,False,2424,24,49,1,26,1162731,1945686,2004,ERROR,42.00661768,-87.676634619,"(42.00661768, -87.676634619)" +4991479,HM599065,2006-09-13 14:20:00,004XX N STATE ST,0460,BATTERY,SIMPLE,RESTAURANT,4867,True,False,1831,18,42,8,08B,1176246,1903580,2006,ERROR,41.890782553,-87.628185879,"(41.890782553, -87.628185879)" +4779523,HM394310,2006-06-02 15:30:00,042XX N CICERO AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,VEHICLE NON-COMMERCIAL,2942,False,False,1624,16,45,15,14,1143559,1928046,2006,2006-07-06 04:08:49,41.958594104,-87.747615647,"(41.958594104, -87.747615647)" +7209980,HR625015,2009-11-04 00:05:00,012XX W DIVISION ST,2022,NARCOTICS,POSS: COCAINE,STREET,851,True,False,1323,12,27,24,18,1168223,1908112,2009,2009-04-11 01:46:02,41.903395808,-87.657518982,"(41.903395808, -87.657518982)" +4855527,HM466819,2006-07-06 21:00:00,055XX N CLARK ST,0890,THEFT,FROM BUILDING,CHURCH/SYNAGOGUE/PLACE OF WORSHIP,3503,False,False,2012,20,40,77,06,1164942,1936997,2006,ERROR,41.982728013,-87.668748145,"(41.982728013, -87.668748145)" +5216189,HN101090,2007-01-01 14:47:51,002XX E 136TH ST,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE,4188,False,False,533,5,9,54,05,1180454,1815072,2007,ERROR,41.647811613,-87.615445268,"(41.647811613, -87.615445268)" +6782563,HR197644,2009-03-03 19:00:00,076XX S CRANDON AVE,0890,THEFT,FROM BUILDING,"SCHOOL, PUBLIC, BUILDING",1821,False,False,414,4,7,43,06,1192816,1854954,2009,2009-04-03 06:03:14,41.756960706,-87.568922754,"(41.756960706, -87.568922754)" +3363754,HK410972,2004-06-05 17:27:50,110XX S AVENUE M,0560,ASSAULT,SIMPLE,STREET,4448,True,False,433,4,10,52,08A,1201506,1832221,2004,ERROR,41.694363358,-87.537846019,"(41.694363358, -87.537846019)" +9749727,HX399758,2014-08-23 16:50:00,101XX S VERNON AVE,0810,THEFT,OVER $500,RESIDENCE,4015,False,False,511,5,9,49,06,1181071,1837939,2014,ERROR,41.71054785,-87.612487964,"(41.71054785, -87.612487964)" +3810647,HL177913,2005-02-12 19:10:00,072XX N SHERIDAN RD,1365,CRIMINAL TRESPASS,TO RESIDENCE,NURSING HOME/RETIREMENT HOME,4948,True,False,2423,24,49,1,26,1166257,1947965,2005,ERROR,42.012796343,-87.66359639,"(42.012796343, -87.66359639)" +2167336,HH418150,2002-06-04 06:00:00,001XX W SUPERIOR ST,0810,THEFT,OVER $500,VEHICLE NON-COMMERCIAL,3092,False,False,1832,NA,42,8,06,1175214,1905375,2002,2014-04-12 12:43:35,41.895731339,-87.631921962,"(41.895731339, -87.631921962)" +2336983,HH635814,2002-09-08 20:00:00,086XX S ELIZABETH ST,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,1484,False,False,613,NA,21,71,14,1169547,1847226,2002,ERROR,41.736289738,-87.654422772,"(41.736289738, -87.654422772)" +2283233,HH562586,2002-08-06 12:00:00,032XX W FLOURNOY ST,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,2392,False,True,1134,NA,24,27,26,1154671,1896885,2002,ERROR,41.87287,-87.707599224,"(41.87287, -87.707599224)" +9145362,HW290799,2013-05-25 18:20:00,066XX S HALSTED ST,1330,CRIMINAL TRESPASS,TO LAND,SMALL RETAIL STORE,2918,False,False,723,7,6,68,26,1172149,1860963,2013,ERROR,41.773929092,-87.644486986,"(41.773929092, -87.644486986)" +8134619,HT369020,2011-06-27 18:35:00,094XX S WESTERN AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,1575,True,False,2221,22,19,72,18,1162082,1841641,2011,ERROR,41.721121942,-87.68192693,"(41.721121942, -87.68192693)" +1662674,G451886,2001-07-31 08:30:00,077XX S CALUMET AV,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,4005,False,False,623,NA,NA,NA,05,1179859,1853565,2001,ERROR,41.753455253,-87.616449881,"(41.753455253, -87.616449881)" +5926846,HN721506,2007-11-21 06:00:00,012XX W 98TH ST,0281,CRIM SEXUAL ASSAULT,NON-AGGRAVATED,RESIDENCE,3077,False,False,2213,22,21,73,02,1169700,1839736,2007,ERROR,41.715732768,-87.654078763,"(41.715732768, -87.654078763)" +2621303,HJ224035,2003-03-05 10:00:00,019XX W GREENLEAF AVE,0930,MOTOR VEHICLE THEFT,THEFT/RECOVERY: AUTOMOBILE,STREET,1523,False,False,2424,NA,49,1,07,1162189,1946919,2003,ERROR,42.010012453,-87.67859402,"(42.010012453, -87.67859402)" +2334124,HH631588,2002-09-06 21:00:00,025XX N LINDER AVE,0610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,3020,False,False,2515,NA,30,19,05,1139329,1916155,2002,ERROR,41.926042297,-87.763457601,"(41.926042297, -87.763457601)" +8519471,HV196442,2012-03-12 21:40:00,100XX S CRANDON AVE,033A,ROBBERY,ATTEMPT: ARMED-HANDGUN,STREET,3149,False,False,431,4,7,51,03,1193441,1839086,2012,2012-11-04 16:56:10,41.713402235,-87.567149974,"(41.713402235, -87.567149974)" +2508938,HH852379,2002-12-21 02:00:00,025XX N KEDZIE BLVD,0890,THEFT,FROM BUILDING,OTHER,3585,False,False,1414,NA,35,22,06,1154663,1916875,2002,ERROR,41.92772456,-87.707092848,"(41.92772456, -87.707092848)" +4690771,HM291349,2006-04-14 20:50:00,012XX W 72ND ST,0460,BATTERY,SIMPLE,SIDEWALK,1089,False,False,734,7,17,67,08B,1169088,1857072,2006,ERROR,41.763318426,-87.655820347,"(41.763318426, -87.655820347)" +3831802,HL200488,2005-02-24 21:20:56,124XX S EGGLESTON AVE,143A,WEAPONS VIOLATION,UNLAWFUL POSS OF HANDGUN,RESIDENCE,3847,False,False,523,5,34,53,15,1175509,1822487,2005,2014-04-12 12:43:35,41.668271184,-87.633316824,"(41.668271184, -87.633316824)" +4288666,HL600343,2005-09-09 00:45:00,033XX W OGDEN AVE,2850,PUBLIC PEACE VIOLATION,BOMB THREAT,POLICE FACILITY/VEH PARKING LOT,4337,False,False,1024,10,24,29,26,1154500,1890985,2005,ERROR,41.856683172,-87.708384737,"(41.856683172, -87.708384737)" +6404121,HP476419,2008-07-26 18:15:00,068XX S CLAREMONT AVE,0530,ASSAULT,AGGRAVATED: OTHER DANG WEAPON,STREET,3421,False,True,832,8,17,66,04A,1161871,1859315,2008,2008-07-08 10:22:37,41.76962648,-87.682209929,"(41.76962648, -87.682209929)" +7629586,HS432289,2010-07-26 14:00:00,011XX W 62ND ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,2636,False,True,712,7,16,68,08B,1169767,1863724,2010,ERROR,41.781557634,-87.653138853,"(41.781557634, -87.653138853)" +1504741,G234331,2001-04-24 19:30:00,027XX N SAWYER AV,2027,NARCOTICS,POSS: CRACK,SIDEWALK,4441,True,False,1412,NA,NA,NA,18,1154131,1917989,2001,ERROR,41.930792109,-87.709017944,"(41.930792109, -87.709017944)" +8748002,HV422718,2012-08-09 12:20:00,103XX S AVENUE L,0915,MOTOR VEHICLE THEFT,"TRUCK, BUS, MOTOR HOME",STREET,3008,False,False,432,4,10,52,07,1201793,1836826,2012,ERROR,41.706992596,-87.536639296,"(41.706992596, -87.536639296)" +3768741,HL139720,2005-01-23 13:30:00,035XX N ELSTON AVE,0810,THEFT,OVER $500,VEHICLE NON-COMMERCIAL,1452,False,False,1733,17,35,21,06,1154312,1923546,2005,2014-04-12 12:43:35,41.946037311,-87.708203891,"(41.946037311, -87.708203891)" +4462522,HL759778,2005-11-28 11:36:00,074XX N GREENVIEW AVE,0460,BATTERY,SIMPLE,APARTMENT,3452,False,False,2422,24,49,1,08B,1164955,1949681,2005,ERROR,42.017532926,-87.668338035,"(42.017532926, -87.668338035)" +6045601,HP146797,2008-01-26 15:00:00,006XX W WAYMAN ST,0610,BURGLARY,FORCIBLE ENTRY,CONSTRUCTION SITE,1006,False,False,1212,12,27,28,05,1171516,1902377,2008,2008-01-02 20:14:43,41.887586797,-87.645592029,"(41.887586797, -87.645592029)" +5682032,HN490569,2007-07-23 00:01:00,004XX N DEARBORN ST,1130,DECEPTIVE PRACTICE,FRAUD OR CONFIDENCE GAME,RESTAURANT,3345,False,False,1831,18,42,8,11,1175894,1903544,2007,ERROR,41.890691698,-87.629479667,"(41.890691698, -87.629479667)" +8520242,HV196949,2012-03-14 12:15:00,015XX W 45TH ST,0312,ROBBERY,ARMED:KNIFE/CUTTING INSTRUMENT,STREET,4644,False,False,924,9,3,61,03,1167048,1874840,2012,ERROR,41.812119833,-87.662789453,"(41.812119833, -87.662789453)" +8071070,HT292750,2011-05-12 17:18:00,006XX E GRAND AVE,0860,THEFT,RETAIL THEFT,OTHER,2071,True,False,1834,18,42,8,06,1180772,1904096,2011,ERROR,41.892095212,-87.611548469,"(41.892095212, -87.611548469)" +9957363,HY146143,2014-12-27 21:00:00,013XX W CHICAGO AVE,0810,THEFT,OVER $500,RESIDENCE,904,False,False,1215,12,27,24,06,1167548,1905439,2014,ERROR,41.896075483,-87.660075443,"(41.896075483, -87.660075443)" +10067136,HY256096,2015-05-11 16:00:00,087XX S BEVERLY AVE,0820,THEFT,$500 AND UNDER,STREET,466,False,False,2221,22,21,71,06,1164962,1846395,2015,ERROR,41.734107372,-87.671244102,"(41.734107372, -87.671244102)" +8115927,HT350334,2011-06-16 12:00:00,058XX N KENMORE AVE,0560,ASSAULT,SIMPLE,RESIDENCE PORCH/HALLWAY,2492,False,False,2022,20,48,77,08A,1168225,1939040,2011,ERROR,41.988263519,-87.656614711,"(41.988263519, -87.656614711)" +3295784,HK327947,2004-04-27 12:42:44,055XX W CHICAGO AVE,0860,THEFT,RETAIL THEFT,DRUG STORE,630,True,False,1524,15,37,25,06,1139149,1904830,2004,ERROR,41.894968468,-87.764395055,"(41.894968468, -87.764395055)" +4118774,HL445725,2005-06-26 18:35:00,076XX S CICERO AVE,0560,ASSAULT,SIMPLE,OTHER,3553,True,False,833,8,13,65,08A,1145766,1853738,2005,ERROR,41.75464162,-87.741385158,"(41.75464162, -87.741385158)" +4313295,HL623082,2005-09-19 22:30:00,078XX S CREGIER AVE,2820,OTHER OFFENSE,TELEPHONE THREAT,RESIDENCE,2726,False,True,414,4,8,43,26,1189530,1853090,2005,ERROR,41.751925214,-87.581024925,"(41.751925214, -87.581024925)" +5275302,HM771318,2006-12-12 14:30:00,016XX N VINE ST,0460,BATTERY,SIMPLE,CHA APARTMENT,3342,False,False,1813,18,43,7,08B,1171741,1910972,2006,2007-04-02 09:14:02,41.911167023,-87.644512341,"(41.911167023, -87.644512341)" +9571206,HX221842,2014-04-12 21:00:00,036XX W ARMITAGE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,4457,True,True,2535,25,26,22,08B,1151558,1912999,2014,ERROR,41.91715014,-87.718604687,"(41.91715014, -87.718604687)" +8225563,HT459910,2011-06-02 10:00:00,034XX N OVERHILL AVE,1120,DECEPTIVE PRACTICE,FORGERY,RESIDENCE,4580,False,True,1631,16,36,17,10,1124187,1921874,2011,ERROR,41.941998641,-87.818972224,"(41.941998641, -87.818972224)" +3698614,HK800968,2004-12-06 07:28:00,039XX W 55TH ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,3905,False,False,822,8,23,62,14,1151189,1867799,2004,ERROR,41.793123142,-87.721144397,"(41.793123142, -87.721144397)" +6279492,HP366498,2008-05-31 01:49:48,024XX E 77TH ST,0560,ASSAULT,SIMPLE,STREET,3375,False,False,421,4,7,43,08A,1193751,1854422,2008,2008-10-06 17:19:23,41.755478007,-87.565513603,"(41.755478007, -87.565513603)" +3809022,HL179005,2005-02-12 23:00:00,079XX S SACRAMENTO AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,4081,False,False,835,8,18,70,14,1157711,1852042,2005,ERROR,41.749753656,-87.69765575,"(41.749753656, -87.69765575)" +2448823,HH773171,2002-11-11 16:55:00,024XX W 46TH ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,4003,False,False,914,NA,12,58,08B,1160595,1874059,2002,ERROR,41.810112404,-87.686480579,"(41.810112404, -87.686480579)" +8784290,HV458206,2012-09-02 15:00:00,010XX W BRYN MAWR AVE,0820,THEFT,$500 AND UNDER,SIDEWALK,324,False,False,2022,20,48,77,06,1168220,1937410,2012,2012-03-09 10:10:19,41.98379087,-87.656680467,"(41.98379087, -87.656680467)" +5066596,HM672873,2006-10-21 01:10:00,063XX S ELIZABETH ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,ALLEY,1796,False,True,724,7,16,67,08B,1169122,1862553,2006,2006-04-11 05:54:38,41.778358245,-87.655537413,"(41.778358245, -87.655537413)" +9049125,HW191204,2013-03-13 19:00:00,031XX N SHEFFIELD AVE,0820,THEFT,$500 AND UNDER,STREET,247,False,False,1933,19,44,6,06,1169094,1921197,2013,ERROR,41.939282865,-87.65393884,"(41.939282865, -87.65393884)" +7764239,HS572350,2010-10-19 16:30:00,048XX N SPAULDING AVE,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,3988,True,False,1713,17,39,14,18,1153546,1931968,2010,ERROR,41.969163134,-87.710794575,"(41.969163134, -87.710794575)" +2410766,HH726508,2002-10-19 20:00:00,041XX W GRANVILLE AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,3555,False,False,1711,NA,39,12,14,1147823,1940905,2002,ERROR,41.993799031,-87.731607315,"(41.993799031, -87.731607315)" +6473955,HP548589,2008-09-02 10:38:27,002XX S LAVERGNE AVE,2017,NARCOTICS,MANU/DELIVER:CRACK,SIDEWALK,4717,True,False,1533,15,28,25,18,1143332,1898687,2008,2008-04-09 10:02:31,41.878034197,-87.749185395,"(41.878034197, -87.749185395)" +3811299,HL180923,2005-02-14 14:30:00,001XX W CERMAK RD,0890,THEFT,FROM BUILDING,RESTAURANT,4526,False,False,2111,9,25,34,06,1175413,1889712,2005,ERROR,41.852746625,-87.631661358,"(41.852746625, -87.631661358)" +8254428,HT488217,2011-09-09 08:20:00,012XX S KILDARE AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,1355,True,True,1011,10,24,29,08B,1147879,1893831,2011,2011-10-09 08:30:21,41.864622619,-87.732614499,"(41.864622619, -87.732614499)" +3052579,HJ766530,2003-11-18 11:15:00,010XX W NORTH AVE,0312,ROBBERY,ARMED:KNIFE/CUTTING INSTRUMENT,SMALL RETAIL STORE,1967,False,False,1811,18,32,7,03,1169395,1910873,2003,ERROR,41.910946738,-87.653133577,"(41.910946738, -87.653133577)" +2802395,HJ456446,2003-06-26 17:00:00,014XX N MAPLEWOOD AVE,0810,THEFT,OVER $500,STREET,3051,False,False,1423,14,26,24,06,1159110,1909223,2003,2014-04-12 12:43:35,41.906636608,-87.690962425,"(41.906636608, -87.690962425)" +8467589,HV143840,2012-02-03 12:45:00,064XX S ST LAWRENCE AVE,1330,CRIMINAL TRESPASS,TO LAND,RESIDENCE-GARAGE,2128,False,False,312,3,20,42,26,1181306,1862283,2012,2012-05-02 07:39:28,41.777345128,-87.610878867,"(41.777345128, -87.610878867)" +8672044,HV346931,2012-06-22 02:30:00,002XX N ASHLAND AVE,0820,THEFT,$500 AND UNDER,STREET,66,False,False,1333,12,27,28,06,1165729,1901839,2012,ERROR,41.88623579,-87.666858935,"(41.88623579, -87.666858935)" +2087824,HH307538,2002-04-14 17:00:00,112XX S CORLISS AV,0820,THEFT,$500 AND UNDER,PARKING LOT/GARAGE(NON.RESID.),121,False,False,531,NA,NA,NA,06,1184357,1830749,2002,2014-04-12 12:43:35,41.690741459,-87.60067812,"(41.690741459, -87.60067812)" +8209337,HT443374,2011-08-11 18:35:00,079XX S HALSTED ST,031A,ROBBERY,ARMED: HANDGUN,ATM (AUTOMATIC TELLER MACHINE),1050,False,False,621,6,17,71,03,1172299,1852411,2011,ERROR,41.750458051,-87.644188274,"(41.750458051, -87.644188274)" +8256672,HT490030,2011-09-10 01:45:00,116XX S DOTY AVE W,0460,BATTERY,SIMPLE,PARKING LOT/GARAGE(NON.RESID.),2252,False,False,532,5,9,54,08B,1183932,1827765,2011,2011-11-09 06:14:39,41.68256287,-87.602326758,"(41.68256287, -87.602326758)" +5916746,HN714104,2007-11-18 02:00:00,026XX W CORTLAND ST,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,644,False,False,1421,14,1,22,14,1158683,1912488,2007,ERROR,41.91560479,-87.692441353,"(41.91560479, -87.692441353)" +8191615,HT405749,2011-07-19 18:00:00,009XX W WINONA ST,2032,NARCOTICS,MANU/DELIVER: METHAMPHETAMINES,RESIDENCE,2116,True,False,2024,20,48,3,18,1169251,1934368,2011,2011-01-08 11:42:26,41.975421135,-87.652977507,"(41.975421135, -87.652977507)" +2973180,HJ659852,2003-09-28 22:39:00,097XX S HARVARD AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3610,False,True,511,5,21,49,08B,1175644,1839981,2003,ERROR,41.716274337,-87.632301694,"(41.716274337, -87.632301694)" +2653149,HJ262801,2003-03-26 22:50:00,083XX S ELLIS AVE,0610,BURGLARY,FORCIBLE ENTRY,APARTMENT,721,False,False,632,NA,8,44,05,1184303,1849697,2003,ERROR,41.742738291,-87.600285187,"(41.742738291, -87.600285187)" +2813880,HJ468442,2003-07-02 17:55:00,103XX S CHARLES ST,0930,MOTOR VEHICLE THEFT,THEFT/RECOVERY: AUTOMOBILE,OTHER,1416,False,False,2212,22,19,72,07,1168443,1836435,2003,ERROR,41.70670143,-87.658777228,"(41.70670143, -87.658777228)" +6853270,HR212470,2009-03-12 20:24:00,014XX W 63RD ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,3705,True,False,725,7,16,67,18,1167591,1862923,2009,ERROR,41.779406548,-87.661139539,"(41.779406548, -87.661139539)" +4345470,HL643338,2005-09-29 12:30:00,054XX S WENTWORTH AVE,0860,THEFT,RETAIL THEFT,SMALL RETAIL STORE,3555,True,False,232,2,3,37,06,1175924,1869100,2005,ERROR,41.796174063,-87.6304048,"(41.796174063, -87.6304048)" +3145808,HK138334,2004-01-20 14:45:00,001XX W 104TH ST,1750,OFFENSE INVOLVING CHILDREN,CHILD ABUSE,RESIDENCE,1034,False,True,512,5,34,49,20,1177238,1836035,2004,ERROR,41.705410205,-87.626582292,"(41.705410205, -87.626582292)" +2564500,HJ117629,2003-01-10 06:30:00,046XX S SACRAMENTO AVE,1513,PROSTITUTION,SOLICIT FOR BUSINESS,STREET,4870,True,False,912,NA,14,58,16,1157201,1873342,2003,ERROR,41.808214288,-87.698948812,"(41.808214288, -87.698948812)" +2168460,HH416094,2002-05-31 17:00:00,063XX S ROCKWELL ST,0460,BATTERY,SIMPLE,STREET,1695,False,False,825,NA,15,66,08B,1160083,1862752,2002,ERROR,41.779095055,-87.688669541,"(41.779095055, -87.688669541)" +3960086,HL326851,2005-04-30 08:50:00,017XX W HOWARD ST,0860,THEFT,RETAIL THEFT,GROCERY FOOD STORE,1739,False,False,2422,24,49,1,06,1163125,1950306,2005,ERROR,42.019286753,-87.675054325,"(42.019286753, -87.675054325)" +7233563,HR577284,2009-10-03 04:35:00,047XX S KILPATRICK AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,2783,False,False,815,8,23,56,14,1145784,1872768,2009,ERROR,41.80686277,-87.740838663,"(41.80686277, -87.740838663)" +5041658,HM636747,2006-10-02 22:15:00,015XX N CALIFORNIA AVE,0560,ASSAULT,SIMPLE,STREET,4574,False,True,1423,14,26,24,08A,1157485,1910358,2006,ERROR,41.90978438,-87.696900782,"(41.90978438, -87.696900782)" +1848119,G681456,2001-11-12 15:00:00,045XX S DAMEN AV,0820,THEFT,$500 AND UNDER,DEPARTMENT STORE,255,True,False,914,NA,NA,NA,06,1163704,1874123,2001,2014-04-12 12:43:35,41.810223247,-87.675075347999993,"(41.810223247, -87.675075348)" +3152831,HK148739,2004-01-26 19:00:00,008XX W 52ND ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,4518,False,False,934,9,3,61,07,1171851,1870417,2004,ERROR,41.799878475,-87.64530206,"(41.799878475, -87.64530206)" +3800712,HL168190,2005-02-06 18:00:00,026XX W 74TH ST,0460,BATTERY,SIMPLE,STREET,4990,False,False,835,8,18,66,08B,1159986,1855543,2005,ERROR,41.759314508,-87.689223078,"(41.759314508, -87.689223078)" +4838698,HM450854,2006-07-02 20:22:36,066XX S DAMEN AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,RESIDENCE,3325,False,True,726,7,15,67,08B,1164211,1860657,2006,2006-08-07 04:09:46,41.773260178,-87.673594762,"(41.773260178, -87.673594762)" +4567800,HM152622,2006-01-30 01:05:00,062XX S PULASKI RD,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,2362,True,True,823,8,13,65,08B,1150823,1862618,2006,2006-04-02 03:37:59,41.778912821,-87.72262144,"(41.778912821, -87.72262144)" +4954048,HM567985,2006-08-28 19:00:00,039XX W NORTH AVE,0820,THEFT,$500 AND UNDER,RESTAURANT,63,False,False,2535,25,30,23,06,1149603,1910380,2006,2014-04-12 12:43:35,41.910001584,-87.725855574,"(41.910001584, -87.725855574)" +6442605,HP523830,2008-08-19 19:15:00,022XX N STOCKTON DR,0810,THEFT,OVER $500,STREET,4144,False,False,1814,18,43,7,06,1174044,1915453,2008,ERROR,41.923412022,-87.635918111,"(41.923412022, -87.635918111)" +8013905,HT245828,2011-04-12 01:00:00,117XX S ASHLAND AVE,0325,ROBBERY,VEHICULAR HIJACKING,STREET,3478,False,False,524,5,34,53,03,1167822,1827065,2011,ERROR,41.681001922,-87.66131902,"(41.681001922, -87.66131902)" +2134668,HH365544,2002-05-11 19:52:00,012XX W 69TH ST,2111,NARCOTICS,SALE/DEL HYPODERMIC NEEDLE,STREET,3604,True,False,724,NA,17,67,26,1169493,1859073,2002,ERROR,41.768800662,-87.654278043,"(41.768800662, -87.654278043)" +5610513,HN414573,2007-06-19 04:45:00,050XX W DICKENS AVE,1320,CRIMINAL DAMAGE,TO VEHICLE,STREET,1912,False,False,2522,25,31,19,14,1142601,1913456,2007,ERROR,41.918575659,-87.75150172,"(41.918575659, -87.75150172)" +8573841,HV248444,2012-04-19 00:45:00,048XX S MICHIGAN AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,OTHER,1936,False,False,224,2,3,38,14,1178018,1872850,2012,ERROR,41.806417165,-87.622612308,"(41.806417165, -87.622612308)" +1561696,G322059,2001-06-04 02:17:00,051XX S WESTERN AV,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,OTHER,1450,True,False,915,NA,NA,NA,07,1161425,1870775,2001,ERROR,41.801083515,-87.683527305,"(41.801083515, -87.683527305)" +3927547,HL299959,2005-04-17 00:05:00,106XX S PERRY AVE,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,4821,False,False,512,5,34,49,14,1177525,1834390,2005,ERROR,41.700889625,-87.625580837,"(41.700889625, -87.625580837)" +4652877,HM250481,2006-03-24 14:50:00,006XX N AVERS AVE,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,STREET,3812,False,False,1122,11,27,23,04B,1150574,1903795,2006,ERROR,41.891912762,-87.722460741,"(41.891912762, -87.722460741)" +5843579,HN653528,2007-10-15 01:00:00,021XX E 83RD ST,1310,CRIMINAL DAMAGE,TO PROPERTY,RESIDENCE,1948,False,False,414,4,8,46,14,1191722,1850396,2007,ERROR,41.744479768,-87.573079607,"(41.744479768, -87.573079607)" +3203166,HK216335,2004-03-02 13:30:00,003XX N OAKLEY BLVD,0810,THEFT,OVER $500,STREET,4568,False,False,1332,12,27,28,06,1160987,1901959,2004,2014-04-12 12:43:35,41.886664822,-87.684269315,"(41.886664822, -87.684269315)" +1606977,G378687,2001-06-29 10:15:00,0000X W RANDOLPH ST,0560,ASSAULT,SIMPLE,OTHER,3282,False,True,122,NA,NA,NA,08A,1176018,1901322,2001,ERROR,41.884591617,-87.629091254,"(41.884591617, -87.629091254)" +4840076,HM452107,2006-07-03 13:00:00,040XX W 26TH ST,1210,DECEPTIVE PRACTICE,THEFT OF LABOR/SERVICES,CTA BUS,3532,True,False,1013,10,22,30,11,1150073,1886464,2006,2006-06-07 04:48:17,41.844364255,-87.724751946,"(41.844364255, -87.724751946)" +2414638,HH721521,2002-10-18 01:45:00,057XX S NEWCASTLE AVE,0560,ASSAULT,SIMPLE,RESIDENCE,4205,True,True,811,NA,23,56,08A,1131678,1865389,2002,ERROR,41.786868643,-87.792746288,"(41.786868643, -87.792746288)" +9984576,HY173853,2015-03-06 12:20:00,036XX S PAULINA ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,2794,True,True,912,9,11,59,08B,1165657,1880409,2015,ERROR,41.827431448,-87.667733383,"(41.827431448, -87.667733383)" +4690605,HM294209,2006-04-15 23:30:00,035XX W DIVERSEY AVE,0810,THEFT,OVER $500,VEHICLE NON-COMMERCIAL,4065,False,False,1412,14,35,21,06,1152451,1918432,2006,2014-04-12 12:43:35,41.93204114,-87.715179885,"(41.93204114, -87.715179885)" +9817517,HX467213,2014-10-14 12:15:00,023XX S MICHIGAN AVE,0560,ASSAULT,SIMPLE,APARTMENT,592,False,False,131,1,2,33,08A,1177532,1889067,2014,ERROR,41.850928923,-87.623903608,"(41.850928923, -87.623903608)" +4924804,HM540163,2006-08-14 05:20:00,040XX W POTOMAC AVE,0820,THEFT,$500 AND UNDER,VEHICLE NON-COMMERCIAL,295,False,False,2534,25,27,23,06,1149398,1908322,2006,2014-04-12 12:43:35,41.90435821,-87.726662138,"(41.90435821, -87.726662138)" +5819771,HN629390,2007-10-04 22:30:00,087XX S SAGINAW AVE,0430,BATTERY,AGGRAVATED: OTHER DANG WEAPON,STREET,2737,False,False,423,4,7,46,04B,1195348,1847736,2007,ERROR,41.737091832,-87.559881365,"(41.737091832, -87.559881365)" +4431290,HL717454,2005-11-05 14:30:00,001XX W LAKE ST,0870,THEFT,POCKET-PICKING,CTA PLATFORM,1953,False,False,113,1,42,32,06,1175460,1901776,2005,ERROR,41.885849966,-87.631126643,"(41.885849966, -87.631126643)" +3020488,HJ684755,2003-10-10 16:45:00,075XX S MORGAN ST,1811,NARCOTICS,POSS: CANNABIS 30GMS OR LESS,STREET,865,True,False,612,6,17,71,18,1170904,1855024,2003,ERROR,41.75765901,-87.649224054,"(41.75765901, -87.649224054)" +7534781,HS338561,2010-06-01 17:30:00,015XX N CICERO AVE,0486,BATTERY,DOMESTIC BATTERY SIMPLE,DEPARTMENT STORE,1334,False,True,2533,25,37,25,08B,1144129,1910138,2010,2010-06-06 10:23:35,41.909442134,-87.745971135,"(41.909442134, -87.745971135)" +8552792,HV228661,2012-04-03 00:00:00,053XX W FOSTER AVE,1720,OFFENSE INVOLVING CHILDREN,CONTRIBUTE DELINQUENCY OF A CHILD,APARTMENT,3251,False,False,1623,16,45,11,20,1139799,1934106,2012,ERROR,41.975292963,-87.761290293,"(41.975292963, -87.761290293)" +2489010,HH826679,2002-11-30 21:00:00,023XX S LAKE SHORE DR E,0890,THEFT,FROM BUILDING,OTHER,2775,False,False,133,NA,2,33,06,NA,NA,2002,ERROR,NA,NA, +2690599,HJ311147,2003-04-19 21:46:00,059XX S ADA ST,0486,BATTERY,DOMESTIC BATTERY SIMPLE,APARTMENT,4856,False,True,713,NA,16,67,08B,1168389,1865153,2003,ERROR,41.785508785,-87.658149785,"(41.785508785, -87.658149785)" +3823642,HL190768,2005-02-19 15:30:00,028XX N MOZART ST,0910,MOTOR VEHICLE THEFT,AUTOMOBILE,STREET,2877,False,False,1411,14,35,21,07,1156853,1918544,2005,ERROR,41.932260245,-87.699000048,"(41.932260245, -87.699000048)" diff --git a/work-with-data/dataprep/data/crime-spring.csv b/work-with-data/dataprep/data/crime-spring.csv new file mode 100644 index 00000000..3750a186 --- /dev/null +++ b/work-with-data/dataprep/data/crime-spring.csv @@ -0,0 +1,11 @@ +ID,Case Number,Date,Block,IUCR,Primary Type,Description,Location Description,Arrest,Domestic,Beat,District,Ward,Community Area,FBI Code,X Coordinate,Y Coordinate,Year,Updated On,Latitude,Longitude,Location +10498554,HZ239907,4/4/2016 23:56,007XX E 111TH ST,1153,DECEPTIVE PRACTICE,FINANCIAL IDENTITY THEFT OVER $ 300,OTHER,FALSE,FALSE,531,5,9,50,11,1183356,1831503,2016,5/11/2016 15:48,41.69283384,-87.60431945,"(41.692833841, -87.60431945)" +10516598,HZ258664,4/15/2016 17:00,082XX S MARSHFIELD AVE,890,THEFT,FROM BUILDING,RESIDENCE,FALSE,FALSE,614,6,21,71,6,1166776,1850053,2016,5/12/2016 15:48,41.74410697,-87.66449429,"(41.744106973, -87.664494285)" +10519196,HZ261252,4/15/2016 10:00,104XX S SACRAMENTO AVE,1154,DECEPTIVE PRACTICE,FINANCIAL IDENTITY THEFT $300 AND UNDER,RESIDENCE,FALSE,FALSE,2211,22,19,74,11,,,2016,5/12/2016 15:50,,, +10519591,HZ261534,4/15/2016 9:00,113XX S PRAIRIE AVE,1120,DECEPTIVE PRACTICE,FORGERY,RESIDENCE,FALSE,FALSE,531,5,9,49,10,,,2016,5/13/2016 15:51,,, +10534446,HZ277630,4/15/2016 10:00,055XX N KEDZIE AVE,890,THEFT,FROM BUILDING,"SCHOOL, PUBLIC, BUILDING",FALSE,FALSE,1712,17,40,13,6,,,2016,5/25/2016 15:59,,, +10535059,HZ278872,4/15/2016 4:30,004XX S KILBOURN AVE,810,THEFT,OVER $500,RESIDENCE,FALSE,FALSE,1131,11,24,26,6,,,2016,5/25/2016 15:59,,, +10499802,HZ240778,4/15/2016 10:00,010XX N MILWAUKEE AVE,1152,DECEPTIVE PRACTICE,ILLEGAL USE CASH CARD,RESIDENCE,FALSE,FALSE,1213,12,27,24,11,,,2016,5/27/2016 15:45,,, +10522293,HZ264802,4/15/2016 16:00,019XX W DIVISION ST,1110,DECEPTIVE PRACTICE,BOGUS CHECK,RESTAURANT,FALSE,FALSE,1424,14,1,24,11,1163094,1908003,2016,5/16/2016 15:48,41.90320604,-87.67636193,"(41.903206037, -87.676361925)" +10523111,HZ265911,4/15/2016 8:00,061XX N SHERIDAN RD,1153,DECEPTIVE PRACTICE,FINANCIAL IDENTITY THEFT OVER $ 300,RESIDENCE,FALSE,FALSE,2433,24,48,77,11,,,2016,5/16/2016 15:50,,, +10525877,HZ268138,4/15/2016 15:00,023XX W EASTWOOD AVE,1153,DECEPTIVE PRACTICE,FINANCIAL IDENTITY THEFT OVER $ 300,,FALSE,FALSE,1911,19,47,4,11,,,2016,5/18/2016 15:50,,, diff --git a/work-with-data/dataprep/data/crime-winter.csv b/work-with-data/dataprep/data/crime-winter.csv new file mode 100644 index 00000000..4c70d468 --- /dev/null +++ b/work-with-data/dataprep/data/crime-winter.csv @@ -0,0 +1,11 @@ +ID,Case Number,Date,Block,IUCR,Primary Type,Description,Location Description,Arrest,Domestic,Beat,District,Ward,Community Area,FBI Code,X Coordinate,Y Coordinate,Year,Updated On,Latitude,Longitude,Location +10378283,HZ114126,1/10/2016 11:00,033XX W IRVING PARK RD,610,BURGLARY,FORCIBLE ENTRY,RESIDENCE-GARAGE,TRUE,FALSE,1724,17,33,16,5,1153593,1926401,2016,5/22/2016 15:51,41.95388599,-87.71077048,"(41.95388599, -87.710770479)" +10382154,HZ118288,1/10/2016 21:00,055XX S FRANCISCO AVE,1754,OFFENSE INVOLVING CHILDREN,AGG SEX ASSLT OF CHILD FAM MBR,RESIDENCE,FALSE,TRUE,824,8,14,63,2,1157983,1867874,2016,6/1/2016 15:51,41.79319349,-87.69622926,"(41.793193489, -87.696229255)" +10374287,HZ110730,1/10/2016 11:50,043XX W ARMITAGE AVE,5002,OTHER OFFENSE,OTHER VEHICLE OFFENSE,STREET,FALSE,TRUE,2522,25,30,20,26,1146917,1912931,2016,6/7/2016 15:55,41.91705356,-87.73565764,"(41.917053561, -87.735657637)" +10374662,HZ110403,1/10/2016 1:30,073XX S CLAREMONT AVE,497,BATTERY,AGGRAVATED DOMESTIC BATTERY: OTHER DANG WEAPON,STREET,FALSE,TRUE,835,8,18,66,04B,1162007,1855951,2016,2/4/2016 15:44,41.76039236,-87.68180481,"(41.760392356, -87.681804812)" +10374720,HZ110836,1/10/2016 7:30,079XX S RHODES AVE,890,THEFT,FROM BUILDING,OTHER,FALSE,FALSE,624,6,6,44,6,1181279,1852568,2016,2/4/2016 15:44,41.75068679,-87.61127681,"(41.75068679, -87.611276811)" +10375178,HZ110832,1/10/2016 14:20,057XX S KEDZIE AVE,460,BATTERY,SIMPLE,RESTAURANT,FALSE,FALSE,824,8,14,63,08B,1156029,1866379,2016,2/4/2016 15:44,41.78913051,-87.7034346,"(41.78913051, -87.703434602)" +10398695,HZ135279,1/10/2016 23:00,031XX S PARNELL AVE,620,BURGLARY,UNLAWFUL ENTRY,RESIDENCE-GARAGE,FALSE,FALSE,915,9,11,60,5,1173138,1884117,2016,2/4/2016 15:44,41.8374442,-87.64017699,"(41.837444199, -87.640176991)" +10402270,HZ138745,1/10/2016 11:00,051XX S ELIZABETH ST,620,BURGLARY,UNLAWFUL ENTRY,APARTMENT,FALSE,FALSE,934,9,16,61,5,,,2016,2/4/2016 6:53,,, +10380619,HZ116583,1/10/2016 9:41,091XX S PAXTON AVE,4387,OTHER OFFENSE,VIOLATE ORDER OF PROTECTION,RESIDENCE,TRUE,TRUE,413,4,7,48,26,1192434,1844707,2016,2/2/2016 15:56,41.72885134,-87.57065553,"(41.728851343, -87.570655525)" +10400131,HZ136171,1/10/2016 18:00,0000X W TERMINAL ST,810,THEFT,OVER $500,AIRPORT BUILDING NON-TERMINAL - SECURE AREA,FALSE,FALSE,1651,16,41,76,6,,,2016,2/2/2016 15:58,,, diff --git a/work-with-data/dataprep/data/crime.dprep b/work-with-data/dataprep/data/crime.dprep new file mode 100644 index 00000000..58a84196 --- /dev/null +++ b/work-with-data/dataprep/data/crime.dprep @@ -0,0 +1,204 @@ +{ + "id": "75637565-60ad-4baa-87d3-396a7930cfe7", + "blocks": [ + { + "id": "ba5a8061-129e-4618-953a-ce3e89c8f2cb", + "type": "Microsoft.DPrep.GetFilesBlock", + "arguments": { + "path": { + "target": 0, + "resourceDetails": [ + { + "path": "./crime-spring.csv" + } + ] + } + }, + "isEnabled": true, + "name": null, + "annotation": null + }, + { + "id": "1b345643-6b60-4ca1-99f9-2a64ae932a23", + "type": "Microsoft.DPrep.ParseDelimitedBlock", + "arguments": { + "columnHeadersMode": 1, + "fileEncoding": 0, + "handleQuotedLineBreaks": false, + "preview": false, + "separator": ",", + "skipRowsMode": 0 + }, + "isEnabled": true, + "name": null, + "annotation": null + }, + { + "id": "12cf73a2-1487-4915-bfa7-c86be7de08c0", + "type": "Microsoft.DPrep.SetColumnTypesBlock", + "arguments": { + "columnConversion": [ + { + "column": { + "type": 2, + "details": { + "selectedColumn": "ID" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "IUCR" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Domestic" + } + }, + "typeProperty": 1 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Beat" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "District" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Ward" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Community Area" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Year" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Longitude" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Arrest" + } + }, + "typeProperty": 1 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "X Coordinate" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Updated On" + } + }, + "typeArguments": { + "dateTimeFormats": [ + "%m/%d/%Y %I:%M:%S %p" + ] + }, + "typeProperty": 4 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Date" + } + }, + "typeArguments": { + "dateTimeFormats": [ + "%m/%d/%Y %I:%M:%S %p" + ] + }, + "typeProperty": 4 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Y Coordinate" + } + }, + "typeProperty": 3 + }, + { + "column": { + "type": 2, + "details": { + "selectedColumn": "Latitude" + } + }, + "typeProperty": 3 + } + ] + }, + "isEnabled": true, + "name": null, + "annotation": null + }, + { + "id": "dfd62543-9285-412b-a930-0aeaaffde699", + "type": "Microsoft.DPrep.HandlePathColumnBlock", + "arguments": { + "pathColumnOperation": 0 + }, + "isEnabled": true, + "name": null, + "annotation": null + } + ], + "inspectors": [] +} \ No newline at end of file diff --git a/work-with-data/dataprep/data/crime.parquet b/work-with-data/dataprep/data/crime.parquet new file mode 100644 index 0000000000000000000000000000000000000000..dc06b73120f80a4d13b3791adc1f7d12c8ebe9f2 GIT binary patch literal 3607 zcma)C|F3u8=U$UC_VhDu7gTPl~CF)EHUDcvN-%wV>;*fCvbw__w(sbtGW z8>WQHrG(RVbtxw*Su471QmNIpDw|L1pk|+WXU5n));a$1KF{y_yZ@f&`3##8<_e?W zatO|b=EJ7oQxDcdp=|czQOKWROiXk%nS(VUg3G2d-G|DtKG?h@H&^dvLfyf*^nrl*F>@&dI+%Xv@5E&Nx++#+LA+4dN{Gk7BfLoXT8xj=O1_$_?*Gd-8#% z19C-m=wUghsWkI|AG>bX%X6#EMsGBof9kTeu?Fieaai0N{>0hY;ZbzQYnoX^eJi(i zPKfjG1xKx2Ja5@7Pp^hFp5uoH3pJi^V&Hc4q_y+e?@DTPAqHY*&@)I`e#})xXE$_~ zt=u*J#>z*dg=yEr*ZcS=mA{^FEm^p+RcJGC5smrr*+KpKyf!!@;vM=)cG}kXO9Y(X z+=R-6>B5$VJE0q+v%d^ul-_^N!U-N^Y*im>m|*Hf~dJ4L;N+NzY2-^(5EgMXnWRFMZ=m9iH3`;>WAbO_+xB+ z1ukD9b57gjf@Avk?`ii-q{(orsSZXO!=DA$#A%vdiH6@zZdiExAA}SaBDQ^A#zm<@ zzdt-?pGaY|>_q!$)b-B8hF(qFWhHBaMUQDMq!re<#V#}N8I?Bg6{1d`@s4dPmdJXr zoo}Dp)!N#8m0Dz!5q)kS`tWzL476L`mAU?V{O^IuP|e?&IR&%6Rp*$FfjcH|&+3pX zghm%~C5b=?O{I~%E?APk6$d5P%4f}ZQ@Q)Ha_^CjmKV=QpKOcV_BhhQ&%Zh44(4_e z-OpnwbDpte-?T$)kL?L-;O`qL73UA$*PmhR;@G_-QP#LJDytxO%|Jt2@YfqNwr^OK ziYx7%X?;`*H`fox*JLj<>Uk-QByT?|F`=br*!?V8cZ5*gr7X13l+oKl+1I3wy$B5t z|4kXQ7=%VTF>t9O)sd0;dSlTL(ROD7t!0@-T94AMP54Xmou3D-#}eoA{US%i=JZb| zMrqt|!mPC?7ew@S+Z^-T9=SWM=I~;A zxmdT9`g({NK^OQsR6r*Ki)u^`t&w-wvo|zo&%T{a*OoAKdLY)+$s44-H;Ke)P9*X} z4Ab3#@t;ukRnJ3P(>g5J57EJw0t6U+ZIJm%_{W+cfz6buZPJULp%ni2cb(F6c6eeE z{D>ZZ;ezv_y4@XAcG5q`OPQW|sUBjfvj%0%{hJ6U)X{9Xe3})*qZ^0+%H5KAYm>Zvu}wgH z!9H^GM-TjhWFqs7*h_}hSns`2*Q%*0u%7>aSU+MZv|q+e{|%NFn?GBNm<$c^WVIPiQFOqh)} zLkF=?FcEwh25=Jjw9qZ07`}8i(A5KbSQbadSBr5>p@h3xB~WQ$gTe#C)B++~DiCp` zNwn2T34E0tF~;I2a;1UUCWX3)DlvFL?qfG7RZul3@M+Z4gng~Kb4zevgP!^T8L5M^gr&+5K$7_=W zSyN2Sspex8dps2;n0xBsQRb>@!&r(br30eWLH6JsvA+sYVWLJlu>}Zr98GD100Ouy z3>DVXa7bIUzz_sv07${<45^!i5N|M)4`?6+#0Py{AV>qXp;SXa27odHoryoS6#+ZZ zlw34m0C?3<2O3W3Cc>3tD8p!g0IrUt<0frCqR@<{6rceM6gtUN7^TTlGZF~mMu`P7 z09-WI0pnvbj$AYbNRi2eG*zR6h}9J@G66u>bR950PzZDrq8x{S96-aSItUpvt(7te z$W(49I^ZwEr$tL3KmoLQhOP|bBZ)xe5TyVDasc(2>FoV!OcAgZqFjamqjGF90h~Cz zTCg6twE$qRr4B?!8UflN%2pr(@-C`wq$bQcLKUGYsh|>6fZey!nZawPU+XI!4dejo zvC%=gGohtY&;U?Twi61WJt+v3j-zB^fE=8f-dPk=l!FypeQP;|@@2HGd}*TU;&5|v Tb@Fg>Tj-5?1m0sd_(}K&3>y?D literal 0 HcmV?d00001 diff --git a/work-with-data/dataprep/data/crime.txt b/work-with-data/dataprep/data/crime.txt new file mode 100644 index 00000000..d6d8b8d7 --- /dev/null +++ b/work-with-data/dataprep/data/crime.txt @@ -0,0 +1,10 @@ +10140490 HY329907 7/5/2015 23:50 050XX N NEWLAND AVE 820 THEFT +10139776 HY329265 7/5/2015 23:30 011XX W MORSE AVE 460 BATTERY +10140270 HY329253 7/5/2015 23:20 121XX S FRONT AVE 486 BATTERY +10139885 HY329308 7/5/2015 23:19 051XX W DIVISION ST 610 BURGLARY +10140379 HY329556 7/5/2015 23:00 012XX W LAKE ST 930 MOTOR VEHICLE THEFT +10140868 HY330421 7/5/2015 22:54 118XX S PEORIA ST 1320 CRIMINAL DAMAGE +10139762 HY329232 7/5/2015 22:42 026XX W 37TH PL 1020 ARSON +10139722 HY329228 7/5/2015 22:30 016XX S CENTRAL PARK AVE 1811 NARCOTICS +10139774 HY329209 7/5/2015 22:15 048XX N ASHLAND AVE 1310 CRIMINAL DAMAGE +10139697 HY329177 7/5/2015 22:10 058XX S ARTESIAN AVE 1320 CRIMINAL DAMAGE diff --git a/work-with-data/dataprep/data/crime.xlsx b/work-with-data/dataprep/data/crime.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..21f200b4e38cb035371f6a27f7325b6af6302bca GIT binary patch literal 16109 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�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 literal 0 HcmV?d00001 diff --git a/work-with-data/dataprep/data/secrets.dprep b/work-with-data/dataprep/data/secrets.dprep new file mode 100644 index 00000000..bf156e3c --- /dev/null +++ b/work-with-data/dataprep/data/secrets.dprep @@ -0,0 +1,63 @@ +{ + "id": "b308e5b8-9b2a-47f8-9d32-0f542b4a34a4", + "name": "read_csv_duplicate_headers", + "blocks": [ + { + "id": "8d9ec228-6a4b-4abf-afb7-65f58dda1581", + "type": "Microsoft.DPrep.GetFilesBlock", + "arguments": { + "path": { + "target": 1, + "resourceDetails": [ + { + "path": "https://dpreptestfiles.blob.core.windows.net/testfiles/read_csv_duplicate_headers.csv", + "sas": { + "id": "https://dpreptestfiles.blob.core.windows.net/testfiles/read_csv_duplicate_headers.csv", + "secretType": "AzureMLSecret" + }, + "storageAccountName": null, + "storageAccountKey": null + } + ] + } + }, + "isEnabled": true, + "name": null, + "annotation": null + }, + { + "id": "4ad0460f-ec65-47c0-a0a4-44345404a462", + "type": "Microsoft.DPrep.ParseDelimitedBlock", + "arguments": { + "columnHeadersMode": 3, + "fileEncoding": 0, + "handleQuotedLineBreaks": false, + "preview": false, + "separator": ",", + "skipRows": 0, + "skipRowsMode": 0 + }, + "isEnabled": true, + "name": null, + "annotation": null + }, + { + "id": "1a3e11ba-5854-48da-aa47-53af61beb782", + "type": "Microsoft.DPrep.DropColumnsBlock", + "arguments": { + "columns": { + "type": 0, + "details": { + "selectedColumns": [ + "Path" + ] + } + } + }, + "isEnabled": true, + "name": null, + "annotation": null + } + ], + "inspectors": [] +} \ No newline at end of file diff --git a/work-with-data/dataprep/data/stream-path.csv b/work-with-data/dataprep/data/stream-path.csv new file mode 100644 index 00000000..175f3801 --- /dev/null +++ b/work-with-data/dataprep/data/stream-path.csv @@ -0,0 +1,11 @@ +Stream Path +https://dataset.blob.core.windows.net/blobstore/container/2019/01/01/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/02/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/03/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/04/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/05/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/06/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/07/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/08/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/09/train.csv +https://dataset.blob.core.windows.net/blobstore/container/2019/01/10/train.csv diff --git a/work-with-data/dataprep/how-to-guides/add-column-using-expression.ipynb b/work-with-data/dataprep/how-to-guides/add-column-using-expression.ipynb new file mode 100644 index 00000000..3fa0e65e --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/add-column-using-expression.ipynb @@ -0,0 +1,360 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/add-column-using-expression.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Add Column using Expression\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With Azure ML Data Prep you can add a new column to data with `Dataflow.add_column` by using a Data Prep expression to calculate the value from existing columns. This is similar to using Python to create a [new script column](./custom-python-transforms.ipynb#New-Script-Column) except the Data Prep expressions are more limited and will execute faster. The expressions used are the same as for [filtering rows](./filtering.ipynb#Filtering-rows) and hence have the same functions and operators available.\n", + "

\n", + "Here we add additional columns. First we get input data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# loading data\n", + "dflow = dprep.auto_read_file('../data/crime-spring.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `substring(start, length)`\n", + "Add a new column \"Case Category\" using the `substring(start, length)` expression to extract the prefix from the \"Case Number\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "case_category = dflow.add_column(new_column_name='Case Category',\n", + " prior_column='Case Number',\n", + " expression=dflow['Case Number'].substring(0, 2))\n", + "case_category.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `substring(start)`\n", + "Add a new column \"Case Id\" using the `substring(start)` expression to extract just the number from \"Case Number\" column and then convert it to numeric." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "case_id = dflow.add_column(new_column_name='Case Id',\n", + " prior_column='Case Number',\n", + " expression=dflow['Case Number'].substring(2))\n", + "case_id = case_id.to_number('Case Id')\n", + "case_id.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `length()`\n", + "Using the length() expression, add a new numeric column \"Length\", which contains the length of the string in \"Primary Type\"." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_length = dflow.add_column(new_column_name='Length',\n", + " prior_column='Primary Type',\n", + " expression=dflow['Primary Type'].length())\n", + "dflow_length.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `to_upper()`\n", + "Using the to_upper() expression, add a new numeric column \"Upper Case\", which contains the length of the string in \"Primary Type\"." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_to_upper = dflow.add_column(new_column_name='Upper Case',\n", + " prior_column='Primary Type',\n", + " expression=dflow['Primary Type'].to_upper())\n", + "dflow_to_upper.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `to_lower()`\n", + "Using the to_lower() expression, add a new numeric column \"Lower Case\", which contains the length of the string in \"Primary Type\"." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_to_lower = dflow.add_column(new_column_name='Lower Case',\n", + " prior_column='Primary Type',\n", + " expression=dflow['Primary Type'].to_lower())\n", + "dflow_to_lower.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `RegEx.extract_record()`\n", + "Using the `RegEx.extract_record()` expression, add a new record column \"Stream Date Record\", which contains the name capturing groups in the regex with value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_regex_extract_record = dprep.auto_read_file('../data/stream-path.csv')\n", + "regex = dprep.RegEx('\\/(?\\d{4})\\/(?\\d{2})\\/(?\\d{2})\\/')\n", + "dflow_regex_extract_record = dflow_regex_extract_record.add_column(new_column_name='Stream Date Record',\n", + " prior_column='Stream Path',\n", + " expression=regex.extract_record(dflow_regex_extract_record['Stream Path']))\n", + "dflow_regex_extract_record.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `create_datetime()`\n", + "Using the `create_datetime()` expression, add a new column \"Stream Date\", which contains datetime values constructed from year, month, day values extracted from a record column \"Stream Date Record\"." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "year = dprep.col('year', dflow_regex_extract_record['Stream Date Record'])\n", + "month = dprep.col('month', dflow_regex_extract_record['Stream Date Record'])\n", + "day = dprep.col('day', dflow_regex_extract_record['Stream Date Record'])\n", + "dflow_create_datetime = dflow_regex_extract_record.add_column(new_column_name='Stream Date',\n", + " prior_column='Stream Date Record',\n", + " expression=dprep.create_datetime(year, month, day))\n", + "dflow_create_datetime.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `col(column1) + col(column2)`\n", + "Add a new column \"Total\" to show the result of adding the values in the \"FBI Code\" column to the \"Community Area\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_total = dflow.add_column(new_column_name='Total',\n", + " prior_column='FBI Code',\n", + " expression=dflow['Community Area']+dflow['FBI Code'])\n", + "dflow_total.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `col(column1) - col(column2)`\n", + "Add a new column \"Subtract\" to show the result of subtracting the values in the \"FBI Code\" column from the \"Community Area\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_diff = dflow.add_column(new_column_name='Difference',\n", + " prior_column='FBI Code',\n", + " expression=dflow['Community Area']-dflow['FBI Code'])\n", + "dflow_diff.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `col(column1) * col(column2)`\n", + "Add a new column \"Product\" to show the result of multiplying the values in the \"FBI Code\" column to the \"Community Area\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_prod = dflow.add_column(new_column_name='Product',\n", + " prior_column='FBI Code',\n", + " expression=dflow['Community Area']*dflow['FBI Code'])\n", + "dflow_prod.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `col(column1) / col(column2)`\n", + "Add a new column \"True Quotient\" to show the result of true (decimal) division of the values in \"Community Area\" column by the \"FBI Code\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_true_div = dflow.add_column(new_column_name='True Quotient',\n", + " prior_column='FBI Code',\n", + " expression=dflow['Community Area']/dflow['FBI Code'])\n", + "dflow_true_div.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `col(column1) // col(column2)`\n", + "Add a new column \"Floor Quotient\" to show the result of floor (integer) division of the values in \"Community Area\" column by the \"FBI Code\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_floor_div = dflow.add_column(new_column_name='Floor Quotient',\n", + " prior_column='FBI Code',\n", + " expression=dflow['Community Area']//dflow['FBI Code'])\n", + "dflow_floor_div.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `col(column1) % col(column2)`\n", + "Add a new column \"Mod\" to show the result of applying the modulo operation on the \"FBI Code\" column and the \"Community Area\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_mod = dflow.add_column(new_column_name='Mod',\n", + " prior_column='FBI Code',\n", + " expression=dflow['Community Area']%dflow['FBI Code'])\n", + "dflow_mod.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### `col(column1) ** col(column2)`\n", + "Add a new column \"Power\" to show the result of applying the exponentiation operation when the base is the \"Community Area\" column and the exponent is \"FBI Code\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_pow = dflow.add_column(new_column_name='Power',\n", + " prior_column='FBI Code',\n", + " expression=dflow['Community Area']**dflow['FBI Code'])\n", + "dflow_pow.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/append-columns-and-rows.ipynb b/work-with-data/dataprep/how-to-guides/append-columns-and-rows.ipynb new file mode 100644 index 00000000..51a55e4a --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/append-columns-and-rows.ipynb @@ -0,0 +1,251 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/append-columns-and-rows.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Append Columns and Rows\n", + "\n", + "Often the data we want does not come in a single dataset: they are coming from different locations, have features that are separated, or are simply not homogeneous. Unsurprisingly, we typically want to work with a single dataset at a time.\n", + "\n", + "Azure ML Data Prep allows the concatenation of two or more dataflows by means of column and row appends.\n", + "\n", + "We will demonstrate this by defining a single dataflow that will pull data from multiple datasets.\n", + "\n", + "## Table of Contents\n", + "[append_columns(dataflows)](#append_columns)
\n", + "[append_rows(dataflows)](#append_rows)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## `append_columns(dataflows)`\n", + "We can append data width-wise, which will change some or all existing rows and potentially adding rows (based on an assumption that data in two datasets are aligned on row number).\n", + "\n", + "However we cannot do this if the reference dataflows have clashing schema with the target dataflow. Observe:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_chicago = auto_read_file(path='../data/chicago-aldermen-2015.csv')\n", + "dflow_chicago.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import ExecutionError\n", + "try:\n", + " dflow_combined_by_column = dflow.append_columns([dflow_chicago])\n", + " dflow_combined_by_column.head(5)\n", + "except ExecutionError:\n", + " print('Cannot append_columns with schema clash!')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As expected, we cannot call `append_columns` with target dataflows that have clashing schema.\n", + "\n", + "We can make the call once we rename or drop the offending columns. In more complex scenarios, we could opt to skip or filter to make rows align before appending columns. Here we will choose to simply drop the clashing column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_combined_by_column = dflow.append_columns([dflow_chicago.drop_columns(['Ward'])])\n", + "dflow_combined_by_column.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice that the resultant schema has more columns in the first N records (N being the number of records in `dataflow` and the extra columns being the width of the schema of our reference dataflow, chicago, minus the `Ward` column). From the N+1th record onwards, we will only have a schema width matching that of the `Ward`-less chicago set.\n", + "\n", + "Why is this? As much as possible, the data from the reference dataflow(s) will be attached to existing rows in the target dataflow. If there are not enough rows in the target dataflow to attach to, we simply append them as new rows.\n", + "\n", + "Note that these are appends, not joins (for joins please reference [Join](join.ipynb)), so the append may not be logically correct, but will take effect as long as there are no schema clashes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Ward-less data after we skip the first N rows\n", + "dflow_len = dflow.row_count\n", + "dflow_combined_by_column.skip(dflow_len).head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## `append_rows(dataflows)`\n", + "We can append data length-wise, which will only have the effect of adding new rows. No existing data will be changed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_spring = auto_read_file(path='../data/crime-spring.csv')\n", + "dflow_spring.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_chicago = auto_read_file(path='../data/chicago-aldermen-2015.csv')\n", + "dflow_chicago.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_combined_by_row = dflow.append_rows([dflow_chicago, dflow_spring])\n", + "dflow_combined_by_row.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice that neither schema nor data has changed for the target dataflow.\n", + "\n", + "If we skip ahead, we will see our target dataflows' data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# chicago data\n", + "dflow_len = dflow.row_count\n", + "dflow_combined_by_row.skip(dflow_len).head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# crimes spring data\n", + "dflow_chicago_len = dflow_chicago.row_count\n", + "dflow_combined_by_row.skip(dflow_len + dflow_chicago_len).head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/assertions.ipynb b/work-with-data/dataprep/how-to-guides/assertions.ipynb new file mode 100644 index 00000000..b33a30ed --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/assertions.ipynb @@ -0,0 +1,133 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/assertions.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Assertions\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Frequently, the data we work with while cleaning and preparing data is just a subset of the total data we will need to work with in production. It is also common to be working on a snapshot of a live dataset that is continuously updated and augmented.\n", + "\n", + "In these cases, some of the assumptions we make as part of our cleaning might turn out to be false. Columns that originally only contained numbers within a certain range might actually contain a wider range of values in later executions. These errors often result in either broken pipelines or bad data.\n", + "\n", + "Azure ML Data Prep supports creating assertions on data, which are evaluated as the pipeline is executed. These assertions enable us to verify that our assumptions on the data continue to be accurate and, when not, to handle failures in a clean way." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To demonstrate, we will load a dataset and then add some assertions based on what we can see in the first few rows." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file\n", + "\n", + "dflow = auto_read_file('../data/crime-dirty.csv')\n", + "dflow.get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see there are latitude and longitude columns present in this dataset. By definition, these are constrained to specific ranges of values. We can assert that this is indeed the case so that if any records come through with invalid values, we detect them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import value\n", + "\n", + "dflow = dflow.assert_value('Latitude', (value <= 90) & (value >= -90), error_code='InvalidLatitude')\n", + "dflow = dflow.assert_value('Longitude', (value <= 180) & (value >= -180), error_code='InvalidLongitude')\n", + "dflow.keep_columns(['Latitude', 'Longitude']).get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Any assertion failures are represented as Errors in the resulting dataset. From the profile above, you can see that the Error Count for both of these columns is 1. We can use a filter to retrieve the error and see what value caused the assertion to fail." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import col\n", + "\n", + "dflow_error = dflow.filter(col('Latitude').is_error())\n", + "error = dflow_error.head(10)['Latitude'][0]\n", + "print(error.originalValue)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Our assertion failed because we were not removing missing values from our data. At this point, we have two options: we can go back and edit our code to avoid this error in the first place or we can resolve it now. In this case, we will just filter these out." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import LocalFileOutput\n", + "dflow_clean = dflow.filter(~dflow['Latitude'].is_error())\n", + "dflow_clean.get_profile()" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/auto-read-file.ipynb b/work-with-data/dataprep/how-to-guides/auto-read-file.ipynb new file mode 100644 index 00000000..2a12288c --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/auto-read-file.ipynb @@ -0,0 +1,189 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/auto-read-file.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Auto Read File\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep has the ability to load different kinds of text files. The `auto_read_file` entry point can take any text based file (including excel, json and parquet) and auto-detect how to parse the file. It will also attempt to auto-detect the types of each column and apply type transformations to the columns it detects.\n", + "\n", + "The result will be a Dataflow object that has all the steps added that are required to read the given file(s) and convert their columns to the predicted types. No parameters are required beyond the file path or `FileDataSource` object." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_auto = dprep.auto_read_file('../data/crime_multiple_separators.csv')\n", + "dflow_auto.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_auto1 = dprep.auto_read_file('../data/crime.xlsx')\n", + "dflow_auto1.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_auto2 = dprep.auto_read_file('../data/crime.parquet')\n", + "dflow_auto2.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Looking at the data, we can see that there are two empty columns either side of the 'Completed' column.\n", + "If we compare the dataframe to a few rows from the original file:\n", + "```\n", + "ID |CaseNumber| |Completed|\n", + "10140490 |HY329907| |Y|\n", + "10139776 |HY329265| |Y|\n", + "```\n", + "We can see that the `|`'s have disappeared in the dataframe. This is because `|` is a very common separator character in csv files, so `auto_read_file` guessed it was the column separator. For this data we actually want the `|`'s to remain and instead use space as the column separator.\n", + "\n", + "To achieve this we can use `detect_file_format`. It takes a file path or datasource object and gives back a `FileFormatBuilder` which has learnt some information about the supplied data.\n", + "This is what `auto_read_file` is using behind the scenes to 'learn' the contents of the given file and determine how to parse it. With the `FileFormatBuilder` we can take advantage of the intelligent learning aspect of `auto_read_file` but have the chance to modify some of the learnt information." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ffb = dprep.detect_file_format('../data/crime_multiple_separators.csv')\n", + "ffb_2 = dprep.detect_file_format('../data/crime.xlsx')\n", + "ffb_3 = dprep.detect_file_format('../data/crime_fixed_width_file.txt')\n", + "ffb_4 = dprep.detect_file_format('../data/json.json')\n", + "\n", + "print(ffb.file_format)\n", + "print(ffb_2.file_format)\n", + "print(ffb_3.file_format)\n", + "print(type(ffb_4.file_format))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After calling `detect_file_format` we get a `FileFormatBuilder` that has had `learn` called on it. This means the `file_format` attribute will be populated with a `Properties` object, it contains all the information that was learnt about the file. As we can see above different file types have corresponding file_formats detected. \n", + "Continuing with our delimited example we can change any of these values and then call `ffb.to_dataflow()` to create a `Dataflow` that has the steps required to parse the datasource." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ffb.file_format.separator = ' '\n", + "dflow = ffb.to_dataflow()\n", + "df = dflow.to_pandas_dataframe()\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The result is our desired dataframe with `|`'s included.\n", + "\n", + "If we refer back to the original data output by `auto_read_file`, the 'ID' column was also detected as numeric and converted to a number data type instead of remaining a string like in the data above.\n", + "We can perform type inference on our new dataflow using the `dataflow.builders` property. This property exposes different builders that can `learn` from a dataflow and `apply` the learning to produce a new dataflow, very similar to the pattern we used above for the `FileFormatBuilder`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ctb = dflow.builders.set_column_types()\n", + "ctb.learn()\n", + "ctb.conversion_candidates" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After learning `ctb.conversion_candidates` has been populated with information about the inferred types for each column, it is possible for there to be multiple candidate types per column, in this example there is only one type for each column.\n", + "\n", + "The candidates look correct, we only want to convert `ID` to be an integer column, so applying this `ColumnTypesBuilder` should result in a Dataflow with our columns converted to their respective types." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_converted = ctb.to_dataflow()\n", + "\n", + "df_converted = dflow_converted.to_pandas_dataframe()\n", + "df_converted" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/cache.ipynb b/work-with-data/dataprep/how-to-guides/cache.ipynb new file mode 100644 index 00000000..fd47cf0f --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/cache.ipynb @@ -0,0 +1,194 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/cache.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Cache\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A Dataflow can be cached as a file on your disk during a local run by calling `dflow_cached = dflow.cache(directory_path)`. Doing this will run all the steps in the Dataflow, `dflow`, and save the cached data to the specified `directory_path`. The returned Dataflow, `dflow_cached`, has a Caching Step added at the end. Any subsequent runs on on the Dataflow `dflow_cached` will reuse the cached data, and the steps before the Caching Step will not be run again.\n", + "\n", + "Caching avoids running transforms multiple times, which can make local runs more efficient. Here are common places to use Caching:\n", + "- after reading data from remote\n", + "- after expensive transforms, such as Sort\n", + "- after transforms that change the shape of data, such as Sampling, Filter and Summarize\n", + "\n", + "Caching Step will be ignored during scale-out run invoked by `to_spark_dataframe()`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will start by reading in a dataset and applying some transforms to the Dataflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.read_csv(path='../data/crime-spring.csv')\n", + "dflow = dflow.take_sample(probability=0.2, seed=7)\n", + "dflow = dflow.sort_asc(columns='Primary Type')\n", + "dflow = dflow.keep_columns(['ID', 'Case Number', 'Date', 'Primary Type'])\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we will choose a directory to store the cached data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pathlib import Path\n", + "cache_dir = str(Path(os.getcwd(), 'dataflow-cache'))\n", + "cache_dir" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now call `dflow.cache(directory_path)` to cache the Dataflow to your directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_cached = dflow.cache(directory_path=cache_dir)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we will check steps in the `dflow_cached` to see that all of the previous steps were cached." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "[s.step_type for s in dflow_cached._get_steps()]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We also check the data stored in the cache directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "os.listdir(cache_dir)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Running against `dflow_cached` will reuse the cached data and skip running all of the previous steps again." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_cached.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Adding additional steps to `dflow_cached` will also reuse the cache data and skip running the steps prior to the Cache Step." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_cached_take = dflow_cached.take(10)\n", + "dflow_cached_skip = dflow_cached.skip(10).take(10)\n", + "\n", + "df_cached_take = dflow_cached_take.to_pandas_dataframe()\n", + "df_cached_skip = dflow_cached_skip.to_pandas_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# shutil.rmtree will then clean up the cached data \n", + "import shutil\n", + "shutil.rmtree(path=cache_dir)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/column-manipulations.ipynb b/work-with-data/dataprep/how-to-guides/column-manipulations.ipynb new file mode 100644 index 00000000..bf1836f9 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/column-manipulations.ipynb @@ -0,0 +1,563 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/column-manipulations.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Column Manipulations\n", + "\n", + "Azure ML Data Prep has many methods for manipulating columns, including basic CUD operations and several other more complex manipulations.\n", + "\n", + "This notebook will focus primarily on data-agnostic operations. For all other column manipulation operations, we will link to their specific how-to guide.\n", + "\n", + "## Table of Contents\n", + "[ColumnSelector](#ColumnSelector)
\n", + "[add_column](#add_column)
\n", + "[append_columns](#append_columns)
\n", + "[drop_columns](#drop_columns)
\n", + "[duplicate_column](#duplicate_column)
\n", + "[fuzzy_group_column](#fuzzy_group_column)
\n", + "[keep_columns](#keep_columns)
\n", + "[map_column](#map_column)
\n", + "[new_script_column](#new_script_column)
\n", + "[rename_columns](#rename_columns)
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ColumnSelector\n", + "`ColumnSelector` is a Data Prep class that allows us to select columns by name. The idea is to be able to describe columns generally instead of explicitly, using a search term or regex expression, with various options.\n", + "\n", + "Note that a `ColumnSelector` does not represent the columns they match themselves, but the selector of the described columns. Therefore if we use the same `ColumnSelector` on two different dataflows, we may get different results depending on the columns of each dataflow.\n", + "\n", + "Column manipulations that can utilize `ColumnSelector` will be noted in their respective sections in this book." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file\n", + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All parameters to a `ColumnSelector` are shown here for completeness. We will use `keep_columns` in our example, which will keep only the columns in the dataflow that we tell it to keep.\n", + "\n", + "In the below example, we match all columns with the letter 'i'. Because we set `ignore_case` to false and `match_whole_word` to false, then any column that contains 'i' or 'I' will be selected." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import ColumnSelector\n", + "column_selector = ColumnSelector(term=\"i\",\n", + " use_regex=False,\n", + " ignore_case=True,\n", + " match_whole_word=False,\n", + " invert=False)\n", + "dflow_selected = dflow.keep_columns(column_selector)\n", + "dflow_selected.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we set `invert` to true, we get the opposite of what we matched earlier." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "column_selector = ColumnSelector(term=\"i\",\n", + " use_regex=False,\n", + " ignore_case=True,\n", + " match_whole_word=False,\n", + " invert=True)\n", + "dflow_selected = dflow.keep_columns(column_selector)\n", + "dflow_selected.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we change the search term to 'I' and set case sensitivity to true, we get only the handful of columns that contain an upper case 'I'." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "column_selector = ColumnSelector(term=\"I\",\n", + " use_regex=False,\n", + " ignore_case=False,\n", + " match_whole_word=False,\n", + " invert=False)\n", + "dflow_selected = dflow.keep_columns(column_selector)\n", + "dflow_selected.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And if we set `match_whole_word` to true, we get no results at all as there is no column called 'I'." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "column_selector = ColumnSelector(term=\"I\",\n", + " use_regex=False,\n", + " ignore_case=False,\n", + " match_whole_word=True,\n", + " invert=False)\n", + "dflow_selected = dflow.keep_columns(column_selector)\n", + "dflow_selected.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, the `use_regex` flag dictates whether or not to treat the search term as a regex. It can be combined still with the other options.\n", + "\n", + "Here we define all columns that begin with the capital letter 'I'." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "column_selector = ColumnSelector(term=\"I.*\",\n", + " use_regex=True,\n", + " ignore_case=True,\n", + " match_whole_word=True,\n", + " invert=False)\n", + "dflow_selected = dflow.keep_columns(column_selector)\n", + "dflow_selected.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## add_column" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Please see [add-column-using-expression](add-column-using-expression.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## append_columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Please see [append-columns-and-rows](append-columns-and-rows.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## drop_columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep supports dropping columns one or more columns in a single statement. Supports `ColumnSelector`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file\n", + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that there are 22 columns to begin with. We will now drop the 'ID' column and observe that the resulting dataflow contains 21 columns." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_dropped = dflow.drop_columns('ID')\n", + "dflow_dropped.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also drop more than one column at once by passing a list of column names." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_dropped = dflow_dropped.drop_columns(['IUCR', 'Description'])\n", + "dflow_dropped.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## duplicate_column" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep supports duplicating columns one or more columns in a single statement.\n", + "\n", + "Duplicated columns are placed to the immediate right of their source column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file\n", + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We decide which column(s) to duplicate and what the new column name(s) should be with a key value pairing (dictionary)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_dupe = dflow.duplicate_column({'ID': 'ID2', 'IUCR': 'IUCR_Clone'})\n", + "dflow_dupe.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## fuzzy_group_column" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Please see [fuzzy-group](fuzzy-group.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## keep_columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep supports keeping one or more columns in a single statement. The resulting dataflow will contain only the column(s) specified; dropping all the other columns. Supports `ColumnSelector`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file\n", + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_keep = dflow.keep_columns(['ID', 'Date', 'Description'])\n", + "dflow_keep.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similar to `drop_columns`, we can pass a single column name or a list of them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_keep = dflow_keep.keep_columns('ID')\n", + "dflow_keep.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## map_column" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep supports string mapping. For a column containing strings, we can provide specific mappings from an original value to a new value, and then produce a new column that contains the mapped values.\n", + "\n", + "The mapped columns are placed to the immediate right of their source column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file\n", + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import ReplacementsValue\n", + "replacements = [ReplacementsValue('THEFT', 'THEFT2'), ReplacementsValue('BATTERY', 'BATTERY!!!')]\n", + "dflow_mapped = dflow.map_column(column='Primary Type', \n", + " new_column_id='Primary Type V2',\n", + " replacements=replacements)\n", + "dflow_mapped.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## new_script_column" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Please see [custom-python-transforms](custom-python-transforms.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## rename_columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep supports renaming one or more columns in a single statement." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import auto_read_file\n", + "dflow = auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We decide which column(s) to rename and what the new column name(s) should be with a key value pairing (dictionary)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_renamed = dflow.rename_columns({'ID': 'ID2', 'IUCR': 'IUCR_Clone'})\n", + "dflow_renamed.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/column-type-transforms.ipynb b/work-with-data/dataprep/how-to-guides/column-type-transforms.ipynb new file mode 100644 index 00000000..bfc4e73f --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/column-type-transforms.ipynb @@ -0,0 +1,473 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/column-type-transforms.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Column Type Transforms\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When consuming a data set, it is highly useful to know as much as possible about the data. Column types can help you understand more about each column, and enable type-specific transformations later. This provides much more insight than treating all data as strings.\n", + "\n", + "In this notebook, you will learn about:\n", + "- [Built-in column types](#types)\n", + "- How to:\n", + " - [Convert to long (integer)](#long)\n", + " - [Convert to double (floating point or decimal number)](#double)\n", + " - [Convert to boolean](#boolean)\n", + " - [Convert to datetime](#datetime)\n", + "- [How to use `ColumnTypesBuilder` to get suggested column types and convert them](#builder)\n", + "- [How to convert column type for multiple columns if types are known](#multiple-columns)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_csv('../data/crime-winter.csv')\n", + "dflow = dflow.keep_columns(['Case Number', 'Date', 'IUCR', 'Arrest', 'Longitude', 'Latitude'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Built-in column types" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Currently, Data Prep supports the following column types: string, long (integer), double (floating point or decimal number), boolean, and datetime.\n", + "\n", + "In the previous step, a data set was read in as a Dataflow, with only a few interesting columns kept. We will use this Dataflow to explore column types throughout the notebook." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the first few rows of the Dataflow, you can see that the columns contain different types of data. However, by looking at `dtypes`, you can see that `read_csv()` treats all columns as string columns.\n", + "\n", + "Note that `auto_read_file()` is a data ingestion function that infers column types. Learn more about it [here](./auto-read-file.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.dtypes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Converting to long (integer)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Suppose the \"IUCR\" column should only contain integers. You can call `to_long` to convert the column type of \"IUCR\" to `FieldType.INTEGER`. If you look at the data profile ([learn more about data profiles](./data-profile.ipynb)), you will see numeric metrics populated for that column such as mean, variance, quantiles, etc. This is helpful for understanding the shape and distribution of numeric data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_conversion = dflow.to_long('IUCR')\n", + "profile = dflow_conversion.get_profile()\n", + "profile" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Converting to double (floating point or decimal number)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Suppose the \"Latitude\" and \"Longitude\" columns should only contain decimal numbers. You can call `to_double` to convert the column type of \"Latitude\" and \"Longitude\" to `FieldType.DECIMAL`. In the data profile, you will see numeric metrics populated for these columns as well. Note that after converting the column types, you can see that there are missing values in these columns. Metrics like this can be helpful for noticing issues with the data set." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_conversion = dflow_conversion.to_number(['Latitude', 'Longitude'])\n", + "profile = dflow_conversion.get_profile()\n", + "profile" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Converting to boolean" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Suppose the \"Arrest\" column should only contain boolean values. You can call `to_bool` to convert the column type of \"Arrest\" to `FieldType.BOOLEAN`.\n", + "\n", + "The `to_bool` function allows you to specify which values should map to `True` and which values should map to `False`. To do so, you can provide those values in an array as parameters `true_values` and `false_values`. Additionally, you can specify whether all other values should become `True`, `False` or Error by using the `mismatch_as` parameter." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_conversion.to_bool('Arrest', \n", + " true_values=[1],\n", + " false_values=[0],\n", + " mismatch_as=dprep.MismatchAsOption.ASERROR).head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the previous conversion, all the values in the \"Arrest\" column became `DataPrepError`, because 'FALSE' didn't match any of the `false_values` nor any of the `true_values`, and all the unmatched values were set to become errors. Let's try the conversion again with different `false_values`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_conversion = dflow_conversion.to_bool('Arrest',\n", + " true_values=['1', 'TRUE'],\n", + " false_values=['0', 'FALSE'],\n", + " mismatch_as=dprep.MismatchAsOption.ASERROR)\n", + "dflow_conversion.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This time, all the string values 'FALSE' have been successfully converted to the boolean value `False`. Take another look at the data profile." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "profile = dflow_conversion.get_profile()\n", + "profile" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Suppose the \"Date\" column should only contain datetime values. You can convert its column type to `FieldType.DateTime` using the `to_datetime` function. Typically, datetime formats can be confusing or inconsistent. Next, we will show you all the tools that can help correctly converting the column to `DateTime`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the first example, directly call `to_datetime` with only the column name. Data Prep will inspect the data in this column and learn what format should be used for the conversion.\n", + "\n", + "Note that if there is data in the column that cannot be converted to datetime, an Error value will be created in that cell." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_conversion_date = dflow_conversion.to_datetime('Date')\n", + "dflow_conversion_date.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this case, we can see that '1/10/2016 11:00' was converted using the format `%m/%d/%Y %H:%M`.\n", + "\n", + "The data in this column is actually somewhat ambiguous. Should the dates be 'October 1' or 'January 10'? The function `to_datetime` determines that both are possible, but defaults to month-first (US format).\n", + "\n", + "If the data was supposed to be day-first, you can customize the conversion." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_alternate_conversion = dflow_conversion.to_datetime('Date', date_time_formats=['%d/%m/%Y %H:%M'])\n", + "dflow_alternate_conversion.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using `ColumnTypesBuilder`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep can help you automatically detect what are the likely column types.\n", + "\n", + "You can call `dflow.builders.set_column_types()` to get a `ColumnTypesBuilder`. Then, calling `learn()` on it will trigger Data Prep to inspect the data in each column. As a result, you can see the suggested column types for each column (conversion candidates)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder = dflow.builders.set_column_types()\n", + "builder.learn()\n", + "builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this case, Data Prep suggested the correct column types for \"Arrest\", \"Case Number\", \"Latitude\", and \"Longitude\".\n", + "\n", + "However, for \"Date\", it has suggested two possible date formats: month-first, or day-first. The ambiguity must be resolved before you complete the conversion. To use the month-first format, you can call `builder.ambiguous_date_conversions_keep_month_day()`. Otherwise, call `builder.ambiguous_date_conversions_keep_day_month()`. Note that if there were multiple datetime columns with ambiguous date conversions, calling one of these functions will apply the resolution to all of them.\n", + "\n", + "If you want to skip all the ambiguous date column conversions instead, you can call: `builder.ambiguous_date_conversions_drop()`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.ambiguous_date_conversions_keep_month_day()\n", + "builder.conversion_candidates" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The conversion candidate for \"IUCR\" is currently `FieldType.INTEGER`. If you know that \"IUCR\" should be floating point (called `FieldType.DECIMAL`), you can tweak the builder to change the conversion candidate for that specific column. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.conversion_candidates['IUCR'] = dprep.FieldType.DECIMAL\n", + "builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this case we are happy with \"IUCR\" as `FieldType.INTEGER`. So we set it back. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.conversion_candidates['IUCR'] = dprep.FieldType.INTEGER\n", + "builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once you are happy with the conversion candidates, you can complete the conversion by calling `builder.to_dataflow()`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_converion_using_builder = builder.to_dataflow()\n", + "dflow_converion_using_builder.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Convert column types for multiple columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you already know the column types, you can simply call `dflow.set_column_types()`. This function allows you to specify multiple columns, and the desired column type for each one. Here's how you can convert all five columns at once.\n", + "\n", + "Note that `set_column_types` only supports a subset of column type conversions. For example, we cannot specify the true/false values for a boolean conversion, so the results of this operation is incorrect for the \"Arrest\" column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_conversion_using_set = dflow.set_column_types({\n", + " 'IUCR': dprep.FieldType.INTEGER,\n", + " 'Latitude': dprep.FieldType.DECIMAL,\n", + " 'Longitude': dprep.FieldType.DECIMAL,\n", + " 'Arrest': dprep.FieldType.BOOLEAN,\n", + " 'Date': (dprep.FieldType.DATE, ['%m/%d/%Y %H:%M']),\n", + "})\n", + "dflow_conversion_using_set.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/custom-python-transforms.ipynb b/work-with-data/dataprep/how-to-guides/custom-python-transforms.ipynb new file mode 100644 index 00000000..30dcb113 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/custom-python-transforms.ipynb @@ -0,0 +1,231 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/custom-python-transforms.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Custom Python Transforms\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There will be scenarios when the easiest thing for you to do is just to write some Python code. This SDK provides three extension points that you can use.\n", + "\n", + "1. New Script Column\n", + "2. New Script Filter\n", + "3. Transform Partition\n", + "\n", + "Each of these are supported in both the scale-up and the scale-out runtime. A key advantage of using these extension points is that you don't need to pull all of the data in order to create a dataframe. Your custom python code will be run just like other transforms, at scale, by partition, and typically in parallel." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initial data prep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We start by loading crime data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "col = dprep.col\n", + "\n", + "dflow = dprep.read_csv(path='../data/crime-spring.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We trim the dataset down and keep only the columns we are interested in. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.keep_columns(['Case Number','Primary Type', 'Description', 'Latitude', 'Longitude'])\n", + "dflow = dflow.replace_na(columns=['Latitude', 'Longitude'], custom_na_list='')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We look for null values using a filter. We found some, so now we'll look at a way to fill these missing values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.filter(col('Latitude').is_null()).head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Transform Partition" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We want to replace all null values with a 0, so we decide to use a handy pandas function. This code will be run by partition, not on all of the dataset at a time. This means that on a large dataset, this code may run in parallel as the runtime processes the data partition by partition." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pt_dflow = dflow\n", + "dflow = pt_dflow.transform_partition(\"\"\"\n", + "def transform(df, index):\n", + " df['Latitude'].fillna('0',inplace=True)\n", + " df['Longitude'].fillna('0',inplace=True)\n", + " return df\n", + "\"\"\")\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Transform Partition With File" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Being able to use any python code to manipulate your data as a pandas DataFrame is extremely useful for complex and specific data operations that DataPrep doesn't handle natively. Though the code isn't very testable unfortunately, it's just sitting inside a string.\n", + "So to improve code testability and ease of script writing there is another transform_partiton interface that takes the path to a python script which must contain a function matching the 'transform' signature defined above.\n", + "\n", + "The `script_path` argument should be a relative path to ensure Dataflow portability. Here `map_func.py` contains the same code as in the previous example." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = pt_dflow.transform_partition_with_file('../data/map_func.py')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## New Script Column" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We want to create a new column that has both the latitude and longitude. We can achieve it easily using [Data Prep expression](./add-column-using-expression.ipynb), which is faster in execution. Alternatively, We can do this using Python code by using the `new_script_column()` method on the dataflow. Note that we use custom Python code here for demo purpose only. In practise, you should always use Data Prep native functions as a preferred method, and use custom Python code when the functionality is not available in Data Prep. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.new_script_column(new_column_name='coordinates', insert_after='Longitude', script=\"\"\"\n", + "def newvalue(row):\n", + " return '(' + row['Latitude'] + ', ' + row['Longitude'] + ')'\n", + "\"\"\")\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## New Script Filter" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we want to filter the dataset down to only the crimes that incurred over $300 in loss. We can build a Python expression that returns True if we want to keep the row, and False to drop the row." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.new_script_filter(\"\"\"\n", + "def includerow(row):\n", + " val = row['Description']\n", + " return 'OVER $ 300' in val\n", + "\"\"\")\n", + "dflow.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/data-ingestion.ipynb b/work-with-data/dataprep/how-to-guides/data-ingestion.ipynb new file mode 100644 index 00000000..fc82dbda --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/data-ingestion.ipynb @@ -0,0 +1,978 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/data-ingestion.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data Ingestion\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep has the ability to load different types of input data. You can use auto-reading functionality to detect the type of a file, or directly specify a file type and its parameters." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Table of Contents\n", + "[Read Lines](#lines)
\n", + "[Read CSV](#csv)
\n", + "[Read Compressed CSV](#compressed-csv)
\n", + "[Read Excel](#excel)
\n", + "[Read Fixed Width Files](#fixed-width)
\n", + "[Read Parquet](#parquet)
\n", + "[Read Part Files Using Globbing](#globbing)
\n", + "[Read JSON](#json)
\n", + "[Read SQL](#sql)
\n", + "[Read PostgreSQL](#postgresql)
\n", + "[Read From Azure Blob](#azure-blob)
\n", + "[Read From ADLS](#adls)
\n", + "[Read Pandas DataFrame](#pandas-df)
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Lines" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One of the simplest ways to read data using Data Prep is to just read it as text lines." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_lines(path='../data/crime.txt')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With ingestion done, you can go ahead and start prepping the dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = dflow.to_pandas_dataframe()\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read CSV" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When reading delimited files, the only required parameter is `path`. Other parameters (e.g. separator, encoding, whether to use headers, etc.) are available to modify default behavior.\n", + "In this case, you can read a file by specifying only its location, then retrieve the first 5 rows to evaluate the result." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_duplicate_headers = dprep.read_csv(path='../data/crime_duplicate_headers.csv')\n", + "dflow_duplicate_headers.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From the result, you can see that the delimiter and encoding were correctly detected. Column headers were also detected. However, the first line seems to be a duplicate of the column headers. One of the parameters is a number of lines to skip from the files being read. You can use this to filter out the duplicate line." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_skip_headers = dprep.read_csv(path='../data/crime_duplicate_headers.csv', skip_rows=1)\n", + "dflow_skip_headers.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now the data set contains the correct headers and the extraneous row has been skipped by `read_csv`. Next, look at the data types of the columns." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_skip_headers.dtypes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Unfortunately, all of the columns came back as strings. This is because, by default, Data Prep will not change the type of the data. Since the data source is a text file, all values are kept as strings. In this case, however, numeric columns should be parsed as numbers. To do this, set the `infer_column_types` parameter to `True`, which will trigger type inference to be performed.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_inferred_types = dprep.read_csv(path='../data/crime_duplicate_headers.csv',\n", + " skip_rows=1,\n", + " infer_column_types=True)\n", + "dflow_inferred_types.dtypes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now several of the columns were correctly detected as numbers and their `FieldType` is Decimal.\n", + "\n", + "With ingestion done, the data set is ready to start preparing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = dflow_inferred_types.to_pandas_dataframe()\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Compressed CSV" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep can also read delimited files compressed in an archive. The `archive_options` parameter specifies the type of archive and glob pattern of entries in the archive.\n", + "\n", + "At this moment, only reading from ZIP archives is supported." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import ArchiveOptions, ArchiveType\n", + "\n", + "dflow = dprep.read_csv(path='../data/crime.zip',\n", + " archive_options=ArchiveOptions(archive_type=ArchiveType.ZIP, entry_glob='*10-20.csv'))\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Excel" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep can also load Excel files using the `read_excel` method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_default_sheet = dprep.read_excel(path='../data/crime.xlsx')\n", + "dflow_default_sheet.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, the first sheet of the Excel document has been loaded. You could achieve the same result by specifying the name of the desired sheet explicitly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_second_sheet = dprep.read_excel(path='../data/crime.xlsx', sheet_name='Sheet2')\n", + "dflow_second_sheet.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see, the table in the second sheet had headers as well as three empty rows, so you can modify the arguments accordingly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_skipped_rows = dprep.read_excel(path='../data/crime.xlsx',\n", + " sheet_name='Sheet2',\n", + " use_column_headers=True,\n", + " skip_rows=3)\n", + "dflow_skipped_rows.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = dflow_skipped_rows.to_pandas_dataframe()\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Fixed Width Files" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For fixed-width files, you can specify a list of offsets. The first column is always assumed to start at offset 0." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_fixed_width = dprep.read_fwf('../data/crime.txt', offsets=[8, 17, 26, 33, 56, 58, 74])\n", + "dflow_fixed_width.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Looking at the data, you can see that the first row was used as headers. In this particular case, however, there are no headers in the file, so the first row should be treated as data.\n", + "\n", + "Passing in `PromoteHeadersMode.NONE` to the `header` keyword argument avoids header detection and gets the correct data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_no_headers = dprep.read_fwf('../data/crime.txt',\n", + " offsets=[8, 17, 26, 33, 56, 58, 74],\n", + " header=dprep.PromoteHeadersMode.NONE)\n", + "dflow_no_headers.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = dflow_no_headers.to_pandas_dataframe()\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Parquet" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep has two different methods for reading data stored as Parquet.\n", + "\n", + "Currently, both methods require the `pyarrow` package to be installed in your Python environment. This can be done via `pip install azureml-dataprep[parquet]`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Read Parquet File" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For reading single `.parquet` files, or a folder full of only Parquet files, use `read_parquet_file`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_parquet_file('../data/crime.parquet')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Parquet data is explicitly typed so no type inference is needed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.dtypes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Read Parquet Dataset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A Parquet Dataset is different from a Parquet file in that it could be a folder containing a number of Parquet files within a complex directory structure. It may have a hierarchical structure that partitions the data by value of a column. These more complex forms of Parquet data are commonly produced by Spark/HIVE.\n", + "\n", + "For these more complex data sets, you can use `read_parquet_dataset`, which uses pyarrow to handle complex Parquet layouts. This will also handle single Parquet files, though these are better read using `read_parquet_file`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_parquet_dataset('../data/parquet_dataset')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The above data was partitioned by the value of the `Arrest` column. It is a boolean column in the original crime0 data set and hence was partitioned by `Arrest=true` and `Arrest=false`.\n", + "\n", + "The directory structure is printed below for clarity." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "for path, dirs, files in os.walk('../data/parquet_dataset'):\n", + " level = path.replace('../data/parquet_dataset', '').count(os.sep)\n", + " indent = ' ' * (level)\n", + " print(indent + os.path.basename(path) + '/')\n", + " fileindent = ' ' * (level + 1)\n", + " for f in files:\n", + " print(fileindent + f)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Part Files Using Globbing" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep supports globbing, which allows you to read partitioned files (or any other type of files) in a folder. Globbing is supported by all of the read transformations that take in file paths, such as `read_csv`, `read_lines`, etc. By specifying `../data/crime_partfiles/part-*` in the path, we will read all files start with `part-`in `crime_partfiles` folder and return them in one Dataflow. [`auto_read_file`](./auto-read-file.ipynb) will detect column types of your part files and parse them automatically." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_partfiles = dprep.auto_read_file(path='../data/crime_partfiles/part-*')\n", + "dflow_partfiles.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read JSON" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep can also load JSON files." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_json = dprep.read_json(path='../data/json.json')\n", + "dflow_json.head(15)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When you use `read_json`, Data Prep will attempt to extract data from the file into a table. You can also control the file encoding Data Prep should use as well as whether Data Prep should flatten nested JSON arrays.\n", + "\n", + "Choosing the option to flatten nested arrays could result in a much larger number of rows." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_flat_arrays = dprep.read_json(path='../data/json.json', flatten_nested_arrays=True)\n", + "dflow_flat_arrays.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read SQL" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep can also fetch data from SQL servers. Currently, only Microsoft SQL Server is supported.\n", + "\n", + "To read data from a SQL server, first create a data source object that contains the connection information." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "secret = dprep.register_secret(value=\"dpr3pTestU$er\", id=\"dprepTestUser\")\n", + "ds = dprep.MSSQLDataSource(server_name=\"dprep-sql-test.database.windows.net\",\n", + " database_name=\"dprep-sql-test\",\n", + " user_name=\"dprepTestUser\",\n", + " password=secret)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see, the password parameter of `MSSQLDataSource` accepts a Secret object. You can get a Secret object in two ways:\n", + "1. Register the secret and its value with the execution engine.\n", + "2. Create the secret with just an id (useful if the secret value was already registered in the execution environment).\n", + "\n", + "Now that you have created a data source object, you can proceed to read data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_sql(ds, \"SELECT top 100 * FROM [SalesLT].[Product]\")\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = dflow.to_pandas_dataframe()\n", + "df.dtypes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read PostgreSQL" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep can also fetch data from Azure PostgreSQL servers.\n", + "\n", + "To read data from a PostgreSQL server, first create a data source object that contains the connection information." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "secret = dprep.register_secret(value=\"dpr3pTestU$er\", id=\"dprepPostgresqlUser\")\n", + "ds = dprep.PostgreSQLDataSource(server_name=\"dprep-postgresql-test.postgres.database.azure.com\",\n", + " database_name=\"dprep-postgresql-testdb\",\n", + " user_name=\"dprepPostgresqlReadOnlyUser@dprep-postgresql-test\",\n", + " password=secret)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see, the password parameter of `PostgreSQLDataSource` accepts a Secret object as well.\n", + "Now that you have created a PostgreSQL data source object, you can proceed to read data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_postgresql(ds, \"SELECT * FROM public.people\")\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.dtypes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read from Azure Blob" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can read files stored in public Azure Blob by directly passing a file url. To read file from a protected Blob, pass SAS (Shared Access Signature) URI with both resource URI and SAS token in the path." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_csv(path='https://dpreptestfiles.blob.core.windows.net/testfiles/read_csv_duplicate_headers.csv', skip_rows=1)\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read from ADLS" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are two ways the Data Prep API can acquire the necessary OAuth token to access Azure DataLake Storage:\n", + "1. Retrieve the access token from a recent login session of the user's [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) login.\n", + "2. Use a ServicePrincipal (SP) and a certificate as a secret." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using Access Token from a recent Azure CLI session" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "On your local machine, run the following command:\n", + "```\n", + "az login\n", + "```\n", + "If your user account is a member of more than one Azure tenant, you need to specify the tenant, either in the AAD url hostname form '.onmicrosoft.com' or the tenantId GUID. The latter can be retrieved as follows:\n", + "```\n", + "az account show --query tenantId\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "dflow = read_csv(path = DataLakeDataSource(path='adl://dpreptestfiles.azuredatalakestore.net/farmers-markets.csv', tenant='microsoft.onmicrosoft.com'))\n", + "head = dflow.head(5)\n", + "head\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a ServicePrincipal via Azure CLI" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A ServicePrincipal and the corresponding certificate can be created via [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest).\n", + "This particular SP is configured as Reader, with its scope reduced to just the ADLS account 'dpreptestfiles'.\n", + "```\n", + "az account set --subscription \"Data Wrangling development\"\n", + "az ad sp create-for-rbac -n \"SP-ADLS-dpreptestfiles\" --create-cert --role reader --scopes /subscriptions/35f16a99-532a-4a47-9e93-00305f6c40f2/resourceGroups/dpreptestfiles/providers/Microsoft.DataLakeStore/accounts/dpreptestfiles\n", + "```\n", + "This command emits the appId and the path to the certificate file (usually in the home folder). The .crt file contains both the public certificate and the private key in PEM format.\n", + "\n", + "Extract the thumbprint with:\n", + "```\n", + "openssl x509 -in adls-dpreptestfiles.crt -noout -fingerprint\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Configure ADLS Account for ServicePrincipal" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To configure the ACL for the ADLS filesystem, use the objectId of the user or, here, ServicePrincipal:\n", + "```\n", + "az ad sp show --id \"8dd38f34-1fcb-4ff9-accd-7cd60b757174\" --query objectId\n", + "```\n", + "Configure Read and Execute access for the ADLS file system. Since the underlying HDFS ACL model doesn't support inheritance, folders and files need to be ACL-ed individually.\n", + "```\n", + "az dls fs access set-entry --account dpreptestfiles --acl-spec \"user:e37b9b1f-6a5e-4bee-9def-402b956f4e6f:r-x\" --path /\n", + "az dls fs access set-entry --account dpreptestfiles --acl-spec \"user:e37b9b1f-6a5e-4bee-9def-402b956f4e6f:r--\" --path /farmers-markets.csv\n", + "```\n", + "\n", + "References:\n", + "- [az ad sp](https://docs.microsoft.com/en-us/cli/azure/ad/sp?view=azure-cli-latest)\n", + "- [az dls fs access](https://docs.microsoft.com/en-us/cli/azure/dls/fs/access?view=azure-cli-latest)\n", + "- [ACL model for ADLS](https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/data-lake-store/data-lake-store-access-control.md)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "certThumbprint = 'C2:08:9D:9E:D1:74:FC:EB:E9:7E:63:96:37:1C:13:88:5E:B9:2C:84'\n", + "certificate = ''\n", + "with open('../data/adls-dpreptestfiles.crt', 'rt', encoding='utf-8') as crtFile:\n", + " certificate = crtFile.read()\n", + "\n", + "servicePrincipalAppId = \"8dd38f34-1fcb-4ff9-accd-7cd60b757174\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Acquire an OAuth Access Token" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use the adal package (via: `pip install adal`) to create an authentication context on the MSFT tenant and acquire an OAuth access token. Note that for ADLS, the `resource` in the token request must be for 'datalake.azure.net', which is different from most other Azure resources." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import adal\n", + "from azureml.dataprep.api.datasources import DataLakeDataSource\n", + "\n", + "ctx = adal.AuthenticationContext('https://login.microsoftonline.com/microsoft.onmicrosoft.com')\n", + "token = ctx.acquire_token_with_client_certificate('https://datalake.azure.net/', servicePrincipalAppId, certificate, certThumbprint)\n", + "dflow = dprep.read_csv(path = DataLakeDataSource(path='adl://dpreptestfiles.azuredatalakestore.net/crime-spring.csv', accessToken=token['accessToken']))\n", + "dflow.to_pandas_dataframe().head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read Pandas DataFrame" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are situations where you may already have some data in the form of a pandas DataFrame.\n", + "The steps taken to get to this DataFrame may be non-trivial or not easy to convert to Data Prep Steps. The `read_pandas_dataframe` reader can take a DataFrame and use it as the data source for a Dataflow.\n", + "\n", + "You can pass in a path to a directory (that doesn't exist yet) for Data Prep to store the contents of the DataFrame; otherwise, a temporary directory will be made in the system's temp folder. The files written to this directory will be named `part-00000` and so on; they are written out in Data Prep's internal row-based file format." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_excel(path='../data/crime.xlsx')\n", + "dflow = dflow.drop_columns(columns=['Column1'])\n", + "df = dflow.to_pandas_dataframe()\n", + "df.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After loading in the data you can now do `read_pandas_dataframe`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import shutil\n", + "cache_dir = 'dflow_df'\n", + "shutil.rmtree(cache_dir, ignore_errors=True)\n", + "dflow_df = dprep.read_pandas_dataframe(df, cache_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_df.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/data-profile.ipynb b/work-with-data/dataprep/how-to-guides/data-profile.ipynb new file mode 100644 index 00000000..97b42ee1 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/data-profile.ipynb @@ -0,0 +1,179 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/data-profile.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data Profile\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A DataProfile collects summary statistics on each column of the data produced by a Dataflow. This can be used to:\n", + "- Understand the input data.\n", + "- Determine which columns might need further preparation.\n", + "- Verify that data preparation operations produced the desired result.\n", + "\n", + "`Dataflow.get_profile()` executes the Dataflow, calculates profile information, and returns a newly constructed DataProfile." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "\n", + "dflow = dprep.auto_read_file('../data/crime-spring.csv')\n", + "\n", + "profile = dflow.get_profile()\n", + "profile" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A DataProfile contains a collection of ColumnProfiles, indexed by column name. Each ColumnProfile has attributes for the calculated column statistics. For non-numeric columns, profiles include only basic statistics like min, max, and error count. For numeric columns, profiles also include statistical moments and estimated quantiles." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "profile.columns['Beat']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also extract and filter data from profiles by using list and dict comprehensions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "variances = [c.variance for c in profile.columns.values() if c.variance]\n", + "variances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "column_types = {c.name: c.type for c in profile.columns.values()}\n", + "column_types" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If a column has fewer than a thousand unique values, its ColumnProfile contains a summary of values with their respective counts." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "profile.columns['Primary Type'].value_counts" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Numeric ColumnProfiles include an estimated histogram of the data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "profile.columns['District'].histogram" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To configure the number of bins in the histogram, you can pass an integer as the `number_of_histogram_bins` parameter." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "profile_more_bins = dflow.get_profile(number_of_histogram_bins=5)\n", + "profile_more_bins.columns['District'].histogram" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For columns containing data of mixed types, the ColumnProfile also provides counts of each type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "profile.columns['X Coordinate'].type_counts" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/datastore.ipynb b/work-with-data/dataprep/how-to-guides/datastore.ipynb new file mode 100644 index 00000000..d6c99a0e --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/datastore.ipynb @@ -0,0 +1,215 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/datastore.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Reading from and Writing to Datastores" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A datastore is a reference that points to an Azure storage service like a blob container for example. It belongs to a workspace and a workspace can have many datastores.\n", + "\n", + "A data path points to a path on the underlying Azure storage service the datastore references. For example, given a datastore named `blob` that points to an Azure blob container, a data path can point to `/test/data/titanic.csv` in the blob container." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read data from Datastore" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep supports reading data from a `Datastore` or a `DataPath` or a `DataReference`. \n", + "\n", + "Passing in a datastore into all the `read_*` methods of Data Prep will result in reading everything in the underlying Azure storage service. To read a specific folder or file in the underlying storage, you have to pass in a data reference." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core import Workspace, Datastore\n", + "from azureml.data.datapath import DataPath\n", + "\n", + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, get or create a workspace. Feel free to replace `subscription_id`, `resource_group`, and `workspace_name` with other values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "subscription_id = '35f16a99-532a-4a47-9e93-00305f6c40f2'\n", + "resource_group = 'DataStoreTest'\n", + "workspace_name = 'dataprep-centraleuap'\n", + "\n", + "workspace = Workspace(subscription_id=subscription_id, resource_group=resource_group, workspace_name=workspace_name)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "workspace.datastores" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can now read a crime data set from the datastore. If you are using your own workspace, the `crime0-10.csv` will not be there by default. You will have to upload the data to the datastore yourself." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datastore = Datastore(workspace=workspace, name='dataprep_blob')\n", + "dflow = dprep.read_csv(path=datastore.path('crime0-10.csv'))\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also read from an Azure SQL database. To do that, you will first get an Azure SQL database datastore instance and pass it to Data Prep for reading." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datastore = Datastore(workspace=workspace, name='test_sql')\n", + "dflow_sql = dprep.read_sql(data_source=datastore, query='SELECT * FROM team')\n", + "dflow_sql.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also read from a PostgreSQL database. To do that, you will first get a PostgreSQL database datastore instance and pass it to Data Prep for reading." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datastore = Datastore(workspace=workspace, name='postgre_test')\n", + "dflow_sql = dprep.read_postgresql(data_source=datastore, query='SELECT * FROM public.people')\n", + "dflow_sql.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Write data to Datastore" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also write a dataflow to a datastore. The code below will write the file you read in earlier to the folder in the datastore." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dest_datastore = Datastore(workspace, 'dataprep_blob_key')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.write_to_csv(directory_path=dest_datastore.path('output/crime0-10')).run_local()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now you can read all the files in the `dataprep_adls` datastore which references an Azure Data Lake store." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datastore = Datastore(workspace=workspace, name='dataprep_adls')\n", + "dflow_adls = dprep.read_csv(path=DataPath(datastore, path_on_datastore='/input/crime0-10.csv'))\n", + "dflow_adls.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/derive-column-by-example.ipynb b/work-with-data/dataprep/how-to-guides/derive-column-by-example.ipynb new file mode 100644 index 00000000..5d1db4ee --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/derive-column-by-example.ipynb @@ -0,0 +1,187 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/derive-column-by-example.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Derive Column By Example\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One of the more advanced tools in Data Prep is the ability to derive columns by providing examples of desired results and letting Data Prep generate code to achieve the intended derivation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_csv(path = '../data/crime-spring.csv')\n", + "df = dflow.head(5)\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see, this is a fairly simple file, but let's assume that we need to be able to join this with a dataset where date and time come in a format 'Apr 4, 2016 | 10PM-12AM'.\n", + "\n", + "Let's wrangle the data into the shape we need." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder = dflow.builders.derive_column_by_example(source_columns = ['Date'], new_column_name = 'date_timerange')\n", + "builder.add_example(source_data = df.iloc[0], example_value = 'Apr 4, 2016 10PM-12AM')\n", + "builder.preview() # will preview top 10 rows" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The code above first creates a builder for the derived column by providing an array of source columns to consider ('DATE') and name for the new column to be added.\n", + "\n", + "Then, we provide the first example by passing in the first row (index 0) of the DataFrame printed above and giving an expected value for the derived column.\n", + "\n", + "Finally, we call `builder.preview()` and observe the derived column next to the source column." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Everything looks good here. However, we just noticed that it's not quite what we wanted. We forgot to separate date and time range by '|' to generate the format we need.\n", + "\n", + "To fix that, we will add another example. This time, instead of passing in a row from the preview, we just construct a dictionary of column name to value for the source_data parameter." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.add_example(source_data = {'Date': '4/15/2016 10:00'}, example_value = 'Apr 15, 2016 | 10AM-12PM')\n", + "builder.preview()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This clearly had negative effects, as now the only rows that have any values in derived column are the ones that match exactly with the examples we have provided.\n", + "\n", + "Let's look at the examples:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "examples = builder.list_examples()\n", + "examples" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we can see that we have provided inconsistent examples. To fix the issue, we need to replace the first example with a correct one (including '|' between date and time).\n", + "\n", + "We can achieve this by deleting examples that are incorrect (by either passing in example_row from examples DataFrame, or by just passing in example_id value) and then adding new modified examples back." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.delete_example(example_id = -1)\n", + "builder.add_example(examples.iloc[0], 'Apr 4, 2016 | 10PM-12AM')\n", + "builder.preview()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now this looks correct and we can finally call to_dataflow() on the builder, which would return a dataflow with the desired derived columns added." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = builder.to_dataflow()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df = dflow.to_pandas_dataframe()\n", + "df" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/external-references.ipynb b/work-with-data/dataprep/how-to-guides/external-references.ipynb new file mode 100644 index 00000000..579d9087 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/external-references.ipynb @@ -0,0 +1,118 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/external-references.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# External References\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In addition to opening existing Dataflows in code and modifying them, it is also possible to create and persist Dataflows that reference another Dataflow that has been persisted to a .dprep file. In this case, executing this Dataflow will load and execute the referenced Dataflow dynamically, and then execute the steps in the referencing Dataflow." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To demonstrate, we will create a Dataflow that loads and transforms some data. After that, we will persist this Dataflow to disk. To learn more about saving and opening .dprep files, see: [Opening and Saving Dataflows](./open-save-dataflows.ipynb)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "import tempfile\n", + "import os\n", + "\n", + "dflow = dprep.auto_read_file('../data/crime.txt')\n", + "dflow = dflow.drop_errors(['Column7', 'Column8', 'Column9'], dprep.ColumnRelationship.ANY)\n", + "dflow_path = os.path.join(tempfile.gettempdir(), 'package.dprep')\n", + "dflow.save(dflow_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we have a .dprep file, we can create a new Dataflow that references it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_new = dprep.Dataflow.reference(dprep.ExternalReference(dflow_path))\n", + "dflow_new.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When executed, the new Dataflow returns the same results as the one we saved to the .dprep file. Since this reference is resolved on execution, updating the referenced Dataflow results in the changes being visible when re-executing the referencing Dataflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.take(5)\n", + "dflow.save(dflow_path)\n", + "\n", + "dflow_new.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see, even though we did not modify `dflow_new`, it now returns only 5 records, as the referenced Dataflow was updated with the result from `dflow.take(5)`." + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/filtering.ipynb b/work-with-data/dataprep/how-to-guides/filtering.ipynb new file mode 100644 index 00000000..545fd3ca --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/filtering.ipynb @@ -0,0 +1,220 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/filtering.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Filtering\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Azure ML Data Prep has the ability to filter out columns or rows using `Dataflow.drop_columns` or `Dataflow.filter`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# initial set up\n", + "import azureml.dataprep as dprep\n", + "from datetime import datetime\n", + "dflow = dprep.read_csv(path='../data/crime-spring.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Filtering columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To filter columns, use `Dataflow.drop_columns`. This method takes a list of columns to drop or a more complex argument called `ColumnSelector`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Filtering columns with list of strings" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this example, `drop_columns` takes a list of strings. Each string should exactly match the desired column to drop." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.drop_columns(['ID', 'Location Description', 'Ward', 'Community Area', 'FBI Code'])\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Filtering columns with regex" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively, a `ColumnSelector` can be used to drop columns that match a regex expression. In this example, we drop all the columns that match the expression `Column*|.*longitud|.*latitude`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.drop_columns(dprep.ColumnSelector('Column*|.*longitud|.*latitude', True, True))\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Filtering rows" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To filter rows, use `DataFlow.filter`. This method takes an `Expression` as an argument, and returns a new dataflow with the rows in which the expression evaluates to `True`. Expressions are built by indexing the `Dataflow` with a column name (`dataflow['myColumn']`) and regular operators (`>`, `<`, `>=`, `<=`, `==`, `!=`)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Filtering rows with simple expressions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Index into the Dataflow specifying the column name as a string argument `dataflow['column_name']` and in combination with one of the following standard operators `>, <, >=, <=, ==, !=`, build an expression such as `dataflow['District'] > 9`. Finally, pass the built expression into the `Dataflow.filter` function.\n", + "\n", + "In this example, `dataflow.filter(dataflow['District'] > 9)` returns a new dataflow with the rows in which the value of \"District\" is greater than '10' \n", + "\n", + "*Note that \"District\" is first converted to numeric, which allows us to build an expression comparing it against other numeric values.*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.to_number(['District'])\n", + "dflow = dflow.filter(dflow['District'] > 9)\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Filtering rows with complex expressions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To filter using complex expressions, combine one or more simple expressions with the operators `&`, `|`, and `~`. Please note that the precedence of these operators is lower than that of the comparison operators; therefore, you'll need to use parentheses to group clauses together. \n", + "\n", + "In this example, `Dataflow.filter` returns a new dataflow with the rows in which \"Primary Type\" equals 'DECEPTIVE PRACTICE' and \"District\" is greater than or equal to '10'." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.to_number(['District'])\n", + "dflow = dflow.filter((dflow['Primary Type'] == 'DECEPTIVE PRACTICE') & (dflow['District'] >= 10))\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is also possible to filter rows combining more than one expression builder to create a nested expression.\n", + "\n", + "*Note that `'Date'` and `'Updated On'` are first converted to datetime, which allows us to build an expression comparing it against other datetime values.*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.to_datetime(['Date', 'Updated On'], ['%Y-%m-%d %H:%M:%S'])\n", + "dflow = dflow.to_number(['District', 'Y Coordinate'])\n", + "comparison_date = datetime(2016,4,13)\n", + "dflow = dflow.filter(\n", + " ((dflow['Date'] > comparison_date) | (dflow['Updated On'] > comparison_date))\n", + " | ((dflow['Y Coordinate'] > 1900000) & (dflow['District'] > 10.0)))\n", + "dflow.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/fuzzy-group.ipynb b/work-with-data/dataprep/how-to-guides/fuzzy-group.ipynb new file mode 100644 index 00000000..53f309a7 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/fuzzy-group.ipynb @@ -0,0 +1,211 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/fuzzy-group.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Fuzzy Grouping\n", + "\n", + "Unprepared data often represents the same entity with multiple values; examples include different spellings, varying capitalizations, and abbreviations. This is common when working with data gathered from multiple sources or through human input. One way to canonicalize and reconcile these variants is to use Data Prep's fuzzy_group_column (also known as \"text clustering\") functionality.\n", + "\n", + "Data Prep inspects a column to determine clusters of similar values. A new column is added in which clustered values are replaced with the canonical value of its cluster, thus significantly reducing the number of distinct values. You can control the degree of similarity required for values to be clustered together, override canonical form, and set clusters if automatic clustering did not provide the desired results.\n", + "\n", + "Let's explore the capabilities of `fuzzy_group_column` by first reading in a dataset and inspecting it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_json(path='../data/json.json')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see above, the column `inspections.business.city` contains several forms of the city name \"San Francisco\".\n", + "Let's add a column with values replaced by the automatically detected canonical form. To do so call fuzzy_group_column() on an existing Dataflow:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_clean = dflow.fuzzy_group_column(source_column='inspections.business.city',\n", + " new_column_name='city_grouped',\n", + " similarity_threshold=0.8,\n", + " similarity_score_column_name='similarity_score')\n", + "dflow_clean.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The arguments `source_column` and `new_column_name` are required, whereas the others are optional.\n", + "If `similarity_threshold` is provided, it will be used to control the required similarity level for the values to be grouped together.\n", + "If `similarity_score_column_name` is provided, a second new column will be added to show similarity score between every pair of original and canonical values.\n", + "\n", + "In the resulting data set, you can see that all the different variations of representing \"San Francisco\" in the data were normalized to the same string, \"San Francisco\".\n", + "\n", + "But what if you want more control over what gets grouped, what doesn't, and what the canonical value should be? \n", + "\n", + "To get more control over grouping, canonical values, and exceptions, you need to use the `FuzzyGroupBuilder` class.\n", + "Let's see what it has to offer below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder = dflow.builders.fuzzy_group_column(source_column='inspections.business.city',\n", + " new_column_name='city_grouped',\n", + " similarity_threshold=0.8,\n", + " similarity_score_column_name='similarity_score')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# calling learn() to get fuzzy groups\n", + "builder.learn()\n", + "builder.groups" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here you can see that `fuzzy_group_column` detected one group with four values that all map to \"San Francisco\" as the canonical value.\n", + "You can see the effects of changing the similarity threshold next:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.similarity_threshold = 0.9\n", + "builder.learn()\n", + "builder.groups" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that you are using a similarity threshold of `0.9`, two distinct groups of values are generated.\n", + "\n", + "Let's tweak some of the detected groups before completing the builder and getting back the Dataflow with the resulting fuzzy grouped column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.similarity_threshold = 0.8\n", + "builder.learn()\n", + "groups = builder.groups\n", + "groups" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# change the canonical value for the first group\n", + "groups[0]['canonicalValue'] = 'SANFRAN'\n", + "duplicates = groups[0]['duplicates']\n", + "# remove the last duplicate value from the cluster\n", + "duplicates = duplicates[:-1]\n", + "# assign modified duplicate array back\n", + "groups[0]['duplicates'] = duplicates\n", + "# assign modified groups back to builder\n", + "builder.groups = groups\n", + "builder.groups" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, the canonical value is modified to be used for the single fuzzy group and removed 'S.F.' from this group's duplicates list.\n", + "\n", + "You can mutate the copy of the `groups` list from the builder (be careful to keep the structure of objects inside this list). After getting the desired groups in the list, you can update the builder with it.\n", + "\n", + "Now you can get a dataflow with the FuzzyGroup step in it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_clean = builder.to_dataflow()\n", + "\n", + "df = dflow_clean.to_pandas_dataframe()\n", + "df" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/impute-missing-values.ipynb b/work-with-data/dataprep/how-to-guides/impute-missing-values.ipynb new file mode 100644 index 00000000..a1b9e46e --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/impute-missing-values.ipynb @@ -0,0 +1,147 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/impute-missing-values.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Impute missing values\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Azure ML Data Prep has the ability to impute missing values in specified columns. In this case, we will attempt to impute the missing _Latitude_ and _Longitude_ values in the input data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# loading input data\n", + "dflow = dprep.read_csv(path= '../data/crime-spring.csv')\n", + "dflow = dflow.keep_columns(['ID', 'Arrest', 'Latitude', 'Longitude'])\n", + "dflow = dflow.to_number(['Latitude', 'Longitude'])\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The third record from input data has _Latitude_ and _Longitude_ missing. To impute those missing values, we can use `ImputeMissingValuesBuilder` to learn a fixed program which imputes the columns with either a calculated `MIN`, `MAX` or `MEAN` value or a `CUSTOM` value. When `group_by_columns` is specified, missing values will be imputed by group with `MIN`, `MAX` and `MEAN` calculated per group." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Firstly, let us quickly see check the `MEAN` value of _Latitude_ column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_mean = dflow.summarize(group_by_columns=['Arrest'],\n", + " summary_columns=[dprep.SummaryColumnsValue(column_id='Latitude',\n", + " summary_column_name='Latitude_MEAN',\n", + " summary_function=dprep.SummaryFunction.MEAN)])\n", + "dflow_mean = dflow_mean.filter(dprep.col('Arrest') == 'FALSE')\n", + "dflow_mean.head(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `MEAN` value of _Latitude_ looks good. So we will impute _Latitude_ with it. As for `Longitude`, we will impute it using `42` based on external knowledge." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# impute with MEAN\n", + "impute_mean = dprep.ImputeColumnArguments(column_id='Latitude',\n", + " impute_function=dprep.ReplaceValueFunction.MEAN)\n", + "# impute with custom value 42\n", + "impute_custom = dprep.ImputeColumnArguments(column_id='Longitude',\n", + " custom_impute_value=42)\n", + "# get instance of ImputeMissingValuesBuilder\n", + "impute_builder = dflow.builders.impute_missing_values(impute_columns=[impute_mean, impute_custom],\n", + " group_by_columns=['Arrest'])\n", + "# call learn() to learn a fixed program to impute missing values\n", + "impute_builder.learn()\n", + "# call to_dataflow() to get a dataflow with impute step added\n", + "dflow_imputed = impute_builder.to_dataflow()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# check impute result\n", + "dflow_imputed.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As the result above, the missing _Latitude_ has been imputed with the `MEAN` value of `Arrest=='false'` group, and the missing _Longitude_ has been imputed with `42`." + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/join.ipynb b/work-with-data/dataprep/how-to-guides/join.ipynb new file mode 100644 index 00000000..2f8c2e47 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/join.ipynb @@ -0,0 +1,265 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/join.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Join\n", + "\n", + "In Data Prep you can easily join two Dataflows." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, get the left side of the data into a shape that is ready for the join." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# get the first Dataflow and derive desired key column\n", + "dflow_left = dprep.read_csv(path='https://dpreptestfiles.blob.core.windows.net/testfiles/BostonWeather.csv')\n", + "dflow_left = dflow_left.derive_column_by_example(source_columns='DATE', new_column_name='date_timerange',\n", + " example_data=[('11/11/2015 0:54', 'Nov 11, 2015 | 12AM-2AM'),\n", + " ('2/1/2015 0:54', 'Feb 1, 2015 | 12AM-2AM'),\n", + " ('1/29/2015 20:54', 'Jan 29, 2015 | 8PM-10PM')])\n", + "dflow_left = dflow_left.drop_columns(['DATE'])\n", + "\n", + "# convert types and summarize data\n", + "dflow_left = dflow_left.set_column_types(type_conversions={'HOURLYDRYBULBTEMPF': dprep.TypeConverter(dprep.FieldType.DECIMAL)})\n", + "dflow_left = dflow_left.filter(expression=~dflow_left['HOURLYDRYBULBTEMPF'].is_error())\n", + "dflow_left = dflow_left.summarize(group_by_columns=['date_timerange'],summary_columns=[dprep.SummaryColumnsValue('HOURLYDRYBULBTEMPF', dprep.api.engineapi.typedefinitions.SummaryFunction.MEAN, 'HOURLYDRYBULBTEMPF_Mean')] )\n", + "\n", + "# cache the result so the steps above are not executed every time we pull on the data\n", + "import os\n", + "from pathlib import Path\n", + "cache_dir = str(Path(os.getcwd(), 'dataflow-cache'))\n", + "dflow_left.cache(directory_path=cache_dir)\n", + "dflow_left.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's prepare the data for the right side of the join." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# get the second Dataflow and desired key column\n", + "dflow_right = dprep.read_csv(path='https://dpreptestfiles.blob.core.windows.net/bike-share/*-hubway-tripdata.csv')\n", + "dflow_right = dflow_right.keep_columns(['starttime', 'start station id'])\n", + "dflow_right = dflow_right.derive_column_by_example(source_columns='starttime', new_column_name='l_date_timerange',\n", + " example_data=[('2015-01-01 00:21:44', 'Jan 1, 2015 | 12AM-2AM')])\n", + "dflow_right = dflow_right.drop_columns('starttime')\n", + "\n", + "# cache the results\n", + "dflow_right.cache(directory_path=cache_dir)\n", + "dflow_right.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are three ways you can join two Dataflows in Data Prep:\n", + "1. Create a `JoinBuilder` object for interactive join configuration.\n", + "2. Call ```join()``` on one of the Dataflows and pass in the other along with all other arguments.\n", + "3. Call ```Dataflow.join()``` method and pass in two Dataflows along with all other arguments.\n", + "\n", + "We will explore the builder object as it simplifies the determination of correct arguments. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# construct a builder for joining dataflow_l with dataflow_r\n", + "join_builder = dflow_left.builders.join(right_dataflow=dflow_right, left_column_prefix='l', right_column_prefix='r')\n", + "\n", + "join_builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So far the builder has no properties set except default values.\n", + "From here you can set each of the options and preview its effect on the join result or use Data Prep to determine some of them.\n", + "\n", + "Let's start with determining appropriate column prefixes for left and right side of the join and lists of columns that would not conflict and therefore don't need to be prefixed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "join_builder.detect_column_info()\n", + "join_builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can see that Data Prep has performed a pull on both Dataflows to determine the column names in them. Given that `dataflow_r` already had a column starting with `l_` new prefix got generated which would not collide with any column names that are already present.\n", + "Additionally columns in each Dataflow that won't conflict during join would remain unprefixed.\n", + "This apprach to column naming is crucial for join robustness to schema changes in the data. Let's say that at some time in future the data consumed by left Dataflow will also have `l_date_timerange` column in it.\n", + "Configured as above the join will still run as expected and the new column will be prefixed with `l2_` ensuring that ig column `l_date_timerange` was consumed by some other future transformation it remains unaffected.\n", + "\n", + "Note: `KEY_generated` is appended to both lists and is reserved for Data Prep use in case Autojoin is performed.\n", + "\n", + "### Autojoin\n", + "Autojoin is a Data prep feature that determines suitable join arguments given data on both sides. In some cases Autojoin can even derive a key column from a number of available columns in the data.\n", + "Here is how you can use Autojoin:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# generate join suggestions\n", + "join_builder.generate_suggested_join()\n", + "\n", + "# list generated suggestions\n", + "join_builder.list_join_suggestions()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's select the first suggestion and preview the result of the join." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# apply first suggestion\n", + "join_builder.apply_suggestion(0)\n", + "\n", + "join_builder.preview(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, get our new joined Dataflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_autojoined = join_builder.to_dataflow().drop_columns(['l_date_timerange'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Joining two Dataflows without pulling the data\n", + "\n", + "If you don't want to pull on data and know what join should look like, you can always use the join method on the Dataflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_joined = dprep.Dataflow.join(left_dataflow=dflow_left,\n", + " right_dataflow=dflow_right,\n", + " join_key_pairs=[('date_timerange', 'l_date_timerange')],\n", + " left_column_prefix='l2_',\n", + " right_column_prefix='r_')\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_joined.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_joined = dflow_joined.filter(expression=dflow_joined['r_start station id'] == '67')\n", + "df = dflow_joined.to_pandas_dataframe()\n", + "df" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.5" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/label-encoder.ipynb b/work-with-data/dataprep/how-to-guides/label-encoder.ipynb new file mode 100644 index 00000000..bc7b78c1 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/label-encoder.ipynb @@ -0,0 +1,168 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/label-encoder.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Label Encoder\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data Prep has the ability to encode labels with values between 0 and (number of classes - 1) using `label_encode`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "from datetime import datetime\n", + "dflow = dprep.read_csv(path='../data/crime-spring.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use `label_encode` from a Dataflow, simply specify the source column and the new column name. `label_encode` will figure out all the distinct values or classes in the source column, and it will return a new Dataflow with a new column containing the labels." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.label_encode(source_column='Primary Type', new_column_name='Primary Type Label')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To have more control over the encoded labels, create a builder with `dataflow.builders.label_encode`.\n", + "The builder allows you to preview and modify the encoded labels before generating a new Dataflow with the results. \n", + "To get started, create a builder object with `dataflow.builders.label_encode` specifying the source column and the new column name. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder = dflow.builders.label_encode(source_column='Location Description', new_column_name='Location Description Label')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To generate the encoded labels, call the `learn` method on the builder object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.learn()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To check the result, access the generated labels through the property `encoded_labels`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.encoded_labels" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To modify the generated results, just assign a new value to `encoded_labels`. The following example adds a missing label not found in the sample data. `builder.encoded_labels` is saved into a variable `encoded_labels`, modified, and assigned back to `builder.encoded_labels`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "encoded_labels = builder.encoded_labels\n", + "encoded_labels['TOWNHOUSE'] = 6\n", + "\n", + "builder.encoded_labels = encoded_labels\n", + "builder.encoded_labels" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the desired results are achieved, call `builder.to_dataflow` to get the new Dataflow with the encoded labels." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataflow = builder.to_dataflow()\n", + "dataflow.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/min-max-scaler.ipynb b/work-with-data/dataprep/how-to-guides/min-max-scaler.ipynb new file mode 100644 index 00000000..a7e5fd65 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/min-max-scaler.ipynb @@ -0,0 +1,239 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/min-max-scaler.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Min-Max Scaler\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The min-max scaler scales all values in a column to a desired range (typically [0, 1]). This is also known as feature scaling or unity-based normalization. Min-max scaling is commonly used to normalize numeric columns in a data set for machine learning algorithms." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, load a data set containing information about crime in Chicago. Keep only a few columns." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_csv('../data/crime-spring.csv')\n", + "dflow = dflow.keep_columns(columns=['ID', 'District', 'FBI Code'])\n", + "dflow = dflow.to_number(columns=['District', 'FBI Code'])\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using `get_profile()`, you can see the shape of the numeric columns such as the minimum, maximum, count, and number of error values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To apply min-max scaling, call the function `min_max_scaler` on the Dataflow and specify the column name. This will trigger a full data scan over the column to determine the min and max values and perform the scaling. Note that the min and max values of the column are preserved at this point. If the same dataflow steps are performed over a different dataset, the min-max scaler must be re-executed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_district = dflow.min_max_scale(column='District')\n", + "dflow_district.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Look at the data profile to see that the \"District\" column is now scaled; the min is 0 and the max is 1. Any error values and missing values from the source column are preserved." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_district.get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also specify a custom range for the scaling. Instead of [0, 1], let's choose [-10, 10]." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_district_range = dflow.min_max_scale(column='District', range_min=-10, range_max=10)\n", + "dflow_district_range.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In some cases, you may want to manually provide the min and max of the data in the source column. For example, you may want to avoid a full data scan because the dataset is large and we already know the min and max. You can provide the known min and max to the `min_max_scaler` function. The column will be scaled using the provided values. For example, if you want to scale the `FBI Code` column with 6 (`data_min`) becoming 0 (`range_min`), the program will scan the data to get `data_max`, which will become 1 (`range_max`)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_fbi = dflow.min_max_scale(column='FBI Code', data_min=6)\n", + "dflow_fbi.get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using a Min-Max Scaler builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For more flexibility when constructing the arguments for the min-max scaling, you can use a Min-Max Scaler builder." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder = dflow.builders.min_max_scale(column='District')\n", + "builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Calling `builder.learn()` will trigger a full data scan to see what `data_min` and `data_max` are. You can choose whether to use these values or set custom values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.learn()\n", + "builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you want to provide custom values for any of the arguments, you can update the builder object." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.range_max = 10\n", + "builder.data_min = 6\n", + "builder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When you are satisfied with the arguments, you will call `builder.to_dataflow()` to get the result. Note that the min and max values of the source column is preserved by the builder at this point. If you need to get the true `data_min` and `data_max` values again, you will need to set those arguments on the builder to `None` and then call `builder.learn()` again." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_builder = builder.to_dataflow()\n", + "dflow_builder.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/one-hot-encoder.ipynb b/work-with-data/dataprep/how-to-guides/one-hot-encoder.ipynb new file mode 100644 index 00000000..72918540 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/one-hot-encoder.ipynb @@ -0,0 +1,179 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/one-hot-encoder.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# One Hot Encoder\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Azure ML Data Prep has the ability to perform one hot encoding on a selected column using `one_hot_encode`. The result Dataflow will have a new binary column for each categorical label encountered in the selected column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.read_csv(path='../data/crime-spring.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use `one_hot_encode` from a Dataflow, simply specify the source column. `one_hot_encode` will figure out all the distinct values or categorical labels in the source column using the current data, and it will return a new Dataflow with a new binary column for each categorical label. Note that the categorical labels are remembered in the Dataflow step." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_result = dflow.one_hot_encode(source_column='Location Description')\n", + "dflow_result.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By default, all the new columns will use the `source_column` name as a prefix. However, if you would like to specify your own prefix, simply pass a `prefix` string as a second parameter." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_result = dflow.one_hot_encode(source_column='Location Description', prefix='LOCATION_')\n", + "dflow_result.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To have more control over the categorical labels, create a builder using `dataflow.builders.one_hot_encode`. The builder allows to preview and modify the categorical labels before generating a new Dataflow with the results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder = dflow.builders.one_hot_encode(source_column='Location Description', prefix='LOCATION_')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To generate the categorical labels, call the `learn` method on the builder object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.learn()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To preview the categorical labels, simply access them through the property `categorical_labels` on the builder object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.categorical_labels" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To modify the generated `categorical_labels`, assign a new value to `categorical_labels` or modify the existing one. The following example adds a missing label not found on the sample data to `categorical_labels`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.categorical_labels.append('TOWNHOUSE')\n", + "builder.categorical_labels" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the desired results are achieved, call `builder.to_dataflow` to get the new Dataflow with the encoded labels." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_result = builder.to_dataflow()\n", + "dflow_result.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/open-save-dataflows.ipynb b/work-with-data/dataprep/how-to-guides/open-save-dataflows.ipynb new file mode 100644 index 00000000..92064377 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/open-save-dataflows.ipynb @@ -0,0 +1,171 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/open-save-dataflows.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Opening and Saving Dataflows\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once you have built a Dataflow, you can save it to a `.dprep` file. This persists all of the information in your Dataflow including steps you've added, examples and programs from by-example steps, computed aggregations, etc.\n", + "\n", + "You can also open `.dprep` files to access any Dataflows you have previously persisted." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Open\n", + "\n", + "Use the `open()` method of the Dataflow class to load existing `.dprep` files." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "dflow_path = os.path.join(os.getcwd(), '..', 'data', 'crime.dprep')\n", + "print(dflow_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import Dataflow" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = Dataflow.open(dflow_path)\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Edit\n", + "\n", + "After a Dataflow is loaded, it can be further edited as needed. In this example, a filter is added." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.dataprep import col" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.filter(col('Description') != 'SIMPLE')\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Save\n", + "\n", + "Use the `save()` method of the Dataflow class to write out the `.dprep` file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import tempfile\n", + "temp_dir = tempfile._get_default_tempdir()\n", + "temp_file_name = next(tempfile._get_candidate_names())\n", + "temp_dflow_path = os.path.join(temp_dir, temp_file_name + '.dprep')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.save(temp_dflow_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Round-trip\n", + "\n", + "This illustrates the ability to load the edited Dataflow back in and use it, in this case to get a pandas DataFrame." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_to_open = Dataflow.open(temp_dflow_path)\n", + "df = dflow_to_open.to_pandas_dataframe()\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if os.path.isfile(temp_dflow_path):\n", + " os.remove(temp_dflow_path)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/quantile-transformation.ipynb b/work-with-data/dataprep/how-to-guides/quantile-transformation.ipynb new file mode 100644 index 00000000..883bc5c8 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/quantile-transformation.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/quantile-transformation.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Quantile Transformation\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "DataPrep has the ability to perform quantile transformation to a numeric column. This transformation can transform the data into a normal or uniform distribution. Values bigger than the learnt boundaries will simply be clipped to the learnt boundaries when applying quantile transformation.\n", + "\n", + "Let's load a sample of the median income of california households in different suburbs from the 1990 census data. From the data profile, we can see that the minimum value and maximum value is 0.9946 and 15 respectively." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "\n", + "dflow = dprep.read_csv(path='../data/median_income.csv').set_column_types(type_conversions={\n", + " 'median_income': dprep.TypeConverter(dprep.FieldType.DECIMAL)\n", + "})\n", + "dflow.get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now apply quantile transformation to `median_income` and see how that affects the data. We will apply quantile transformation twice, one that maps the data to a Uniform(0, 1) distribution, one that maps it to a Normal(0, 1) distribution.\n", + "\n", + "From the data profile, we can see that the min and max of the uniform median income is strictly between 0 and 1 and the mean and standard deviation of the normal median income is close to 0 and 1 respectively.\n", + "\n", + "*Note: for normal distribution, we will clip the values at the ends as the 0th percentile and the 100th percentile are -Inf and Inf respectively.*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.quantile_transform(source_column='median_income', new_column='median_income_uniform', quantiles_count=5)\n", + "dflow = dflow.quantile_transform(source_column='median_income', new_column='median_income_normal', \n", + " quantiles_count=5, output_distribution=\"Normal\")\n", + "dflow.get_profile()" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/random-split.ipynb b/work-with-data/dataprep/how-to-guides/random-split.ipynb new file mode 100644 index 00000000..4f87af22 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/random-split.ipynb @@ -0,0 +1,145 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/random-split.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Random Split\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Azure ML Data Prep provides the functionality of splitting a data set into two. When training a machine learning model, it is often desirable to train the model on a subset of data, then validate the model on a different subset." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `random_split(percentage, seed=None)` function in Data Prep takes in a Dataflow and randomly splitting it into two distinct subsets (approximately by the percentage specified)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `seed` parameter is optional. If a seed is not provided, a stable one is generated, ensuring that the results for a specific Dataflow remain consistent. Different calls to `random_split` will receive different seeds." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To demonstrate, you can go through the following example. First, you can read the first 10,000 lines from a file. Since the contents of the file don't matter, just the first two columns can be used for a simple example." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_csv(path='https://dpreptestfiles.blob.core.windows.net/testfiles/crime0.csv').take(10000)\n", + "dflow = dflow.keep_columns(['ID', 'Date'])\n", + "profile = dflow.get_profile()\n", + "print('Row count: %d' % (profile.columns['ID'].count))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, you can call `random_split` with the percentage set to 10% (the actual split ratio will be an approximation of `percentage`). You can take a look at the row count of the first returned Dataflow. You should see that `dflow_test` has approximately 1,000 rows (10% of 10,000)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(dflow_test, dflow_train) = dflow.random_split(percentage=0.1)\n", + "profile_test = dflow_test.get_profile()\n", + "print('Row count of \"test\": %d' % (profile_test.columns['ID'].count))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now you can take a look at the row count of the second returned Dataflow. The row count of `dflow_test` and `dflow_train` sums exactly to 10,000, because `random_split` results in two subsets that make up the original Dataflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "profile_train = dflow_train.get_profile()\n", + "print('Row count of \"train\": %d' % (profile_train.columns['ID'].count))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To specify a fixed seed, simply provide it to the `random_split` function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(dflow_test, dflow_train) = dflow.random_split(percentage=0.1, seed=12345)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/replace-datasource-replace-reference.ipynb b/work-with-data/dataprep/how-to-guides/replace-datasource-replace-reference.ipynb new file mode 100644 index 00000000..e8d62acf --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/replace-datasource-replace-reference.ipynb @@ -0,0 +1,130 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/replace-datasource-replace-reference.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Replace DataSource Reference\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A common practice when performing DataPrep is to build up a script or set of cleaning operations on a smaller example file locally. This is quicker and easier than dealing with large amounts of data initially.\n", + "\n", + "After building a Dataflow that performs the desired steps, it's time to run it against the larger dataset, which may be stored in the cloud, or even locally just in a different file. This is where we can use `Dataflow.replace_datasource` to get a Dataflow identical to the one built on the small data, but referencing the newly specified DataSource." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "\n", + "dflow = dprep.read_csv('../data/crime-spring.csv')\n", + "df = dflow.to_pandas_dataframe()\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we have the first 10 rows of a dataset called 'Crime'. The original dataset is over 100MB (admittedly not that large of a dataset but this is just an example).\n", + "\n", + "We'll perform a few cleaning operations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_dropped = dflow.drop_columns(['Location', 'Updated On', 'X Coordinate', 'Y Coordinate', 'Description'])\n", + "sctb = dflow_dropped.builders.set_column_types()\n", + "sctb.learn(inference_arguments=dprep.InferenceArguments(day_first=False))\n", + "dflow_typed = sctb.to_dataflow()\n", + "dflow_typed.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we have a Dataflow with all our desired steps, we're ready to run against the 'full' dataset stored in Azure Blob.\n", + "All we need to do is pass the BlobDataSource into `replace_datasource` and we'll get back an identical Dataflow with the new DataSource substituted in." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_replaced = dflow_typed.replace_datasource(dprep.BlobDataSource('https://dpreptestfiles.blob.core.windows.net/testfiles/crime0.csv'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "'replaced_dflow' will now pull data from the 168MB (729734 rows) version of Crime0.csv stored in Azure Blob!\n", + "\n", + "NOTE: Dataflows can also be created by referencing a different Dataflow. Instead of using `replace_datasource`, there is a corresponding `replace_reference` method.\n", + "\n", + "We should be careful now since pulling all that data down and putting it in a pandas dataframe isn't an ideal way to inspect the result of our Dataflow. So instead, to see that our steps are being applied to all the new data, we can add a `take_sample` step, which will select records at random (based on a given probability) to be returned.\n", + "\n", + "The probability below takes the ~730000 rows down to a more inspectable ~73, though the number will vary each time `to_pandas_dataframe()` is run, since they are being randomly selected based on the probability." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_random_sample= dflow_replaced.take_sample(probability=0.0001)\n", + "sample = dflow_random_sample.to_pandas_dataframe()\n", + "sample" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/replace-fill-error.ipynb b/work-with-data/dataprep/how-to-guides/replace-fill-error.ipynb new file mode 100644 index 00000000..04dad995 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/replace-fill-error.ipynb @@ -0,0 +1,239 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/replace-fill-error.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Replace, Fill, Error\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can use the methods in this notebook to change values in your dataset.\n", + "\n", + "* replace - use this method to replace a value with another value. You can also use this to replace null with a value, or a value with null\n", + "* error - use this method to replace a value with an error.\n", + "* fill_nulls - this method lets you fill all nulls in a column with a certain value.\n", + "* fill_errors - this method lets you fill all errors in a column with a certain value." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_csv('../data/crime-spring.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.to_datetime('Date', ['%m/%d/%Y %H:%M'])\n", + "dflow = dflow.to_number(['IUCR', 'District', 'FBI Code'])\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Replace " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### String\n", + "Use `replace` to swap a string value with another string value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.replace('Primary Type', 'THEFT', 'STOLEN')\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use `replace` to remove a certain string value from the column, replacing it with null. Note that Pandas shows null values as None." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.replace('Primary Type', 'DECEPTIVE PRACTICE', None)\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Numeric\n", + "Use `replace` to swap a numeric value with another numeric value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.replace('District', 5, 1)\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Date\n", + "Use `replace` to swap in a new Date for an existing Date in the data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from datetime import datetime, timezone\n", + "dflow = dflow.replace('Date', \n", + " datetime(2016, 4, 15, 9, 0, tzinfo=timezone.utc), \n", + " datetime(2018, 7, 4, 0, 0, tzinfo=timezone.utc))\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Error \n", + "\n", + "The `error` method lets you create Error values. You can pass to this function the value that you want to find, along with the Error code to use in any Errors created." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.error('IUCR', 890, 'Invalid value')\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fill Nulls \n", + "\n", + "Use the `fill_nulls` method to replace all null values in columns with another value. This is similar to Panda's fillna() method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.fill_nulls('Primary Type', 'N/A')\n", + "head = dflow.head(5)\n", + "head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fill Errors \n", + "\n", + "Use the `fill_errors` method to replace all error values in columns with another value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.fill_errors('IUCR', -1)\n", + "head = dflow.head(5)\n", + "head" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/secrets.ipynb b/work-with-data/dataprep/how-to-guides/secrets.ipynb new file mode 100644 index 00000000..c77169c9 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/secrets.ipynb @@ -0,0 +1,140 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/secrets.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Providing Secrets\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Currently, secrets are only persisted for the lifetime of the engine process. Even if the dataflow is saved to a file, the secrets are not persisted in the dprep file. If you started a new session (i.e. start a new engine process), loaded a dataflow and wanted to run it, you will need to call `use_secrets` to register the required secrets to use during execution, otherwise the execution will fail as the required secrets are not available.\n", + "\n", + "In this notebook, we will:\n", + "1. Loading a previously saved dataflow\n", + "2. Call `get_missing_secrets` to determine the missing secrets\n", + "3. Call `use_secrets` and pass in the missing secrets to register it with the engine for this session\n", + "4. Call `head` to see the a preview of the data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "\n", + "import os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's load the previously saved dataflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.Dataflow.open(file_path='../data/secrets.dprep')\n", + "dflow" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can call `get_missing_secrets` to see which required secrets are missing in the engine." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.get_missing_secrets()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can now read the secrets from an environment variable, put it in a secret dictionary, and call `use_secrets` with the secrets. This will register the secrets in the engine so you don't need to provide them again in this session.\n", + "\n", + "_Note: It is a bad practice to have secrets in files that will be checked into source control._" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sas = os.environ['SCENARIOS_SECRETS']\n", + "secrets = {\n", + " 'https://dpreptestfiles.blob.core.windows.net/testfiles/read_csv_duplicate_headers.csv': sas\n", + "}\n", + "dflow.use_secrets(secrets=secrets)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can now call `head` without passing in `secrets` and the engine will successfully execute. Here is a preview of the data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/semantic-types.ipynb b/work-with-data/dataprep/how-to-guides/semantic-types.ipynb new file mode 100644 index 00000000..266353df --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/semantic-types.ipynb @@ -0,0 +1,164 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/semantic-types.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Semantic Types\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Some string values can be recognized as semantic types. For example, email addresses, US zip codes or IP addresses have specific formats that can be recognized, and then split in specific ways.\n", + "\n", + "When getting a DataProfile you can optionally ask to collect counts of values recognized as semantic types. [`Dataflow.get_profile()`](./data-profile.ipynb) executes the Dataflow, calculates profile information, and returns a newly constructed DataProfile. Semantic type counts can be included in the data profile by calling `get_profile` with the `include_stype_counts` argument set to true.\n", + "\n", + "The `stype_counts` property of the DataProfile will then include entries for columns where some semantic types were recognized for some values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.read_json(path='../data/json.json')\n", + "\n", + "profile = dflow.get_profile(include_stype_counts=True)\n", + "\n", + "print(\"row count: \" + str(profile.row_count))\n", + "profile.stype_counts" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To see all the supported semantic types, you can examine the `SType` enumeration. More types will be added over time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "[t.name for t in dprep.SType]\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can filter the found semantic types down to just those where all non-empty values matched. The `DataProfile.stype_counts` gives a list of semantic type counts for each column, where at least some matches were found. Those lists are in desecending order of count, so here we consider only the first in each list, as that will be the one with the highest count of values that match.\n", + "\n", + "In this example, the column `inspections.business.postal_code` looks to be a US zip code." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stypes_counts = profile.stype_counts\n", + "all_match = [\n", + " (column, stypes_counts[column][0].stype)\n", + " for column in stypes_counts\n", + " if profile.row_count - profile.columns[column].empty_count == stypes_counts[column][0].count\n", + "]\n", + "all_match" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can use semantic types to compute new columns. The new columns are the values split up into elements, or canonicalized.\n", + "\n", + "Here we reduce our data down to just the `postal` column so we can better see what a `split_stype` operation can do." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_postal = dflow.keep_columns(['inspections.business.postal_code']).rename_columns({'inspections.business.postal_code': 'postal'})\n", + "dflow_postal.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With `SType.ZipCode`, values are split into their basic five digit zip code and the plus-four add-on of the Zip+4 format." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_split = dflow_postal.split_stype('postal', dprep.SType.ZIPCODE)\n", + "dflow_split.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`split_stype` also allows you to specify the fields of the stype to use and the name of the new columns. For example, if you just needed to strip the plus four from our zip codes, you could use this." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_no_plus4 = dflow_postal.split_stype('postal', dprep.SType.ZIPCODE, ['zip'], ['zipNoPlus4'])\n", + "dflow_no_plus4.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/split-column-by-example.ipynb b/work-with-data/dataprep/how-to-guides/split-column-by-example.ipynb new file mode 100644 index 00000000..9b709207 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/split-column-by-example.ipynb @@ -0,0 +1,220 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/split-column-by-example.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Split column by example\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "DataPrep also offers you a way to easily split a column into multiple columns.\n", + "The SplitColumnByExampleBuilder class lets you generate a proper split program that will work even when the cases are not trivial, like in example below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.read_lines(path='../data/crime.txt')\n", + "df = dflow.head(10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df['Line'].iloc[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see above, you can't split this particular file by space character as it will create too many columns.\n", + "That's where split_column_by_example could be quite useful." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder = dflow.builders.split_column_by_example('Line', keep_delimiters=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.preview()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Couple things to take note of here. No examples were given, and yet DataPrep was able to generate quite reasonable split program. \n", + "We have passed keep_delimiters=True so we can see all the data split into columns. In practice, though, delimiters are rarely useful, so let's exclude them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.keep_delimiters = False\n", + "builder.preview()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This looks pretty good already, except that one case number is split into 2 columns. Taking the first row as an example, we want to keep case number as \"HY329907\" instead of \"HY\" and \"329907\" seperately. \n", + "If we request generation of suggested examples we will get a list of examples that require input." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "suggestions = builder.generate_suggested_examples()\n", + "suggestions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "suggestions.iloc[0]['Line']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Having retrieved source value we can now provide an example of desired split.\n", + "Notice that we chose not to split date and time but rather keep them together in one column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.add_example(example=(suggestions['Line'].iloc[0], ['10140490','HY329907','7/5/2015 23:50','050XX N NEWLAND AVE','820','THEFT']))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.preview()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see from the preview, some of the crime types (`Line_6`) do not show up as expected. Let's try to add one more example. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "builder.add_example(example=(df['Line'].iloc[1],['10139776','HY329265','7/5/2015 23:30','011XX W MORSE AVE','460','BATTERY']))\n", + "builder.preview()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This looks just like what we need. Let's get a dataflow with splited columns and drop original column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = builder.to_dataflow()\n", + "dflow = dflow.drop_columns(['Line'])\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we have successfully split the data into useful columns through examples. " + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/subsetting-sampling.ipynb b/work-with-data/dataprep/how-to-guides/subsetting-sampling.ipynb new file mode 100644 index 00000000..d1abb62f --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/subsetting-sampling.ipynb @@ -0,0 +1,217 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/subsetting-sampling.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Sampling and Subsetting\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once a Dataflow has been created, it is possible to act on only a subset of the records contained in it. This can help when working with very large datasets or when only a portion of the records is truly relevant." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Head\n", + "\n", + "The `head` method will take the number of records specified, run them through the transformations in the Dataflow, and then return the result as a Pandas dataframe." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "\n", + "dflow = dprep.read_csv('../data/crime_duplicate_headers.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Take\n", + "\n", + "The `take` method adds a step to the Dataflow that will keep the number of records specified (counting from the beginning) and drop the rest. Unlike `head`, which does not modify the Dataflow, all operations applied on a Dataflow on which `take` has been applied will affect only the records kept." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_top_five = dflow.take(5)\n", + "dflow_top_five.to_pandas_dataframe()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Skip\n", + "\n", + "It is also possible to skip a certain number of records in a Dataflow, such that transformations are only applied after a specific point. Depending on the underlying data source, a Dataflow with a `skip` step might still have to scan through the data in order to skip past the records." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_skip_top_one = dflow_top_five.skip(1)\n", + "dflow_skip_top_one.to_pandas_dataframe()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Take Sample\n", + "\n", + "In addition to taking records from the top of the dataset, it's also possible to take a random sample of the dataset. This is done through the `take_sample(probability, seed=None)` method. This method will scan through all of the records available in the Dataflow and include them based on the probability specified. The `seed` parameter is optional. If a seed is not provided, a stable one is generated, ensuring that the results for a specific Dataflow remain consistent. Different calls to `take_sample` will receive different seeds." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_sampled = dflow.take_sample(0.1)\n", + "dflow_sampled.to_pandas_dataframe()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`skip`, `take`, and `take_sample` can all be combined. With this, we can achieve behaviors like getting a random 10% sample fo the middle N records of a dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "seed = 1\n", + "dflow_nested_sample = dflow.skip(1).take(5).take_sample(0.5, seed)\n", + "dflow_nested_sample.to_pandas_dataframe()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Take Stratified Sample\n", + "Besides sampling all by a probability, we also have stratified sampling, provided the strata and strata weights, the probability to sample each stratum with.\n", + "This is done through the `take_stratified_sample(columns, fractions, seed=None)` method.\n", + "For all records, we will group each record by the columns specified to stratify, and based on the stratum x weight information in `fractions`, include said record.\n", + "\n", + "Seed behavior is same as in `take_sample`.\n", + "\n", + "If a stratum is not specified or the record cannot be grouped by said stratum, we default the weight to sample by to 0 (it will not be included).\n", + "\n", + "The order of `fractions` must match the order of `columns`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fractions = {}\n", + "fractions[('ASSAULT',)] = 0.5\n", + "fractions[('BATTERY',)] = 0.2\n", + "fractions[('ARSON',)] = 0.5\n", + "fractions[('THEFT',)] = 1.0\n", + "\n", + "columns = ['Primary Type']\n", + "\n", + "single_strata_sample = dflow.take_stratified_sample(columns=columns, fractions = fractions, seed = 42)\n", + "single_strata_sample.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Stratified sampling on multiple columns is also supported." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fractions = {}\n", + "fractions[('ASSAULT', '560')] = 0.5\n", + "fractions[('BATTERY', '460')] = 0.2\n", + "fractions[('ARSON', '1020')] = 0.5\n", + "fractions[('THEFT', '820')] = 1.0\n", + "\n", + "columns = ['Primary Type', 'IUCR']\n", + "\n", + "multi_strata_sample = dflow.take_stratified_sample(columns=columns, fractions = fractions, seed = 42)\n", + "multi_strata_sample.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Caching\n", + "It is usually a good idea to cache the sampled Dataflow for later uses.\n", + "\n", + "See [here](cache.ipynb) for more details about caching." + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/summarize.ipynb b/work-with-data/dataprep/how-to-guides/summarize.ipynb new file mode 100644 index 00000000..56a37bee --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/summarize.ipynb @@ -0,0 +1,590 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/summarize.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Summarize\n", + "\n", + "Azure ML Data Prep can help summarize your data by providing you a synopsis based on aggregates over specific columns.\n", + "\n", + "## Table of Contents\n", + "[Overview](#overview)
\n", + "[Summmary Functions](#summary)
\n", + "* [SummaryFunction.MIN](#min)
\n", + "* [SummaryFunction.MAX](#max)
\n", + "* [SummaryFunction.MEAN](#mean)
\n", + "* [SummaryFunction.MEDIAN](#median)
\n", + "* [SummaryFunction.VAR](#var)
\n", + "* [SummaryFunction.SD](#sd)
\n", + "* [SummaryFunction.COUNT](#count)
\n", + "* [SummaryFunction.SUM](#sum)
\n", + "* [SummaryFunction.SKEWNESS](#skewness)
\n", + "* [SummaryFunction.KURTOSIS](#kurtosis)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Overview\n", + "Before we drill down into each aggregate function, let us observe `summarize` end to end.\n", + "\n", + "We will start by reading some data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we count (`SummaryFunction.COUNT`) the number of rows with column ID with non-null values grouped by Primary Type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_summarize = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID',\n", + " summary_column_name='Primary Type ID Counts', \n", + " summary_function=dprep.SummaryFunction.COUNT)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_summarize.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we choose to not group by anything, we will instead get a single record over the entire dataset. Here we will get the number of rows that have the column ID with non-null values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_summarize_nogroup = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID',\n", + " summary_column_name='ID Count', \n", + " summary_function=dprep.SummaryFunction.COUNT)])\n", + "dflow_summarize_nogroup.head(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Conversely, we can group by multiple columns." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_summarize_2group = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID',\n", + " summary_column_name='Primary Type & Location Description ID Counts', \n", + " summary_function=dprep.SummaryFunction.COUNT)],\n", + " group_by_columns=['Primary Type', 'Location Description'])\n", + "dflow_summarize_2group.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In a similar vein, we can compute multiple aggregates in a single summary. Each aggregate function is independent and it is possible to aggregate the same column multiple times." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_summarize_multi_agg = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID',\n", + " summary_column_name='Primary Type ID Counts', \n", + " summary_function=dprep.SummaryFunction.COUNT),\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID',\n", + " summary_column_name='Primary Type Min ID', \n", + " summary_function=dprep.SummaryFunction.MIN),\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Date',\n", + " summary_column_name='Primary Type Max Date', \n", + " summary_function=dprep.SummaryFunction.MAX)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_summarize_multi_agg.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we wanted this summary data back into our original data set, we can make use of `join_back` and optionally `join_back_columns_prefix` for easy naming distinctions. Summary columns will be added to the end. `group_by_columns` is not necessary for using `join_back`, however the behavior will be more like an append instead of a join." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_summarize_join = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID',\n", + " summary_column_name='Primary Type ID Counts', \n", + " summary_function=dprep.SummaryFunction.COUNT)],\n", + " group_by_columns=['Primary Type'],\n", + " join_back=True,\n", + " join_back_columns_prefix='New_')\n", + "dflow_summarize_join.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary Functions\n", + "Here we will go over all the possible aggregates in Data Prep.\n", + "The most up to date set of functions can be found by enumerating the `SummaryFunction` enum." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "[x.name for x in dprep.SummaryFunction]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.MIN\n", + "Data Prep can aggregate and find the minimum value of a column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Date',\n", + " summary_column_name='Primary Type Min Date', \n", + " summary_function=dprep.SummaryFunction.MIN)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.MAX\n", + "Data Prep can find the maximum value of a column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Date',\n", + " summary_column_name='Primary Type Max Date', \n", + " summary_function=dprep.SummaryFunction.MAX)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.MEAN\n", + "Data Prep can find the statistical mean of a column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Latitude',\n", + " summary_column_name='Primary Type Latitude Mean', \n", + " summary_function=dprep.SummaryFunction.MEAN)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.MEDIAN\n", + "Data Prep can find the median value of a column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Latitude',\n", + " summary_column_name='Primary Type Latitude Median', \n", + " summary_function=dprep.SummaryFunction.MEDIAN)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.VAR\n", + "Data Prep can find the statistical variance of a column. We will need more than one data point to calculate this, otherwise we will be unable to give results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Latitude',\n", + " summary_column_name='Primary Type Latitude Variance', \n", + " summary_function=dprep.SummaryFunction.VAR)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that despite there being two cases of BATTERY, one of them is missing geographical location, thus only CRIMINAL DAMAGE can yield variance information. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.SD\n", + "Data Prep can find the standard deviation of a column. We will need more than one data point to calculate this, otherwise we will be unable to give results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Latitude',\n", + " summary_column_name='Primary Type Latitude Standard Deviation', \n", + " summary_function=dprep.SummaryFunction.SD)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similar to when we calculate variance, despite there being two cases of BATTERY, one of them is missing geographical location, thus only CRIMINAL DAMAGE can yield variance information. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.COUNT\n", + "Data Prep can count the number of rows that have a column with non-null values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Latitude',\n", + " summary_column_name='Primary Type Latitude Count', \n", + " summary_function=dprep.SummaryFunction.COUNT)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that despite there being two cases of BATTERY, one of them is missing geographical location, thus when we group by Primary Type, we only get a count of one for Latitude." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.SUM\n", + "Data Prep can aggregate and sum the values of a column. Our dataset does not have many numerical facts, but here we sum IDs grouped by Primary Type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID',\n", + " summary_column_name='Primary Type ID Sum', \n", + " summary_function=dprep.SummaryFunction.SUM)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.SKEWNESS\n", + "Data Prep can calculate the skewness of data in a column. We will need more than one data point to calculate this, otherwise we will be unable to give results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Latitude',\n", + " summary_column_name='Primary Type Latitude Skewness', \n", + " summary_function=dprep.SummaryFunction.SKEWNESS)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### SummaryFunction.KURTOSIS\n", + "Data Prep can calculate the kurtosis of data in a column. We will need more than one data point to calculate this, otherwise we will be unable to give results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "dflow = dprep.auto_read_file(path='../data/crime-dirty.csv')\n", + "dflow_min = dflow.summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='Latitude',\n", + " summary_column_name='Primary Type Latitude Kurtosis', \n", + " summary_function=dprep.SummaryFunction.KURTOSIS)],\n", + " group_by_columns=['Primary Type'])\n", + "dflow_min.head(10)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/working-with-file-streams.ipynb b/work-with-data/dataprep/how-to-guides/working-with-file-streams.ipynb new file mode 100644 index 00000000..e92c1e1c --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/working-with-file-streams.ipynb @@ -0,0 +1,192 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/working-with-file-streams.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Working With File Streams\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In addition to loading and parsing tabular data (see [here](./data-ingestion.ipynb) for more details), Data Prep also supports a variety of operations on raw file streams. \n", + "\n", + "File streams are usually created by calling `Dataflow.get_files`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.Dataflow.get_files(path='../data/*.csv')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The result of this operation is a Dataflow with a single column named \"Path\". This column contains values of type `StreamInfo`, each of which represents a different file matched by the search pattern specified when calling `get_files`. The string representation of a `StreamInfo` follows this pattern:\n", + "\n", + "StreamInfo(_Location_://_ResourceIdentifier_\\[_Arguments_\\])\n", + "\n", + "Location is the type of storage where the stream is located (e.g. Azure Blob, Local, or ADLS); ResouceIdentifier is the name of the file within that storage, such as a file path; and Arguments is a list of arguments required to load and read the file.\n", + "\n", + "On their own, `StreamInfo` objects are not particularly useful; however, you can use them as input to other functions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieving File Names\n", + "\n", + "In the example above, we matched a set of CSV files by using a search pattern and got back a column with several `StreamInfo` objects, each representing a different file. Now, we will extract the file path and name for each of these values into a new string column." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dflow.add_column(expression=dprep.get_stream_name(dflow['Path']),\n", + " new_column_name='FilePath',\n", + " prior_column='Path')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `get_stream_name` function will return the full name of the file referenced by a `StreamInfo`. In the case of a local file, this will be an absolute path. From here, you can use the `derive_column_by_example` method to extract just the file name." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "first_file_path = dflow.head(1)['FilePath'][0]\n", + "first_file_name = os.path.basename(first_file_path)\n", + "dflow = dflow.derive_column_by_example(new_column_name='FileName',\n", + " source_columns=['FilePath'],\n", + " example_data=(first_file_path, first_file_name))\n", + "dflow = dflow.drop_columns(['FilePath'])\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Writing Streams\n", + "\n", + "Whenever you have a column containing `StreamInfo` objects, it's possible to write these out to any of the locations Data Prep supports. You can do this by calling `Dataflow.write_streams`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.write_streams(streams_column='Path', base_path=dprep.LocalFileOutput('./test_out/')).run_local()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `base_path` parameter specifies the location the files will be written to. By default, the name of the file will be the resource identifier of the stream with any invalid characters replaced by `_`. In the case of streams referencing local files, this would be the full path of the original file. You can also specify the desired file names by referencing a column containing them:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow.write_streams(streams_column='Path',\n", + " base_path=dprep.LocalFileOutput('./test_out/'),\n", + " file_names_column='FileName').run_local()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using this functionality, you can transfer files from any source to any destination supported by Data Prep. In addition, since the streams are just values in the Dataflow, you can use all of the functionality available.\n", + "\n", + "Here, for example, we will write out only the files that start with the prefix \"crime-\". The resulting file names will have the prefix stripped and will be written to a folder named \"crime\"." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "prefix = 'crime-'\n", + "dflow = dflow.filter(dflow['FileName'].starts_with(prefix))\n", + "dflow = dflow.add_column(expression=dflow['FileName'].substring(len(prefix)),\n", + " new_column_name='CleanName',\n", + " prior_column='FileName')\n", + "dflow.write_streams(streams_column='Path',\n", + " base_path=dprep.LocalFileOutput('./test_out/crime/'),\n", + " file_names_column='CleanName').run_local()" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/how-to-guides/writing-data.ipynb b/work-with-data/dataprep/how-to-guides/writing-data.ipynb new file mode 100644 index 00000000..bfbe3865 --- /dev/null +++ b/work-with-data/dataprep/how-to-guides/writing-data.ipynb @@ -0,0 +1,183 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/how-to-guides/writing-data.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Writing Data\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is possible to write out the data at any point in a Dataflow. These writes are added as steps to the resulting Dataflow and will be executed every time the Dataflow is executed. Since there are no limitations to how many write steps there are in a pipeline, this makes it easy to write out intermediate results for troubleshooting or to be picked up by other pipelines.\n", + "\n", + "It is important to note that the execution of each write results in a full pull of the data in the Dataflow. For example, a Dataflow with three write steps will read and process every record in the dataset three times." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Writing to Files" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Data can be written to files in any of our supported locations (Local File System, Azure Blob Storage, and Azure Data Lake Storage). In order to parallelize the write, the data is written to multiple partition files. A sentinel file named SUCCESS is also output once the write has completed. This makes it possible to identify when an intermediate write has completed without having to wait for the whole pipeline to complete.\n", + "\n", + "> When running a Dataflow in Spark, attempting to execute a write to an existing folder will fail. It is important to ensure the folder is empty or use a different target location per execution.\n", + "\n", + "The following file formats are currently supported:\n", + "- Delimited Files (CSV, TSV, etc.)\n", + "- Parquet Files" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We'll start by loading data into a Dataflow which will be re-used with different formats." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow = dprep.auto_read_file('../data/crime.txt')\n", + "dflow = dflow.to_number('Column2')\n", + "dflow.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delimited Files" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we create a dataflow with a write step.\n", + "\n", + "This operation is lazy until we invoke `run_local` (or any operation that forces execution like `to_pandas_dataframe`), only then will we execute the write operation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_write = dflow.write_to_csv(directory_path=dprep.LocalFileOutput('./test_out/'))\n", + "\n", + "dflow_write.run_local()\n", + "\n", + "dflow_written_files = dprep.read_csv('./test_out/part-*')\n", + "dflow_written_files.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The data we wrote out contains several errors in the numeric columns due to numbers that we were unable to parse. When written out to CSV, these are replaced with the string \"ERROR\" by default. We can parameterize this as part of our write call. In the same vein, it is also possible to set what string to use to represent null values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_write_errors = dflow.write_to_csv(directory_path=dprep.LocalFileOutput('./test_out/'), \n", + " error='BadData',\n", + " na='NA')\n", + "dflow_write_errors.run_local()\n", + "dflow_written = dprep.read_csv('./test_out/part-*')\n", + "dflow_written.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Parquet Files" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similar to `write_to_csv`, `write_to_parquet` returns a new Dataflow with a Write Parquet Step which hasn't been executed yet.\n", + "\n", + "Then we run the Dataflow with `run_local`, which executes the write operation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dflow_write_parquet = dflow.write_to_parquet(directory_path=dprep.LocalFileOutput('./test_parquet_out/'),\n", + " error='MiscreantData')\n", + "\n", + "dflow_write_parquet.run_local()\n", + "\n", + "dflow_written_parquet = dprep.read_parquet_file('./test_parquet_out/part-*')\n", + "dflow_written_parquet.head(5)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/dataprep/tutorials/getting-started/getting-started.ipynb b/work-with-data/dataprep/tutorials/getting-started/getting-started.ipynb new file mode 100644 index 00000000..729af0db --- /dev/null +++ b/work-with-data/dataprep/tutorials/getting-started/getting-started.ipynb @@ -0,0 +1,441 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Getting started with Azure ML Data Prep SDK\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Wonder how you can make the most of the Azure ML Data Prep SDK? In this \"Getting Started\" guide, we'll demonstrate how to do your normal data wrangling with this SDK and showcase a few highlights that make this SDK shine. Using a sample of this [Kaggle crime dataset](https://www.kaggle.com/currie32/crimes-in-chicago/home) as an example, we'll cover how to:\n", + "\n", + "* [Read in data](#Read)\n", + "* [Profile your data](#Profile)\n", + "* [Append rows](#Append)\n", + "* [Apply common data science transforms](#Data-science-transforms)\n", + " * [Summarize](#Summarize)\n", + " * [Join](#Join)\n", + " * [Filter](#Filter)\n", + " * [Replace](#Replace)\n", + "* [Consume your cleaned dataset](#Consume)\n", + "* [Explore advanced features](#Explore)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import display\n", + "from os import path\n", + "from tempfile import mkdtemp\n", + "\n", + "import pandas as pd\n", + "import azureml.dataprep as dprep\n", + "\n", + "# Paths for datasets\n", + "file_crime_dirty = '../../data/crime-dirty.csv'\n", + "file_crime_spring = '../../data/crime-spring.csv'\n", + "file_crime_winter = '../../data/crime-winter.csv'\n", + "file_aldermen = '../../data/chicago-aldermen-2015.csv'\n", + "\n", + "# Seed\n", + "RAND_SEED = 7251" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read in data\n", + "\n", + "Azure ML Data Prep supports many different file reading formats (i.e. CSV, Excel, Parquet) and the ability to infer column types automatically. To see how powerful the `auto_read_file` capability is, let's take a peek at the `dirty-crime.csv`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dprep.read_csv(path=file_crime_dirty).head(7)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A common occurrence in many datasets is to have a column of values with commas; in our case, the last column represents location in the form of longitude-latitude pair. The default CSV reader interprets this comma as a delimiter and thus splits the data into two columns. Furthermore, it incorrectly reads in the header as the column name. Normally, we would need to `skip` the header and specify the delimiter as `|`, but our `auto_read_file` eliminates that work:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "crime_dirty = dprep.auto_read_file(path=file_crime_dirty)\n", + "\n", + "crime_dirty.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Advanced features:__ if you'd like to specify the file type and adjust how you want to read files in, you can see the list of our specialized file readers and how to use them [here](../../how-to-guides/data-ingestion.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Profile your data\n", + "\n", + "Let's understand what our data looks like. Azure ML Data Prep facilitates this process by offering data profiles that help us glimpse into column types and column summary statistics. Notice that our auto file reader automatically guessed the column type:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "crime_dirty.get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Append rows\n", + "\n", + "What if your data is split across multiple files? We support the ability to append multiple datasets column-wise and row-wise. Here, we demonstrate how you can coalesce datasets row-wise:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Datasets with the same schema as crime_dirty\n", + "crime_winter = dprep.auto_read_file(path=file_crime_winter)\n", + "crime_spring = dprep.auto_read_file(path=file_crime_spring)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "crime = (crime_dirty.append_rows(dataflows=[crime_winter, crime_spring]))\n", + "\n", + "crime.take_sample(probability=0.25, seed=RAND_SEED).head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Advanced features:__ you can learn how to append column-wise and how to deal with appending data with different schemas [here](../../how-to-guides/append-columns-and-rows.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Apply common data science transforms\n", + "\n", + "Azure ML Data Prep supports almost all common data science transforms found in other industry-standard data science libraries. Here, we'll explore the ability to `summarize`, `join`, `filter`, and `replace`. \n", + "\n", + "__Advanced features:__\n", + "* We also provide \"smart\" transforms not found in pandas that use machine learning to [derive new columns](../../how-to-guides/derive-column-by-example.ipynb), [split columns](../../how-to-guides/split-column-by-example.ipynb), and [fuzzy grouping](../../how-to-guides/fuzzy-group.ipynb).\n", + "* Finally, we also help featurize your dataset to prepare it for machine learning; learn more about our featurizers like [one-hot encoder](../../how-to-guides/one-hot-encoder.ipynb), [label encoder](../../how-to-guides/label-encoder.ipynb), [min-max scaler](../../how-to-guides/min-max-scaler.ipynb), and [random (train-test) split](../../how-to-guides/random-split.ipynb).\n", + "* Our complete list of example Notebooks for transforms can be found in our [How-to Guides](../../how-to-guides)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Summarize\n", + "\n", + "Let's see which wards had the most crimes in our sample dataset:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "crime_summary = (crime\n", + " .summarize(\n", + " summary_columns=[\n", + " dprep.SummaryColumnsValue(\n", + " column_id='ID', \n", + " summary_column_name='total_ward_crimes', \n", + " summary_function=dprep.SummaryFunction.COUNT\n", + " )\n", + " ],\n", + " group_by_columns=['Ward']\n", + " )\n", + ")\n", + "\n", + "(crime_summary\n", + " .sort(sort_order=[('total_ward_crimes', True)])\n", + " .head(5)\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Join\n", + "\n", + "Let's annotate each observation with more information about the ward where the crime occurred. Let's do so by joining `crime` with a dataset which lists the current aldermen for each ward:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aldermen = dprep.auto_read_file(path=file_aldermen)\n", + "\n", + "aldermen.head(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "crime.join(\n", + " left_dataflow=crime,\n", + " right_dataflow=aldermen,\n", + " join_key_pairs=[\n", + " ('Ward', 'Ward')\n", + " ]\n", + ").head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Advanced features:__ [Learn more](../../how-to-guides/join.ipynb) about how you can do all variants of `join`, like inner-, left-, right-, anti-, and semi-joins." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Filter\n", + "\n", + "Let's look at theft crimes:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "theft = crime.filter(crime['Primary Type'] == 'THEFT')\n", + "\n", + "theft.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Replace\n", + "\n", + "Notice that our `theft` dataset has empty strings in column `Location`. Let's replace those with a missing value:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "theft_replaced = (theft\n", + " .replace_na(\n", + " columns=['Location'], \n", + " use_empty_string_as_na=True\n", + " )\n", + ")\n", + "\n", + "theft_replaced.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Advanced features:__ [Learn more](../../how-to-guides/replace-fill-error.ipynb) about more advanced `replace` and `fill` capabilities." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Consume your cleaned dataset\n", + "\n", + "Azure ML Data Prep allows you to \"choose your own adventure\" once you're done wrangling. You can:\n", + "\n", + "1. Write to a pandas dataframe\n", + "2. Execute on Spark\n", + "3. Consume directly in Azure Machine Learning models\n", + "\n", + "In this quickstart guide, we'll show how you can export to a pandas dataframe.\n", + "\n", + "__Advanced features:__ \n", + "* One of the beautiful features of Azure ML Data Prep is that you only need to write your code once and choose whether to scale up or out.\n", + "* You can directly consume your new DataFlow in model builders like Azure Machine Learning's [automated machine learning](https://github.com/Azure/MachineLearningNotebooks/blob/master/how-to-use-azureml/automated-machine-learning/dataprep/auto-ml-dataprep.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "theft_replaced.to_pandas_dataframe()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Explore advanced features\n", + "\n", + "Congratulations on finishing your introduction to the Azure ML Data Prep SDK! If you'd like more detailed tutorials on how to construct machine learning datasets or dive deeper into all of its functionality, you can find more information in our detailed notebooks [here](https://github.com/Microsoft/PendletonDocs). There, we cover topics including how to:\n", + "\n", + "* [Cache your Dataflow to speed up your iterations](../../how-to-guides/cache.ipynb)\n", + "* [Add your custom Python transforms](../../how-to-guides/custom-python-transforms.ipynb)\n", + "* [Impute missing values](../../how-to-guides/impute-missing-values.ipynb)\n", + "* [Sample your data](../../how-to-guides/subsetting-sampling.ipynb)\n", + "* [Reference and link between Dataflows](../../how-to-guides/join.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/dataprep/tutorials/getting-started/getting-started.png)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "sihhu" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.2" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/datasets/README.md b/work-with-data/datasets/README.md new file mode 100644 index 00000000..26b22054 --- /dev/null +++ b/work-with-data/datasets/README.md @@ -0,0 +1,159 @@ +# Azure Machine Learning Datasets (preview) + +Azure Machine Learning Datasets (preview) make it easier to access and work with your data. Datasets manage data in various scenarios such as model training and pipeline creation. Using the Azure Machine Learning SDK, you can access underlying storage, explore and prepare data, manage the life cycle of different Dataset definitions, and compare between Datasets used in training and in production. + +## Create and Register Datasets + +It's easy to create Datasets from either local files, or Azure Datastores. + +```Python +from azureml.core.workspace import Workspace +from azureml.core.datastore import Datastore +from azureml.core.dataset import Dataset + +datastore_name = 'your datastore name' + +# get existing workspace +workspace = Workspace.from_config() + +# get Datastore from the workspace +dstore = Datastore.get(workspace, datastore_name) + +# create an in-memory Dataset on your local machine +dataset = Dataset.from_delimited_files(dstore.path('data/src/crime.csv')) +``` + +To consume Datasets across various scenarios in Azure Machine Learning service such as automated machine learning, model training and pipeline creation, you need to register the Datasets with your workspace. By doing so, you can also share and reuse the Datasets within your organization. + +```Python +dataset = dataset.register(workspace = workspace, + name = 'dataset_crime', + description = 'Training data' + ) +``` + +## Sampling + +Sampling can be particular useful with Datasets that are too large to efficiently analyze in full. It enables data scientists to work with a manageable amount of data to build and train machine learning models. At this time, the [`sample()`](https://docs.microsoft.com//python/api/azureml-core/azureml.core.dataset(class)?view=azure-ml-py#sample-sample-strategy--arguments-) method from the Dataset class supports Top N, Simple Random, and Stratified sampling strategies. + +After sampling, you can convert your sampled Dataset to pandas DataFrame for training. By using the native [`sample()`](https://docs.microsoft.com//python/api/azureml-core/azureml.core.dataset(class)?view=azure-ml-py#sample-sample-strategy--arguments-) method from the Dataset class, you will load the sampled data on the fly instead of loading full data into memory. + +### Top N sample + +For Top N sampling, the first n records of your Dataset are your sample. This is helpful if you are just trying to get an idea of what your data records look like or to see what fields are in your data. + +```Python +top_n_sample_dataset = dataset.sample('top_n', {'n': 5}) +top_n_sample_dataset.to_pandas_dataframe() +``` + +### Simple random sample + +In Simple Random sampling, every member of the data population has an equal chance of being selected as a part of the sample. In the `simple_random` sample strategy, the records from your Dataset are selected based on the probability specified and returns a modified Dataset. The seed parameter is optional. + +```Python +simple_random_sample_dataset = dataset.sample('simple_random', {'probability':0.3, 'seed': seed}) +simple_random_sample_dataset.to_pandas_dataframe() +``` + +### Stratified sample + +Stratified samples ensure that certain groups of a population are represented in the sample. In the `stratified` sample strategy, the population is divided into strata, or subgroups, based on similarities, and records are randomly selected from each strata according to the strata weights indicated by the `fractions` parameter. + +In the following example, we group each record by the specified columns, and include said record based on the strata X weight information in `fractions`. If a strata is not specified or the record cannot be grouped, the default weight to sample is 0. + +```Python +# take 50% of records with `Primary Type` as `THEFT` and 20% of records with `Primary Type` as `DECEPTIVE PRACTICE` into sample Dataset +fractions = {} +fractions[('THEFT',)] = 0.5 +fractions[('DECEPTIVE PRACTICE',)] = 0.2 + +sample_dataset = dataset.sample('stratified', {'columns': ['Primary Type'], 'fractions': fractions, 'seed': seed}) + +sample_dataset.to_pandas_dataframe() +``` + +## Explore with summary statistics + + Detect anomalies, missing values, or error counts with the [`get_profile()`](https://docs.microsoft.com/python/api/azureml-core/azureml.core.dataset.dataset?view=azure-ml-py#get-profile-arguments-none--generate-if-not-exist-true--workspace-none--compute-target-none-) method. This function gets the profile and summary statistics of your data, which in turn helps determine the necessary data preparation operations to apply. + +```Python +# get pre-calculated profile +# if there is no precalculated profile available or the precalculated profile is not up-to-date, this method will generate a new profile of the Dataset +dataset.get_profile() +``` + +||Type|Min|Max|Count|Missing Count|Not Missing Count|Percent missing|Error Count|Empty count|0.1% Quantile|1% Quantile|5% Quantile|25% Quantile|50% Quantile|75% Quantile|95% Quantile|99% Quantile|99.9% Quantile|Mean|Standard Deviation|Variance|Skewness|Kurtosis +-|----|---|---|-----|-------------|-----------------|---------------|-----------|-----------|-------------|-----------|-----------|------------|------------|------------|------------|------------|--------------|----|------------------|--------|--------|-------- +ID|FieldType.INTEGER|1.04986e+07|1.05351e+07|10.0|0.0|10.0|0.0|0.0|0.0|1.04986e+07|1.04992e+07|1.04986e+07|1.05166e+07|1.05209e+07|1.05259e+07|1.05351e+07|1.05351e+07|1.05351e+07|1.05195e+07|12302.7|1.51358e+08|-0.495701|-1.02814 +Case Number|FieldType.STRING|HZ239907|HZ278872|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +Date|FieldType.DATE|2016-04-04 23:56:00+00:00|2016-04-15 17:00:00+00:00|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +Block|FieldType.STRING|004XX S KILBOURN AVE|113XX S PRAIRIE AVE|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +IUCR|FieldType.INTEGER|810|1154|10.0|0.0|10.0|0.0|0.0|0.0|810|850|810|890|1136|1153|1154|1154|1154|1058.5|137.285|18847.2|-0.785501|-1.3543 +Primary Type|FieldType.STRING|DECEPTIVE PRACTICE|THEFT|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +Description|FieldType.STRING|BOGUS CHECK|OVER $500|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +Location Description|FieldType.STRING||SCHOOL, PUBLIC, BUILDING|10.0|0.0|10.0|0.0|0.0|1.0|||||||||||||| +Arrest|FieldType.BOOLEAN|False|False|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +Domestic|FieldType.BOOLEAN|False|False|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +Beat|FieldType.INTEGER|531|2433|10.0|0.0|10.0|0.0|0.0|0.0|531|531|531|614|1318.5|1911|2433|2433|2433|1371.1|692.094|478994|0.105418|-1.60684 +District|FieldType.INTEGER|5|24|10.0|0.0|10.0|0.0|0.0|0.0|5|5|5|6|13|19|24|24|24|13.5|6.94822|48.2778|0.0930109|-1.62325 +Ward|FieldType.INTEGER|1|48|10.0|0.0|10.0|0.0|0.0|0.0|1|5|1|9|22.5|40|48|48|48|24.5|16.2635|264.5|0.173723|-1.51271 +Community Area|FieldType.INTEGER|4|77|10.0|0.0|10.0|0.0|0.0|0.0|4|8.5|4|24|37.5|71|77|77|77|41.2|26.6366|709.511|0.112157|-1.73379 +FBI Code|FieldType.INTEGER|6|11|10.0|0.0|10.0|0.0|0.0|0.0|6|6|6|6|11|11|11|11|11|9.4|2.36643|5.6|-0.702685|-1.59582 +X Coordinate|FieldType.INTEGER|1.16309e+06|1.18336e+06|10.0|7.0|3.0|0.7|0.0|0.0|1.16309e+06|1.16309e+06|1.16309e+06|1.16401e+06|1.16678e+06|1.17921e+06|1.18336e+06|1.18336e+06|1.18336e+06|1.17108e+06|10793.5|1.165e+08|0.335126|-2.33333 +Y Coordinate|FieldType.INTEGER|1.8315e+06|1.908e+06|10.0|7.0|3.0|0.7|0.0|0.0|1.8315e+06|1.8315e+06|1.8315e+06|1.83614e+06|1.85005e+06|1.89352e+06|1.908e+06|1.908e+06|1.908e+06|1.86319e+06|39905.2|1.59243e+09|0.293465|-2.33333 +Year|FieldType.INTEGER|2016|2016|10.0|0.0|10.0|0.0|0.0|0.0|2016|2016|2016|2016|2016|2016|2016|2016|2016|2016|0|0|NaN|NaN +Updated On|FieldType.DATE|2016-05-11 15:48:00+00:00|2016-05-27 15:45:00+00:00|10.0|0.0|10.0|0.0|0.0|0.0|||||||||||||| +Latitude|FieldType.DECIMAL|41.6928|41.9032|10.0|7.0|3.0|0.7|0.0|0.0|41.6928|41.6928|41.6928|41.7057|41.7441|41.8634|41.9032|41.9032|41.9032|41.78|0.109695|0.012033|0.292478|-2.33333 +Longitude|FieldType.DECIMAL|-87.6764|-87.6043|10.0|7.0|3.0|0.7|0.0|0.0|-87.6764|-87.6764|-87.6764|-87.6734|-87.6645|-87.6194|-87.6043|-87.6043|-87.6043|-87.6484|0.0386264|0.001492|0.344429|-2.33333 +Location|FieldType.STRING||(41.903206037, -87.676361925)|10.0|0.0|10.0|0.0|0.0|7.0|||||||||||||| + + +## Training with Dataset + +Now that you have registered your Dataset, you can call up the Dataset and convert it to pandas DataFrame or Spark DataFrame easily in your train.py script. + +```Python +# Sample train.py script +import azureml.core +import pandas as pd +import datetime +import shutil +from azureml.core import Workspace, Datastore, Dataset, Experiment, Run +from sklearn.model_selection import train_test_split +from azureml.core.compute import ComputeTarget, AmlCompute +from azureml.core.compute_target import ComputeTargetException +from sklearn.tree import DecisionTreeClassifier + +run = Run.get_context() +workspace = run.experiment.workspace + +# Access Dataset registered with the workspace by name +dataset_name = 'training_data' +dataset = Dataset.get(workspace=workspace, name=dataset_name) + +ds_def = dataset.get_definition() +dataset_val, dataset_train = ds_def.random_split(percentage=0.3) +y_df = dataset_train.keep_columns(['HasDetections']).to_pandas_dataframe() +x_df = dataset_train.drop_columns(['HasDetections']).to_pandas_dataframe() +y_val = dataset_val.keep_columns(['HasDetections']).to_pandas_dataframe() +x_val = dataset_val.drop_columns(['HasDetections']).to_pandas_dataframe() + +data = {"train": {"X": x_df, "y": y_df}, + "validation": {"X": x_val, "y": y_val}} + +clf = DecisionTreeClassifier().fit(data["train"]["X"], data["train"]["y"]) +print('Accuracy of Decision Tree classifier on training set: {:.2f}'.format(clf.score(x_df, y_df))) +print('Accuracy of Decision Tree classifier on validation set: {:.2f}'.format(clf.score(x_val, y_val))) +``` + +For an end-to-end tutorial, you may refer to [Dataset tutorial](datasets-tutorial.ipynb). You will learn how to: +- Explore and prepare data for training the model. +- Register the Dataset in your workspace for easy access in training. +- Take snapshots of data to ensure models can be trained with the same data every time. +- Use registered Dataset in your training script. +- Create and use multiple Dataset definitions to ensure that updates to the definition don't break existing pipelines/scripts. + + + +![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/datasets/README.png) \ No newline at end of file diff --git a/work-with-data/datasets/datasets-tutorial/datasets-tutorial.ipynb b/work-with-data/datasets/datasets-tutorial/datasets-tutorial.ipynb new file mode 100644 index 00000000..b28adf5c --- /dev/null +++ b/work-with-data/datasets/datasets-tutorial/datasets-tutorial.ipynb @@ -0,0 +1,376 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tutorial: Learn how to use Datasets in Azure ML" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial, you learn how to use Azure ML Datasets to train a regression model with the Azure Machine Learning SDK for Python. You will\n", + "\n", + "* Explore and prepare data for training the model\n", + "* Register the Dataset in your workspace to share it with others\n", + "* Create and use multiple Dataset definitions to ensure that updates to the definition don't break existing pipelines/scripts\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this tutorial, you:\n", + "\n", + "☑ Setup a Python environment and import packages\n", + "\n", + "☑ Load the Titanic data from your Azure Blob Storage. (The [original data](https://www.kaggle.com/c/titanic/data) can be found on Kaggle)\n", + "\n", + "☑ Explore and cleanse the data to remove anomalies\n", + "\n", + "☑ Register the Dataset in your workspace, allowing you to use it in model training \n", + "\n", + "☑ Make changes to the dataset's definition without breaking the production model or the daily data pipeline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Pre-requisites:\n", + "Skip to Set up your development environment to read through the notebook steps, or use the instructions below to get the notebook and run it on Azure Notebooks or your own notebook server. To run the notebook you will need:\n", + "\n", + "A Python 3.6 notebook server with the following installed:\n", + "* The Azure Machine Learning SDK for Python\n", + "* The Azure Machine Learning Data Prep SDK for Python\n", + "* The tutorial notebook\n", + "\n", + "Data and train.py script to store in your Azure Blob Storage Account.\n", + " * [Titanic data](./train-dataset/Titanic.csv)\n", + " * [train.py](./train-dataset/train.py)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To create and register Datasets you need:\n", + "\n", + " * An Azure subscription. If you don\u00e2\u20ac\u2122t have an Azure subscription, create a free account before you begin. Try the [free or paid version of Azure Machine Learning service](https://aka.ms/AMLFree) today.\n", + "\n", + " * An Azure Machine Learning service workspace. See the [Create an Azure Machine Learning service workspace](https://docs.microsoft.com/en-us/azure/machine-learning/service/setup-create-workspace?branch=release-build-amls).\n", + "\n", + " * The Azure Machine Learning SDK for Python (version 1.0.21 or later). To install or update to the latest version of the SDK, see [Install or update the SDK](https://docs.microsoft.com/python/api/overview/azure/ml/install?view=azure-ml-py).\n", + "\n", + "\n", + "For more information on how to set up your workspace, see the [Create an Azure Machine Learning service workspace](https://docs.microsoft.com/en-us/azure/machine-learning/service/setup-create-workspace?branch=release-build-amls).\n", + "\n", + "The first part that needs to be done is setting up your python environment. You will need to import all of your python packages including `azureml.dataprep` and `azureml.core.dataset`. Then access your workspace through your Azure subscription and set up your compute target. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import azureml.dataprep as dprep\n", + "import azureml.core\n", + "import pandas as pd\n", + "import logging\n", + "import os\n", + "import shutil\n", + "from azureml.core import Workspace, Datastore, Dataset\n", + "\n", + "# Get existing workspace from config.json file in the same folder as the tutorial notebook\n", + "# You can download the config file from your workspace\n", + "workspace = Workspace.from_config()\n", + "print(\"Workspace\")\n", + "print(workspace)\n", + "print(\"Compute targets\")\n", + "print(workspace.compute_targets)\n", + "\n", + "# Get compute target that has already been attached to the workspace\n", + "# Pick the right compute target from the list of computes attached to your workspace\n", + "\n", + "compute_target_name = 'datasetBugBash'\n", + "remote_compute_target = workspace.compute_targets[compute_target_name]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To load data to your dataset, you will access the data through your datastore. After you create your dataset, you can use `get_profile()` to see your data's statistics.\n", + "\n", + "We will now upload the [original data](https://www.kaggle.com/c/titanic/data) to the default datastore(blob) within your workspace.." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datastore = workspace.get_default_datastore()\n", + "datastore.upload_files(files=['./train-dataset/Titanic.csv'],\n", + " target_path='train-dataset/',\n", + " overwrite=True,\n", + " show_progress=True)\n", + "\n", + "dataset = Dataset.auto_read_files(path=datastore.path('train-dataset/Titanic.csv'))\n", + "\n", + "#Display Dataset Profile of the Titanic Dataset\n", + "dataset.get_profile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To predict if a person survived the Titanic's sinking or not, the columns that are relevant to train the model are 'Survived','Pclass', 'Sex','SibSp', and 'Parch'. You can update your dataset's deinition and only keep these columns you will need. You will also need to convert values (\"male\",\"female\") in the \"Sex\" column to 0 or 1, because the algorithm in the train.py file will be using numeric values instead of strings.\n", + "\n", + "For more examples of preparing data with Datasets, see [Explore and prepare data with the Dataset class](aka.ms/azureml/howto/exploreandpreparedata)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ds_def = dataset.get_definition()\n", + "ds_def = ds_def.keep_columns(['Survived','Pclass', 'Sex','SibSp', 'Parch', 'Fare'])\n", + "ds_def = ds_def.replace('Sex','male', 0)\n", + "ds_def = ds_def.replace('Sex','female', 1)\n", + "ds_def.head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once you have cleaned your data, you can register your dataset in your workspace. \n", + "\n", + "Registering your dataset allows you to easily have access to your processed data and share it with other people in your organization using the same workspace. It can be accessed in any notebook or script that is connected to your workspace." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset = dataset.update_definition(ds_def, 'Cleaned Data')\n", + "\n", + "dataset.generate_profile(compute_target='local').get_result()\n", + "\n", + "dataset_name = 'clean_Titanic_tutorial'\n", + "dataset = dataset.register(workspace=workspace,\n", + " name=dataset_name,\n", + " description='training dataset',\n", + " tags = {'year':'2019', 'month':'Apr'},\n", + " exist_ok=True)\n", + "workspace.datasets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The following code snippet will train your model locally using the train.py script." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core import Experiment, RunConfiguration\n", + "\n", + "experiment_name = 'training-datasets'\n", + "experiment = Experiment(workspace = workspace, name = experiment_name)\n", + "project_folder = './train-dataset/'\n", + "\n", + "# create a new RunConfig object\n", + "run_config = RunConfiguration()\n", + "\n", + "run_config.environment.python.user_managed_dependencies = True\n", + "\n", + "from azureml.core import Run\n", + "from azureml.core import ScriptRunConfig\n", + "\n", + "src = ScriptRunConfig(source_directory=project_folder, \n", + " script='train.py', \n", + " run_config=run_config) \n", + "run = experiment.submit(config=src)\n", + "run.wait_for_completion(show_output=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also use the same script with your dataset for your Pipeline's Python Script Step.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.pipeline.core import Pipeline, PipelineData\n", + "from azureml.pipeline.steps import PythonScriptStep\n", + "from azureml.data.data_reference import DataReference\n", + "\n", + "trainStep = PythonScriptStep(script_name=\"train.py\",\n", + " compute_target=remote_compute_target,\n", + " source_directory=project_folder)\n", + "\n", + "pipeline = Pipeline(workspace=workspace,\n", + " steps=trainStep)\n", + "\n", + "pipeline_run = experiment.submit(pipeline)\n", + "pipeline_run.wait_for_completion()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can make changes to the dataset's definition without breaking the production model or the daily data pipeline. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can call get_definitions to see that there are several versions. After each change to a dataset's version, another one is added." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset.get_definitions()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset = Dataset.get(workspace=workspace, name=dataset_name)\n", + "ds_def = dataset.get_definition()\n", + "ds_def = ds_def.drop_columns(['Fare'])\n", + "dataset = dataset.update_definition(ds_def, 'Dropping Fare as PClass and Fare are strongly correlated')\n", + "dataset.generate_profile(compute_target='local').get_result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dataset definitions can be deprecated when usage is no longer recommended and a replacement is available. When a deprecated dataset definition is used in an AML Experimentation/Pipeline scenario, a warning message gets returned but execution will not be blocked. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Deprecate dataset definition 1 by the 2nd definition\n", + "ds_def = dataset.get_definition('1')\n", + "ds_def.deprecate(deprecate_by_dataset_id=dataset._id, deprecated_by_definition_version='2')\n", + "dataset.get_definitions()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dataset definitions can be archived when definitions are not supposed to be used for any reasons (such as underlying data no longer available). When an archived dataset definition is used in an AML Experimentation/Pipeline scenario, execution will be blocked with error. No further actions can be performed on archived Dataset definitions, but the references will be kept intact. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Archive the deprecated dataset definition #1\n", + "ds_def = dataset.get_definition('1')\n", + "ds_def.archive()\n", + "dataset.get_definitions()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also reactivate any defition that you archived for later use." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ds_def = dataset.get_definition('1')\n", + "ds_def.reactivate()\n", + "dataset.get_definitions()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You have now finished using a dataset from start to finish of your experiment!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/work-with-data/datasets/datasets-tutorial/datasets-tutorial.png)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "cforbe" + } + ], + "kernelspec": { + "display_name": "Python 3.6", + "language": "python", + "name": "python36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + }, + "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License." + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/work-with-data/datasets/datasets-tutorial/train-dataset/Titanic.csv b/work-with-data/datasets/datasets-tutorial/train-dataset/Titanic.csv new file mode 100644 index 00000000..5cc466e9 --- /dev/null +++ b/work-with-data/datasets/datasets-tutorial/train-dataset/Titanic.csv @@ -0,0 +1,892 @@ +PassengerId,Survived,Pclass,Name,Sex,Age,SibSp,Parch,Ticket,Fare,Cabin,Embarked +1,0,3,"Braund, Mr. Owen Harris",male,22,1,0,A/5 21171,7.25,,S +2,1,1,"Cumings, Mrs. John Bradley (Florence Briggs Thayer)",female,38,1,0,PC 17599,71.2833,C85,C +3,1,3,"Heikkinen, Miss. Laina",female,26,0,0,STON/O2. 3101282,7.925,,S +4,1,1,"Futrelle, Mrs. Jacques Heath (Lily May Peel)",female,35,1,0,113803,53.1,C123,S +5,0,3,"Allen, Mr. William Henry",male,35,0,0,373450,8.05,,S +6,0,3,"Moran, Mr. James",male,,0,0,330877,8.4583,,Q +7,0,1,"McCarthy, Mr. Timothy J",male,54,0,0,17463,51.8625,E46,S +8,0,3,"Palsson, Master. Gosta Leonard",male,2,3,1,349909,21.075,,S +9,1,3,"Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)",female,27,0,2,347742,11.1333,,S +10,1,2,"Nasser, Mrs. Nicholas (Adele Achem)",female,14,1,0,237736,30.0708,,C +11,1,3,"Sandstrom, Miss. Marguerite Rut",female,4,1,1,PP 9549,16.7,G6,S +12,1,1,"Bonnell, Miss. Elizabeth",female,58,0,0,113783,26.55,C103,S +13,0,3,"Saundercock, Mr. William Henry",male,20,0,0,A/5. 2151,8.05,,S +14,0,3,"Andersson, Mr. Anders Johan",male,39,1,5,347082,31.275,,S +15,0,3,"Vestrom, Miss. Hulda Amanda Adolfina",female,14,0,0,350406,7.8542,,S +16,1,2,"Hewlett, Mrs. (Mary D Kingcome) ",female,55,0,0,248706,16,,S +17,0,3,"Rice, Master. Eugene",male,2,4,1,382652,29.125,,Q +18,1,2,"Williams, Mr. Charles Eugene",male,,0,0,244373,13,,S +19,0,3,"Vander Planke, Mrs. Julius (Emelia Maria Vandemoortele)",female,31,1,0,345763,18,,S +20,1,3,"Masselmani, Mrs. Fatima",female,,0,0,2649,7.225,,C +21,0,2,"Fynney, Mr. Joseph J",male,35,0,0,239865,26,,S +22,1,2,"Beesley, Mr. Lawrence",male,34,0,0,248698,13,D56,S +23,1,3,"McGowan, Miss. Anna ""Annie""",female,15,0,0,330923,8.0292,,Q +24,1,1,"Sloper, Mr. William Thompson",male,28,0,0,113788,35.5,A6,S +25,0,3,"Palsson, Miss. Torborg Danira",female,8,3,1,349909,21.075,,S +26,1,3,"Asplund, Mrs. Carl Oscar (Selma Augusta Emilia Johansson)",female,38,1,5,347077,31.3875,,S +27,0,3,"Emir, Mr. Farred Chehab",male,,0,0,2631,7.225,,C +28,0,1,"Fortune, Mr. Charles Alexander",male,19,3,2,19950,263,C23 C25 C27,S +29,1,3,"O'Dwyer, Miss. Ellen ""Nellie""",female,,0,0,330959,7.8792,,Q +30,0,3,"Todoroff, Mr. Lalio",male,,0,0,349216,7.8958,,S +31,0,1,"Uruchurtu, Don. Manuel E",male,40,0,0,PC 17601,27.7208,,C +32,1,1,"Spencer, Mrs. William Augustus (Marie Eugenie)",female,,1,0,PC 17569,146.5208,B78,C +33,1,3,"Glynn, Miss. Mary Agatha",female,,0,0,335677,7.75,,Q +34,0,2,"Wheadon, Mr. Edward H",male,66,0,0,C.A. 24579,10.5,,S +35,0,1,"Meyer, Mr. Edgar Joseph",male,28,1,0,PC 17604,82.1708,,C +36,0,1,"Holverson, Mr. Alexander Oskar",male,42,1,0,113789,52,,S +37,1,3,"Mamee, Mr. Hanna",male,,0,0,2677,7.2292,,C +38,0,3,"Cann, Mr. Ernest Charles",male,21,0,0,A./5. 2152,8.05,,S +39,0,3,"Vander Planke, Miss. Augusta Maria",female,18,2,0,345764,18,,S +40,1,3,"Nicola-Yarred, Miss. Jamila",female,14,1,0,2651,11.2417,,C +41,0,3,"Ahlin, Mrs. Johan (Johanna Persdotter Larsson)",female,40,1,0,7546,9.475,,S +42,0,2,"Turpin, Mrs. William John Robert (Dorothy Ann Wonnacott)",female,27,1,0,11668,21,,S +43,0,3,"Kraeff, Mr. Theodor",male,,0,0,349253,7.8958,,C +44,1,2,"Laroche, Miss. Simonne Marie Anne Andree",female,3,1,2,SC/Paris 2123,41.5792,,C +45,1,3,"Devaney, Miss. Margaret Delia",female,19,0,0,330958,7.8792,,Q +46,0,3,"Rogers, Mr. William John",male,,0,0,S.C./A.4. 23567,8.05,,S +47,0,3,"Lennon, Mr. Denis",male,,1,0,370371,15.5,,Q +48,1,3,"O'Driscoll, Miss. Bridget",female,,0,0,14311,7.75,,Q +49,0,3,"Samaan, Mr. Youssef",male,,2,0,2662,21.6792,,C +50,0,3,"Arnold-Franchi, Mrs. Josef (Josefine Franchi)",female,18,1,0,349237,17.8,,S +51,0,3,"Panula, Master. Juha Niilo",male,7,4,1,3101295,39.6875,,S +52,0,3,"Nosworthy, Mr. Richard Cater",male,21,0,0,A/4. 39886,7.8,,S +53,1,1,"Harper, Mrs. Henry Sleeper (Myna Haxtun)",female,49,1,0,PC 17572,76.7292,D33,C +54,1,2,"Faunthorpe, Mrs. Lizzie (Elizabeth Anne Wilkinson)",female,29,1,0,2926,26,,S +55,0,1,"Ostby, Mr. Engelhart Cornelius",male,65,0,1,113509,61.9792,B30,C +56,1,1,"Woolner, Mr. Hugh",male,,0,0,19947,35.5,C52,S +57,1,2,"Rugg, Miss. Emily",female,21,0,0,C.A. 31026,10.5,,S +58,0,3,"Novel, Mr. Mansouer",male,28.5,0,0,2697,7.2292,,C +59,1,2,"West, Miss. Constance Mirium",female,5,1,2,C.A. 34651,27.75,,S +60,0,3,"Goodwin, Master. William Frederick",male,11,5,2,CA 2144,46.9,,S +61,0,3,"Sirayanian, Mr. Orsen",male,22,0,0,2669,7.2292,,C +62,1,1,"Icard, Miss. Amelie",female,38,0,0,113572,80,B28, +63,0,1,"Harris, Mr. Henry Birkhardt",male,45,1,0,36973,83.475,C83,S +64,0,3,"Skoog, Master. Harald",male,4,3,2,347088,27.9,,S +65,0,1,"Stewart, Mr. Albert A",male,,0,0,PC 17605,27.7208,,C +66,1,3,"Moubarek, Master. Gerios",male,,1,1,2661,15.2458,,C +67,1,2,"Nye, Mrs. (Elizabeth Ramell)",female,29,0,0,C.A. 29395,10.5,F33,S +68,0,3,"Crease, Mr. Ernest James",male,19,0,0,S.P. 3464,8.1583,,S +69,1,3,"Andersson, Miss. Erna Alexandra",female,17,4,2,3101281,7.925,,S +70,0,3,"Kink, Mr. Vincenz",male,26,2,0,315151,8.6625,,S +71,0,2,"Jenkin, Mr. Stephen Curnow",male,32,0,0,C.A. 33111,10.5,,S +72,0,3,"Goodwin, Miss. Lillian Amy",female,16,5,2,CA 2144,46.9,,S +73,0,2,"Hood, Mr. Ambrose Jr",male,21,0,0,S.O.C. 14879,73.5,,S +74,0,3,"Chronopoulos, Mr. Apostolos",male,26,1,0,2680,14.4542,,C +75,1,3,"Bing, Mr. Lee",male,32,0,0,1601,56.4958,,S +76,0,3,"Moen, Mr. Sigurd Hansen",male,25,0,0,348123,7.65,F G73,S +77,0,3,"Staneff, Mr. Ivan",male,,0,0,349208,7.8958,,S +78,0,3,"Moutal, Mr. Rahamin Haim",male,,0,0,374746,8.05,,S +79,1,2,"Caldwell, Master. Alden Gates",male,0.83,0,2,248738,29,,S +80,1,3,"Dowdell, Miss. Elizabeth",female,30,0,0,364516,12.475,,S +81,0,3,"Waelens, Mr. Achille",male,22,0,0,345767,9,,S +82,1,3,"Sheerlinck, Mr. Jan Baptist",male,29,0,0,345779,9.5,,S +83,1,3,"McDermott, Miss. Brigdet Delia",female,,0,0,330932,7.7875,,Q +84,0,1,"Carrau, Mr. Francisco M",male,28,0,0,113059,47.1,,S +85,1,2,"Ilett, Miss. Bertha",female,17,0,0,SO/C 14885,10.5,,S +86,1,3,"Backstrom, Mrs. Karl Alfred (Maria Mathilda Gustafsson)",female,33,3,0,3101278,15.85,,S +87,0,3,"Ford, Mr. William Neal",male,16,1,3,W./C. 6608,34.375,,S +88,0,3,"Slocovski, Mr. Selman Francis",male,,0,0,SOTON/OQ 392086,8.05,,S +89,1,1,"Fortune, Miss. Mabel Helen",female,23,3,2,19950,263,C23 C25 C27,S +90,0,3,"Celotti, Mr. Francesco",male,24,0,0,343275,8.05,,S +91,0,3,"Christmann, Mr. Emil",male,29,0,0,343276,8.05,,S +92,0,3,"Andreasson, Mr. Paul Edvin",male,20,0,0,347466,7.8542,,S +93,0,1,"Chaffee, Mr. Herbert Fuller",male,46,1,0,W.E.P. 5734,61.175,E31,S +94,0,3,"Dean, Mr. Bertram Frank",male,26,1,2,C.A. 2315,20.575,,S +95,0,3,"Coxon, Mr. Daniel",male,59,0,0,364500,7.25,,S +96,0,3,"Shorney, Mr. Charles Joseph",male,,0,0,374910,8.05,,S +97,0,1,"Goldschmidt, Mr. George B",male,71,0,0,PC 17754,34.6542,A5,C +98,1,1,"Greenfield, Mr. William Bertram",male,23,0,1,PC 17759,63.3583,D10 D12,C +99,1,2,"Doling, Mrs. John T (Ada Julia Bone)",female,34,0,1,231919,23,,S +100,0,2,"Kantor, Mr. Sinai",male,34,1,0,244367,26,,S +101,0,3,"Petranec, Miss. Matilda",female,28,0,0,349245,7.8958,,S +102,0,3,"Petroff, Mr. Pastcho (""Pentcho"")",male,,0,0,349215,7.8958,,S +103,0,1,"White, Mr. Richard Frasar",male,21,0,1,35281,77.2875,D26,S +104,0,3,"Johansson, Mr. Gustaf Joel",male,33,0,0,7540,8.6542,,S +105,0,3,"Gustafsson, Mr. Anders Vilhelm",male,37,2,0,3101276,7.925,,S +106,0,3,"Mionoff, Mr. Stoytcho",male,28,0,0,349207,7.8958,,S +107,1,3,"Salkjelsvik, Miss. Anna Kristine",female,21,0,0,343120,7.65,,S +108,1,3,"Moss, Mr. Albert Johan",male,,0,0,312991,7.775,,S +109,0,3,"Rekic, Mr. Tido",male,38,0,0,349249,7.8958,,S +110,1,3,"Moran, Miss. Bertha",female,,1,0,371110,24.15,,Q +111,0,1,"Porter, Mr. Walter Chamberlain",male,47,0,0,110465,52,C110,S +112,0,3,"Zabour, Miss. Hileni",female,14.5,1,0,2665,14.4542,,C +113,0,3,"Barton, Mr. David John",male,22,0,0,324669,8.05,,S +114,0,3,"Jussila, Miss. Katriina",female,20,1,0,4136,9.825,,S +115,0,3,"Attalah, Miss. Malake",female,17,0,0,2627,14.4583,,C +116,0,3,"Pekoniemi, Mr. Edvard",male,21,0,0,STON/O 2. 3101294,7.925,,S +117,0,3,"Connors, Mr. Patrick",male,70.5,0,0,370369,7.75,,Q +118,0,2,"Turpin, Mr. William John Robert",male,29,1,0,11668,21,,S +119,0,1,"Baxter, Mr. Quigg Edmond",male,24,0,1,PC 17558,247.5208,B58 B60,C +120,0,3,"Andersson, Miss. Ellis Anna Maria",female,2,4,2,347082,31.275,,S +121,0,2,"Hickman, Mr. Stanley George",male,21,2,0,S.O.C. 14879,73.5,,S +122,0,3,"Moore, Mr. Leonard Charles",male,,0,0,A4. 54510,8.05,,S +123,0,2,"Nasser, Mr. Nicholas",male,32.5,1,0,237736,30.0708,,C +124,1,2,"Webber, Miss. Susan",female,32.5,0,0,27267,13,E101,S +125,0,1,"White, Mr. Percival Wayland",male,54,0,1,35281,77.2875,D26,S +126,1,3,"Nicola-Yarred, Master. Elias",male,12,1,0,2651,11.2417,,C +127,0,3,"McMahon, Mr. Martin",male,,0,0,370372,7.75,,Q +128,1,3,"Madsen, Mr. Fridtjof Arne",male,24,0,0,C 17369,7.1417,,S +129,1,3,"Peter, Miss. Anna",female,,1,1,2668,22.3583,F E69,C +130,0,3,"Ekstrom, Mr. Johan",male,45,0,0,347061,6.975,,S +131,0,3,"Drazenoic, Mr. Jozef",male,33,0,0,349241,7.8958,,C +132,0,3,"Coelho, Mr. Domingos Fernandeo",male,20,0,0,SOTON/O.Q. 3101307,7.05,,S +133,0,3,"Robins, Mrs. Alexander A (Grace Charity Laury)",female,47,1,0,A/5. 3337,14.5,,S +134,1,2,"Weisz, Mrs. Leopold (Mathilde Francoise Pede)",female,29,1,0,228414,26,,S +135,0,2,"Sobey, Mr. Samuel James Hayden",male,25,0,0,C.A. 29178,13,,S +136,0,2,"Richard, Mr. Emile",male,23,0,0,SC/PARIS 2133,15.0458,,C +137,1,1,"Newsom, Miss. Helen Monypeny",female,19,0,2,11752,26.2833,D47,S +138,0,1,"Futrelle, Mr. Jacques Heath",male,37,1,0,113803,53.1,C123,S +139,0,3,"Osen, Mr. Olaf Elon",male,16,0,0,7534,9.2167,,S +140,0,1,"Giglio, Mr. Victor",male,24,0,0,PC 17593,79.2,B86,C +141,0,3,"Boulos, Mrs. Joseph (Sultana)",female,,0,2,2678,15.2458,,C +142,1,3,"Nysten, Miss. Anna Sofia",female,22,0,0,347081,7.75,,S +143,1,3,"Hakkarainen, Mrs. Pekka Pietari (Elin Matilda Dolck)",female,24,1,0,STON/O2. 3101279,15.85,,S +144,0,3,"Burke, Mr. Jeremiah",male,19,0,0,365222,6.75,,Q +145,0,2,"Andrew, Mr. Edgardo Samuel",male,18,0,0,231945,11.5,,S +146,0,2,"Nicholls, Mr. Joseph Charles",male,19,1,1,C.A. 33112,36.75,,S +147,1,3,"Andersson, Mr. August Edvard (""Wennerstrom"")",male,27,0,0,350043,7.7958,,S +148,0,3,"Ford, Miss. Robina Maggie ""Ruby""",female,9,2,2,W./C. 6608,34.375,,S +149,0,2,"Navratil, Mr. Michel (""Louis M Hoffman"")",male,36.5,0,2,230080,26,F2,S +150,0,2,"Byles, Rev. Thomas Roussel Davids",male,42,0,0,244310,13,,S +151,0,2,"Bateman, Rev. Robert James",male,51,0,0,S.O.P. 1166,12.525,,S +152,1,1,"Pears, Mrs. Thomas (Edith Wearne)",female,22,1,0,113776,66.6,C2,S +153,0,3,"Meo, Mr. Alfonzo",male,55.5,0,0,A.5. 11206,8.05,,S +154,0,3,"van Billiard, Mr. Austin Blyler",male,40.5,0,2,A/5. 851,14.5,,S +155,0,3,"Olsen, Mr. Ole Martin",male,,0,0,Fa 265302,7.3125,,S +156,0,1,"Williams, Mr. Charles Duane",male,51,0,1,PC 17597,61.3792,,C +157,1,3,"Gilnagh, Miss. Katherine ""Katie""",female,16,0,0,35851,7.7333,,Q +158,0,3,"Corn, Mr. Harry",male,30,0,0,SOTON/OQ 392090,8.05,,S +159,0,3,"Smiljanic, Mr. Mile",male,,0,0,315037,8.6625,,S +160,0,3,"Sage, Master. Thomas Henry",male,,8,2,CA. 2343,69.55,,S +161,0,3,"Cribb, Mr. John Hatfield",male,44,0,1,371362,16.1,,S +162,1,2,"Watt, Mrs. James (Elizabeth ""Bessie"" Inglis Milne)",female,40,0,0,C.A. 33595,15.75,,S +163,0,3,"Bengtsson, Mr. John Viktor",male,26,0,0,347068,7.775,,S +164,0,3,"Calic, Mr. Jovo",male,17,0,0,315093,8.6625,,S +165,0,3,"Panula, Master. Eino Viljami",male,1,4,1,3101295,39.6875,,S +166,1,3,"Goldsmith, Master. Frank John William ""Frankie""",male,9,0,2,363291,20.525,,S +167,1,1,"Chibnall, Mrs. (Edith Martha Bowerman)",female,,0,1,113505,55,E33,S +168,0,3,"Skoog, Mrs. William (Anna Bernhardina Karlsson)",female,45,1,4,347088,27.9,,S +169,0,1,"Baumann, Mr. John D",male,,0,0,PC 17318,25.925,,S +170,0,3,"Ling, Mr. Lee",male,28,0,0,1601,56.4958,,S +171,0,1,"Van der hoef, Mr. Wyckoff",male,61,0,0,111240,33.5,B19,S +172,0,3,"Rice, Master. Arthur",male,4,4,1,382652,29.125,,Q +173,1,3,"Johnson, Miss. Eleanor Ileen",female,1,1,1,347742,11.1333,,S +174,0,3,"Sivola, Mr. Antti Wilhelm",male,21,0,0,STON/O 2. 3101280,7.925,,S +175,0,1,"Smith, Mr. James Clinch",male,56,0,0,17764,30.6958,A7,C +176,0,3,"Klasen, Mr. Klas Albin",male,18,1,1,350404,7.8542,,S +177,0,3,"Lefebre, Master. Henry Forbes",male,,3,1,4133,25.4667,,S +178,0,1,"Isham, Miss. Ann Elizabeth",female,50,0,0,PC 17595,28.7125,C49,C +179,0,2,"Hale, Mr. Reginald",male,30,0,0,250653,13,,S +180,0,3,"Leonard, Mr. Lionel",male,36,0,0,LINE,0,,S +181,0,3,"Sage, Miss. Constance Gladys",female,,8,2,CA. 2343,69.55,,S +182,0,2,"Pernot, Mr. Rene",male,,0,0,SC/PARIS 2131,15.05,,C +183,0,3,"Asplund, Master. Clarence Gustaf Hugo",male,9,4,2,347077,31.3875,,S +184,1,2,"Becker, Master. Richard F",male,1,2,1,230136,39,F4,S +185,1,3,"Kink-Heilmann, Miss. Luise Gretchen",female,4,0,2,315153,22.025,,S +186,0,1,"Rood, Mr. Hugh Roscoe",male,,0,0,113767,50,A32,S +187,1,3,"O'Brien, Mrs. Thomas (Johanna ""Hannah"" Godfrey)",female,,1,0,370365,15.5,,Q +188,1,1,"Romaine, Mr. Charles Hallace (""Mr C Rolmane"")",male,45,0,0,111428,26.55,,S +189,0,3,"Bourke, Mr. John",male,40,1,1,364849,15.5,,Q +190,0,3,"Turcin, Mr. Stjepan",male,36,0,0,349247,7.8958,,S +191,1,2,"Pinsky, Mrs. (Rosa)",female,32,0,0,234604,13,,S +192,0,2,"Carbines, Mr. William",male,19,0,0,28424,13,,S +193,1,3,"Andersen-Jensen, Miss. Carla Christine Nielsine",female,19,1,0,350046,7.8542,,S +194,1,2,"Navratil, Master. Michel M",male,3,1,1,230080,26,F2,S +195,1,1,"Brown, Mrs. James Joseph (Margaret Tobin)",female,44,0,0,PC 17610,27.7208,B4,C +196,1,1,"Lurette, Miss. Elise",female,58,0,0,PC 17569,146.5208,B80,C +197,0,3,"Mernagh, Mr. Robert",male,,0,0,368703,7.75,,Q +198,0,3,"Olsen, Mr. Karl Siegwart Andreas",male,42,0,1,4579,8.4042,,S +199,1,3,"Madigan, Miss. Margaret ""Maggie""",female,,0,0,370370,7.75,,Q +200,0,2,"Yrois, Miss. Henriette (""Mrs Harbeck"")",female,24,0,0,248747,13,,S +201,0,3,"Vande Walle, Mr. Nestor Cyriel",male,28,0,0,345770,9.5,,S +202,0,3,"Sage, Mr. Frederick",male,,8,2,CA. 2343,69.55,,S +203,0,3,"Johanson, Mr. Jakob Alfred",male,34,0,0,3101264,6.4958,,S +204,0,3,"Youseff, Mr. Gerious",male,45.5,0,0,2628,7.225,,C +205,1,3,"Cohen, Mr. Gurshon ""Gus""",male,18,0,0,A/5 3540,8.05,,S +206,0,3,"Strom, Miss. Telma Matilda",female,2,0,1,347054,10.4625,G6,S +207,0,3,"Backstrom, Mr. Karl Alfred",male,32,1,0,3101278,15.85,,S +208,1,3,"Albimona, Mr. Nassef Cassem",male,26,0,0,2699,18.7875,,C +209,1,3,"Carr, Miss. Helen ""Ellen""",female,16,0,0,367231,7.75,,Q +210,1,1,"Blank, Mr. Henry",male,40,0,0,112277,31,A31,C +211,0,3,"Ali, Mr. Ahmed",male,24,0,0,SOTON/O.Q. 3101311,7.05,,S +212,1,2,"Cameron, Miss. Clear Annie",female,35,0,0,F.C.C. 13528,21,,S +213,0,3,"Perkin, Mr. John Henry",male,22,0,0,A/5 21174,7.25,,S +214,0,2,"Givard, Mr. Hans Kristensen",male,30,0,0,250646,13,,S +215,0,3,"Kiernan, Mr. Philip",male,,1,0,367229,7.75,,Q +216,1,1,"Newell, Miss. Madeleine",female,31,1,0,35273,113.275,D36,C +217,1,3,"Honkanen, Miss. Eliina",female,27,0,0,STON/O2. 3101283,7.925,,S +218,0,2,"Jacobsohn, Mr. Sidney Samuel",male,42,1,0,243847,27,,S +219,1,1,"Bazzani, Miss. Albina",female,32,0,0,11813,76.2917,D15,C +220,0,2,"Harris, Mr. Walter",male,30,0,0,W/C 14208,10.5,,S +221,1,3,"Sunderland, Mr. Victor Francis",male,16,0,0,SOTON/OQ 392089,8.05,,S +222,0,2,"Bracken, Mr. James H",male,27,0,0,220367,13,,S +223,0,3,"Green, Mr. George Henry",male,51,0,0,21440,8.05,,S +224,0,3,"Nenkoff, Mr. Christo",male,,0,0,349234,7.8958,,S +225,1,1,"Hoyt, Mr. Frederick Maxfield",male,38,1,0,19943,90,C93,S +226,0,3,"Berglund, Mr. Karl Ivar Sven",male,22,0,0,PP 4348,9.35,,S +227,1,2,"Mellors, Mr. William John",male,19,0,0,SW/PP 751,10.5,,S +228,0,3,"Lovell, Mr. John Hall (""Henry"")",male,20.5,0,0,A/5 21173,7.25,,S +229,0,2,"Fahlstrom, Mr. Arne Jonas",male,18,0,0,236171,13,,S +230,0,3,"Lefebre, Miss. Mathilde",female,,3,1,4133,25.4667,,S +231,1,1,"Harris, Mrs. Henry Birkhardt (Irene Wallach)",female,35,1,0,36973,83.475,C83,S +232,0,3,"Larsson, Mr. Bengt Edvin",male,29,0,0,347067,7.775,,S +233,0,2,"Sjostedt, Mr. Ernst Adolf",male,59,0,0,237442,13.5,,S +234,1,3,"Asplund, Miss. Lillian Gertrud",female,5,4,2,347077,31.3875,,S +235,0,2,"Leyson, Mr. Robert William Norman",male,24,0,0,C.A. 29566,10.5,,S +236,0,3,"Harknett, Miss. Alice Phoebe",female,,0,0,W./C. 6609,7.55,,S +237,0,2,"Hold, Mr. Stephen",male,44,1,0,26707,26,,S +238,1,2,"Collyer, Miss. Marjorie ""Lottie""",female,8,0,2,C.A. 31921,26.25,,S +239,0,2,"Pengelly, Mr. Frederick William",male,19,0,0,28665,10.5,,S +240,0,2,"Hunt, Mr. George Henry",male,33,0,0,SCO/W 1585,12.275,,S +241,0,3,"Zabour, Miss. Thamine",female,,1,0,2665,14.4542,,C +242,1,3,"Murphy, Miss. Katherine ""Kate""",female,,1,0,367230,15.5,,Q +243,0,2,"Coleridge, Mr. Reginald Charles",male,29,0,0,W./C. 14263,10.5,,S +244,0,3,"Maenpaa, Mr. Matti Alexanteri",male,22,0,0,STON/O 2. 3101275,7.125,,S +245,0,3,"Attalah, Mr. Sleiman",male,30,0,0,2694,7.225,,C +246,0,1,"Minahan, Dr. William Edward",male,44,2,0,19928,90,C78,Q +247,0,3,"Lindahl, Miss. Agda Thorilda Viktoria",female,25,0,0,347071,7.775,,S +248,1,2,"Hamalainen, Mrs. William (Anna)",female,24,0,2,250649,14.5,,S +249,1,1,"Beckwith, Mr. Richard Leonard",male,37,1,1,11751,52.5542,D35,S +250,0,2,"Carter, Rev. Ernest Courtenay",male,54,1,0,244252,26,,S +251,0,3,"Reed, Mr. James George",male,,0,0,362316,7.25,,S +252,0,3,"Strom, Mrs. Wilhelm (Elna Matilda Persson)",female,29,1,1,347054,10.4625,G6,S +253,0,1,"Stead, Mr. William Thomas",male,62,0,0,113514,26.55,C87,S +254,0,3,"Lobb, Mr. William Arthur",male,30,1,0,A/5. 3336,16.1,,S +255,0,3,"Rosblom, Mrs. Viktor (Helena Wilhelmina)",female,41,0,2,370129,20.2125,,S +256,1,3,"Touma, Mrs. Darwis (Hanne Youssef Razi)",female,29,0,2,2650,15.2458,,C +257,1,1,"Thorne, Mrs. Gertrude Maybelle",female,,0,0,PC 17585,79.2,,C +258,1,1,"Cherry, Miss. Gladys",female,30,0,0,110152,86.5,B77,S +259,1,1,"Ward, Miss. Anna",female,35,0,0,PC 17755,512.3292,,C +260,1,2,"Parrish, Mrs. (Lutie Davis)",female,50,0,1,230433,26,,S +261,0,3,"Smith, Mr. Thomas",male,,0,0,384461,7.75,,Q +262,1,3,"Asplund, Master. Edvin Rojj Felix",male,3,4,2,347077,31.3875,,S +263,0,1,"Taussig, Mr. Emil",male,52,1,1,110413,79.65,E67,S +264,0,1,"Harrison, Mr. William",male,40,0,0,112059,0,B94,S +265,0,3,"Henry, Miss. Delia",female,,0,0,382649,7.75,,Q +266,0,2,"Reeves, Mr. David",male,36,0,0,C.A. 17248,10.5,,S +267,0,3,"Panula, Mr. Ernesti Arvid",male,16,4,1,3101295,39.6875,,S +268,1,3,"Persson, Mr. Ernst Ulrik",male,25,1,0,347083,7.775,,S +269,1,1,"Graham, Mrs. William Thompson (Edith Junkins)",female,58,0,1,PC 17582,153.4625,C125,S +270,1,1,"Bissette, Miss. Amelia",female,35,0,0,PC 17760,135.6333,C99,S +271,0,1,"Cairns, Mr. Alexander",male,,0,0,113798,31,,S +272,1,3,"Tornquist, Mr. William Henry",male,25,0,0,LINE,0,,S +273,1,2,"Mellinger, Mrs. (Elizabeth Anne Maidment)",female,41,0,1,250644,19.5,,S +274,0,1,"Natsch, Mr. Charles H",male,37,0,1,PC 17596,29.7,C118,C +275,1,3,"Healy, Miss. Hanora ""Nora""",female,,0,0,370375,7.75,,Q +276,1,1,"Andrews, Miss. Kornelia Theodosia",female,63,1,0,13502,77.9583,D7,S +277,0,3,"Lindblom, Miss. Augusta Charlotta",female,45,0,0,347073,7.75,,S +278,0,2,"Parkes, Mr. Francis ""Frank""",male,,0,0,239853,0,,S +279,0,3,"Rice, Master. Eric",male,7,4,1,382652,29.125,,Q +280,1,3,"Abbott, Mrs. Stanton (Rosa Hunt)",female,35,1,1,C.A. 2673,20.25,,S +281,0,3,"Duane, Mr. Frank",male,65,0,0,336439,7.75,,Q +282,0,3,"Olsson, Mr. Nils Johan Goransson",male,28,0,0,347464,7.8542,,S +283,0,3,"de Pelsmaeker, Mr. Alfons",male,16,0,0,345778,9.5,,S +284,1,3,"Dorking, Mr. Edward Arthur",male,19,0,0,A/5. 10482,8.05,,S +285,0,1,"Smith, Mr. Richard William",male,,0,0,113056,26,A19,S +286,0,3,"Stankovic, Mr. Ivan",male,33,0,0,349239,8.6625,,C +287,1,3,"de Mulder, Mr. Theodore",male,30,0,0,345774,9.5,,S +288,0,3,"Naidenoff, Mr. Penko",male,22,0,0,349206,7.8958,,S +289,1,2,"Hosono, Mr. Masabumi",male,42,0,0,237798,13,,S +290,1,3,"Connolly, Miss. Kate",female,22,0,0,370373,7.75,,Q +291,1,1,"Barber, Miss. Ellen ""Nellie""",female,26,0,0,19877,78.85,,S +292,1,1,"Bishop, Mrs. Dickinson H (Helen Walton)",female,19,1,0,11967,91.0792,B49,C +293,0,2,"Levy, Mr. Rene Jacques",male,36,0,0,SC/Paris 2163,12.875,D,C +294,0,3,"Haas, Miss. Aloisia",female,24,0,0,349236,8.85,,S +295,0,3,"Mineff, Mr. Ivan",male,24,0,0,349233,7.8958,,S +296,0,1,"Lewy, Mr. Ervin G",male,,0,0,PC 17612,27.7208,,C +297,0,3,"Hanna, Mr. Mansour",male,23.5,0,0,2693,7.2292,,C +298,0,1,"Allison, Miss. Helen Loraine",female,2,1,2,113781,151.55,C22 C26,S +299,1,1,"Saalfeld, Mr. Adolphe",male,,0,0,19988,30.5,C106,S +300,1,1,"Baxter, Mrs. James (Helene DeLaudeniere Chaput)",female,50,0,1,PC 17558,247.5208,B58 B60,C +301,1,3,"Kelly, Miss. Anna Katherine ""Annie Kate""",female,,0,0,9234,7.75,,Q +302,1,3,"McCoy, Mr. Bernard",male,,2,0,367226,23.25,,Q +303,0,3,"Johnson, Mr. William Cahoone Jr",male,19,0,0,LINE,0,,S +304,1,2,"Keane, Miss. Nora A",female,,0,0,226593,12.35,E101,Q +305,0,3,"Williams, Mr. Howard Hugh ""Harry""",male,,0,0,A/5 2466,8.05,,S +306,1,1,"Allison, Master. Hudson Trevor",male,0.92,1,2,113781,151.55,C22 C26,S +307,1,1,"Fleming, Miss. Margaret",female,,0,0,17421,110.8833,,C +308,1,1,"Penasco y Castellana, Mrs. Victor de Satode (Maria Josefa Perez de Soto y Vallejo)",female,17,1,0,PC 17758,108.9,C65,C +309,0,2,"Abelson, Mr. Samuel",male,30,1,0,P/PP 3381,24,,C +310,1,1,"Francatelli, Miss. Laura Mabel",female,30,0,0,PC 17485,56.9292,E36,C +311,1,1,"Hays, Miss. Margaret Bechstein",female,24,0,0,11767,83.1583,C54,C +312,1,1,"Ryerson, Miss. Emily Borie",female,18,2,2,PC 17608,262.375,B57 B59 B63 B66,C +313,0,2,"Lahtinen, Mrs. William (Anna Sylfven)",female,26,1,1,250651,26,,S +314,0,3,"Hendekovic, Mr. Ignjac",male,28,0,0,349243,7.8958,,S +315,0,2,"Hart, Mr. Benjamin",male,43,1,1,F.C.C. 13529,26.25,,S +316,1,3,"Nilsson, Miss. Helmina Josefina",female,26,0,0,347470,7.8542,,S +317,1,2,"Kantor, Mrs. Sinai (Miriam Sternin)",female,24,1,0,244367,26,,S +318,0,2,"Moraweck, Dr. Ernest",male,54,0,0,29011,14,,S +319,1,1,"Wick, Miss. Mary Natalie",female,31,0,2,36928,164.8667,C7,S +320,1,1,"Spedden, Mrs. Frederic Oakley (Margaretta Corning Stone)",female,40,1,1,16966,134.5,E34,C +321,0,3,"Dennis, Mr. Samuel",male,22,0,0,A/5 21172,7.25,,S +322,0,3,"Danoff, Mr. Yoto",male,27,0,0,349219,7.8958,,S +323,1,2,"Slayter, Miss. Hilda Mary",female,30,0,0,234818,12.35,,Q +324,1,2,"Caldwell, Mrs. Albert Francis (Sylvia Mae Harbaugh)",female,22,1,1,248738,29,,S +325,0,3,"Sage, Mr. George John Jr",male,,8,2,CA. 2343,69.55,,S +326,1,1,"Young, Miss. Marie Grice",female,36,0,0,PC 17760,135.6333,C32,C +327,0,3,"Nysveen, Mr. Johan Hansen",male,61,0,0,345364,6.2375,,S +328,1,2,"Ball, Mrs. (Ada E Hall)",female,36,0,0,28551,13,D,S +329,1,3,"Goldsmith, Mrs. Frank John (Emily Alice Brown)",female,31,1,1,363291,20.525,,S +330,1,1,"Hippach, Miss. Jean Gertrude",female,16,0,1,111361,57.9792,B18,C +331,1,3,"McCoy, Miss. Agnes",female,,2,0,367226,23.25,,Q +332,0,1,"Partner, Mr. Austen",male,45.5,0,0,113043,28.5,C124,S +333,0,1,"Graham, Mr. George Edward",male,38,0,1,PC 17582,153.4625,C91,S +334,0,3,"Vander Planke, Mr. Leo Edmondus",male,16,2,0,345764,18,,S +335,1,1,"Frauenthal, Mrs. Henry William (Clara Heinsheimer)",female,,1,0,PC 17611,133.65,,S +336,0,3,"Denkoff, Mr. Mitto",male,,0,0,349225,7.8958,,S +337,0,1,"Pears, Mr. Thomas Clinton",male,29,1,0,113776,66.6,C2,S +338,1,1,"Burns, Miss. Elizabeth Margaret",female,41,0,0,16966,134.5,E40,C +339,1,3,"Dahl, Mr. Karl Edwart",male,45,0,0,7598,8.05,,S +340,0,1,"Blackwell, Mr. Stephen Weart",male,45,0,0,113784,35.5,T,S +341,1,2,"Navratil, Master. Edmond Roger",male,2,1,1,230080,26,F2,S +342,1,1,"Fortune, Miss. Alice Elizabeth",female,24,3,2,19950,263,C23 C25 C27,S +343,0,2,"Collander, Mr. Erik Gustaf",male,28,0,0,248740,13,,S +344,0,2,"Sedgwick, Mr. Charles Frederick Waddington",male,25,0,0,244361,13,,S +345,0,2,"Fox, Mr. Stanley Hubert",male,36,0,0,229236,13,,S +346,1,2,"Brown, Miss. Amelia ""Mildred""",female,24,0,0,248733,13,F33,S +347,1,2,"Smith, Miss. Marion Elsie",female,40,0,0,31418,13,,S +348,1,3,"Davison, Mrs. Thomas Henry (Mary E Finck)",female,,1,0,386525,16.1,,S +349,1,3,"Coutts, Master. William Loch ""William""",male,3,1,1,C.A. 37671,15.9,,S +350,0,3,"Dimic, Mr. Jovan",male,42,0,0,315088,8.6625,,S +351,0,3,"Odahl, Mr. Nils Martin",male,23,0,0,7267,9.225,,S +352,0,1,"Williams-Lambert, Mr. Fletcher Fellows",male,,0,0,113510,35,C128,S +353,0,3,"Elias, Mr. Tannous",male,15,1,1,2695,7.2292,,C +354,0,3,"Arnold-Franchi, Mr. Josef",male,25,1,0,349237,17.8,,S +355,0,3,"Yousif, Mr. Wazli",male,,0,0,2647,7.225,,C +356,0,3,"Vanden Steen, Mr. Leo Peter",male,28,0,0,345783,9.5,,S +357,1,1,"Bowerman, Miss. Elsie Edith",female,22,0,1,113505,55,E33,S +358,0,2,"Funk, Miss. Annie Clemmer",female,38,0,0,237671,13,,S +359,1,3,"McGovern, Miss. Mary",female,,0,0,330931,7.8792,,Q +360,1,3,"Mockler, Miss. Helen Mary ""Ellie""",female,,0,0,330980,7.8792,,Q +361,0,3,"Skoog, Mr. Wilhelm",male,40,1,4,347088,27.9,,S +362,0,2,"del Carlo, Mr. Sebastiano",male,29,1,0,SC/PARIS 2167,27.7208,,C +363,0,3,"Barbara, Mrs. (Catherine David)",female,45,0,1,2691,14.4542,,C +364,0,3,"Asim, Mr. Adola",male,35,0,0,SOTON/O.Q. 3101310,7.05,,S +365,0,3,"O'Brien, Mr. Thomas",male,,1,0,370365,15.5,,Q +366,0,3,"Adahl, Mr. Mauritz Nils Martin",male,30,0,0,C 7076,7.25,,S +367,1,1,"Warren, Mrs. Frank Manley (Anna Sophia Atkinson)",female,60,1,0,110813,75.25,D37,C +368,1,3,"Moussa, Mrs. (Mantoura Boulos)",female,,0,0,2626,7.2292,,C +369,1,3,"Jermyn, Miss. Annie",female,,0,0,14313,7.75,,Q +370,1,1,"Aubart, Mme. Leontine Pauline",female,24,0,0,PC 17477,69.3,B35,C +371,1,1,"Harder, Mr. George Achilles",male,25,1,0,11765,55.4417,E50,C +372,0,3,"Wiklund, Mr. Jakob Alfred",male,18,1,0,3101267,6.4958,,S +373,0,3,"Beavan, Mr. William Thomas",male,19,0,0,323951,8.05,,S +374,0,1,"Ringhini, Mr. Sante",male,22,0,0,PC 17760,135.6333,,C +375,0,3,"Palsson, Miss. Stina Viola",female,3,3,1,349909,21.075,,S +376,1,1,"Meyer, Mrs. Edgar Joseph (Leila Saks)",female,,1,0,PC 17604,82.1708,,C +377,1,3,"Landergren, Miss. Aurora Adelia",female,22,0,0,C 7077,7.25,,S +378,0,1,"Widener, Mr. Harry Elkins",male,27,0,2,113503,211.5,C82,C +379,0,3,"Betros, Mr. Tannous",male,20,0,0,2648,4.0125,,C +380,0,3,"Gustafsson, Mr. Karl Gideon",male,19,0,0,347069,7.775,,S +381,1,1,"Bidois, Miss. Rosalie",female,42,0,0,PC 17757,227.525,,C +382,1,3,"Nakid, Miss. Maria (""Mary"")",female,1,0,2,2653,15.7417,,C +383,0,3,"Tikkanen, Mr. Juho",male,32,0,0,STON/O 2. 3101293,7.925,,S +384,1,1,"Holverson, Mrs. Alexander Oskar (Mary Aline Towner)",female,35,1,0,113789,52,,S +385,0,3,"Plotcharsky, Mr. Vasil",male,,0,0,349227,7.8958,,S +386,0,2,"Davies, Mr. Charles Henry",male,18,0,0,S.O.C. 14879,73.5,,S +387,0,3,"Goodwin, Master. Sidney Leonard",male,1,5,2,CA 2144,46.9,,S +388,1,2,"Buss, Miss. Kate",female,36,0,0,27849,13,,S +389,0,3,"Sadlier, Mr. Matthew",male,,0,0,367655,7.7292,,Q +390,1,2,"Lehmann, Miss. Bertha",female,17,0,0,SC 1748,12,,C +391,1,1,"Carter, Mr. William Ernest",male,36,1,2,113760,120,B96 B98,S +392,1,3,"Jansson, Mr. Carl Olof",male,21,0,0,350034,7.7958,,S +393,0,3,"Gustafsson, Mr. Johan Birger",male,28,2,0,3101277,7.925,,S +394,1,1,"Newell, Miss. Marjorie",female,23,1,0,35273,113.275,D36,C +395,1,3,"Sandstrom, Mrs. Hjalmar (Agnes Charlotta Bengtsson)",female,24,0,2,PP 9549,16.7,G6,S +396,0,3,"Johansson, Mr. Erik",male,22,0,0,350052,7.7958,,S +397,0,3,"Olsson, Miss. Elina",female,31,0,0,350407,7.8542,,S +398,0,2,"McKane, Mr. Peter David",male,46,0,0,28403,26,,S +399,0,2,"Pain, Dr. Alfred",male,23,0,0,244278,10.5,,S +400,1,2,"Trout, Mrs. William H (Jessie L)",female,28,0,0,240929,12.65,,S +401,1,3,"Niskanen, Mr. Juha",male,39,0,0,STON/O 2. 3101289,7.925,,S +402,0,3,"Adams, Mr. John",male,26,0,0,341826,8.05,,S +403,0,3,"Jussila, Miss. Mari Aina",female,21,1,0,4137,9.825,,S +404,0,3,"Hakkarainen, Mr. Pekka Pietari",male,28,1,0,STON/O2. 3101279,15.85,,S +405,0,3,"Oreskovic, Miss. Marija",female,20,0,0,315096,8.6625,,S +406,0,2,"Gale, Mr. Shadrach",male,34,1,0,28664,21,,S +407,0,3,"Widegren, Mr. Carl/Charles Peter",male,51,0,0,347064,7.75,,S +408,1,2,"Richards, Master. William Rowe",male,3,1,1,29106,18.75,,S +409,0,3,"Birkeland, Mr. Hans Martin Monsen",male,21,0,0,312992,7.775,,S +410,0,3,"Lefebre, Miss. Ida",female,,3,1,4133,25.4667,,S +411,0,3,"Sdycoff, Mr. Todor",male,,0,0,349222,7.8958,,S +412,0,3,"Hart, Mr. Henry",male,,0,0,394140,6.8583,,Q +413,1,1,"Minahan, Miss. Daisy E",female,33,1,0,19928,90,C78,Q +414,0,2,"Cunningham, Mr. Alfred Fleming",male,,0,0,239853,0,,S +415,1,3,"Sundman, Mr. Johan Julian",male,44,0,0,STON/O 2. 3101269,7.925,,S +416,0,3,"Meek, Mrs. Thomas (Annie Louise Rowley)",female,,0,0,343095,8.05,,S +417,1,2,"Drew, Mrs. James Vivian (Lulu Thorne Christian)",female,34,1,1,28220,32.5,,S +418,1,2,"Silven, Miss. Lyyli Karoliina",female,18,0,2,250652,13,,S +419,0,2,"Matthews, Mr. William John",male,30,0,0,28228,13,,S +420,0,3,"Van Impe, Miss. Catharina",female,10,0,2,345773,24.15,,S +421,0,3,"Gheorgheff, Mr. Stanio",male,,0,0,349254,7.8958,,C +422,0,3,"Charters, Mr. David",male,21,0,0,A/5. 13032,7.7333,,Q +423,0,3,"Zimmerman, Mr. Leo",male,29,0,0,315082,7.875,,S +424,0,3,"Danbom, Mrs. Ernst Gilbert (Anna Sigrid Maria Brogren)",female,28,1,1,347080,14.4,,S +425,0,3,"Rosblom, Mr. Viktor Richard",male,18,1,1,370129,20.2125,,S +426,0,3,"Wiseman, Mr. Phillippe",male,,0,0,A/4. 34244,7.25,,S +427,1,2,"Clarke, Mrs. Charles V (Ada Maria Winfield)",female,28,1,0,2003,26,,S +428,1,2,"Phillips, Miss. Kate Florence (""Mrs Kate Louise Phillips Marshall"")",female,19,0,0,250655,26,,S +429,0,3,"Flynn, Mr. James",male,,0,0,364851,7.75,,Q +430,1,3,"Pickard, Mr. Berk (Berk Trembisky)",male,32,0,0,SOTON/O.Q. 392078,8.05,E10,S +431,1,1,"Bjornstrom-Steffansson, Mr. Mauritz Hakan",male,28,0,0,110564,26.55,C52,S +432,1,3,"Thorneycroft, Mrs. Percival (Florence Kate White)",female,,1,0,376564,16.1,,S +433,1,2,"Louch, Mrs. Charles Alexander (Alice Adelaide Slow)",female,42,1,0,SC/AH 3085,26,,S +434,0,3,"Kallio, Mr. Nikolai Erland",male,17,0,0,STON/O 2. 3101274,7.125,,S +435,0,1,"Silvey, Mr. William Baird",male,50,1,0,13507,55.9,E44,S +436,1,1,"Carter, Miss. Lucile Polk",female,14,1,2,113760,120,B96 B98,S +437,0,3,"Ford, Miss. Doolina Margaret ""Daisy""",female,21,2,2,W./C. 6608,34.375,,S +438,1,2,"Richards, Mrs. Sidney (Emily Hocking)",female,24,2,3,29106,18.75,,S +439,0,1,"Fortune, Mr. Mark",male,64,1,4,19950,263,C23 C25 C27,S +440,0,2,"Kvillner, Mr. Johan Henrik Johannesson",male,31,0,0,C.A. 18723,10.5,,S +441,1,2,"Hart, Mrs. Benjamin (Esther Ada Bloomfield)",female,45,1,1,F.C.C. 13529,26.25,,S +442,0,3,"Hampe, Mr. Leon",male,20,0,0,345769,9.5,,S +443,0,3,"Petterson, Mr. Johan Emil",male,25,1,0,347076,7.775,,S +444,1,2,"Reynaldo, Ms. Encarnacion",female,28,0,0,230434,13,,S +445,1,3,"Johannesen-Bratthammer, Mr. Bernt",male,,0,0,65306,8.1125,,S +446,1,1,"Dodge, Master. Washington",male,4,0,2,33638,81.8583,A34,S +447,1,2,"Mellinger, Miss. Madeleine Violet",female,13,0,1,250644,19.5,,S +448,1,1,"Seward, Mr. Frederic Kimber",male,34,0,0,113794,26.55,,S +449,1,3,"Baclini, Miss. Marie Catherine",female,5,2,1,2666,19.2583,,C +450,1,1,"Peuchen, Major. Arthur Godfrey",male,52,0,0,113786,30.5,C104,S +451,0,2,"West, Mr. Edwy Arthur",male,36,1,2,C.A. 34651,27.75,,S +452,0,3,"Hagland, Mr. Ingvald Olai Olsen",male,,1,0,65303,19.9667,,S +453,0,1,"Foreman, Mr. Benjamin Laventall",male,30,0,0,113051,27.75,C111,C +454,1,1,"Goldenberg, Mr. Samuel L",male,49,1,0,17453,89.1042,C92,C +455,0,3,"Peduzzi, Mr. Joseph",male,,0,0,A/5 2817,8.05,,S +456,1,3,"Jalsevac, Mr. Ivan",male,29,0,0,349240,7.8958,,C +457,0,1,"Millet, Mr. Francis Davis",male,65,0,0,13509,26.55,E38,S +458,1,1,"Kenyon, Mrs. Frederick R (Marion)",female,,1,0,17464,51.8625,D21,S +459,1,2,"Toomey, Miss. Ellen",female,50,0,0,F.C.C. 13531,10.5,,S +460,0,3,"O'Connor, Mr. Maurice",male,,0,0,371060,7.75,,Q +461,1,1,"Anderson, Mr. Harry",male,48,0,0,19952,26.55,E12,S +462,0,3,"Morley, Mr. William",male,34,0,0,364506,8.05,,S +463,0,1,"Gee, Mr. Arthur H",male,47,0,0,111320,38.5,E63,S +464,0,2,"Milling, Mr. Jacob Christian",male,48,0,0,234360,13,,S +465,0,3,"Maisner, Mr. Simon",male,,0,0,A/S 2816,8.05,,S +466,0,3,"Goncalves, Mr. Manuel Estanslas",male,38,0,0,SOTON/O.Q. 3101306,7.05,,S +467,0,2,"Campbell, Mr. William",male,,0,0,239853,0,,S +468,0,1,"Smart, Mr. John Montgomery",male,56,0,0,113792,26.55,,S +469,0,3,"Scanlan, Mr. James",male,,0,0,36209,7.725,,Q +470,1,3,"Baclini, Miss. Helene Barbara",female,0.75,2,1,2666,19.2583,,C +471,0,3,"Keefe, Mr. Arthur",male,,0,0,323592,7.25,,S +472,0,3,"Cacic, Mr. Luka",male,38,0,0,315089,8.6625,,S +473,1,2,"West, Mrs. Edwy Arthur (Ada Mary Worth)",female,33,1,2,C.A. 34651,27.75,,S +474,1,2,"Jerwan, Mrs. Amin S (Marie Marthe Thuillard)",female,23,0,0,SC/AH Basle 541,13.7917,D,C +475,0,3,"Strandberg, Miss. Ida Sofia",female,22,0,0,7553,9.8375,,S +476,0,1,"Clifford, Mr. George Quincy",male,,0,0,110465,52,A14,S +477,0,2,"Renouf, Mr. Peter Henry",male,34,1,0,31027,21,,S +478,0,3,"Braund, Mr. Lewis Richard",male,29,1,0,3460,7.0458,,S +479,0,3,"Karlsson, Mr. Nils August",male,22,0,0,350060,7.5208,,S +480,1,3,"Hirvonen, Miss. Hildur E",female,2,0,1,3101298,12.2875,,S +481,0,3,"Goodwin, Master. Harold Victor",male,9,5,2,CA 2144,46.9,,S +482,0,2,"Frost, Mr. Anthony Wood ""Archie""",male,,0,0,239854,0,,S +483,0,3,"Rouse, Mr. Richard Henry",male,50,0,0,A/5 3594,8.05,,S +484,1,3,"Turkula, Mrs. (Hedwig)",female,63,0,0,4134,9.5875,,S +485,1,1,"Bishop, Mr. Dickinson H",male,25,1,0,11967,91.0792,B49,C +486,0,3,"Lefebre, Miss. Jeannie",female,,3,1,4133,25.4667,,S +487,1,1,"Hoyt, Mrs. Frederick Maxfield (Jane Anne Forby)",female,35,1,0,19943,90,C93,S +488,0,1,"Kent, Mr. Edward Austin",male,58,0,0,11771,29.7,B37,C +489,0,3,"Somerton, Mr. Francis William",male,30,0,0,A.5. 18509,8.05,,S +490,1,3,"Coutts, Master. Eden Leslie ""Neville""",male,9,1,1,C.A. 37671,15.9,,S +491,0,3,"Hagland, Mr. Konrad Mathias Reiersen",male,,1,0,65304,19.9667,,S +492,0,3,"Windelov, Mr. Einar",male,21,0,0,SOTON/OQ 3101317,7.25,,S +493,0,1,"Molson, Mr. Harry Markland",male,55,0,0,113787,30.5,C30,S +494,0,1,"Artagaveytia, Mr. Ramon",male,71,0,0,PC 17609,49.5042,,C +495,0,3,"Stanley, Mr. Edward Roland",male,21,0,0,A/4 45380,8.05,,S +496,0,3,"Yousseff, Mr. Gerious",male,,0,0,2627,14.4583,,C +497,1,1,"Eustis, Miss. Elizabeth Mussey",female,54,1,0,36947,78.2667,D20,C +498,0,3,"Shellard, Mr. Frederick William",male,,0,0,C.A. 6212,15.1,,S +499,0,1,"Allison, Mrs. Hudson J C (Bessie Waldo Daniels)",female,25,1,2,113781,151.55,C22 C26,S +500,0,3,"Svensson, Mr. Olof",male,24,0,0,350035,7.7958,,S +501,0,3,"Calic, Mr. Petar",male,17,0,0,315086,8.6625,,S +502,0,3,"Canavan, Miss. Mary",female,21,0,0,364846,7.75,,Q +503,0,3,"O'Sullivan, Miss. Bridget Mary",female,,0,0,330909,7.6292,,Q +504,0,3,"Laitinen, Miss. Kristina Sofia",female,37,0,0,4135,9.5875,,S +505,1,1,"Maioni, Miss. Roberta",female,16,0,0,110152,86.5,B79,S +506,0,1,"Penasco y Castellana, Mr. Victor de Satode",male,18,1,0,PC 17758,108.9,C65,C +507,1,2,"Quick, Mrs. Frederick Charles (Jane Richards)",female,33,0,2,26360,26,,S +508,1,1,"Bradley, Mr. George (""George Arthur Brayton"")",male,,0,0,111427,26.55,,S +509,0,3,"Olsen, Mr. Henry Margido",male,28,0,0,C 4001,22.525,,S +510,1,3,"Lang, Mr. Fang",male,26,0,0,1601,56.4958,,S +511,1,3,"Daly, Mr. Eugene Patrick",male,29,0,0,382651,7.75,,Q +512,0,3,"Webber, Mr. James",male,,0,0,SOTON/OQ 3101316,8.05,,S +513,1,1,"McGough, Mr. James Robert",male,36,0,0,PC 17473,26.2875,E25,S +514,1,1,"Rothschild, Mrs. Martin (Elizabeth L. Barrett)",female,54,1,0,PC 17603,59.4,,C +515,0,3,"Coleff, Mr. Satio",male,24,0,0,349209,7.4958,,S +516,0,1,"Walker, Mr. William Anderson",male,47,0,0,36967,34.0208,D46,S +517,1,2,"Lemore, Mrs. (Amelia Milley)",female,34,0,0,C.A. 34260,10.5,F33,S +518,0,3,"Ryan, Mr. Patrick",male,,0,0,371110,24.15,,Q +519,1,2,"Angle, Mrs. William A (Florence ""Mary"" Agnes Hughes)",female,36,1,0,226875,26,,S +520,0,3,"Pavlovic, Mr. Stefo",male,32,0,0,349242,7.8958,,S +521,1,1,"Perreault, Miss. Anne",female,30,0,0,12749,93.5,B73,S +522,0,3,"Vovk, Mr. Janko",male,22,0,0,349252,7.8958,,S +523,0,3,"Lahoud, Mr. Sarkis",male,,0,0,2624,7.225,,C +524,1,1,"Hippach, Mrs. Louis Albert (Ida Sophia Fischer)",female,44,0,1,111361,57.9792,B18,C +525,0,3,"Kassem, Mr. Fared",male,,0,0,2700,7.2292,,C +526,0,3,"Farrell, Mr. James",male,40.5,0,0,367232,7.75,,Q +527,1,2,"Ridsdale, Miss. Lucy",female,50,0,0,W./C. 14258,10.5,,S +528,0,1,"Farthing, Mr. John",male,,0,0,PC 17483,221.7792,C95,S +529,0,3,"Salonen, Mr. Johan Werner",male,39,0,0,3101296,7.925,,S +530,0,2,"Hocking, Mr. Richard George",male,23,2,1,29104,11.5,,S +531,1,2,"Quick, Miss. Phyllis May",female,2,1,1,26360,26,,S +532,0,3,"Toufik, Mr. Nakli",male,,0,0,2641,7.2292,,C +533,0,3,"Elias, Mr. Joseph Jr",male,17,1,1,2690,7.2292,,C +534,1,3,"Peter, Mrs. Catherine (Catherine Rizk)",female,,0,2,2668,22.3583,,C +535,0,3,"Cacic, Miss. Marija",female,30,0,0,315084,8.6625,,S +536,1,2,"Hart, Miss. Eva Miriam",female,7,0,2,F.C.C. 13529,26.25,,S +537,0,1,"Butt, Major. Archibald Willingham",male,45,0,0,113050,26.55,B38,S +538,1,1,"LeRoy, Miss. Bertha",female,30,0,0,PC 17761,106.425,,C +539,0,3,"Risien, Mr. Samuel Beard",male,,0,0,364498,14.5,,S +540,1,1,"Frolicher, Miss. Hedwig Margaritha",female,22,0,2,13568,49.5,B39,C +541,1,1,"Crosby, Miss. Harriet R",female,36,0,2,WE/P 5735,71,B22,S +542,0,3,"Andersson, Miss. Ingeborg Constanzia",female,9,4,2,347082,31.275,,S +543,0,3,"Andersson, Miss. Sigrid Elisabeth",female,11,4,2,347082,31.275,,S +544,1,2,"Beane, Mr. Edward",male,32,1,0,2908,26,,S +545,0,1,"Douglas, Mr. Walter Donald",male,50,1,0,PC 17761,106.425,C86,C +546,0,1,"Nicholson, Mr. Arthur Ernest",male,64,0,0,693,26,,S +547,1,2,"Beane, Mrs. Edward (Ethel Clarke)",female,19,1,0,2908,26,,S +548,1,2,"Padro y Manent, Mr. Julian",male,,0,0,SC/PARIS 2146,13.8625,,C +549,0,3,"Goldsmith, Mr. Frank John",male,33,1,1,363291,20.525,,S +550,1,2,"Davies, Master. John Morgan Jr",male,8,1,1,C.A. 33112,36.75,,S +551,1,1,"Thayer, Mr. John Borland Jr",male,17,0,2,17421,110.8833,C70,C +552,0,2,"Sharp, Mr. Percival James R",male,27,0,0,244358,26,,S +553,0,3,"O'Brien, Mr. Timothy",male,,0,0,330979,7.8292,,Q +554,1,3,"Leeni, Mr. Fahim (""Philip Zenni"")",male,22,0,0,2620,7.225,,C +555,1,3,"Ohman, Miss. Velin",female,22,0,0,347085,7.775,,S +556,0,1,"Wright, Mr. George",male,62,0,0,113807,26.55,,S +557,1,1,"Duff Gordon, Lady. (Lucille Christiana Sutherland) (""Mrs Morgan"")",female,48,1,0,11755,39.6,A16,C +558,0,1,"Robbins, Mr. Victor",male,,0,0,PC 17757,227.525,,C +559,1,1,"Taussig, Mrs. Emil (Tillie Mandelbaum)",female,39,1,1,110413,79.65,E67,S +560,1,3,"de Messemaeker, Mrs. Guillaume Joseph (Emma)",female,36,1,0,345572,17.4,,S +561,0,3,"Morrow, Mr. Thomas Rowan",male,,0,0,372622,7.75,,Q +562,0,3,"Sivic, Mr. Husein",male,40,0,0,349251,7.8958,,S +563,0,2,"Norman, Mr. Robert Douglas",male,28,0,0,218629,13.5,,S +564,0,3,"Simmons, Mr. John",male,,0,0,SOTON/OQ 392082,8.05,,S +565,0,3,"Meanwell, Miss. (Marion Ogden)",female,,0,0,SOTON/O.Q. 392087,8.05,,S +566,0,3,"Davies, Mr. Alfred J",male,24,2,0,A/4 48871,24.15,,S +567,0,3,"Stoytcheff, Mr. Ilia",male,19,0,0,349205,7.8958,,S +568,0,3,"Palsson, Mrs. Nils (Alma Cornelia Berglund)",female,29,0,4,349909,21.075,,S +569,0,3,"Doharr, Mr. Tannous",male,,0,0,2686,7.2292,,C +570,1,3,"Jonsson, Mr. Carl",male,32,0,0,350417,7.8542,,S +571,1,2,"Harris, Mr. George",male,62,0,0,S.W./PP 752,10.5,,S +572,1,1,"Appleton, Mrs. Edward Dale (Charlotte Lamson)",female,53,2,0,11769,51.4792,C101,S +573,1,1,"Flynn, Mr. John Irwin (""Irving"")",male,36,0,0,PC 17474,26.3875,E25,S +574,1,3,"Kelly, Miss. Mary",female,,0,0,14312,7.75,,Q +575,0,3,"Rush, Mr. Alfred George John",male,16,0,0,A/4. 20589,8.05,,S +576,0,3,"Patchett, Mr. George",male,19,0,0,358585,14.5,,S +577,1,2,"Garside, Miss. Ethel",female,34,0,0,243880,13,,S +578,1,1,"Silvey, Mrs. William Baird (Alice Munger)",female,39,1,0,13507,55.9,E44,S +579,0,3,"Caram, Mrs. Joseph (Maria Elias)",female,,1,0,2689,14.4583,,C +580,1,3,"Jussila, Mr. Eiriik",male,32,0,0,STON/O 2. 3101286,7.925,,S +581,1,2,"Christy, Miss. Julie Rachel",female,25,1,1,237789,30,,S +582,1,1,"Thayer, Mrs. John Borland (Marian Longstreth Morris)",female,39,1,1,17421,110.8833,C68,C +583,0,2,"Downton, Mr. William James",male,54,0,0,28403,26,,S +584,0,1,"Ross, Mr. John Hugo",male,36,0,0,13049,40.125,A10,C +585,0,3,"Paulner, Mr. Uscher",male,,0,0,3411,8.7125,,C +586,1,1,"Taussig, Miss. Ruth",female,18,0,2,110413,79.65,E68,S +587,0,2,"Jarvis, Mr. John Denzil",male,47,0,0,237565,15,,S +588,1,1,"Frolicher-Stehli, Mr. Maxmillian",male,60,1,1,13567,79.2,B41,C +589,0,3,"Gilinski, Mr. Eliezer",male,22,0,0,14973,8.05,,S +590,0,3,"Murdlin, Mr. Joseph",male,,0,0,A./5. 3235,8.05,,S +591,0,3,"Rintamaki, Mr. Matti",male,35,0,0,STON/O 2. 3101273,7.125,,S +592,1,1,"Stephenson, Mrs. Walter Bertram (Martha Eustis)",female,52,1,0,36947,78.2667,D20,C +593,0,3,"Elsbury, Mr. William James",male,47,0,0,A/5 3902,7.25,,S +594,0,3,"Bourke, Miss. Mary",female,,0,2,364848,7.75,,Q +595,0,2,"Chapman, Mr. John Henry",male,37,1,0,SC/AH 29037,26,,S +596,0,3,"Van Impe, Mr. Jean Baptiste",male,36,1,1,345773,24.15,,S +597,1,2,"Leitch, Miss. Jessie Wills",female,,0,0,248727,33,,S +598,0,3,"Johnson, Mr. Alfred",male,49,0,0,LINE,0,,S +599,0,3,"Boulos, Mr. Hanna",male,,0,0,2664,7.225,,C +600,1,1,"Duff Gordon, Sir. Cosmo Edmund (""Mr Morgan"")",male,49,1,0,PC 17485,56.9292,A20,C +601,1,2,"Jacobsohn, Mrs. Sidney Samuel (Amy Frances Christy)",female,24,2,1,243847,27,,S +602,0,3,"Slabenoff, Mr. Petco",male,,0,0,349214,7.8958,,S +603,0,1,"Harrington, Mr. Charles H",male,,0,0,113796,42.4,,S +604,0,3,"Torber, Mr. Ernst William",male,44,0,0,364511,8.05,,S +605,1,1,"Homer, Mr. Harry (""Mr E Haven"")",male,35,0,0,111426,26.55,,C +606,0,3,"Lindell, Mr. Edvard Bengtsson",male,36,1,0,349910,15.55,,S +607,0,3,"Karaic, Mr. Milan",male,30,0,0,349246,7.8958,,S +608,1,1,"Daniel, Mr. Robert Williams",male,27,0,0,113804,30.5,,S +609,1,2,"Laroche, Mrs. Joseph (Juliette Marie Louise Lafargue)",female,22,1,2,SC/Paris 2123,41.5792,,C +610,1,1,"Shutes, Miss. Elizabeth W",female,40,0,0,PC 17582,153.4625,C125,S +611,0,3,"Andersson, Mrs. Anders Johan (Alfrida Konstantia Brogren)",female,39,1,5,347082,31.275,,S +612,0,3,"Jardin, Mr. Jose Neto",male,,0,0,SOTON/O.Q. 3101305,7.05,,S +613,1,3,"Murphy, Miss. Margaret Jane",female,,1,0,367230,15.5,,Q +614,0,3,"Horgan, Mr. John",male,,0,0,370377,7.75,,Q +615,0,3,"Brocklebank, Mr. William Alfred",male,35,0,0,364512,8.05,,S +616,1,2,"Herman, Miss. Alice",female,24,1,2,220845,65,,S +617,0,3,"Danbom, Mr. Ernst Gilbert",male,34,1,1,347080,14.4,,S +618,0,3,"Lobb, Mrs. William Arthur (Cordelia K Stanlick)",female,26,1,0,A/5. 3336,16.1,,S +619,1,2,"Becker, Miss. Marion Louise",female,4,2,1,230136,39,F4,S +620,0,2,"Gavey, Mr. Lawrence",male,26,0,0,31028,10.5,,S +621,0,3,"Yasbeck, Mr. Antoni",male,27,1,0,2659,14.4542,,C +622,1,1,"Kimball, Mr. Edwin Nelson Jr",male,42,1,0,11753,52.5542,D19,S +623,1,3,"Nakid, Mr. Sahid",male,20,1,1,2653,15.7417,,C +624,0,3,"Hansen, Mr. Henry Damsgaard",male,21,0,0,350029,7.8542,,S +625,0,3,"Bowen, Mr. David John ""Dai""",male,21,0,0,54636,16.1,,S +626,0,1,"Sutton, Mr. Frederick",male,61,0,0,36963,32.3208,D50,S +627,0,2,"Kirkland, Rev. Charles Leonard",male,57,0,0,219533,12.35,,Q +628,1,1,"Longley, Miss. Gretchen Fiske",female,21,0,0,13502,77.9583,D9,S +629,0,3,"Bostandyeff, Mr. Guentcho",male,26,0,0,349224,7.8958,,S +630,0,3,"O'Connell, Mr. Patrick D",male,,0,0,334912,7.7333,,Q +631,1,1,"Barkworth, Mr. Algernon Henry Wilson",male,80,0,0,27042,30,A23,S +632,0,3,"Lundahl, Mr. Johan Svensson",male,51,0,0,347743,7.0542,,S +633,1,1,"Stahelin-Maeglin, Dr. Max",male,32,0,0,13214,30.5,B50,C +634,0,1,"Parr, Mr. William Henry Marsh",male,,0,0,112052,0,,S +635,0,3,"Skoog, Miss. Mabel",female,9,3,2,347088,27.9,,S +636,1,2,"Davis, Miss. Mary",female,28,0,0,237668,13,,S +637,0,3,"Leinonen, Mr. Antti Gustaf",male,32,0,0,STON/O 2. 3101292,7.925,,S +638,0,2,"Collyer, Mr. Harvey",male,31,1,1,C.A. 31921,26.25,,S +639,0,3,"Panula, Mrs. Juha (Maria Emilia Ojala)",female,41,0,5,3101295,39.6875,,S +640,0,3,"Thorneycroft, Mr. Percival",male,,1,0,376564,16.1,,S +641,0,3,"Jensen, Mr. Hans Peder",male,20,0,0,350050,7.8542,,S +642,1,1,"Sagesser, Mlle. Emma",female,24,0,0,PC 17477,69.3,B35,C +643,0,3,"Skoog, Miss. Margit Elizabeth",female,2,3,2,347088,27.9,,S +644,1,3,"Foo, Mr. Choong",male,,0,0,1601,56.4958,,S +645,1,3,"Baclini, Miss. Eugenie",female,0.75,2,1,2666,19.2583,,C +646,1,1,"Harper, Mr. Henry Sleeper",male,48,1,0,PC 17572,76.7292,D33,C +647,0,3,"Cor, Mr. Liudevit",male,19,0,0,349231,7.8958,,S +648,1,1,"Simonius-Blumer, Col. Oberst Alfons",male,56,0,0,13213,35.5,A26,C +649,0,3,"Willey, Mr. Edward",male,,0,0,S.O./P.P. 751,7.55,,S +650,1,3,"Stanley, Miss. Amy Zillah Elsie",female,23,0,0,CA. 2314,7.55,,S +651,0,3,"Mitkoff, Mr. Mito",male,,0,0,349221,7.8958,,S +652,1,2,"Doling, Miss. Elsie",female,18,0,1,231919,23,,S +653,0,3,"Kalvik, Mr. Johannes Halvorsen",male,21,0,0,8475,8.4333,,S +654,1,3,"O'Leary, Miss. Hanora ""Norah""",female,,0,0,330919,7.8292,,Q +655,0,3,"Hegarty, Miss. Hanora ""Nora""",female,18,0,0,365226,6.75,,Q +656,0,2,"Hickman, Mr. Leonard Mark",male,24,2,0,S.O.C. 14879,73.5,,S +657,0,3,"Radeff, Mr. Alexander",male,,0,0,349223,7.8958,,S +658,0,3,"Bourke, Mrs. John (Catherine)",female,32,1,1,364849,15.5,,Q +659,0,2,"Eitemiller, Mr. George Floyd",male,23,0,0,29751,13,,S +660,0,1,"Newell, Mr. Arthur Webster",male,58,0,2,35273,113.275,D48,C +661,1,1,"Frauenthal, Dr. Henry William",male,50,2,0,PC 17611,133.65,,S +662,0,3,"Badt, Mr. Mohamed",male,40,0,0,2623,7.225,,C +663,0,1,"Colley, Mr. Edward Pomeroy",male,47,0,0,5727,25.5875,E58,S +664,0,3,"Coleff, Mr. Peju",male,36,0,0,349210,7.4958,,S +665,1,3,"Lindqvist, Mr. Eino William",male,20,1,0,STON/O 2. 3101285,7.925,,S +666,0,2,"Hickman, Mr. Lewis",male,32,2,0,S.O.C. 14879,73.5,,S +667,0,2,"Butler, Mr. Reginald Fenton",male,25,0,0,234686,13,,S +668,0,3,"Rommetvedt, Mr. Knud Paust",male,,0,0,312993,7.775,,S +669,0,3,"Cook, Mr. Jacob",male,43,0,0,A/5 3536,8.05,,S +670,1,1,"Taylor, Mrs. Elmer Zebley (Juliet Cummins Wright)",female,,1,0,19996,52,C126,S +671,1,2,"Brown, Mrs. Thomas William Solomon (Elizabeth Catherine Ford)",female,40,1,1,29750,39,,S +672,0,1,"Davidson, Mr. Thornton",male,31,1,0,F.C. 12750,52,B71,S +673,0,2,"Mitchell, Mr. Henry Michael",male,70,0,0,C.A. 24580,10.5,,S +674,1,2,"Wilhelms, Mr. Charles",male,31,0,0,244270,13,,S +675,0,2,"Watson, Mr. Ennis Hastings",male,,0,0,239856,0,,S +676,0,3,"Edvardsson, Mr. Gustaf Hjalmar",male,18,0,0,349912,7.775,,S +677,0,3,"Sawyer, Mr. Frederick Charles",male,24.5,0,0,342826,8.05,,S +678,1,3,"Turja, Miss. Anna Sofia",female,18,0,0,4138,9.8417,,S +679,0,3,"Goodwin, Mrs. Frederick (Augusta Tyler)",female,43,1,6,CA 2144,46.9,,S +680,1,1,"Cardeza, Mr. Thomas Drake Martinez",male,36,0,1,PC 17755,512.3292,B51 B53 B55,C +681,0,3,"Peters, Miss. Katie",female,,0,0,330935,8.1375,,Q +682,1,1,"Hassab, Mr. Hammad",male,27,0,0,PC 17572,76.7292,D49,C +683,0,3,"Olsvigen, Mr. Thor Anderson",male,20,0,0,6563,9.225,,S +684,0,3,"Goodwin, Mr. Charles Edward",male,14,5,2,CA 2144,46.9,,S +685,0,2,"Brown, Mr. Thomas William Solomon",male,60,1,1,29750,39,,S +686,0,2,"Laroche, Mr. Joseph Philippe Lemercier",male,25,1,2,SC/Paris 2123,41.5792,,C +687,0,3,"Panula, Mr. Jaako Arnold",male,14,4,1,3101295,39.6875,,S +688,0,3,"Dakic, Mr. Branko",male,19,0,0,349228,10.1708,,S +689,0,3,"Fischer, Mr. Eberhard Thelander",male,18,0,0,350036,7.7958,,S +690,1,1,"Madill, Miss. Georgette Alexandra",female,15,0,1,24160,211.3375,B5,S +691,1,1,"Dick, Mr. Albert Adrian",male,31,1,0,17474,57,B20,S +692,1,3,"Karun, Miss. Manca",female,4,0,1,349256,13.4167,,C +693,1,3,"Lam, Mr. Ali",male,,0,0,1601,56.4958,,S +694,0,3,"Saad, Mr. Khalil",male,25,0,0,2672,7.225,,C +695,0,1,"Weir, Col. John",male,60,0,0,113800,26.55,,S +696,0,2,"Chapman, Mr. Charles Henry",male,52,0,0,248731,13.5,,S +697,0,3,"Kelly, Mr. James",male,44,0,0,363592,8.05,,S +698,1,3,"Mullens, Miss. Katherine ""Katie""",female,,0,0,35852,7.7333,,Q +699,0,1,"Thayer, Mr. John Borland",male,49,1,1,17421,110.8833,C68,C +700,0,3,"Humblen, Mr. Adolf Mathias Nicolai Olsen",male,42,0,0,348121,7.65,F G63,S +701,1,1,"Astor, Mrs. John Jacob (Madeleine Talmadge Force)",female,18,1,0,PC 17757,227.525,C62 C64,C +702,1,1,"Silverthorne, Mr. Spencer Victor",male,35,0,0,PC 17475,26.2875,E24,S +703,0,3,"Barbara, Miss. Saiide",female,18,0,1,2691,14.4542,,C +704,0,3,"Gallagher, Mr. Martin",male,25,0,0,36864,7.7417,,Q +705,0,3,"Hansen, Mr. Henrik Juul",male,26,1,0,350025,7.8542,,S +706,0,2,"Morley, Mr. Henry Samuel (""Mr Henry Marshall"")",male,39,0,0,250655,26,,S +707,1,2,"Kelly, Mrs. Florence ""Fannie""",female,45,0,0,223596,13.5,,S +708,1,1,"Calderhead, Mr. Edward Pennington",male,42,0,0,PC 17476,26.2875,E24,S +709,1,1,"Cleaver, Miss. Alice",female,22,0,0,113781,151.55,,S +710,1,3,"Moubarek, Master. Halim Gonios (""William George"")",male,,1,1,2661,15.2458,,C +711,1,1,"Mayne, Mlle. Berthe Antonine (""Mrs de Villiers"")",female,24,0,0,PC 17482,49.5042,C90,C +712,0,1,"Klaber, Mr. Herman",male,,0,0,113028,26.55,C124,S +713,1,1,"Taylor, Mr. Elmer Zebley",male,48,1,0,19996,52,C126,S +714,0,3,"Larsson, Mr. August Viktor",male,29,0,0,7545,9.4833,,S +715,0,2,"Greenberg, Mr. Samuel",male,52,0,0,250647,13,,S +716,0,3,"Soholt, Mr. Peter Andreas Lauritz Andersen",male,19,0,0,348124,7.65,F G73,S +717,1,1,"Endres, Miss. Caroline Louise",female,38,0,0,PC 17757,227.525,C45,C +718,1,2,"Troutt, Miss. Edwina Celia ""Winnie""",female,27,0,0,34218,10.5,E101,S +719,0,3,"McEvoy, Mr. Michael",male,,0,0,36568,15.5,,Q +720,0,3,"Johnson, Mr. Malkolm Joackim",male,33,0,0,347062,7.775,,S +721,1,2,"Harper, Miss. Annie Jessie ""Nina""",female,6,0,1,248727,33,,S +722,0,3,"Jensen, Mr. Svend Lauritz",male,17,1,0,350048,7.0542,,S +723,0,2,"Gillespie, Mr. William Henry",male,34,0,0,12233,13,,S +724,0,2,"Hodges, Mr. Henry Price",male,50,0,0,250643,13,,S +725,1,1,"Chambers, Mr. Norman Campbell",male,27,1,0,113806,53.1,E8,S +726,0,3,"Oreskovic, Mr. Luka",male,20,0,0,315094,8.6625,,S +727,1,2,"Renouf, Mrs. Peter Henry (Lillian Jefferys)",female,30,3,0,31027,21,,S +728,1,3,"Mannion, Miss. Margareth",female,,0,0,36866,7.7375,,Q +729,0,2,"Bryhl, Mr. Kurt Arnold Gottfrid",male,25,1,0,236853,26,,S +730,0,3,"Ilmakangas, Miss. Pieta Sofia",female,25,1,0,STON/O2. 3101271,7.925,,S +731,1,1,"Allen, Miss. Elisabeth Walton",female,29,0,0,24160,211.3375,B5,S +732,0,3,"Hassan, Mr. Houssein G N",male,11,0,0,2699,18.7875,,C +733,0,2,"Knight, Mr. Robert J",male,,0,0,239855,0,,S +734,0,2,"Berriman, Mr. William John",male,23,0,0,28425,13,,S +735,0,2,"Troupiansky, Mr. Moses Aaron",male,23,0,0,233639,13,,S +736,0,3,"Williams, Mr. Leslie",male,28.5,0,0,54636,16.1,,S +737,0,3,"Ford, Mrs. Edward (Margaret Ann Watson)",female,48,1,3,W./C. 6608,34.375,,S +738,1,1,"Lesurer, Mr. Gustave J",male,35,0,0,PC 17755,512.3292,B101,C +739,0,3,"Ivanoff, Mr. Kanio",male,,0,0,349201,7.8958,,S +740,0,3,"Nankoff, Mr. Minko",male,,0,0,349218,7.8958,,S +741,1,1,"Hawksford, Mr. Walter James",male,,0,0,16988,30,D45,S +742,0,1,"Cavendish, Mr. Tyrell William",male,36,1,0,19877,78.85,C46,S +743,1,1,"Ryerson, Miss. Susan Parker ""Suzette""",female,21,2,2,PC 17608,262.375,B57 B59 B63 B66,C +744,0,3,"McNamee, Mr. Neal",male,24,1,0,376566,16.1,,S +745,1,3,"Stranden, Mr. Juho",male,31,0,0,STON/O 2. 3101288,7.925,,S +746,0,1,"Crosby, Capt. Edward Gifford",male,70,1,1,WE/P 5735,71,B22,S +747,0,3,"Abbott, Mr. Rossmore Edward",male,16,1,1,C.A. 2673,20.25,,S +748,1,2,"Sinkkonen, Miss. Anna",female,30,0,0,250648,13,,S +749,0,1,"Marvin, Mr. Daniel Warner",male,19,1,0,113773,53.1,D30,S +750,0,3,"Connaghton, Mr. Michael",male,31,0,0,335097,7.75,,Q +751,1,2,"Wells, Miss. Joan",female,4,1,1,29103,23,,S +752,1,3,"Moor, Master. Meier",male,6,0,1,392096,12.475,E121,S +753,0,3,"Vande Velde, Mr. Johannes Joseph",male,33,0,0,345780,9.5,,S +754,0,3,"Jonkoff, Mr. Lalio",male,23,0,0,349204,7.8958,,S +755,1,2,"Herman, Mrs. Samuel (Jane Laver)",female,48,1,2,220845,65,,S +756,1,2,"Hamalainen, Master. Viljo",male,0.67,1,1,250649,14.5,,S +757,0,3,"Carlsson, Mr. August Sigfrid",male,28,0,0,350042,7.7958,,S +758,0,2,"Bailey, Mr. Percy Andrew",male,18,0,0,29108,11.5,,S +759,0,3,"Theobald, Mr. Thomas Leonard",male,34,0,0,363294,8.05,,S +760,1,1,"Rothes, the Countess. of (Lucy Noel Martha Dyer-Edwards)",female,33,0,0,110152,86.5,B77,S +761,0,3,"Garfirth, Mr. John",male,,0,0,358585,14.5,,S +762,0,3,"Nirva, Mr. Iisakki Antino Aijo",male,41,0,0,SOTON/O2 3101272,7.125,,S +763,1,3,"Barah, Mr. Hanna Assi",male,20,0,0,2663,7.2292,,C +764,1,1,"Carter, Mrs. William Ernest (Lucile Polk)",female,36,1,2,113760,120,B96 B98,S +765,0,3,"Eklund, Mr. Hans Linus",male,16,0,0,347074,7.775,,S +766,1,1,"Hogeboom, Mrs. John C (Anna Andrews)",female,51,1,0,13502,77.9583,D11,S +767,0,1,"Brewe, Dr. Arthur Jackson",male,,0,0,112379,39.6,,C +768,0,3,"Mangan, Miss. Mary",female,30.5,0,0,364850,7.75,,Q +769,0,3,"Moran, Mr. Daniel J",male,,1,0,371110,24.15,,Q +770,0,3,"Gronnestad, Mr. Daniel Danielsen",male,32,0,0,8471,8.3625,,S +771,0,3,"Lievens, Mr. Rene Aime",male,24,0,0,345781,9.5,,S +772,0,3,"Jensen, Mr. Niels Peder",male,48,0,0,350047,7.8542,,S +773,0,2,"Mack, Mrs. (Mary)",female,57,0,0,S.O./P.P. 3,10.5,E77,S +774,0,3,"Elias, Mr. Dibo",male,,0,0,2674,7.225,,C +775,1,2,"Hocking, Mrs. Elizabeth (Eliza Needs)",female,54,1,3,29105,23,,S +776,0,3,"Myhrman, Mr. Pehr Fabian Oliver Malkolm",male,18,0,0,347078,7.75,,S +777,0,3,"Tobin, Mr. Roger",male,,0,0,383121,7.75,F38,Q +778,1,3,"Emanuel, Miss. Virginia Ethel",female,5,0,0,364516,12.475,,S +779,0,3,"Kilgannon, Mr. Thomas J",male,,0,0,36865,7.7375,,Q +780,1,1,"Robert, Mrs. Edward Scott (Elisabeth Walton McMillan)",female,43,0,1,24160,211.3375,B3,S +781,1,3,"Ayoub, Miss. Banoura",female,13,0,0,2687,7.2292,,C +782,1,1,"Dick, Mrs. Albert Adrian (Vera Gillespie)",female,17,1,0,17474,57,B20,S +783,0,1,"Long, Mr. Milton Clyde",male,29,0,0,113501,30,D6,S +784,0,3,"Johnston, Mr. Andrew G",male,,1,2,W./C. 6607,23.45,,S +785,0,3,"Ali, Mr. William",male,25,0,0,SOTON/O.Q. 3101312,7.05,,S +786,0,3,"Harmer, Mr. Abraham (David Lishin)",male,25,0,0,374887,7.25,,S +787,1,3,"Sjoblom, Miss. Anna Sofia",female,18,0,0,3101265,7.4958,,S +788,0,3,"Rice, Master. George Hugh",male,8,4,1,382652,29.125,,Q +789,1,3,"Dean, Master. Bertram Vere",male,1,1,2,C.A. 2315,20.575,,S +790,0,1,"Guggenheim, Mr. Benjamin",male,46,0,0,PC 17593,79.2,B82 B84,C +791,0,3,"Keane, Mr. Andrew ""Andy""",male,,0,0,12460,7.75,,Q +792,0,2,"Gaskell, Mr. Alfred",male,16,0,0,239865,26,,S +793,0,3,"Sage, Miss. Stella Anna",female,,8,2,CA. 2343,69.55,,S +794,0,1,"Hoyt, Mr. William Fisher",male,,0,0,PC 17600,30.6958,,C +795,0,3,"Dantcheff, Mr. Ristiu",male,25,0,0,349203,7.8958,,S +796,0,2,"Otter, Mr. Richard",male,39,0,0,28213,13,,S +797,1,1,"Leader, Dr. Alice (Farnham)",female,49,0,0,17465,25.9292,D17,S +798,1,3,"Osman, Mrs. Mara",female,31,0,0,349244,8.6833,,S +799,0,3,"Ibrahim Shawah, Mr. Yousseff",male,30,0,0,2685,7.2292,,C +800,0,3,"Van Impe, Mrs. Jean Baptiste (Rosalie Paula Govaert)",female,30,1,1,345773,24.15,,S +801,0,2,"Ponesell, Mr. Martin",male,34,0,0,250647,13,,S +802,1,2,"Collyer, Mrs. Harvey (Charlotte Annie Tate)",female,31,1,1,C.A. 31921,26.25,,S +803,1,1,"Carter, Master. William Thornton II",male,11,1,2,113760,120,B96 B98,S +804,1,3,"Thomas, Master. Assad Alexander",male,0.42,0,1,2625,8.5167,,C +805,1,3,"Hedman, Mr. Oskar Arvid",male,27,0,0,347089,6.975,,S +806,0,3,"Johansson, Mr. Karl Johan",male,31,0,0,347063,7.775,,S +807,0,1,"Andrews, Mr. Thomas Jr",male,39,0,0,112050,0,A36,S +808,0,3,"Pettersson, Miss. Ellen Natalia",female,18,0,0,347087,7.775,,S +809,0,2,"Meyer, Mr. August",male,39,0,0,248723,13,,S +810,1,1,"Chambers, Mrs. Norman Campbell (Bertha Griggs)",female,33,1,0,113806,53.1,E8,S +811,0,3,"Alexander, Mr. William",male,26,0,0,3474,7.8875,,S +812,0,3,"Lester, Mr. James",male,39,0,0,A/4 48871,24.15,,S +813,0,2,"Slemen, Mr. Richard James",male,35,0,0,28206,10.5,,S +814,0,3,"Andersson, Miss. Ebba Iris Alfrida",female,6,4,2,347082,31.275,,S +815,0,3,"Tomlin, Mr. Ernest Portage",male,30.5,0,0,364499,8.05,,S +816,0,1,"Fry, Mr. Richard",male,,0,0,112058,0,B102,S +817,0,3,"Heininen, Miss. Wendla Maria",female,23,0,0,STON/O2. 3101290,7.925,,S +818,0,2,"Mallet, Mr. Albert",male,31,1,1,S.C./PARIS 2079,37.0042,,C +819,0,3,"Holm, Mr. John Fredrik Alexander",male,43,0,0,C 7075,6.45,,S +820,0,3,"Skoog, Master. Karl Thorsten",male,10,3,2,347088,27.9,,S +821,1,1,"Hays, Mrs. Charles Melville (Clara Jennings Gregg)",female,52,1,1,12749,93.5,B69,S +822,1,3,"Lulic, Mr. Nikola",male,27,0,0,315098,8.6625,,S +823,0,1,"Reuchlin, Jonkheer. John George",male,38,0,0,19972,0,,S +824,1,3,"Moor, Mrs. (Beila)",female,27,0,1,392096,12.475,E121,S +825,0,3,"Panula, Master. Urho Abraham",male,2,4,1,3101295,39.6875,,S +826,0,3,"Flynn, Mr. John",male,,0,0,368323,6.95,,Q +827,0,3,"Lam, Mr. Len",male,,0,0,1601,56.4958,,S +828,1,2,"Mallet, Master. Andre",male,1,0,2,S.C./PARIS 2079,37.0042,,C +829,1,3,"McCormack, Mr. Thomas Joseph",male,,0,0,367228,7.75,,Q +830,1,1,"Stone, Mrs. George Nelson (Martha Evelyn)",female,62,0,0,113572,80,B28, +831,1,3,"Yasbeck, Mrs. Antoni (Selini Alexander)",female,15,1,0,2659,14.4542,,C +832,1,2,"Richards, Master. George Sibley",male,0.83,1,1,29106,18.75,,S +833,0,3,"Saad, Mr. Amin",male,,0,0,2671,7.2292,,C +834,0,3,"Augustsson, Mr. Albert",male,23,0,0,347468,7.8542,,S +835,0,3,"Allum, Mr. Owen George",male,18,0,0,2223,8.3,,S +836,1,1,"Compton, Miss. Sara Rebecca",female,39,1,1,PC 17756,83.1583,E49,C +837,0,3,"Pasic, Mr. Jakob",male,21,0,0,315097,8.6625,,S +838,0,3,"Sirota, Mr. Maurice",male,,0,0,392092,8.05,,S +839,1,3,"Chip, Mr. Chang",male,32,0,0,1601,56.4958,,S +840,1,1,"Marechal, Mr. Pierre",male,,0,0,11774,29.7,C47,C +841,0,3,"Alhomaki, Mr. Ilmari Rudolf",male,20,0,0,SOTON/O2 3101287,7.925,,S +842,0,2,"Mudd, Mr. Thomas Charles",male,16,0,0,S.O./P.P. 3,10.5,,S +843,1,1,"Serepeca, Miss. Augusta",female,30,0,0,113798,31,,C +844,0,3,"Lemberopolous, Mr. Peter L",male,34.5,0,0,2683,6.4375,,C +845,0,3,"Culumovic, Mr. Jeso",male,17,0,0,315090,8.6625,,S +846,0,3,"Abbing, Mr. Anthony",male,42,0,0,C.A. 5547,7.55,,S +847,0,3,"Sage, Mr. Douglas Bullen",male,,8,2,CA. 2343,69.55,,S +848,0,3,"Markoff, Mr. Marin",male,35,0,0,349213,7.8958,,C +849,0,2,"Harper, Rev. John",male,28,0,1,248727,33,,S +850,1,1,"Goldenberg, Mrs. Samuel L (Edwiga Grabowska)",female,,1,0,17453,89.1042,C92,C +851,0,3,"Andersson, Master. Sigvard Harald Elias",male,4,4,2,347082,31.275,,S +852,0,3,"Svensson, Mr. Johan",male,74,0,0,347060,7.775,,S +853,0,3,"Boulos, Miss. Nourelain",female,9,1,1,2678,15.2458,,C +854,1,1,"Lines, Miss. Mary Conover",female,16,0,1,PC 17592,39.4,D28,S +855,0,2,"Carter, Mrs. Ernest Courtenay (Lilian Hughes)",female,44,1,0,244252,26,,S +856,1,3,"Aks, Mrs. Sam (Leah Rosen)",female,18,0,1,392091,9.35,,S +857,1,1,"Wick, Mrs. George Dennick (Mary Hitchcock)",female,45,1,1,36928,164.8667,,S +858,1,1,"Daly, Mr. Peter Denis ",male,51,0,0,113055,26.55,E17,S +859,1,3,"Baclini, Mrs. Solomon (Latifa Qurban)",female,24,0,3,2666,19.2583,,C +860,0,3,"Razi, Mr. Raihed",male,,0,0,2629,7.2292,,C +861,0,3,"Hansen, Mr. Claus Peter",male,41,2,0,350026,14.1083,,S +862,0,2,"Giles, Mr. Frederick Edward",male,21,1,0,28134,11.5,,S +863,1,1,"Swift, Mrs. Frederick Joel (Margaret Welles Barron)",female,48,0,0,17466,25.9292,D17,S +864,0,3,"Sage, Miss. Dorothy Edith ""Dolly""",female,,8,2,CA. 2343,69.55,,S +865,0,2,"Gill, Mr. John William",male,24,0,0,233866,13,,S +866,1,2,"Bystrom, Mrs. (Karolina)",female,42,0,0,236852,13,,S +867,1,2,"Duran y More, Miss. Asuncion",female,27,1,0,SC/PARIS 2149,13.8583,,C +868,0,1,"Roebling, Mr. Washington Augustus II",male,31,0,0,PC 17590,50.4958,A24,S +869,0,3,"van Melkebeke, Mr. Philemon",male,,0,0,345777,9.5,,S +870,1,3,"Johnson, Master. Harold Theodor",male,4,1,1,347742,11.1333,,S +871,0,3,"Balkic, Mr. Cerin",male,26,0,0,349248,7.8958,,S +872,1,1,"Beckwith, Mrs. Richard Leonard (Sallie Monypeny)",female,47,1,1,11751,52.5542,D35,S +873,0,1,"Carlsson, Mr. Frans Olof",male,33,0,0,695,5,B51 B53 B55,S +874,0,3,"Vander Cruyssen, Mr. Victor",male,47,0,0,345765,9,,S +875,1,2,"Abelson, Mrs. Samuel (Hannah Wizosky)",female,28,1,0,P/PP 3381,24,,C +876,1,3,"Najib, Miss. Adele Kiamie ""Jane""",female,15,0,0,2667,7.225,,C +877,0,3,"Gustafsson, Mr. Alfred Ossian",male,20,0,0,7534,9.8458,,S +878,0,3,"Petroff, Mr. Nedelio",male,19,0,0,349212,7.8958,,S +879,0,3,"Laleff, Mr. Kristo",male,,0,0,349217,7.8958,,S +880,1,1,"Potter, Mrs. Thomas Jr (Lily Alexenia Wilson)",female,56,0,1,11767,83.1583,C50,C +881,1,2,"Shelley, Mrs. William (Imanita Parrish Hall)",female,25,0,1,230433,26,,S +882,0,3,"Markun, Mr. Johann",male,33,0,0,349257,7.8958,,S +883,0,3,"Dahlberg, Miss. Gerda Ulrika",female,22,0,0,7552,10.5167,,S +884,0,2,"Banfield, Mr. Frederick James",male,28,0,0,C.A./SOTON 34068,10.5,,S +885,0,3,"Sutehall, Mr. Henry Jr",male,25,0,0,SOTON/OQ 392076,7.05,,S +886,0,3,"Rice, Mrs. William (Margaret Norton)",female,39,0,5,382652,29.125,,Q +887,0,2,"Montvila, Rev. Juozas",male,27,0,0,211536,13,,S +888,1,1,"Graham, Miss. Margaret Edith",female,19,0,0,112053,30,B42,S +889,0,3,"Johnston, Miss. Catherine Helen ""Carrie""",female,,1,2,W./C. 6607,23.45,,S +890,1,1,"Behr, Mr. Karl Howell",male,26,0,0,111369,30,C148,C +891,0,3,"Dooley, Mr. Patrick",male,32,0,0,370376,7.75,,Q diff --git a/work-with-data/datasets/datasets-tutorial/train-dataset/train.py b/work-with-data/datasets/datasets-tutorial/train-dataset/train.py new file mode 100644 index 00000000..9fbca891 --- /dev/null +++ b/work-with-data/datasets/datasets-tutorial/train-dataset/train.py @@ -0,0 +1,37 @@ +import azureml.dataprep as dprep +import azureml.core +import pandas as pd +import logging +import os +import datetime +import shutil + +from azureml.core import Workspace, Datastore, Dataset, Experiment, Run +from sklearn.model_selection import train_test_split +from azureml.core.compute import ComputeTarget, AmlCompute +from azureml.core.compute_target import ComputeTargetException +from sklearn.tree import DecisionTreeClassifier + +run = Run.get_context() +workspace = run.experiment.workspace + +dataset_name = 'clean_Titanic_tutorial' + +dataset = Dataset.get(workspace=workspace, name=dataset_name) +df = dataset.to_pandas_dataframe() + +x_col = ['Pclass', 'Sex', 'SibSp', 'Parch'] +y_col = ['Survived'] +x_df = df.loc[:, x_col] +y_df = df.loc[:, y_col] + +x_train, x_test, y_train, y_test = train_test_split(x_df, y_df, test_size=0.2, random_state=223) + +data = {"train": {"X": x_train, "y": y_train}, + + "test": {"X": x_test, "y": y_test}} + +clf = DecisionTreeClassifier().fit(data["train"]["X"], data["train"]["y"]) + +print('Accuracy of Decision Tree classifier on training set: {:.2f}'.format(clf.score(x_train, y_train))) +print('Accuracy of Decision Tree classifier on test set: {:.2f}'.format(clf.score(x_test, y_test)))