diff --git a/README.md b/README.md
index e22b0a35..9989fe78 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,9 @@
-# RAPIDS Notebooks-Contrib
+# RAPIDS Community Contrib
---
## Table of Contents
* [Intro](#intro)
-* [Installation](#install)
-* [Exploring the Repo](#explore)
-
-Notebooks:
-* [Getting Started](#get_started)
-* [Intermideate](#middle)
-* [Advanced](#advanced)
-* [BLOGS](#blogs)
-* [Conference](#conference)
+* [Exploring the Repo](#exploring)
+* [Great places to get started](#get_started)
---
@@ -18,151 +11,165 @@ Notebooks:
Welcome to the community contributed notebooks repo! (formerly known as Notebooks-Extended)
-The purpose of this collection of notebooks is to help users understand what RAPIDS has to offer, learn why, how, and when including RAPIDS in a data science pipeline makes sense, and contain community contributions of RAPIDS knowledge. The difference between this repo and the [Notebooks Repo](https://github.com/rapidsai/notebooks) are:
-1. These are vetted, community-contributed notebooks (includes RAPIDS team member contributions).
-1. These notebooks won't run on air gapped systems, which is one of our container requirements. Many RAPIDS notebooks use additional PyData ecosystem packages, and include code for downloading datasets, thus they require network connectivity. If running on a system with no network access, please download all the data that you plan to use ahead of time or simply use the [core notebooks repo](https://github.com/rapidsai/notebooks).
+The purpose of this collection is to introduce RAPIDS to new users by providing useful jupyter notebooks as learning aides. This collection of notebooks are direct community contributions by the RAPIDS team, our Ecosystem Partners, and RAPIDS users like you!
+### What do you mean "Community Notebooks"
-## Installation
+These notebooks are for the community. It means:
+1. YOU can contribute workflow examples, tips and tricks, or tutorials for others to use and share! [We ask that you follow our Testing and PR process.](#contributing)
+2. If your notebook is awesome, your notebook can be featured
-Please use the [BUILD.md](BUILD.md) to check the pre-requisite packages and installation steps.
+There are some additional Community Responsibilities, as the RAPIDS team isn't maintaining these notebooks
+- If you write an awesome notebook, please try to keep it maintained. You'll be mentioned on the issue.
+- If you find an issue, don't just file an issue - please attempt to fix it!
+- If a notebook has a problem and/or its last tested RAPIDS release version is in legacy, it may be removed to archives.
-## Contributing
+### RAPIDS Showcase Notebooks
+These notebooks are built by the RAPIDS team and will be maintained by them. When we remove the notebooks, it will become community maintained until it hits `the_archive`
+
+### How to Contribute
Please see our [guide for contributing to notebooks-contrib](CONTRIBUTING.md).
Once you've followed our guide, please don't forget to [test your notebooks!](TESTING.md) before making a PR.
-## Exploring the Repo
+## Exploring the Repo
### Folders
- `getting_started_notebooks` - “how to start using RAPIDS”. Contains notebooks showing "hello worlds", getting started with RAPIDS libraries, and tutorials around RAPIDS concepts.
-- `intermediate_notebooks` - “how to accomplish your workflows with RAPIDS”. Contains notebooks showing algorithm and workflow examples, benchmarking tools, and some complete end-to-end (E2E) workflows.
-- `advanced_notebooks` - "how to master RAPIDS". Contains notebooks showing kernel customization and advanced end-to-end workflows.
-- `blog notebooks` - contains shared notebooks mentioned and used in blogs that showcase RAPIDS workflows and capabilities
-- `conference notebooks` - contains notebooks used in conferences, such as GTC
+- `community_tutorials_and_guides` - community contributed “how to accomplish your workflows with RAPIDS”. Contains notebooks showing algorithm and workflow examples, benchmarking tools, and some complete end-to-end (E2E) workflows.
+- `community_archive` - This contains notebooks with known issues that have not have not been fixed in 45 days or more. contains shared notebooks mentioned and used in blogs that showcase RAPIDS workflows and capabilities
+- `the_archive` - contains older notebooks from community members as well as notebooks that the RAPIDS team no longer updates, but are useful to the community, such as [`archived_rapids_blog_notebooks`](community_relaunch/the_archive/archived_rapids_blog_notebooks), [`archived_rapids_event_notebooks`](the_archive/archived_rapids_event_notebooks), and [`competition_notebooks`](the_archive/archived_rapids_competition_notebooks)
- `data` - contains small data samples used for purely functional demonstrations. Some notebooks include cells that download larger datasets from external websites.
-### Lists
-- `multimedia_links.md` is a [list of videos](multimedia_links.md) by RAPIDS or our community talking about or showing how to use RAPIDS. Feel free to contribute your videos and RAPIDS themed playlists as well!
-- `competition_notebooks.md` - contains archived notebooks that were used in competitions, such as Kaggle. Some of these notebooks were blogged about and can also be found in our `blog notebooks` folder.
-
-# Our Notebooks
-Below is a listing of the notebooks in this repository. Each row will tell you the notebook's
-- Location in **Folder**
-- Notebook Title and Direct Link in **Notebook Title**
-- Description in **Description**
-- Design is for a `Single GPU`(SG) or `Multiple GPUs`(MG) in **GPU** (don't worry, you can still run the multi-GPU notebooks with a single GPU)
-- Data can be found in **Dataset Used**
-
-
-## Getting Started Notebooks:
-
-| Folder | Notebook Title | Description | GPU | Dataset Used |
-|-----------|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|------|--------------|
-| basics | [Getting_Started_with_cuDF](getting_started_notebooks/basics/Getting_Started_with_cuDF.ipynb) | This notebook shows how to get started with GPU DataFrames (single GPU only) using cuDF in RAPIDS. | SG | Self Generated |
-| basics | [Dask_Hello_World](getting_started_notebooks/basics/Dask_Hello_World.ipynb) | This notebook shows how to quickly setup Dask and run a "Hello World" example. | MG | Self Generated |
-| basics | [Getting_Started_with_Dask](getting_started_notebooks/basics/Getting_Started_with_Dask.ipynb) | This notebook shows how to get started with multi-GPU DataFrames using Dask and cuDF in RAPIDS. | MG | Self Generated |
-| basics | [hello_streamz](getting_started_notebooks/basics/hello_streamz.ipynb) | This notebook demonstrates use of cuDF to perform streaming word-count using a small portion of the Streamz API. | SG | Self Generated |
-|basics -> blazingsql| [Getting Started with BlazingSQL](getting_started_notebooks/basics/blazingsql/getting_started_with_blazingsql.ipynb) | How to set up and get started with BlazingSQL and the RAPIDS AI suite. | SG | [Music Dataset](https://github.com/BlazingDB/bsql-demos/blob/master/data/Music.csv) |
-|basics -> blazingsql| [Federated Query Demo](getting_started_notebooks/basics/blazingsql/federated_query_demo.ipynb) | In a single query, join an Apache Parquet file, a CSV file, and a GPU DataFrame (GDF) in GPU memory. | SG | [Breast Cancer Diagnostic](https://archive.ics.uci.edu/ml/datasets/Breast+Cancer+Wisconsin+%28Diagnostic%29) |
-| intro_tutorials | [01_Introduction_to_RAPIDS](getting_started_notebooks/intro_tutorials/01_Introduction_to_RAPIDS.ipynb) | This notebook shows at a high level what each of the packages in RAPIDS are as well as what they do. | MG | Self Generated |
-| intro_tutorials | [02_Introduction_to_cuDF](getting_started_notebooks/intro_tutorials/02_Introduction_to_cuDF.ipynb) | This notebook shows how to work with cuDF DataFrames in RAPIDS. | SG | Self Generated |
-| intro_tutorials | [03_Introduction_to_Dask](getting_started_notebooks/intro_tutorials/03_Introduction_to_Dask.ipynb) | This notebook shows how to work with Dask using basic Python primitives like integers and strings. | MG | Self Generated |
-| intro_tutorials | [04_Introduction_to_Dask_using_cuDF_DataFrames](getting_started_notebooks/intro_tutorials/04_Introduction_to_Dask_using_cuDF_DataFrames.ipynb) | This notebook shows how to work with cuDF DataFrames using Dask. | MG | Self Generated |
-| intro_tutorials | [06_Introduction_to_Supervised_Learning](getting_started_notebooks/intro_tutorials/06_Introduction_to_Supervised_Learning.ipynb) | This notebook shows how to do GPU accelerated Supervised Learning in RAPIDS. | SG | Self Generated |
-| intro_tutorials | [07_Introduction_to_XGBoost](getting_started_notebooks/intro_tutorials/07_Introduction_to_XGBoost.ipynb) | This notebook shows how to work with GPU accelerated XGBoost in RAPIDS. | SG | Self Generated |
-| intro_tutorials | [08_Introduction_to_Dask_XGBoost](getting_started_notebooks/intro_tutorials/08_Introduction_to_Dask_XGBoost.ipynb) | This notebook shows how to work with Dask XGBoost in RAPIDS. | MG | Self Generated |
-| intro_tutorials | [09_Introduction_to_Dimensionality_Reduction](getting_started_notebooks/intro_tutorials/09_Introduction_to_Dimensionality_Reduction.ipynb) | This notebook shows how to do GPU accelerated Dimensionality Reduction in RAPIDS. | SG | Self Generated |
-| intro_tutorials | [10_Introduction_to_Clustering](getting_started_notebooks/intro_tutorials/10_Introduction_to_Clustering.ipynb) | This notebook shows how to do GPU accelerated Clustering in RAPIDS. | SG | Self Generated |
----
+### Additional Resources
+- [Visit out Youtube Channel](https://www.youtube.com/channel/UCsoi4wfweA3I5FsPgyQnnqw/featured?view_as=subscriber) or see [list of videos](multimedia_links.md) by RAPIDS or our community. Feel free to contribute your videos and RAPIDS themed playlists as well!
+- [Visit our Blogs on Medium](https://medium.com/rapids-ai/)
+
+## Great places to get started
+
+### Topics
+Click each topic to expand
+
+ RAPIDS Libraries Basics
+
+##### Getting Started Document
+* [Intro to RAPIDS](getting_started_materials/README.md)
+
+##### Teaching Notebooks
+* [Intro Notebooks to RAPIDS](getting_started_materials/intro_tutorials_and_guides)- covers cuDF, Dask, cuML and XGBoost.
+* [Learn RAPIDS Getting Started Tour (External)](https://github.com/RAPIDSAcademy/rapidsacademy/tree/master/tutorials/datasci/tour)
+* [Hello Worlds](getting_started_materials/hello_worlds)
+
+
+
+ Cloud Service Providers
+
+ * [AWS](https://rapids.ai/cloud#aws)
+ * [Single Instance](https://rapids.ai/cloud#AWS-EC2)
+ * [Multi GPU Dask](https://rapids.ai/cloud#AWS-Dask)
+ * [Kubernetes](https://rapids.ai/cloud#AWS-Kubernetes)
+ * [Sagemaker](https://rapids.ai/cloud#AWS-Sagemaker)
+ * [Video- Tutorial of RAPIDS on AWS Sagemaker](https://www.youtube.com/watch?v=BtE4d0v6Css)
+ * [Azure](https://rapids.ai/cloud#azure)
+ * [Single Instance](https://rapids.ai/cloud#AZ-single)
+ * [Multi GPU Dask](https://rapids.ai/cloud#AZ-Dask)
+ * [Kubernetes](https://rapids.ai/cloud#AZ-Kubernetes)
+ * [AzureML Service](https://rapids.ai/cloud#AZ-ML)
+ * [Video- Tutorial of RAPIDS on AzureML](https://www.youtube.com/watch?v=aqTmVVFnEwI)
+ * [GCP](https://rapids.ai/cloud#googlecloud)
+ * [Single Instance](https://rapids.ai/cloud#GC-single)
+ * [Multi GPU Dask (Dataproc)](https://rapids.ai/cloud#GC-Dask)
+ * [Kubernetes](https://rapids.ai/cloud#GC-Kubernetes)
+ * [CloudAI](https://rapids.ai/cloud#GC-AI)
+
+
+
+ Multi GPU
+
+* [Hello Word to Dask](getting_started_materials/hello_worlds/Dask_Hello_World.ipynb)
+* [Intro to Dask](getting_started_materials/intro_tutorials_and_guides/03_Introduction_to_Dask.ipynb)
+* [Dask using cuDF](getting_started_materials/intro_tutorials_and_guides/04_Introduction_to_Dask_using_cuDF_DataFrames.ipynb)
+* [Learn RAPIDS Multi GPU Mini Tour (External)](https://github.com/RAPIDSAcademy/rapidsacademy/tree/master/tutorials/multigpu/minitour)
+* NYC taxi on Dataproc
+* [Weather Analysis](community_tutorials_and_guides/intermediate_notebooks/examples/weather.ipynb)
+
+
+
+ Streaming Data
+
+* [Chinmay Chandak's cuStreamz Gists (External)](https://gist.github.com/chinmaychandak)
+* [Using cuStreamz to Accelerate your Kafka Datasource (Blog)](https://medium.com/rapids-ai/the-custreamz-series-the-accelerated-kafka-datasource-4faf0baeb3f6)
+* [GPU accelerated Stream processing with RAPIDS (Blog)](https://medium.com/rapids-ai/gpu-accelerated-stream-processing-with-rapids-f2b725696a61)
+* [Hello World Streaming Data](getting_started_materials/hello_worlds/hello_streamz.ipynb)
+
+
+
+ NLP
+
+* [NLP with Hashing Vectorizer (Blog)](https://medium.com/rapids-ai/gpu-text-processing-now-even-simpler-and-faster-bde7e42c8c8a)
+* [Show me the Word Count (Archives)](the_archive/archived_rapids_blog_notebooks/nlp/show_me_the_word_count_gutenberg)
+
+
+ Graph Analytics
+
+
+ GIS/Spatial Analytics
-## Intermediate Notebooks:
-| Folder | Notebook Title | Description | GPU | Dataset Used |
-|-----------|------------------------|-------------------------------------------------------------|------|--------------|
-| examples | [linear_regression_demo.ipynb](intermediate_notebooks/examples/linear_regression_demo.ipynb) | This notebook demos how to implement simple and multiple linear regression with cuML to predict median housing price on sklearn's Boston Housing dataset. With corresponding [Medium Story](http://bit.ly/cuml_lin_reg_friend). | SG | [SKLearn Boston Housing](https://scikit-learn.org/stable/datasets/index.html#boston-dataset)|
-| examples | [umap_demo_full](intermediate_notebooks/examples/umap_demo_full.ipynb) | In this notebook we will show how to use UMAP and its GPU accelerated implementation present in RAPIDS. | SG | [Fashion MNIST](https://github.com/zalandoresearch/fashion-mnist)|
-| examples | [rf_demo](intermediate_notebooks/examples/rf_demo.ipynb) | Demonstration of using both cuml and sklearn to train a RandomForestClassifier on the Higgs dataset. | SG | [Higgs Boson](https://archive.ics.uci.edu/ml/machine-learning-databases/00280/HIGGS.csv.gz)
-| examples | [weather](intermediate_notebooks/examples/weather.ipynb) | Demonstration of using Dask and cuDF to process and analyze weather history | MG | [NOAA Annual Weather Data](ftp://ftp.ncdc.noaa.gov/pub/data/ghcn/daily/by_year/) |
-| examples -> blazingsql| [BlazingSQL vs Spark](intermediate_notebooks/examples/blazingsql/vs_pyspark_netflow.ipynb) | Analyze 73 million rows of net flow data. Compare BlazingSQL and Apache Spark timings for the same workload. | SG | [University of New South Wales LanL Dataset](https://www.unsw.adfa.edu.au/unsw-canberra-cyber/cybersecurity/ADFA-NB15-Datasets/) |
-| examples -> blazingsql| [Taxi Fare Prediction](intermediate_notebooks/examples/blazingsql/taxi_fare_prediction.ipynb) | Build & test a cuML Linear Regression model to predict the cost of a ride from 20 million rows of NYC Taxi data. | SG | [NYC Taxi Dataset](https://blazingsql-colab.s3.amazonaws.com/taxi_data/taxi_00.csv) |
-| examples -> custreamz | [parsing_haproxy_logs](intermediate_notebooks/examples/custreamz/parsing_haproxy_logs.ipynb) | This notebook builds upon the weblogs streaming notebook and demonstrates more advanced features for parsing HAProxy logs. | SG | Self Generated
-| examples -> cugraph | [MG Pagerank](intermediate_notebooks/examples/cugraph/multi_gpu_pagerank.ipynb) | Analyze a Twitter dataset (26GB on disk) with 41.7 million users with 1.47 billion social relations (edges) to find out the most influential profiles. | MG | [Twitter](https://s3.us-east-2.amazonaws.com/rapidsai-data/cugraph/benchmark/twitter-2010.csv.gz) |
-| E2E -> taxi | [NYCTaxi](intermediate_notebooks/E2E/taxi/NYCTaxi-E2E.ipynb) | Demonstrates multi-node ETL for cleanup of raw data into cleaned train and test dataframes. Shows how to run multi-node XGBoost training with dask-xgboost. **Please Note: requires Google Dataproc to run!** [Blog](https://medium.com/rapids-ai/scale-out-rapids-on-google-cloud-dataproc-8a873233258f) | MG | [Google Dataproc Hosted NYC Taxi Data](https://console.cloud.google.com/storage/browser/anaconda-public-data/nyc-taxi/csv/?pli=1) |
-| E2E -> synthetic_3D | [rapids_ml_workflow_demo](intermediate_notebooks/E2E/synthetic_3D/rapids_ml_workflow_demo.ipynb) | A 3D visual showcase of a machine learning workflow with RAPIDS (load data, transform/normalize, train XGBoost model, evaluate accuracy, use model for inference). Along the way we compare the performance gains of RAPIDS [GPU] vs sklearn/pandas methods [CPU]. | SG | SciKit-Learn's demo datasets |
-| E2E -> census | [census_education2income_demo](intermediate_notebooks/E2E/census/census_education2income_demo.ipynb) | In this notebook we use 50 years of census data to see how education affects income. | SG | [Custom IPUMS Data pull](https://rapidsai-data.s3.us-east-2.amazonaws.com/datasets/ipums_education2income_1970-2010.csv.gz)
-| E2E -> mortgage | [mortgage_e2e](intermediate_notebooks/E2E/mortgage/mortgage_e2e.ipynb) | This notebook demonstrates multi-GPU ETL and XGBoost for data preprocessing and training on 17 years of [Fannie Mae’s Single-Family Loan Performance Data](http://www.fanniemae.com/portal/funding-the-market/data/loan-performance-data.html). | MG | [Mortgage Loan Data](https://docs.rapids.ai/datasets/mortgage-data)
-| benchmarks | [cuml_benchmarks](intermediate_notebooks/benchmarks/cuml_benchmarks.ipynb) | The purpose of this notebook is to extensively benchmark all of the single GPU cuML algorithms against their skLearn counterparts, while also providing the ability to find and verify upper bounds. **Note: Best on large memory GPUs** | SG | Self Generated |
-| benchmarks | [rapids_decomposition](intermediate_notebooks/benchmarks/rapids_decomposition.ipynb) | This notebook benchmarks and visualize RAPIDS decomposition methods against each other. You have the opportunity to self-compare it to CPU speeds and methods | SG | SciKit-Learn's demo datasets |
-| benchmarks -> cugraph_benchmarks | [louvain_benchmark](intermediate_notebooks/benchmarks/cugraph_benchmarks/louvain_benchmark.ipynb) | This notebook benchmarks performance improvement of running the Louvain clustering algorithm within cuGraph against NetworkX. | SG | Sparse collection | SG | SciKit-Learn's demo datasets |
-| benchmarks -> cugraph_benchmarks | [pagerank_benchmark](intermediate_notebooks/benchmarks/cugraph_benchmarks/pagerank_benchmark.ipynb) | This notebook benchmarks performance improvement of running PageRank within cuGraph against NetworkX. | SG | Sparse collection |
-| benchmarks -> cugraph_benchmarks | [BFS benchmark](intermediate_notebooks/benchmarks/cugraph_benchmarks/bfs_benchmark.ipynb) | This notebook benchmarks performance improvement of running BFS within cuGraph against NetworkX. | SG | Sparse collection |
-| benchmarks -> cugraph_benchmarks | [SSSP_benchmark](intermediate_notebooks/benchmarks/cugraph_benchmarks/sssp_benchmark.ipynb) | This notebook benchmarks performance improvement of running SSSP within cuGraph against NetworkX. | SG | Sparse collection |
-| benchmarks -> cugraph_mg_hibench | [MG pagerank_benchmark](intermediate_notebooks/benchmarks/cugraph_mg_hibench/multi_gpu_pagerank.ipynb) | This notebook runs cuGraph's multi-GPU PageRank on a dataset of 300GB. It designed for DGX-2 machines. | MG | [HiBench](https://rapidsai-data.s3.us-east-2.amazonaws.com/cugraph/benchmark/hibench/HiBench_300GB.tar.gz) |
----
+* [Seismic Facies Analysis (External)](https://github.com/NVIDIA/energy-sdk/tree/master/rapids_seismic_facies)
+
+
+ Genomics
-## Advanced Notebooks:
-| Folder | Notebook Title | Description | GPU | Dataset Used |
-|-----------|------------------------|----------------------------------------------------------|------|--------------|
-| tutorials | [rapids_customized_kernels](advanced_notebooks/tutorials/rapids_customized_kernels.ipynb) | **Archive Only.** This notebook shows how create customized kernels using CUDA to make your workflow in RAPIDS even faster. | SG | Self Generated |
----
+ * [Video- GPU accelerated Single Cell Analytics](https://www.youtube.com/watch?v=nYneL_uif3Q)
+
+
+ Cybersecurity
+* [RAPIDS CLX](https://docs.rapids.ai/api/clx/stable/)
+ * [CLX API Docs](https://docs.rapids.ai/api/clx/stable/api.html)
+ * [10 Minutes to CLX](https://docs.rapids.ai/api/clx/stable/10min-clx.html)
+ * [Getting Started with CLX and Streamz](https://docs.rapids.ai/api/clx/stable/intro-clx-streamz.html)
+* [Learn RAPIDS Cyber Security mini Tour (External)](https://github.com/RAPIDSAcademy/rapidsacademy/tree/master/tutorials/security/tour)
+* [Cyber Blog Notebooks (Archives)](the_archive/archived_rapids_blog_notebooks/cyber)
-## Blog Notebooks:
-| Folder | Notebook Title | Description | GPU | Dataset Used |
-|-----------|------------------------|------------------------------------------------------------|------|--------------|
-| cyber | [flow_classification_rapids](blog_notebooks/cyber/flow_classification/flow_classification_rapids.ipynb) | **Archive Only.** The `cyber` folder contains the associated companion files for the blog [GPU Accelerated Cyber Log Parsing with RAPIDS](https://medium.com/rapids-ai/gpu-accelerated-cyber-log-parsing-with-rapids-10896f57eee9), by Bianca Rhodes US, Bhargav Suryadevara, and Nick Becker. This notebook demonstrates how to load netflow data into cuDF and create a multiclass classification model using XGBoost. Uses [run_raw_data_generator](blog_notebooks/cyber/raw_data_generator/run_raw_data_generator.py) | SG | [University of New South Wales LanL Dataset](https://iotanalytics.unsw.edu.au/) |
-| cyber | [lanl_network_mapping_using_rapids](blog_notebooks/cyber/network_mapping/lanl_network_mapping_using_rapids.ipynb) | **Archive Only.** The `cyber` folder contains the associated companion files for the blog [GPU Accelerated Cyber Log Parsing with RAPIDS](https://medium.com/rapids-ai/gpu-accelerated-cyber-log-parsing-with-rapids-10896f57eee9), by Bianca Rhodes US, Bhargav Suryadevara, and Nick Becker. This notebook demonstrates how to parse raw windows event logs using cudf and uses cuGraph's pagerank model to build a network graph. Uses [run_raw_data_generator](blog_notebooks/cyber/raw_data_generator/run_raw_data_generator.py) | SG | [University of New South Wales LanL Dataset](https://iotanalytics.unsw.edu.au/) |
-| databricks | [RAPIDS_PCA_demo_avro_read](blog_notebooks/databricks/RAPIDS_PCA_demo_avro_read.ipynb) | The `databricks` folder is the companion file repository to the blog [RAPIDS can now be accessed on Databricks Unified Analytics Platform](https://medium.com/rapids-ai/rapids-can-now-be-accessed-on-databricks-unified-analytics-platform-666e42284bd1) by Ikroop Dhillon, Karthikeyan Rajendran, and Taurean Dyer. This notebooks purpose is to showcase RAPIDS on Databricks use their sample datasets and show the CPU vs GPU comparison for the PCA algorithm. There is also an accompanying HTML file for easy Databricks import. **This notebook is for illustrative purposes only! Do not expect this notebook to successfully run on its own- this notebook's code is replicates a workflow meant to run on a specific platform, `Databricks`** | SG | [RAPIDS Toy Data](https://s3.us-east-2.amazonaws.com/rapidsai-data/datasets/mortgage/mortgage.npy.gz)|
-| plasticc | [rapids_lsst_full_demo](blog_notebooks/plasticc/notebooks/rapids_lsst_full_demo.ipynb) | **Archive Only.** This notebook demos the full CPU and GPU implementation of the RAPIDS.ai team's model that placed 8/1094 in the PLAsTiCC Astronomical Classification competition. [Blog](https://medium.com/rapids-ai/make-sense-of-the-universe-with-rapids-ai-d105b0e5ec95). [Updated notebooks found here](conference_notebooks/KDD_2019/plasticc/) | MG | [Kaggle PLAsTiCC-2018 dataset](https://www.kaggle.com/c/PLAsTiCC-2018/data) |
-| plasticc | [rapids_lsst_gpu_only_demo](blog_notebooks/plasticc/notebooks/rapids_lsst_gpu_only_demo.ipynb) | **Archive Only.** This GPU only based notebook shows the RAPIDS speedup of the RAPIDS.ai team's model that placed 8/1094 in the PLAsTiCC Astronomical Classification competition. [Blog](https://medium.com/rapids-ai/make-sense-of-the-universe-with-rapids-ai-d105b0e5ec95). [Updated notebooks found here](conference_notebooks/KDD_2019/plasticc/) | MG | [Kaggle PLAsTiCC-2018 dataset](https://www.kaggle.com/c/PLAsTiCC-2018/data) |
-| santander | [cudf_tf_demo](blog_notebooks/santander/cudf_tf_demo.ipynb) | **Archive Only.** This financial industry facing notebook is the cudf-tensorflow approach from the RAPIDS.ai team for Santander Customer Transaction Prediction. Placed 17/8808. [Blog](https://medium.com/rapids-ai/financial-data-modeling-with-rapids-5bca466f348) | SG | [Kaggle Santander Customer Transaction Prediction Dataset]( https://www.kaggle.com/c/santander-customer-transaction-prediction/data)
-| santander | [E2E_santander_pandas](blog_notebooks/santander/E2E_santander_pandas.ipynb) | **Archive Only.** This This financial data modelling notebook is the Pandas based version the RAPIDS.ai team's best single model for Santander Customer Transaction Prediction competition. Placed 17/8808. [Blog](https://medium.com/rapids-ai/financial-data-modeling-with-rapids-5bca466f348) | SG | [Kaggle Santander Customer Transaction Prediction Dataset]( https://www.kaggle.com/c/santander-customer-transaction-prediction/data)
-| santander | [E2E_santander](blog_notebooks/santander/E2E_santander.ipynb) | **Archive Only.** This financial data modelling notebook is the cuDF based version of the RAPIDS.ai team's best single model for Santander Customer Transaction Prediction competition. It allows you to compare cuDF performance to the Pandas version. Placed 17/8808. [Blog](https://medium.com/rapids-ai/financial-data-modeling-with-rapids-5bca466f348). | SG | [Kaggle Santander Customer Transaction Prediction Dataset]( https://www.kaggle.com/c/santander-customer-transaction-prediction/data)
-| regression | [regression_blog_notebook](blog_notebooks/regression/regression_blog_notebook.ipynb) | This is the companion notebook for the blog [Essential Machine Learning with Linear Models in RAPIDS: part 1 of a series](https://medium.com/rapids-ai/essential-machine-learning-with-linear-models-in-rapids-part-1-of-a-series-992fab0240da) by Paul Mahler. It showcases an end to end notebook using the Bike Share dataset and cuML's implementation of ridge regression. | SG | [Bike Share Dataset]() |
-| regression | [regression_2_blog](blog_notebooks/regression/regression_2_blog.ipynb) | This is the companion notebook for the blog [Regression Blog 2: We’re Practically Giving These Regressions Away](https://medium.com/rapids-ai/regression-blog-2-were-practically-giving-these-regressions-away-932669f52d3b) by Paul Mahler. It showcases an end to end notebook using the Black Friday dataset and cuML's implementations of L1 and L2 regularizations using Ridge, Lasso, and ElasticNet regression techniques. | SG | [Analytics Vidhya Black Friday Hackathon Dataset](https://datahack.analyticsvidhya.com/contest/black-friday/) |
-| NLP | [show_me_the_word_count_gutenberg](blog_notebooks/nlp/show_me_the_word_count_gutenberg/show_me_the_word_count_gutenberg.ipynb) | This is the notebook for blog [Show Me The Word Count](https://medium.com/rapids-ai/show-me-the-word-count-3146e1173801) by Vibhu Jawa, Nick Becker, David Wendt, and Randy Gelhausen. This notebook showcases NLP pre-processing capabilties of nvstrings+cudf on the Gutenberg dataset. | SG | [Gutenburg Dataset](https://web.eecs.umich.edu/~lahiri/gutenberg_dataset.html) |
-|cuspatial | [accelerate_geospatial_processing](blog_notebooks/cuspatial/trajectory_clustering.ipynb) | This is the notebook for blog [cuSpatial Accelerates Geospatial and Spatiotemporal Processing](https://medium.com/rapids-ai/releasing-cuspatial-to-accelerate-geospatial-and-spatiotemporal-processing-b686d8b32a9) by Milind Naphade, Jianting Zhang, Shuo Wang, Thomson Comer, Josh Paterson, Keith Kraus, Mark Harris, and Sujit Biswas. This notebook showcases cuSpatial benchmarking of directed Hausdorff distance for computing trajectory clustering on a large dataset. | SG | Trajectories Data and target_intersection.png |
-| randomforest | [fruits_rf_notebook](blog_notebooks/randomforest/fruits_rf_notebook.ipynb) | This is the notebook for blog [GPU-accelerated Random Forest]() by Vishal Mehta, Myrto Papadopoulou, Thejaswi Rao. This notebook showcases how to use GPU accelerated Random Forest Classification in cuML. The fruit dataset used is Self generated and used as an example in the [Blog](https://medium.com/rapids-ai/accelerating-random-forests-up-to-45x-using-cuml-dfb782a31bea) | SG | Self Generated
-| mortgage deep learning | [mortgage_e2e_deep_learning](blog_notebooks/mortgage_deep_learning/mortgage_e2e_deep_learning.ipynb) | **Archive Only.** This end to end notebook for the blog, [Using RAPIDS with PyTorch](https://medium.com/rapids-ai/using-rapids-with-pytorch-e602da018285), by Even Oldridge, combines the RAPIDS GPU data processing with a PyTorch deep learning neural network to predict mortgage loan delinquency. | MG | [Fannie Mae Mortgage Dataset](https://rapidsai.github.io/demos/datasets/mortgage-data)
-| svm | [svc_covertype](blog_notebooks/svm/svc_covertype.ipynb) | This notebook provides supplementary information for the Benchmark section of the [RAPIDS cuML SVC blog](https://nvda.ws/3c3Qy8H) post. | SG | [UCI Forest covertype dataset](https://archive.ics.uci.edu/ml/datasets/covertype)
----
+
+
+ Past Competitions
+
+- [RAPIDS.AI KGMON Competition Notebooks](the_archive/archived_competition_notebooks/kaggle)- contains a selection of notebooks that were used in Kaggle competitions.
+
-## Conference Notebooks:
-
-| Folder | Notebook Title | Description | GPU | Dataset Used |
-| ----------- | ------------------------ | --------------------------------------------------------------- | ---- | ------------ |
-| GTC_SJ_2019 | [GTC_tutorial_instructor](conference_notebooks/GTC_SJ_2019/GTC_tutorial_instructor.ipynb) | This is the instructor notebook for the hands on RAPIDS tutorial presented at San Jose's GTC 2019. It contains all the demonstrated solutions. | SG | [Analytics Vidhya Black Friday Hackathon Dataset](https://datahack.analyticsvidhya.com/contest/black-friday/) |
-| GTC_SJ_2019 | [GTC_tutorial_student](conference_notebooks/GTC_SJ_2019/GTC_tutorial_student.ipynb) | This is the exercise-filled student notebook for the hands on RAPIDS tutorial presented at San Jose's GTC 2019 | SG | [Analytics Vidhya Black Friday Hackathon Dataset](https://datahack.analyticsvidhya.com/contest/black-friday/) |
-| | | | | |
-| KDD_2019 | [Cybersecurity_KDD](conference_notebooks/KDD_2019/cyber/Cybersecurity_KDD.ipynb) | Using RAPIDS on network traffic and metadata, we demonstrate how to: 1. Triage and perform data exploration, 2. Model network data as a graph, 3. Perform graph analytics on the graph representation of the cyber network data, and 4. Prepare the results in a way that is suitable for visualization. | SG | [IDS 2018 dataset](https://www.unb.ca/cic/datasets/ids-2018.html) |
-| KDD_2019 | [MiningFrequentPatternsFromGraphs](conference_notebooks/KDD_2019/graph_pattern_mining/MiningFrequentPatternsFromGraphs.ipynb) | This notebook uses PC failure metadata, turns it into a coordinate list, and uses cugraph to find frequent patterns about the population that has failed | SG | [Microsoft PC Failure Metadata Graph](https://s3.us-east-2.amazonaws.com/rapidsai-data/datasets/fpm_graph/coo_fpm.csv.lzma) |
-| KDD_2019 | [Part 1.1 RNN Feature Engineering](conference_notebooks/KDD_2019/plasticc/Part_1-1_RNN_Feature_Engineering.ipynb) | Part 1.1 of this GPU only based notebook shows the RAPIDS speedup of the the RAPIDS.ai team's model that placed 8/1094 in the PLAsTiCC Astronomical Classification competition. [Blog](https://medium.com/rapids-ai/make-sense-of-the-universe-with-rapids-ai-d105b0e5ec95). - [Introduction found here.](conference_notebooks/KDD_2019/plasticc/Introduction.ipynb) - [Exercise Answers found here](conference_notebooks/KDD_2019/plasticc/Exercise_Answers.ipynb) - [Original submission found here](competition_notebooks/kaggle/plasticc/notebooks/rapids_lsst_gpu_only_demo.ipynb) | MG | [Kaggle PLAsTiCC-2018 dataset](https://www.kaggle.com/c/PLAsTiCC-2018/data) |
-| KDD_2019 | [Part 1.2 RNN Extract Bottleneck](conference_notebooks/KDD_2019/plasticc/Part_1-2_RNN_Extract_Bottleneck.ipynb) | Part 1.2 of this GPU only based notebook shows the RAPIDS speedup of the RAPIDS.ai team's model that placed 8/1094 in the PLAsTiCC Astronomical Classification competition. [Blog](https://medium.com/rapids-ai/make-sense-of-the-universe-with-rapids-ai-d105b0e5ec95). - [Introduction found here.](conference_notebooks/KDD_2019/plasticc/Introduction.ipynb) - [Exercise Answers found here](conference_notebooks/KDD_2019/plasticc/Exercise_Answers.ipynb) - [Original submission found here](competition_notebooks/kaggle/plasticc/notebooks/rapids_lsst_gpu_only_demo.ipynb) | MG | [Kaggle PLAsTiCC-2018 dataset](https://www.kaggle.com/c/PLAsTiCC-2018/data) |
-| KDD_2019 | [Part 2.1 Feature Engineering](contrib/conference_notebooks/KDD_2019/plasticc/Part_2-1_Feature_Engineering.ipynb) | Part 2.1 of this GPU only based notebook shows the RAPIDS speedup of the the RAPIDS.ai team's model that placed 8/1094 in the PLAsTiCC Astronomical Classification competition. [Blog](https://medium.com/rapids-ai/make-sense-of-the-universe-with-rapids-ai-d105b0e5ec95). - [Introduction found here.](conference_notebooks/KDD_2019/plasticc/Introduction.ipynb) - [Exercise Answers found here](conference_notebooks/KDD_2019/plasticc/Exercise_Answers.ipynb) - [Original submission found here](competition_notebooks/kaggle/plasticc/notebooks/rapids_lsst_gpu_only_demo.ipynb) | MG | [Kaggle PLAsTiCC-2018 dataset](https://www.kaggle.com/c/PLAsTiCC-2018/data) |
-| KDD_2019 | [Part 2.2 Train XGBoost & MLP](conference_notebooks/KDD_2019/plasticc/Part_2-2_Train_XGBoost_&_MLP.ipynb) | Part 2.2 of this GPU only based notebook shows the RAPIDS speedup of the the RAPIDS.ai team's model that placed 8/1094 in the PLAsTiCC Astronomical Classification competition. [Blog](https://medium.com/rapids-ai/make-sense-of-the-universe-with-rapids-ai-d105b0e5ec95). - [Introduction found here.](conference_notebooks/KDD_2019/plasticc/Introduction.ipynb) - [Exercise Answers found here](conference_notebooks/KDD_2019/plasticc/Exercise_Answers.ipynb) - [Original submission found here](competition_notebooks/kaggle/plasticc/notebooks/rapids_lsst_gpu_only_demo.ipynb) | MG | [Kaggle PLAsTiCC-2018 dataset](https://www.kaggle.com/c/PLAsTiCC-2018/data) |
-| | | | | |
-| SCIPY_2019 | [SCIPY_2019 Tutorial Index](conference_notebooks/SCIPY_2019/index.ipynb) | This index outlines the "getting started" style tutorials within the folder. The tutorials cover cudf, cuml, and cugraph. These tutorials were presented at SCIPY 2019 | SG | Various Self Generated datasets and Zachary Karate Club Data Set |
-| | | | | |
-| ASONAM 2019 | [Cyber](conference_notebooks/ASONAM_2019/Cyber.ipynb) | Example notebook using RAPIDS to let an organization's security and forensics experts collect vast amounts of network traffic and network metadata and perform fast triage, processing, modeling, and visualization capabilities. | MG | [IDS 2018 dataset](https://www.unb.ca/cic/datasets/ids-2018.html) from the [Canadian Institute for Cybersecurity](https://www.unb.ca/cic/) |
-| ASONAM 2019 | [Spotify Playlist](conference_notebooks/ASONAM_2019/Spotify_Playlist.ipynb) | Shows how you can quickly use RAPIDS to explore the Spotify Million Playlist Dataset, which was created for the RecSys 2018 competition, and build a playlist recommender **Note: this dataset requires an independent user download and cannot be pulled from the notebook** | MG | RecSys 2018 competition |
-| ASONAM 2019 | [Weighted Link Prediction](conference_notebooks/ASONAM_2019/Weighted_Link_Prediction.ipynb) | This notebook uses cuGraph for Weighted Link Prediction to mitigate uncertainty on the Epinions Trust Network Dataset to predict the likelihood of trust or distrust between vertices. **Note: this dataset requires an independent user download and cannot be pulled from the notebook** | SG | Epinions Trust Network Dataset |
-| | | | | |
-| KDD 2020 | [KDD 2020](conference_notebooks/KDD_2020/README.md) | Conference material for the KDD 2020 hands-on tutorial | SG | |
-| KDD 2020 | [Taxi](conference_notebooks/KDD_2020/notebooks/Taxi/NYCTax.ipynb) | Analysis of the New York City Taxi dataset. Introductory notebook showing ETL, Statistical Analysis, Machine Learning, Graph, and Visualization | SG | 2016 New York Taxi Data |
-| KDD 2020 | [Tabular](conference_notebooks/KDD_2020/notebooks/nvtabular/rossmann-store-sales-example.ipynb) | Perform store sales prediction using tabular deep learning | SG | [ Kaggle Rossmann Store Sales competition](https://www.kaggle.com/c/rossmann-store-sales) |
-| KDD 2020 | [Cell RNA](conference_notebooks/KDD_2020/notebooks/Lungs/hlca_lung_gpu_analysis.ipynb) | Single-Cell RNA Sequencing Analysis | SG | human lung cells from [Travaglini et al. 2020](https://www.biorxiv.org/content/10.1101/742320v2) |
-| KDD 2020 | Parking | Analyzing Seattle Parking data and determining the best parking spot within a walkable distance from Space Needle | SG | |
-| KDD 2020 | CyBERT | Cyber Log Parsing using Neural Networks and Language Based Model | SG | |
+
+ Benchmarks
+
+* [MultiGPU PageRank Benchmark (Archived)](the_archive/archived_rapids_benchmarks/cugraph)
+* [RAPIDS Decomposition (Archived)](the_archive/archived_rapids_benchmarks/rapids_decomposition.ipynb)
+
+
+
+ Random Tips and Tricks
+
+* [Synthetic 3D End-to-End ML Workflow](community_tutorials_and_guides/synthetic)
+
+
+
+### How-Tos with our Ecosystem Partners
+
+- [BlazingSQL](#) - these notebooks supplement app.blazingsql.com and provide tutorials for local BlazingSQL workflows. Make List.
+- cuStreamz
+- [LearnRAPIDS](https://www.learnrapids.com/)
+- Graphistry
## Additional Information
* The `data` folder also includes the full image set from the [Fashion MNIST dataset](https://github.com/zalandoresearch/fashion-mnist).
-* `utils`: contains a set of useful scripts for interacting with RAPIDS Notebooks-Contrib
+* `utils`: contains a set of useful scripts for interacting with RAPIDS Community Notebooks
+
+* For our notebook examples and tutorials found on [github](https://github.com/rapidsai), in each respective repo.
-* For our notebook examples and tutorials found in our standard containers, please see the [Notebooks Repo](https://github.com/rapidsai/notebooks)
diff --git a/intermediate_notebooks/examples/blazingsql/README.md b/community_tutorials_and_guides/blazingsql/README.md
similarity index 100%
rename from intermediate_notebooks/examples/blazingsql/README.md
rename to community_tutorials_and_guides/blazingsql/README.md
diff --git a/intermediate_notebooks/examples/blazingsql/taxi_fare_prediction.ipynb b/community_tutorials_and_guides/blazingsql/bsql_taxi_fare_prediction.ipynb
similarity index 87%
rename from intermediate_notebooks/examples/blazingsql/taxi_fare_prediction.ipynb
rename to community_tutorials_and_guides/blazingsql/bsql_taxi_fare_prediction.ipynb
index 483b325e..59b90e59 100644
--- a/intermediate_notebooks/examples/blazingsql/taxi_fare_prediction.ipynb
+++ b/community_tutorials_and_guides/blazingsql/bsql_taxi_fare_prediction.ipynb
@@ -21,7 +21,7 @@
"metadata": {},
"source": [
"#### BlazingSQL install check\n",
- "The next cell checks that you have BlazingSQL installed, and offers to install it if not (making sure the notebook will run as expected)."
+ "The next cell checks to determine if you have BlazingSQL installed. If you do not have BlazingSQL installed, please first install RAPIDS and BlazingSQL via your preferred installation method (Docker or conda) from our [Release Selector](https://rapids.ai/start.html#rapids-release-selector). "
]
},
{
@@ -42,10 +42,10 @@
],
"source": [
"import sys \n",
- "# point import path notebooks-contrib/utils\n",
- "sys.path.append('../../../utils/')\n",
+ "# point import path notebooks-contrib/utils\n",
+ "sys.path.append('../../utils/')\n",
"from sql_check import bsql_start\n",
- "# check that BlazingSQL is installed\n",
+ "# check that BlazingSQL is installed\n",
"bsql_start()"
]
},
@@ -118,12 +118,12 @@
"import urllib.request\n",
"\n",
"# relative path to data folder\n",
- "data_dir = '../../../data/blazingsql/'\n",
+ "data_dir = '../../utils/blazingsql/'\n",
"# does folder exist?\n",
"if not os.path.exists(data_dir):\n",
" print('creating blazingsql directory')\n",
" # create folder\n",
- " os.system('mkdir ../../data/blazingsql')"
+ " os.system('mkdir ../../utils/blazingsql')"
]
},
{
@@ -135,13 +135,20 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "Downloading https://blazingsql-colab.s3.amazonaws.com/taxi_data/taxi_00.csv to ../../../data/blazingsql/taxi_00.csv\n",
- "Downloading https://blazingsql-colab.s3.amazonaws.com/taxi_data/taxi_01.csv to ../../../data/blazingsql/taxi_01.csv\n",
- "Downloading https://blazingsql-colab.s3.amazonaws.com/taxi_data/taxi_02.csv to ../../../data/blazingsql/taxi_02.csv\n",
- "Downloading https://blazingsql-colab.s3.amazonaws.com/taxi_data/taxi_03.csv to ../../../data/blazingsql/taxi_03.csv\n"
+ "blazingsql __pycache__ sql_check.py\n",
+ "env-check.py rapids-colab.sh update_pyarrow.py\n"
]
}
],
+ "source": [
+ "!ls ../../utils/"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
"source": [
"# download taxi data\n",
"base_url = 'https://blazingsql-colab.s3.amazonaws.com/taxi_data/'\n",
@@ -169,7 +176,7 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 7,
"metadata": {
"colab": {},
"colab_type": "code",
@@ -277,7 +284,7 @@
"4 -74.012459 40.713932 1.0 "
]
},
- "execution_count": 6,
+ "execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
@@ -307,7 +314,22 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# delete used dataframes gdf00 etc \n",
+ "del gdf_00\n",
+ "del gdf_01\n",
+ "del gdf_02\n",
+ "del gdf_03\n",
+ "\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
"metadata": {},
"outputs": [
{
@@ -438,7 +460,7 @@
"max 3.537133e+03 2.080000e+02 "
]
},
- "execution_count": 7,
+ "execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
@@ -459,7 +481,7 @@
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": 10,
"metadata": {
"colab": {},
"colab_type": "code",
@@ -470,25 +492,15 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "CPU times: user 2 µs, sys: 1 µs, total: 3 µs\n",
+ "CPU times: user 3 µs, sys: 1e+03 ns, total: 4 µs\n",
"Wall time: 6.2 µs\n"
]
- },
- {
- "data": {
- "text/plain": [
- ""
- ]
- },
- "execution_count": 8,
- "metadata": {},
- "output_type": "execute_result"
}
],
"source": [
"%time\n",
"# make a table from the combined df\n",
- "bc.create_table('train_taxi', gdf, column_names=col_names)"
+ "bc.create_table('train_taxi', gdf)"
]
},
{
@@ -503,7 +515,7 @@
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": 11,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@@ -615,7 +627,7 @@
"4 1.0 "
]
},
- "execution_count": 9,
+ "execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
@@ -649,7 +661,7 @@
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": 12,
"metadata": {},
"outputs": [
{
@@ -710,7 +722,7 @@
"4 10.5"
]
},
- "execution_count": 10,
+ "execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
@@ -735,7 +747,7 @@
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": 13,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@@ -751,20 +763,20 @@
"output_type": "stream",
"text": [
"Coefficients:\n",
- "0 -0.027290\n",
- "1 0.003329\n",
- "2 0.106803\n",
- "3 0.637564\n",
+ "0 -0.027293\n",
+ "1 0.003330\n",
+ "2 0.106819\n",
+ "3 0.637570\n",
"4 0.000871\n",
"5 -0.000516\n",
- "6 0.092400\n",
+ "6 0.092438\n",
"dtype: float32\n",
"\n",
"Y intercept:\n",
- "3.3568549156188965\n",
+ "3.356637954711914\n",
"\n",
- "CPU times: user 689 ms, sys: 590 ms, total: 1.28 s\n",
- "Wall time: 1.28 s\n"
+ "CPU times: user 421 ms, sys: 120 ms, total: 541 ms\n",
+ "Wall time: 540 ms\n"
]
}
],
@@ -796,31 +808,31 @@
},
{
"cell_type": "code",
- "execution_count": 12,
+ "execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "--2020-01-21 17:21:52-- https://blazingsql-demos.s3-us-west-1.amazonaws.com/test.csv\n",
- "Resolving blazingsql-demos.s3-us-west-1.amazonaws.com (blazingsql-demos.s3-us-west-1.amazonaws.com)... 52.219.120.105\n",
- "Connecting to blazingsql-demos.s3-us-west-1.amazonaws.com (blazingsql-demos.s3-us-west-1.amazonaws.com)|52.219.120.105|:443... connected.\n",
+ "--2021-04-09 07:34:59-- https://blazingsql-demos.s3-us-west-1.amazonaws.com/test.csv\n",
+ "Resolving blazingsql-demos.s3-us-west-1.amazonaws.com (blazingsql-demos.s3-us-west-1.amazonaws.com)... 52.219.112.217\n",
+ "Connecting to blazingsql-demos.s3-us-west-1.amazonaws.com (blazingsql-demos.s3-us-west-1.amazonaws.com)|52.219.112.217|:443... connected.\n",
"HTTP request sent, awaiting response... 200 OK\n",
"Length: 982916 (960K) [text/csv]\n",
- "Saving to: ‘../../../data/blazingsql/test.csv’\n",
+ "Saving to: ‘../../data/blazingsql/test.csv.5’\n",
"\n",
- "test.csv 100%[===================>] 959.88K 2.56MB/s in 0.4s \n",
+ "test.csv.5 100%[===================>] 959.88K 2.64MB/s in 0.4s \n",
"\n",
- "2020-01-21 17:21:53 (2.56 MB/s) - ‘../../../data/blazingsql/test.csv’ saved [982916/982916]\n",
+ "2021-04-09 07:35:00 (2.64 MB/s) - ‘../../data/blazingsql/test.csv.5’ saved [982916/982916]\n",
"\n"
]
}
],
"source": [
"# do we have Test taxi file?\n",
- "if not os.path.isfile('../../../data/blazingsql/test.csv'):\n",
- " !wget -P ../../../data/blazingsql https://blazingsql-demos.s3-us-west-1.amazonaws.com/test.csv"
+ "if not os.path.isfile('../../utils/blazingsql/test.csv'):\n",
+ " !wget -P ../../data/blazingsql https://blazingsql-demos.s3-us-west-1.amazonaws.com/test.csv"
]
},
{
@@ -832,16 +844,16 @@
},
{
"cell_type": "code",
- "execution_count": 13,
+ "execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
- "'/home/jupyter-winston/notebooks-contrib/data/blazingsql/test.csv'"
+ "'/rapids/notebooks-contrib//data/blazingsql/test.csv'"
]
},
- "execution_count": 13,
+ "execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
@@ -849,47 +861,36 @@
"source": [
"# identify path to this notebook, !pwd returns SList w/ path (str) at 0th index\n",
"path = !pwd\n",
- "# extract path notebooks-contrib then\n",
- "path = path[0].split('intermediate_notebooks')[0] \n",
+ "# extract path community_tutorials_and_guides/blazingsql then\n",
+ "path = path[0].split('community_tutorials_and_guides/blazingsql')[0] \n",
"# add path to data from there\n",
- "path = path + 'data/blazingsql/' + 'test.csv'\n",
+ "path = path + '/data/blazingsql/' + 'test.csv'\n",
"# how's it look?\n",
"path"
]
},
{
"cell_type": "code",
- "execution_count": 14,
+ "execution_count": 16,
"metadata": {
"colab": {},
"colab_type": "code",
"id": "yRM5PosNiuGh"
},
- "outputs": [
- {
- "data": {
- "text/plain": [
- ""
- ]
- },
- "execution_count": 14,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# set column names and types\n",
"col_names = ['key', 'fare_amount', 'pickup_longitude', 'pickup_latitude', \n",
" 'dropoff_longitude', 'dropoff_latitude', 'passenger_count']\n",
"col_types = ['date64', 'float32', 'float32', 'float32', 'float32', 'float32', 'float32']\n",
"\n",
- "# create test table directly from CSV\n",
- "bc.create_table('test_taxi', path, names=col_names, dtype=col_types)"
+ "# create test table directly from CSV - this doesnt make sense\n",
+ "bc.create_table('test_taxi', path, names=col_names, dtype=col_types)\n"
]
},
{
"cell_type": "code",
- "execution_count": 15,
+ "execution_count": 17,
"metadata": {
"colab": {},
"colab_type": "code",
@@ -997,7 +998,7 @@
"4 1.0 "
]
},
- "execution_count": 15,
+ "execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
@@ -1031,7 +1032,7 @@
},
{
"cell_type": "code",
- "execution_count": 16,
+ "execution_count": 18,
"metadata": {
"colab": {},
"colab_type": "code",
@@ -1041,71 +1042,21 @@
{
"data": {
"text/plain": [
- "0 12.854630\n",
- "1 12.854605\n",
- "2 11.256927\n",
- "3 11.811884\n",
- "4 11.811888\n",
- "5 11.811880\n",
- "6 11.222965\n",
- "7 11.222733\n",
- "8 11.222973\n",
- "9 12.239309\n",
- "10 12.239325\n",
- "11 12.239347\n",
- "12 9.696036\n",
- "13 9.696022\n",
- "14 11.468582\n",
- "15 11.468594\n",
- "16 11.460928\n",
- "17 11.460958\n",
- "18 11.460936\n",
- "19 11.460926\n",
- "20 13.485119\n",
- "21 12.707811\n",
- "22 12.707788\n",
- "23 12.707800\n",
- "24 12.707800\n",
- "25 12.707785\n",
- "26 12.707952\n",
- "27 12.707806\n",
- "28 12.707804\n",
- "29 12.707785\n",
+ "0 12.854544\n",
+ "1 12.854520\n",
+ "2 11.256961\n",
+ "3 11.811929\n",
+ "4 11.811933\n",
" ... \n",
- "9884 12.643631\n",
- "9885 12.643671\n",
- "9886 12.643652\n",
- "9887 12.643633\n",
- "9888 12.643650\n",
- "9889 12.643656\n",
- "9890 12.643648\n",
- "9891 12.643673\n",
- "9892 12.643652\n",
- "9893 12.643667\n",
- "9894 12.643648\n",
- "9895 12.643719\n",
- "9896 12.643631\n",
- "9897 13.454716\n",
- "9898 13.212105\n",
- "9899 14.138895\n",
- "9900 13.368757\n",
- "9901 13.635015\n",
- "9902 14.171509\n",
- "9903 13.832354\n",
- "9904 13.669437\n",
- "9905 13.259691\n",
- "9906 14.138172\n",
- "9907 13.452593\n",
- "9908 13.717201\n",
- "9909 13.714552\n",
- "9910 13.157532\n",
- "9911 13.419586\n",
- "9912 13.657433\n",
- "9913 13.259361\n",
+ "9909 13.714720\n",
+ "9910 13.157619\n",
+ "9911 13.419721\n",
+ "9912 13.657573\n",
+ "9913 13.259460\n",
"Length: 9914, dtype: float32"
]
},
- "execution_count": 16,
+ "execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
@@ -1120,7 +1071,7 @@
},
{
"cell_type": "code",
- "execution_count": 17,
+ "execution_count": 19,
"metadata": {
"colab": {},
"colab_type": "code",
@@ -1168,7 +1119,7 @@
"
-0.008110
\n",
"
-0.019970
\n",
"
1.0
\n",
- "
12.854630
\n",
+ "
12.854544
\n",
" \n",
"
\n",
"
1
\n",
@@ -1179,7 +1130,7 @@
"
-0.012024
\n",
"
0.019814
\n",
"
1.0
\n",
- "
12.854605
\n",
+ "
12.854520
\n",
"
\n",
"
\n",
"
2
\n",
@@ -1190,7 +1141,7 @@
"
0.002869
\n",
"
-0.005119
\n",
"
1.0
\n",
- "
11.256927
\n",
+ "
11.256961
\n",
"
\n",
"
\n",
"
3
\n",
@@ -1201,7 +1152,7 @@
"
-0.009277
\n",
"
-0.016178
\n",
"
1.0
\n",
- "
11.811884
\n",
+ "
11.811929
\n",
"
\n",
"
\n",
"
4
\n",
@@ -1212,7 +1163,7 @@
"
-0.022537
\n",
"
-0.045345
\n",
"
1.0
\n",
- "
11.811888
\n",
+ "
11.811933
\n",
"
\n",
" \n",
"\n",
@@ -1227,14 +1178,14 @@
"4 21.0 1.0 12.0 12.0 -0.022537 -0.045345 \n",
"\n",
" passenger_count predicted_fare \n",
- "0 1.0 12.854630 \n",
- "1 1.0 12.854605 \n",
- "2 1.0 11.256927 \n",
- "3 1.0 11.811884 \n",
- "4 1.0 11.811888 "
+ "0 1.0 12.854544 \n",
+ "1 1.0 12.854520 \n",
+ "2 1.0 11.256961 \n",
+ "3 1.0 11.811929 \n",
+ "4 1.0 11.811933 "
]
},
- "execution_count": 17,
+ "execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
@@ -1246,6 +1197,20 @@
"# how's that look?\n",
"X_test.head()"
]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
}
],
"metadata": {
@@ -1270,7 +1235,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.6.7"
+ "version": "3.7.10"
}
},
"nbformat": 4,
diff --git a/intermediate_notebooks/examples/blazingsql/vs_pyspark_netflow.ipynb b/community_tutorials_and_guides/blazingsql/bsql_vs_pyspark_netflow.ipynb
similarity index 60%
rename from intermediate_notebooks/examples/blazingsql/vs_pyspark_netflow.ipynb
rename to community_tutorials_and_guides/blazingsql/bsql_vs_pyspark_netflow.ipynb
index e50b7fb1..dd8a7760 100644
--- a/intermediate_notebooks/examples/blazingsql/vs_pyspark_netflow.ipynb
+++ b/community_tutorials_and_guides/blazingsql/bsql_vs_pyspark_netflow.ipynb
@@ -21,7 +21,7 @@
"metadata": {},
"source": [
"#### BlazingSQL install check\n",
- "The next cell checks that you have BlazingSQL installed, and offers to install it if not (making sure the notebook will run as expected)."
+ "The next cell checks to determine if you have BlazingSQL installed. If you do not have BlazingSQL installed, please first install RAPIDS and BlazingSQL via your preferred installation method (Docker or conda) from our [Release Selector](https://rapids.ai/start.html#rapids-release-selector). "
]
},
{
@@ -43,7 +43,7 @@
"source": [
"import sys \n",
"# point import path notebooks-contrib/utils\n",
- "sys.path.append('../../../utils/')\n",
+ "sys.path.append('../../utils') \n",
"from sql_check import bsql_start\n",
"# check that BlazingSQL is installed\n",
"bsql_start()"
@@ -66,17 +66,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "--2020-01-21 17:27:12-- https://blazingsql-colab.s3.amazonaws.com/netflow_data/nf-chunk2.csv\n",
- "Resolving blazingsql-colab.s3.amazonaws.com (blazingsql-colab.s3.amazonaws.com)... 52.216.115.3\n",
- "Connecting to blazingsql-colab.s3.amazonaws.com (blazingsql-colab.s3.amazonaws.com)|52.216.115.3|:443... connected.\n",
- "HTTP request sent, awaiting response... 200 OK\n",
- "Length: 2725056295 (2.5G) [text/csv]\n",
- "Saving to: ‘../../../data/blazingsql/nf-chunk2.csv’\n",
- "\n",
- "nf-chunk2.csv 100%[===================>] 2.54G 43.5MB/s in 56s \n",
- "\n",
- "2020-01-21 17:28:14 (46.0 MB/s) - ‘../../../data/blazingsql/nf-chunk2.csv’ saved [2725056295/2725056295]\n",
- "\n"
+ "You've got the data!\n"
]
}
],
@@ -84,7 +74,7 @@
"import os\n",
"\n",
"# relative path to data folder\n",
- "data_dir = '../../../data/blazingsql/'\n",
+ "data_dir = '../../data/blazingsql/'\n",
"# file name\n",
"fn = 'nf-chunk2.csv'\n",
"\n",
@@ -97,7 +87,7 @@
"# do we have music file?\n",
"if not os.path.isfile(data_dir + fn):\n",
" # save nf-chunk2 to data folder, may take a few minutes to download (21,526,138 records)\n",
- " !wget -P ../../../data/blazingsql https://blazingsql-colab.s3.amazonaws.com/netflow_data/nf-chunk2.csv\n",
+ " !wget -P ../../data/blazingsql https://blazingsql-colab.s3.amazonaws.com/netflow_data/nf-chunk2.csv\n",
"else:\n",
" print(\"You've got the data!\")"
]
@@ -166,8 +156,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "CPU times: user 3.75 s, sys: 1.19 s, total: 4.94 s\n",
- "Wall time: 4.93 s\n"
+ "CPU times: user 1.63 s, sys: 236 ms, total: 1.87 s\n",
+ "Wall time: 1.86 s\n"
]
}
],
@@ -194,19 +184,9 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "CPU times: user 4.11 ms, sys: 23 µs, total: 4.13 ms\n",
- "Wall time: 3.32 ms\n"
+ "CPU times: user 1.36 s, sys: 43.5 ms, total: 1.4 s\n",
+ "Wall time: 545 ms\n"
]
- },
- {
- "data": {
- "text/plain": [
- ""
- ]
- },
- "execution_count": 5,
- "metadata": {},
- "output_type": "execute_result"
}
],
"source": [
@@ -232,8 +212,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "CPU times: user 1.98 s, sys: 514 ms, total: 2.49 s\n",
- "Wall time: 1.95 s\n"
+ "CPU times: user 567 ms, sys: 52.4 ms, total: 619 ms\n",
+ "Wall time: 290 ms\n"
]
}
],
@@ -307,152 +287,152 @@
" \n",
"
\n",
"
0
\n",
- "
172.10.1.162
\n",
+ "
172.10.1.33
\n",
"
10.0.0.11
\n",
- "
87
\n",
- "
39628
\n",
- "
53983
\n",
- "
24
\n",
- "
2013-04-03 06:50:13
\n",
- "
2013-04-03 14:58:35
\n",
- "
87
\n",
+ "
110
\n",
+ "
49886
\n",
+ "
69630
\n",
+ "
0
\n",
+ "
2013-04-03 06:51:58
\n",
+ "
2013-04-03 14:45:47
\n",
+ "
110
\n",
"
\n",
"
\n",
"
1
\n",
- "
172.30.2.60
\n",
- "
10.0.0.9
\n",
- "
82
\n",
- "
34839
\n",
- "
47716
\n",
- "
134
\n",
- "
2013-04-03 06:48:47
\n",
- "
2013-04-03 12:12:37
\n",
- "
82
\n",
+ "
172.30.1.126
\n",
+ "
239.255.255.250
\n",
+ "
9
\n",
+ "
2275
\n",
+ "
0
\n",
+ "
12
\n",
+ "
2013-04-03 06:35:52
\n",
+ "
2013-04-03 12:05:31
\n",
+ "
9
\n",
"
\n",
"
\n",
"
2
\n",
- "
172.30.1.56
\n",
- "
172.0.0.1
\n",
- "
25
\n",
- "
3330
\n",
- "
3240
\n",
- "
67
\n",
- "
2013-04-03 01:59:09
\n",
- "
2013-04-03 22:05:39
\n",
- "
25
\n",
+ "
172.30.2.133
\n",
+ "
239.255.255.250
\n",
+ "
5
\n",
+ "
1225
\n",
+ "
0
\n",
+ "
6
\n",
+ "
2013-04-03 06:36:08
\n",
+ "
2013-04-03 06:36:15
\n",
+ "
5
\n",
"
\n",
"
\n",
"
3
\n",
- "
172.10.1.234
\n",
- "
10.0.0.5
\n",
- "
104
\n",
- "
47287
\n",
- "
64750
\n",
- "
18
\n",
- "
2013-04-03 06:53:55
\n",
- "
2013-04-03 15:11:07
\n",
- "
104
\n",
+ "
172.30.1.149
\n",
+ "
10.0.0.9
\n",
+ "
78
\n",
+ "
35309
\n",
+ "
48556
\n",
+ "
30
\n",
+ "
2013-04-03 06:48:27
\n",
+ "
2013-04-03 11:52:55
\n",
+ "
78
\n",
"
\n",
"
\n",
"
4
\n",
- "
10.1.0.76
\n",
- "
172.10.1.82
\n",
+ "
10.0.0.13
\n",
+ "
172.10.1.81
\n",
"
1
\n",
"
633
\n",
"
392
\n",
"
0
\n",
- "
2013-04-03 09:55:05
\n",
- "
2013-04-03 09:55:05
\n",
+ "
2013-04-03 09:48:26
\n",
+ "
2013-04-03 09:48:26
\n",
"
1
\n",
"
\n",
"
\n",
"
5
\n",
- "
172.30.1.85
\n",
- "
10.0.0.8
\n",
- "
84
\n",
- "
37828
\n",
- "
52864
\n",
+ "
172.10.0.5
\n",
+ "
10.247.58.129
\n",
+ "
3
\n",
+ "
1617
\n",
+ "
108
\n",
+ "
4
\n",
+ "
2013-04-03 10:16:11
\n",
+ "
2013-04-03 11:37:15
\n",
"
3
\n",
- "
2013-04-03 06:48:21
\n",
- "
2013-04-03 12:06:53
\n",
- "
84
\n",
"
\n",
"
\n",
"
6
\n",
- "
172.30.1.10
\n",
- "
10.0.0.12
\n",
- "
69
\n",
- "
31042
\n",
- "
43044
\n",
- "
25
\n",
- "
2013-04-03 06:48:01
\n",
- "
2013-04-03 12:11:40
\n",
- "
69
\n",
+ "
10.0.0.14
\n",
+ "
172.10.2.143
\n",
+ "
1
\n",
+ "
571
\n",
+ "
108
\n",
+ "
0
\n",
+ "
2013-04-03 10:13:57
\n",
+ "
2013-04-03 10:13:57
\n",
+ "
1
\n",
"
\n",
"
\n",
"
7
\n",
- "
172.30.1.201
\n",
- "
172.0.0.1
\n",
- "
29
\n",
- "
2610
\n",
- "
2610
\n",
- "
0
\n",
- "
2013-04-03 00:26:46
\n",
- "
2013-04-03 23:06:00
\n",
- "
29
\n",
+ "
172.10.1.2
\n",
+ "
10.0.0.10
\n",
+ "
97
\n",
+ "
44092
\n",
+ "
61401
\n",
+ "
2
\n",
+ "
2013-04-03 06:48:54
\n",
+ "
2013-04-03 15:05:37
\n",
+ "
97
\n",
"
\n",
"
\n",
"
8
\n",
- "
172.30.2.125
\n",
+ "
172.10.1.212
\n",
"
10.0.0.9
\n",
- "
69
\n",
- "
30701
\n",
- "
41558
\n",
- "
341
\n",
- "
2013-04-03 06:50:50
\n",
- "
2013-04-03 12:12:37
\n",
- "
69
\n",
+ "
102
\n",
+ "
46260
\n",
+ "
64410
\n",
+ "
23
\n",
+ "
2013-04-03 06:50:02
\n",
+ "
2013-04-03 14:31:50
\n",
+ "
102
\n",
"
\n",
"
\n",
"
9
\n",
- "
172.10.1.89
\n",
- "
10.0.0.5
\n",
- "
112
\n",
- "
51222
\n",
- "
70260
\n",
- "
24
\n",
- "
2013-04-03 06:48:24
\n",
- "
2013-04-03 15:17:39
\n",
- "
112
\n",
+ "
172.30.1.160
\n",
+ "
10.0.0.12
\n",
+ "
65
\n",
+ "
29402
\n",
+ "
40520
\n",
+ "
16
\n",
+ "
2013-04-03 06:55:18
\n",
+ "
2013-04-03 11:52:13
\n",
+ "
65
\n",
"
\n",
" \n",
"\n",
""
],
"text/plain": [
- " source destination targetPorts bytesOut bytesIn durationSeconds \\\n",
- "0 172.10.1.162 10.0.0.11 87 39628 53983 24 \n",
- "1 172.30.2.60 10.0.0.9 82 34839 47716 134 \n",
- "2 172.30.1.56 172.0.0.1 25 3330 3240 67 \n",
- "3 172.10.1.234 10.0.0.5 104 47287 64750 18 \n",
- "4 10.1.0.76 172.10.1.82 1 633 392 0 \n",
- "5 172.30.1.85 10.0.0.8 84 37828 52864 3 \n",
- "6 172.30.1.10 10.0.0.12 69 31042 43044 25 \n",
- "7 172.30.1.201 172.0.0.1 29 2610 2610 0 \n",
- "8 172.30.2.125 10.0.0.9 69 30701 41558 341 \n",
- "9 172.10.1.89 10.0.0.5 112 51222 70260 24 \n",
+ " source destination targetPorts bytesOut bytesIn \\\n",
+ "0 172.10.1.33 10.0.0.11 110 49886 69630 \n",
+ "1 172.30.1.126 239.255.255.250 9 2275 0 \n",
+ "2 172.30.2.133 239.255.255.250 5 1225 0 \n",
+ "3 172.30.1.149 10.0.0.9 78 35309 48556 \n",
+ "4 10.0.0.13 172.10.1.81 1 633 392 \n",
+ "5 172.10.0.5 10.247.58.129 3 1617 108 \n",
+ "6 10.0.0.14 172.10.2.143 1 571 108 \n",
+ "7 172.10.1.2 10.0.0.10 97 44092 61401 \n",
+ "8 172.10.1.212 10.0.0.9 102 46260 64410 \n",
+ "9 172.30.1.160 10.0.0.12 65 29402 40520 \n",
"\n",
- " firstFlowDate lastFlowDate attemptCount \n",
- "0 2013-04-03 06:50:13 2013-04-03 14:58:35 87 \n",
- "1 2013-04-03 06:48:47 2013-04-03 12:12:37 82 \n",
- "2 2013-04-03 01:59:09 2013-04-03 22:05:39 25 \n",
- "3 2013-04-03 06:53:55 2013-04-03 15:11:07 104 \n",
- "4 2013-04-03 09:55:05 2013-04-03 09:55:05 1 \n",
- "5 2013-04-03 06:48:21 2013-04-03 12:06:53 84 \n",
- "6 2013-04-03 06:48:01 2013-04-03 12:11:40 69 \n",
- "7 2013-04-03 00:26:46 2013-04-03 23:06:00 29 \n",
- "8 2013-04-03 06:50:50 2013-04-03 12:12:37 69 \n",
- "9 2013-04-03 06:48:24 2013-04-03 15:17:39 112 "
+ " durationSeconds firstFlowDate lastFlowDate attemptCount \n",
+ "0 0 2013-04-03 06:51:58 2013-04-03 14:45:47 110 \n",
+ "1 12 2013-04-03 06:35:52 2013-04-03 12:05:31 9 \n",
+ "2 6 2013-04-03 06:36:08 2013-04-03 06:36:15 5 \n",
+ "3 30 2013-04-03 06:48:27 2013-04-03 11:52:55 78 \n",
+ "4 0 2013-04-03 09:48:26 2013-04-03 09:48:26 1 \n",
+ "5 4 2013-04-03 10:16:11 2013-04-03 11:37:15 3 \n",
+ "6 0 2013-04-03 10:13:57 2013-04-03 10:13:57 1 \n",
+ "7 2 2013-04-03 06:48:54 2013-04-03 15:05:37 97 \n",
+ "8 23 2013-04-03 06:50:02 2013-04-03 14:31:50 102 \n",
+ "9 16 2013-04-03 06:55:18 2013-04-03 11:52:13 65 "
]
},
"execution_count": 7,
@@ -478,7 +458,7 @@
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": 8,
"metadata": {
"colab": {},
"colab_type": "code",
@@ -490,18 +470,17 @@
"output_type": "stream",
"text": [
"Collecting pyspark\n",
- "\u001b[?25l Downloading https://files.pythonhosted.org/packages/87/21/f05c186f4ddb01d15d0ddc36ef4b7e3cedbeb6412274a41f26b55a650ee5/pyspark-2.4.4.tar.gz (215.7MB)\n",
- "\u001b[K |################################| 215.7MB 77.0MB/s eta 0:00:011\n",
- "\u001b[?25hCollecting py4j==0.10.7 (from pyspark)\n",
- "\u001b[?25l Downloading https://files.pythonhosted.org/packages/e3/53/c737818eb9a7dc32a7cd4f1396e787bd94200c3997c72c1dbe028587bd76/py4j-0.10.7-py2.py3-none-any.whl (197kB)\n",
- "\u001b[K |################################| 204kB 72.4MB/s eta 0:00:01\n",
+ " Using cached pyspark-3.1.1.tar.gz (212.3 MB)\n",
+ "Collecting py4j==0.10.9\n",
+ " Downloading py4j-0.10.9-py2.py3-none-any.whl (198 kB)\n",
+ "\u001b[K |████████████████████████████████| 198 kB 27.8 MB/s eta 0:00:01\n",
"\u001b[?25hBuilding wheels for collected packages: pyspark\n",
" Building wheel for pyspark (setup.py) ... \u001b[?25ldone\n",
- "\u001b[?25h Created wheel for pyspark: filename=pyspark-2.4.4-py2.py3-none-any.whl size=216130387 sha256=7879a54a037a812709763c4abf7d3d85b5b9b9f8ac6278785942767dc8032f54\n",
- " Stored in directory: /root/.cache/pip/wheels/ab/09/4d/0d184230058e654eb1b04467dbc1292f00eaa186544604b471\n",
+ "\u001b[?25h Created wheel for pyspark: filename=pyspark-3.1.1-py2.py3-none-any.whl size=212767604 sha256=f924906e4f699df53b02459122288353d7e6790a0d5cb0181040406516c56b44\n",
+ " Stored in directory: /root/.cache/pip/wheels/43/47/42/bc413c760cf9d3f7b46ab7cd6590e8c47ebfd19a7386cd4a57\n",
"Successfully built pyspark\n",
"Installing collected packages: py4j, pyspark\n",
- "Successfully installed py4j-0.10.7 pyspark-2.4.4\n"
+ "Successfully installed py4j-0.10.9 pyspark-3.1.1\n"
]
}
],
@@ -523,7 +502,7 @@
},
{
"cell_type": "code",
- "execution_count": 12,
+ "execution_count": 9,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@@ -538,8 +517,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "CPU times: user 73.4 ms, sys: 27.2 ms, total: 101 ms\n",
- "Wall time: 6.59 s\n"
+ "CPU times: user 39.5 ms, sys: 39.9 ms, total: 79.4 ms\n",
+ "Wall time: 6.87 s\n"
]
}
],
@@ -573,7 +552,7 @@
},
{
"cell_type": "code",
- "execution_count": 17,
+ "execution_count": 10,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@@ -588,8 +567,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "CPU times: user 20.4 ms, sys: 33.4 ms, total: 53.8 ms\n",
- "Wall time: 48.8 s\n"
+ "CPU times: user 27.5 ms, sys: 33.8 ms, total: 61.3 ms\n",
+ "Wall time: 40 s\n"
]
}
],
@@ -601,7 +580,7 @@
},
{
"cell_type": "code",
- "execution_count": 18,
+ "execution_count": 11,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@@ -616,8 +595,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "CPU times: user 1.87 ms, sys: 0 ns, total: 1.87 ms\n",
- "Wall time: 28.6 ms\n"
+ "CPU times: user 1.21 ms, sys: 283 µs, total: 1.49 ms\n",
+ "Wall time: 155 ms\n"
]
}
],
@@ -629,7 +608,7 @@
},
{
"cell_type": "code",
- "execution_count": 19,
+ "execution_count": 12,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
@@ -644,19 +623,19 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "+------------+---------------+-----------+--------+-------+---------------+-------------------+-------------------+------------+\n",
- "| source| destination|targetPorts|bytesOut|bytesIn|durationSeconds| firstFlowDate| lastFlowDate|attemptCount|\n",
- "+------------+---------------+-----------+--------+-------+---------------+-------------------+-------------------+------------+\n",
- "| 172.10.1.13|239.255.255.250| 15| 2975| 0| 6|2013-04-03 06:36:19|2013-04-03 06:36:27| 15|\n",
- "|172.30.1.204|239.255.255.250| 8| 1750| 0| 6|2013-04-03 06:36:13|2013-04-03 06:36:20| 8|\n",
- "| 172.30.2.86| 172.0.0.1| 1| 540| 0| 2|2013-04-03 06:36:09|2013-04-03 06:36:09| 1|\n",
- "|172.30.1.246| 172.0.0.1| 29| 2610| 2610| 0|2013-04-03 00:26:46|2013-04-03 23:06:00| 29|\n",
- "| 172.30.1.51|239.255.255.250| 16| 3850| 0| 18|2013-04-03 06:35:22|2013-04-03 06:44:08| 16|\n",
- "+------------+---------------+-----------+--------+-------+---------------+-------------------+-------------------+------------+\n",
+ "+---------+------------+-----------+--------+-------+---------------+-------------------+-------------------+------------+\n",
+ "| source| destination|targetPorts|bytesOut|bytesIn|durationSeconds| firstFlowDate| lastFlowDate|attemptCount|\n",
+ "+---------+------------+-----------+--------+-------+---------------+-------------------+-------------------+------------+\n",
+ "|10.0.0.10| 172.20.1.73| 1| 571| 108| 0|2013-04-03 10:08:30|2013-04-03 10:08:30| 1|\n",
+ "|10.0.0.10|172.30.1.221| 1| 633| 392| 0|2013-04-03 10:10:39|2013-04-03 10:10:39| 1|\n",
+ "|10.0.0.10| 172.30.2.67| 1| 633| 392| 0|2013-04-03 10:43:48|2013-04-03 10:43:48| 1|\n",
+ "|10.0.0.11| 172.20.1.55| 1| 571| 108| 0|2013-04-03 10:11:52|2013-04-03 10:11:52| 1|\n",
+ "|10.0.0.11|172.30.1.245| 3| 1837| 892| 0|2013-04-03 09:45:12|2013-04-03 11:27:32| 3|\n",
+ "+---------+------------+-----------+--------+-------+---------------+-------------------+-------------------+------------+\n",
"only showing top 5 rows\n",
"\n",
- "CPU times: user 12.1 ms, sys: 11.2 ms, total: 23.4 ms\n",
- "Wall time: 21.1 s\n"
+ "CPU times: user 33 ms, sys: 24.3 ms, total: 57.3 ms\n",
+ "Wall time: 38.8 s\n"
]
}
],
@@ -712,7 +691,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.6.7"
+ "version": "3.7.10"
}
},
"nbformat": 4,
diff --git a/community_tutorials_and_guides/census_education2income_demo.ipynb b/community_tutorials_and_guides/census_education2income_demo.ipynb
new file mode 100644
index 00000000..f724a6d0
--- /dev/null
+++ b/community_tutorials_and_guides/census_education2income_demo.ipynb
@@ -0,0 +1,1496 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Census Notebook\n",
+ "**Authorship** \n",
+ "Original Author: Taurean Dyer \n",
+ "Last Edit: Taurean Dyer, 9/26/2019 \n",
+ "\n",
+ "**Test System Specs** \n",
+ "Test System Hardware: GV100 \n",
+ "Test System Software: Ubuntu 18.04 \n",
+ "RAPIDS Version: 0.10.0a - Docker Install \n",
+ "Driver: 410.79 \n",
+ "CUDA: 10.0 \n",
+ "\n",
+ "\n",
+ "**Known Working Systems** \n",
+ "RAPIDS Versions:0.8, 0.9, 0.10\n",
+ "\n",
+ "# Intro\n",
+ "Held every 10 years, the US census gives a detailed snapshot in time about the makeup of the country. The last census in 2010 surveyed nearly 309 million people. IPUMS.org provides researchers an open source data set with 1% to 10% of the census data set. In this notebook, we want to see how education affects total income earned in the US based on data from each census from the 1970 to 2010 and see if we can predict some results if the census was held today, according to the national average. We will go through the ETL, training the model, and then testing the prediction. We'll make every effort to get as balanced of a dataset as we can. We'll also pull some extra variables to allow for further self-exploration of gender based education and income breakdowns. On a single Titan RTX, you can run the whole notebook workflow on the 4GB dataset of 14 million rows by 44 columns in less than 3 minutes. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Let's begin!**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Imports"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "import cuml\n",
+ "import cudf\n",
+ "import dask_cudf\n",
+ "import sys\n",
+ "import os\n",
+ "from pprint import pprint\n",
+ "import warnings\n",
+ "warnings.filterwarnings('ignore')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Get your data!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from dask.distributed import Client, wait\n",
+ "from dask_cuda import LocalCUDACluster\n",
+ "import dask, dask_cudf\n",
+ "from dask.diagnostics import ProgressBar\n",
+ "\n",
+ "# Use dask-cuda to start one worker per GPU on a single-node system\n",
+ "# When you shutdown this notebook kernel, the Dask cluster also shuts down.\n",
+ "cluster = LocalCUDACluster(ip='0.0.0.0')\n",
+ "client = Client(cluster)\n",
+ "# print client info\n",
+ "client"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Ok, we've got a cluster of GPU workers. Notice also the link to the Dask status dashboard. It provides lots of useful information while running data processing tasks.\n",
+ "\n",
+ "## Accessing Data\n",
+ "\n",
+ "Now, let's download a dataset.\n",
+ "\n",
+ "If you're working on a local machine, you'd normally use wget, Python's `urllib` package, or another tool to pull down the data you want to analyze.\n",
+ "\n",
+ "For the sake of not making you wait for 200+ files to download, the cell below uses urllib to download just 20 years of weather records, and a metadata file about the stations that recorded it. You can update the `years` list if you want to download more, but it wont change the logic in the notebook either way, it'll just process more data.\n",
+ "\n",
+ "*Note*: The rest of the markdown commentary in this notebook assumes you're operating on all 232 years of data."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Make and set a home for your data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "import urllib.request\n",
+ "\n",
+ "data_dir = '../../data/weather/'\n",
+ "if not os.path.exists(data_dir):\n",
+ " print('creating weather directory')\n",
+ " os.system('mkdir ../../data/weather')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Choose and Download your data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# download weather observations\n",
+ "base_url = 'ftp://ftp.ncdc.noaa.gov/pub/data/ghcn/daily/by_year/'\n",
+ "years = list(range(2000, 2020))\n",
+ "for year in years:\n",
+ " fn = str(year) + '.csv.gz'\n",
+ " if not os.path.isfile(data_dir+fn):\n",
+ " print(f'Downloading {base_url+fn} to {data_dir+fn}')\n",
+ " urllib.request.urlretrieve(base_url+fn, data_dir+fn)\n",
+ " \n",
+ "# download weather station metadata\n",
+ "station_meta_url = 'https://www1.ncdc.noaa.gov/pub/data/ghcn/daily/ghcnd-stations.txt'\n",
+ "if not os.path.isfile(data_dir+'ghcnd-stations.txt'):\n",
+ " print('Downloading station meta..')\n",
+ " urllib.request.urlretrieve(station_meta_url, data_dir+'ghcnd-stations.txt')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Alternatives to Pre-Downloading Data\n",
+ "\n",
+ "While downloading or copying data to your local environment is a good way to get started, many users will want other options:\n",
+ "\n",
+ "1. Reading directly from distributed storage, like HDFS\n",
+ "2. Reading from cloud storage (S3, GCS, ADLS, etc)\n",
+ "\n",
+ "See [Dask Remote Data Services](http://docs.dask.org/en/latest/remote-data-services.html) for more details on supported providers, authentication, and other storage configuration options.\n",
+ "\n",
+ "Here's an example of reading the same weather data, conveniently available in a public Amazon S3 bucket.\n",
+ "\n",
+ "But first make sure your Python environment has the right packages to read from your storage system of choice.\n",
+ "\n",
+ "For this example: ```conda install -y s3fs```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
Dask DataFrame Structure:
\n",
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
station_id
\n",
+ "
date
\n",
+ "
type
\n",
+ "
val
\n",
+ "
\n",
+ "
\n",
+ "
npartitions=1
\n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
object
\n",
+ "
int64
\n",
+ "
object
\n",
+ "
int64
\n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
...
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
Dask Name: read-csv, 1 tasks
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# these CSV files don't have headers, we specify column names manually\n",
+ "names = [\"station_id\", \"date\", \"type\", \"val\"]\n",
+ "# there are more fields, but only the first 4 are relevant in this notebook\n",
+ "usecols = names[0:4]\n",
+ "\n",
+ "url = 's3://noaa-ghcn-pds/csv/1788.csv'\n",
+ "dask_cudf.read_csv(url, names=names, usecols=usecols, storage_options={'anon': True})"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Reading Large & Multi-File DataSets\n",
+ "\n",
+ "Wait... there are many weather files: one for each year going back to the 1780s.\n",
+ "\n",
+ "Before RAPIDS 0.6, if you wanted to read all these files in, you'd need to either use a for-loop, manually concatenating dataframes, or use [`dask.delayed`](http://docs.dask.org/en/latest/delayed.html) functions that invoke cuDF.read_csv.\n",
+ "\n",
+ "Fortunately, now there's `dask_cudf.read_csv`, which supports file globs, _and_ automatically splits files into chunks that can be processed serially when needed, so you're less likely to run out of memory.\n",
+ "\n",
+ "When you call `dask_cudf.read_csv`, Dask reads metadata for each CSV file and tasks workers with lists of filenames & byte-ranges that they're responsible for loading with cuDF's GPU CSV reader.\n",
+ "\n",
+ "*Note*: compressed files are not splittable on read, but you can [repartition](https://docs.dask.org/en/latest/dataframe-best-practices.html#repartition-to-reduce-overhead) them downstream."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/opt/conda/envs/rapids/lib/python3.7/site-packages/dask_cudf/io/csv.py:60: UserWarning: Warning gzip compression does not support breaking apart files\n",
+ "Please ensure that each individual file can fit in memory and\n",
+ "use the keyword ``chunksize=None to remove this message``\n",
+ "Setting ``chunksize=(size of file)``\n",
+ " \"Setting ``chunksize=(size of file)``\" % compression\n"
+ ]
+ }
+ ],
+ "source": [
+ "weather_ddf = dask_cudf.read_csv(data_dir+'*.csv.gz', names=names, usecols=usecols, compression='gzip')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Let's Process Some Data\n",
+ "\n",
+ "Per the [readme](https://docs.opendata.aws/noaa-ghcn-pds/readme.html) for this dataset, multiple types of weather observations are in the same files, and each carries a different units of measure:\n",
+ "\n",
+ "| Observation Type | Existing Units | Action |\n",
+ "| ------------- | ------------- | ------------- |\n",
+ "| PRCP | Precipitation (tenths of mm) | convert to inches |\n",
+ "| SNWD | Snow depth (mm) | convert to inches |\n",
+ "| TMAX | tenths of degrees C | convert to fahrenheit |\n",
+ "| TMIN | tenths of degrees C | convert to fahrenheit |\n",
+ "\n",
+ "There are more even more observation types, each with their own units of measure, but I wont list them all. In this notebook, I'm going to focus specifically on precipitation.\n",
+ "\n",
+ "The `type` column tells us what kind of weather observation each record represents. Ordinarily, you might use `query` to filter out subsets of records and apply different logic to each subset. However, [query doesn't support string datatypes yet](https://github.com/rapidsai/cudf/issues/111). Instead, you can use boolean indexing.\n",
+ "\n",
+ "For numeric types, Dask with cuDF works mostly like regular Dask. For instance, you can define new columns as combinations of other columns:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "precip_index = weather_ddf['type'] == 'PRCP'\n",
+ "precip_ddf = weather_ddf[precip_index]\n",
+ "\n",
+ "# convert 10ths of mm to inches\n",
+ "mm_to_inches = 0.0393701\n",
+ "precip_ddf['val'] = precip_ddf['val'] * 1/10 * mm_to_inches"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Note: Calling .head() will read the first few rows, usually from the first partition.\n",
+ "\n",
+ "In our case, the first partition represents weather data from 1788. Apparently, there wasn't _any_ precipitation data collected that year:\n",
+ "\n",
+ "Beware in your own analyes, that you .head() from partitions that you haven't already filtered everything out of!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
station_id
\n",
+ "
date
\n",
+ "
type
\n",
+ "
val
\n",
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ "
27
\n",
+ "
AGM00060355
\n",
+ "
20010101
\n",
+ "
PRCP
\n",
+ "
0.039370
\n",
+ "
\n",
+ "
\n",
+ "
30
\n",
+ "
AGM00060360
\n",
+ "
20010101
\n",
+ "
PRCP
\n",
+ "
0.118110
\n",
+ "
\n",
+ "
\n",
+ "
33
\n",
+ "
AGM00060402
\n",
+ "
20010101
\n",
+ "
PRCP
\n",
+ "
0.161417
\n",
+ "
\n",
+ "
\n",
+ "
37
\n",
+ "
AGM00060419
\n",
+ "
20010101
\n",
+ "
PRCP
\n",
+ "
0.078740
\n",
+ "
\n",
+ "
\n",
+ "
47
\n",
+ "
AGM00060445
\n",
+ "
20010101
\n",
+ "
PRCP
\n",
+ "
0.039370
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " station_id date type val\n",
+ "27 AGM00060355 20010101 PRCP 0.039370\n",
+ "30 AGM00060360 20010101 PRCP 0.118110\n",
+ "33 AGM00060402 20010101 PRCP 0.161417\n",
+ "37 AGM00060419 20010101 PRCP 0.078740\n",
+ "47 AGM00060445 20010101 PRCP 0.039370"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "precip_ddf.get_partition(1).head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Ok, we have a lot of weather observations. Now what?\n",
+ "\n",
+ "# Answering Questions With Data ##\n",
+ "\n",
+ "For some reason, residents of particular cities like to lay claim to having the best, or the worst of something. For Los Angeles, it's having the worst traffic. New Yorkers and Chicagoans argue over who has the best pizza. [West Coasters argue about who has the most rain](https://twitter.com/MikeNiccoABC7/status/1105184947663396864).\n",
+ "\n",
+ "Well... as a longtime Atlanta resident suffering from humidity exhaustion, I like to joke that with all the spring showers, _Atlanta_ is the new Seattle.\n",
+ "\n",
+ "Does my theory hold water? Or will the data rain on my bad pun parade?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# How Can I Test My Theory?\n",
+ "\n",
+ "We've already created `precip_df`, which is only the precipitation observations, but it's for all 100k weather stations, most of them no-where near Atlanta, and this is time-series data, so we'll need to aggregate over time ranges.\n",
+ "\n",
+ "To get down to just Atlanta and Seattle precipitation records, we have to...\n",
+ "\n",
+ "1. Extract year, month, and day from the compound \"date\" column, so that we can compare total rainfall across time.\n",
+ "\n",
+ "2. Load up the station metadata file.\n",
+ "\n",
+ "3. There's no \"city\" in the station metadata, so we'll do some geo-math and keep only stations near Atlanta and Seattle.\n",
+ "\n",
+ "4. Use a Groupby to compare changing precipitation patterns across time\n",
+ "\n",
+ "5. Use inner joins to filter the precipitation dataframe down to just Atlanta & Seattle data."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 1. Extracting Finer Grained Date Fields\n",
+ "\n",
+ "We _can_ do a bit of math to separate date parts.."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
station_id
\n",
+ "
date
\n",
+ "
type
\n",
+ "
val
\n",
+ "
year
\n",
+ "
month
\n",
+ "
day
\n",
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ "
27
\n",
+ "
AGM00060355
\n",
+ "
20010101
\n",
+ "
PRCP
\n",
+ "
0.039370
\n",
+ "
2001
\n",
+ "
1
\n",
+ "
1
\n",
+ "
\n",
+ "
\n",
+ "
30
\n",
+ "
AGM00060360
\n",
+ "
20010101
\n",
+ "
PRCP
\n",
+ "
0.118110
\n",
+ "
2001
\n",
+ "
1
\n",
+ "
1
\n",
+ "
\n",
+ "
\n",
+ "
33
\n",
+ "
AGM00060402
\n",
+ "
20010101
\n",
+ "
PRCP
\n",
+ "
0.161417
\n",
+ "
2001
\n",
+ "
1
\n",
+ "
1
\n",
+ "
\n",
+ "
\n",
+ "
37
\n",
+ "
AGM00060419
\n",
+ "
20010101
\n",
+ "
PRCP
\n",
+ "
0.078740
\n",
+ "
2001
\n",
+ "
1
\n",
+ "
1
\n",
+ "
\n",
+ "
\n",
+ "
47
\n",
+ "
AGM00060445
\n",
+ "
20010101
\n",
+ "
PRCP
\n",
+ "
0.039370
\n",
+ "
2001
\n",
+ "
1
\n",
+ "
1
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " station_id date type val year month day\n",
+ "27 AGM00060355 20010101 PRCP 0.039370 2001 1 1\n",
+ "30 AGM00060360 20010101 PRCP 0.118110 2001 1 1\n",
+ "33 AGM00060402 20010101 PRCP 0.161417 2001 1 1\n",
+ "37 AGM00060419 20010101 PRCP 0.078740 2001 1 1\n",
+ "47 AGM00060445 20010101 PRCP 0.039370 2001 1 1"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "precip_ddf['year'] = precip_ddf['date']/10000\n",
+ "precip_ddf['year'] = precip_ddf['year'].astype('int')\n",
+ "\n",
+ "precip_ddf['month'] = (precip_ddf['date'] - precip_ddf['year']*10000)/100\n",
+ "precip_ddf['month'] = precip_ddf['month'].astype('int')\n",
+ "\n",
+ "precip_ddf['day'] = (precip_ddf['date'] - precip_ddf['year']*10000 - precip_ddf['month']*100)\n",
+ "precip_ddf['day'] = precip_ddf['day'].astype('int')\n",
+ "\n",
+ "precip_ddf.get_partition(1).head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "For this dataset, getting date parts is easier with string slicing. However, as is sometimes the case, Dask expects some aspect of cuDF's Python API to match Pandas in a way that [isn't fully compatible yet](https://github.com/rapidsai/cudf/issues/2367).\n",
+ "\n",
+ "That bug will likely be resolved quickly. But, this example is a good chance to show how to workaround similar problems.\n",
+ "\n",
+ "Dask has a [map_partitions](https://docs.dask.org/en/latest/dataframe-api.html#dask.dataframe.Series.map_partitions) function which will apply a given Python function to all partitions of a distributed DataFrame. When you do this on a dask_cudf df, your input is a cuDF object:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " station_id date type val year month day\n",
+ "0 cat 0 cat 0.0 0 0 0\n",
+ "1 dog 1 dog 1.0 1 1 1\n",
+ "0 0\n",
+ "1 1\n",
+ "Name: date, dtype: int64\n",
+ "0 0\n",
+ "1 1\n",
+ "Name: date, dtype: object\n",
+ "0 0\n",
+ "1 1\n",
+ "Name: date, dtype: int64\n"
+ ]
+ },
+ {
+ "ename": "ValueError",
+ "evalue": "Metadata inference failed in `get_date_parts`.\n\nYou have supplied a custom function and Dask is unable to \ndetermine the type of output that that function returns. \n\nTo resolve this please provide a meta= keyword.\nThe docstring of the Dask function you ran should have more information.\n\nOriginal error is below:\n------------------------\nValueError('Could not convert strings to integer type due to presence of non-integer values.')\n\nTraceback:\n---------\n File \"/opt/conda/envs/rapids/lib/python3.7/site-packages/dask/dataframe/utils.py\", line 180, in raise_on_meta_error\n yield\n File \"/opt/conda/envs/rapids/lib/python3.7/site-packages/dask/dataframe/core.py\", line 5316, in _emulate\n return func(*_extract_meta(args, True), **_extract_meta(kwargs, True))\n File \"\", line 8, in get_date_parts\n df['month'] = date_str.str.slice(4, 6).astype('int')\n File \"/opt/conda/envs/rapids/lib/python3.7/site-packages/cudf/core/series.py\", line 2190, in astype\n raise e\n File \"/opt/conda/envs/rapids/lib/python3.7/site-packages/cudf/core/series.py\", line 2182, in astype\n data = self._column.astype(dtype)\n File \"/opt/conda/envs/rapids/lib/python3.7/site-packages/cudf/core/column/column.py\", line 1009, in astype\n return self.as_numerical_column(dtype)\n File \"/opt/conda/envs/rapids/lib/python3.7/site-packages/cudf/core/column/string.py\", line 4825, in as_numerical_column\n \"Could not convert strings to integer \"\n",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m/opt/conda/envs/rapids/lib/python3.7/site-packages/dask/dataframe/utils.py\u001b[0m in \u001b[0;36mraise_on_meta_error\u001b[0;34m(funcname, udf)\u001b[0m\n\u001b[1;32m 179\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 180\u001b[0;31m \u001b[0;32myield\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 181\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mException\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m/opt/conda/envs/rapids/lib/python3.7/site-packages/dask/dataframe/core.py\u001b[0m in \u001b[0;36m_emulate\u001b[0;34m(func, *args, **kwargs)\u001b[0m\n\u001b[1;32m 5315\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mraise_on_meta_error\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfuncname\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfunc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mudf\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"udf\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 5316\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0m_extract_meta\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0m_extract_meta\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 5317\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36mget_date_parts\u001b[0;34m(df)\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdate_str\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mslice\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m4\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mastype\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'int'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m \u001b[0mdf\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'month'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdate_str\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mslice\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m4\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m6\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mastype\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'int'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 9\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdate_str\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mslice\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m4\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m6\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mastype\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'int'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m/opt/conda/envs/rapids/lib/python3.7/site-packages/cudf/core/series.py\u001b[0m in \u001b[0;36mastype\u001b[0;34m(self, dtype, copy, errors)\u001b[0m\n\u001b[1;32m 2189\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0merrors\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"raise\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2190\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2191\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0merrors\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"warn\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m/opt/conda/envs/rapids/lib/python3.7/site-packages/cudf/core/series.py\u001b[0m in \u001b[0;36mastype\u001b[0;34m(self, dtype, copy, errors)\u001b[0m\n\u001b[1;32m 2181\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2182\u001b[0;31m \u001b[0mdata\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_column\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mastype\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdtype\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2183\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m/opt/conda/envs/rapids/lib/python3.7/site-packages/cudf/core/column/column.py\u001b[0m in \u001b[0;36mastype\u001b[0;34m(self, dtype, **kwargs)\u001b[0m\n\u001b[1;32m 1008\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1009\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mas_numerical_column\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdtype\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1010\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m/opt/conda/envs/rapids/lib/python3.7/site-packages/cudf/core/column/string.py\u001b[0m in \u001b[0;36mas_numerical_column\u001b[0;34m(self, dtype)\u001b[0m\n\u001b[1;32m 4824\u001b[0m raise ValueError(\n\u001b[0;32m-> 4825\u001b[0;31m \u001b[0;34m\"Could not convert strings to integer \"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4826\u001b[0m \u001b[0;34m\"type due to presence of non-integer values.\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mValueError\u001b[0m: Could not convert strings to integer type due to presence of non-integer values.",
+ "\nThe above exception was the direct cause of the following exception:\n",
+ "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mdf\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0;31m# any single-GPU function that works in cuDF may be called via dask.map_partitions\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 13\u001b[0;31m \u001b[0mprecip_ddf\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mprecip_ddf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmap_partitions\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mget_date_parts\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 14\u001b[0m \u001b[0mprecip_ddf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_partition\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mhead\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m/opt/conda/envs/rapids/lib/python3.7/site-packages/dask/dataframe/core.py\u001b[0m in \u001b[0;36mmap_partitions\u001b[0;34m(self, func, *args, **kwargs)\u001b[0m\n\u001b[1;32m 676\u001b[0m \u001b[0;32mNone\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mthe\u001b[0m \u001b[0mdivision\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 677\u001b[0m \"\"\"\n\u001b[0;32m--> 678\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mmap_partitions\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfunc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 679\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 680\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0minsert_meta_param_description\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpad\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m12\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m/opt/conda/envs/rapids/lib/python3.7/site-packages/dask/dataframe/core.py\u001b[0m in \u001b[0;36mmap_partitions\u001b[0;34m(func, meta, enforce_metadata, transform_divisions, *args, **kwargs)\u001b[0m\n\u001b[1;32m 5367\u001b[0m \u001b[0;31m# Use non-normalized kwargs here, as we want the real values (not\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5368\u001b[0m \u001b[0;31m# delayed values)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 5369\u001b[0;31m \u001b[0mmeta\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_emulate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfunc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mudf\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 5370\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5371\u001b[0m \u001b[0mmeta\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmake_meta\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmeta\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mindex\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mmeta_index\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m/opt/conda/envs/rapids/lib/python3.7/site-packages/dask/dataframe/core.py\u001b[0m in \u001b[0;36m_emulate\u001b[0;34m(func, *args, **kwargs)\u001b[0m\n\u001b[1;32m 5314\u001b[0m \"\"\"\n\u001b[1;32m 5315\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mraise_on_meta_error\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfuncname\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfunc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mudf\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"udf\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 5316\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0m_extract_meta\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0m_extract_meta\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 5317\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5318\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m/opt/conda/envs/rapids/lib/python3.7/contextlib.py\u001b[0m in \u001b[0;36m__exit__\u001b[0;34m(self, type, value, traceback)\u001b[0m\n\u001b[1;32m 128\u001b[0m \u001b[0mvalue\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtype\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 129\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 130\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgen\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mthrow\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtype\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvalue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtraceback\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 131\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mStopIteration\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mexc\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 132\u001b[0m \u001b[0;31m# Suppress StopIteration *unless* it's the same exception that\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m/opt/conda/envs/rapids/lib/python3.7/site-packages/dask/dataframe/utils.py\u001b[0m in \u001b[0;36mraise_on_meta_error\u001b[0;34m(funcname, udf)\u001b[0m\n\u001b[1;32m 199\u001b[0m )\n\u001b[1;32m 200\u001b[0m \u001b[0mmsg\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmsg\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\" in `{0}`\"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfuncname\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mfuncname\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0;34m\"\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrepr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0me\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 201\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 202\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 203\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mValueError\u001b[0m: Metadata inference failed in `get_date_parts`.\n\nYou have supplied a custom function and Dask is unable to \ndetermine the type of output that that function returns. \n\nTo resolve this please provide a meta= keyword.\nThe docstring of the Dask function you ran should have more information.\n\nOriginal error is below:\n------------------------\nValueError('Could not convert strings to integer type due to presence of non-integer values.')\n\nTraceback:\n---------\n File \"/opt/conda/envs/rapids/lib/python3.7/site-packages/dask/dataframe/utils.py\", line 180, in raise_on_meta_error\n yield\n File \"/opt/conda/envs/rapids/lib/python3.7/site-packages/dask/dataframe/core.py\", line 5316, in _emulate\n return func(*_extract_meta(args, True), **_extract_meta(kwargs, True))\n File \"\", line 8, in get_date_parts\n df['month'] = date_str.str.slice(4, 6).astype('int')\n File \"/opt/conda/envs/rapids/lib/python3.7/site-packages/cudf/core/series.py\", line 2190, in astype\n raise e\n File \"/opt/conda/envs/rapids/lib/python3.7/site-packages/cudf/core/series.py\", line 2182, in astype\n data = self._column.astype(dtype)\n File \"/opt/conda/envs/rapids/lib/python3.7/site-packages/cudf/core/column/column.py\", line 1009, in astype\n return self.as_numerical_column(dtype)\n File \"/opt/conda/envs/rapids/lib/python3.7/site-packages/cudf/core/column/string.py\", line 4825, in as_numerical_column\n \"Could not convert strings to integer \"\n"
+ ]
+ }
+ ],
+ "source": [
+ "def get_date_parts(df):\n",
+ " print(df.head(10))\n",
+ " print(df[\"date\"])\n",
+ " date_str = df['date'].astype('str')\n",
+ " print(date_str)\n",
+ " df['year'] = date_str.str.slice(0, 4).astype('int')\n",
+ " print(date_str.str.slice(0, 4).astype('int'))\n",
+ " df['month'] = date_str.str.slice(4, 6).astype('int')\n",
+ " print(date_str.str.slice(4, 6).astype('int'))\n",
+ " df['day'] = date_str.str.slice(6, 8).astype('int')\n",
+ " return df\n",
+ "# any single-GPU function that works in cuDF may be called via dask.map_partitions\n",
+ "precip_ddf = precip_ddf.map_partitions(get_date_parts)\n",
+ "precip_ddf.get_partition(1).head()\n",
+ "\n",
+ "# def get_date_parts(df):\n",
+ "# date_str = df['date'].astype('str')\n",
+ " \n",
+ "# df['year'] = date_str.str.slice(0, 4)\n",
+ "# print(df['year'])\n",
+ "# df['month'] = date_str.str.slice(4, 6)\n",
+ "# print(df['month'])\n",
+ "# df['day'] = date_str.str.slice(6, 8)\n",
+ "# print(df['month'])\n",
+ " \n",
+ "# df['year'] = date_str.str.slice(0, 4).astype('int')\n",
+ "# df['month'] = date_str.str.slice(4, 6).astype('int')\n",
+ "# df['day'] = date_str.str.slice(6, 8).astype('int')\n",
+ "# return df\n",
+ "\n",
+ "# any single-GPU function that works in cuDF may be called via dask.map_partitions\n",
+ "# precip_ddf = precip_ddf.map_partitions(get_date_parts)\n",
+ "# precip_ddf.get_partition(1).head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The map_partitions pattern is also useful whenever there are cuDF specific functions without a direct mapping into Dask."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 2. Loading Station Metadata ##"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!head -n 5 /data/weather/ghcnd-stations.txt"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Wait... That's no CSV file! It's fixed-width!\n",
+ "\n",
+ "That's annoying because we don't have a reader for it. We could use CPU code to pre-process the file, making it friendlier for loading into a DataFrame, but, RAPIDS is about end-to-end data processing without leaving the GPU.\n",
+ "\n",
+ "This file is small enough that we can handle it directly with cuDF on a single GPU.\n",
+ "\n",
+ "*Warning*: Make sure you [create your dask-cuda cluster _before_ importing cudf](https://github.com/rapidsai/dask-cuda/issues/32).\n",
+ "\n",
+ "Here's how to cleanup this metadata using cuDF and string operations:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import cudf\n",
+ "\n",
+ "fn = data_dir+'ghcnd-stations.txt'\n",
+ "# There are no '|' chars in the file. Use that to read the file as a single column per line\n",
+ "# quoting=3 handles misplaced quotes in the `name` field \n",
+ "station_df = cudf.read_csv(fn, sep='|', quoting=3, names=['lines'], header=None)\n",
+ "\n",
+ "# you can use normal DataFrame .str accessor, and chain operators together\n",
+ "station_df['station_id'] = station_df['lines'].str.slice(0, 11).str.strip()\n",
+ "station_df['latitude'] = station_df['lines'].str.slice(12, 20).str.strip()\n",
+ "station_df['longitude'] = station_df['lines'].str.slice(21, 30).str.strip()\n",
+ "station_df = station_df.drop('lines')\n",
+ "\n",
+ "station_df.head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Managing Memory\n",
+ "\n",
+ "While GPU memory is very fast, there's less of it than host RAM. It's a good idea to avoid storing lots of columns that aren't useful for what you're trying to do, especially when they're strings.\n",
+ "\n",
+ "For example, for the station metadata, there are more columns than we parsed out above. In this workflow we only need `station_id`, `latitude`, and `longitude`, so we skipped parsing the rest of the columns.\n",
+ "\n",
+ "We also need to convert latitude and longitude from strings to floats, and convert the single-GPU DataFrame to a Dask DataFrame that can be distributed across workers."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# you can cast string columns to numerics\n",
+ "station_df['latitude'] = station_df['latitude'].astype('float')\n",
+ "station_df['longitude'] = station_df['longitude'].astype('float')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 3. Filtering Weather Stations by Distance\n",
+ "\n",
+ "Initially we planned to use our [existing Haversine Distance user defined function](https://medium.com/rapids-ai/user-defined-functions-in-rapids-cudf-2d7c3fc2728d) to figure out which stations are within a given distance from a city. However, that relies on a [numba CUDA JIT'ed kernel](https://numba.pydata.org/numba-doc/dev/cuda/index.html), which would be slower and would incur compilation time the first time you call it.\n",
+ "\n",
+ "Now that [cuSpatial](https://github.com/rapidsai/cuspatial) is available as [a nightly conda package](https://anaconda.org/rapidsai-nightly/cuspatial), we can use it without having to build from source:\n",
+ "\n",
+ "```\n",
+ "conda install -c conda-forge -c rapidsai-nightly cuspatial\n",
+ "```\n",
+ "\n",
+ "For this scenario, we've manually looked up Atlanta and Seattle's city centers and will fill `cudf.Series` with their latitude and longitude values. Then we can call a cuSpatial function to compute the distance between each station and each city."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import cuspatial\n",
+ "\n",
+ "# fill new Series with Atlanta lat/lng\n",
+ "station_df['atlanta_lat'] = 33.7490\n",
+ "station_df['atlanta_lng'] = -84.3880\n",
+ "# compute distance from each station to Atlanta\n",
+ "station_df['atlanta_dist'] = cuspatial.haversine_distance(\n",
+ " station_df['longitude'], station_df['latitude'],\n",
+ " station_df['atlanta_lng'], station_df['atlanta_lat']\n",
+ ")\n",
+ "\n",
+ "# fill new Series with Seattle lat/lng\n",
+ "station_df['seattle_lat'] = 47.6219\n",
+ "station_df['seattle_lng'] = -122.3517\n",
+ "# compute distance from each station to Seattle\n",
+ "station_df['seattle_dist'] = cuspatial.haversine_distance(\n",
+ " station_df['longitude'], station_df['latitude'],\n",
+ " station_df['seattle_lng'], station_df['seattle_lat']\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Checking the Results"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Inspect the results:\n",
+ "atlanta_stations_df = station_df.query('atlanta_dist <= 25')\n",
+ "seattle_stations_df = station_df.query('seattle_dist <= 25')\n",
+ "\n",
+ "print(f'Atlanta Stations: {len(atlanta_stations_df)}')\n",
+ "print(f'Seattle Stations: {len(seattle_stations_df)}')\n",
+ "\n",
+ "atlanta_stations_df.head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[Google tells me those station ids are from Smyrna](https://geographic.org/global_weather/georgia/smyrna_23_ne_002.html), a town just outside of Atlanta's perimeter. Our distance calculation worked!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 4. Grouping & Aggregating by Time Range\n",
+ "\n",
+ "Before using an inner join to filter down to city-specific precipitation data, we can use a groupby to sum the precipitation for station and year. That'll allow the join to proceed faster and use less memory.\n",
+ "\n",
+ "One total precipitation record per station per year is relatively small, and we're going to need to graph this data, so we'll go ahead and `compute()` the result, asking Dask to aggregate across the 200+ years worth of data, bringing the results back to the client as a single GPU cuDF DataFrame.\n",
+ "\n",
+ "Note that with Dask, data is partitioned and distributed across multiple workers. Some operations require that workers \"[shuffle](http://docs.dask.org/en/latest/dataframe-groupby.html#)\" data from their partitions back and forth across the network, which has major performance implications. Today join, groupby, and sort operations can be fairly network constrained.\n",
+ "\n",
+ "See the [slides](https://www.slideshare.net/MatthewRocklin/ucxpython-a-flexible-communication-library-for-python-applications) from a recent talk at GTC San Jose to learn more about [ongoing efforts to integrate Dask with UCX](https://github.com/rapidsai/ucx-py/) and allow it to use accelerated networking hardware like Infiniband and [nvlink](https://www.nvidia.com/en-us/data-center/nvlink/).\n",
+ "\n",
+ "In the meantime, distributed operators that require shuffling like joins, groupbys, and sorts work, albeit not as fast as we'd like."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "precip_year_ddf = precip_ddf.groupby(by=['station_id', 'year']).val.sum()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Note that we're calling `compute` again here. This tells Dask to actually start computing the full set of processing logic defined thus far:\n",
+ "\n",
+ "1. Read and decompress 232 gzipped files (about 100 GB decompressed)\n",
+ "2. Send to the GPU and parse\n",
+ "3. Filter down to precipitation records\n",
+ "4. Apply a conversion to inches\n",
+ "5. Sum total inches of rain per year per each of the 108k weather stations\n",
+ "6. Combine and pull results a single GPU DataFrame on the client host\n",
+ "\n",
+ "To wit.. this will take time."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%time precip_year_df = precip_year_ddf.compute()\n",
+ "\n",
+ "# Convert from the groupby multi-indexed DataFrame back to a normal DF which we can use with merge\n",
+ "precip_year_df = precip_year_df.reset_index()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 5. Using Inner Joins to Filter Weather Observations\n",
+ "\n",
+ "We have separate DataFrames containing Atlanta and Seattle stations, and we have our total precipitation grouped by `station_id` and `year`. Computing inner joins can let us compute total precipitation by year for just Atlanta and Seattle."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%time atlanta_precip_df = precip_year_df.merge(atlanta_stations_df, on=['station_id'], how='inner')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "atlanta_precip_df.head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%time seattle_precip_df = precip_year_df.merge(seattle_stations_df, on=['station_id'], how='inner')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "seattle_precip_df.head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Lastly, we need to normalize the total amount of rain in each city by the number of stations which collected rainfall: Seattle had twice as many stations collecting, but that doesn't mean more total rain fell! "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "atlanta_rain = atlanta_precip_df.groupby(['year']).val.sum()/len(atlanta_stations_df)\n",
+ "atlanta_rain.head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "seattle_rain = seattle_precip_df.groupby(['year']).val.sum()/len(seattle_stations_df)\n",
+ "\n",
+ "seattle_rain.head()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Visualizing the Answer\n",
+ "\n",
+ "To generate the graphs in the cells below, first you'll need to ```conda install -y python-graphviz matplotlib```"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%matplotlib inline\n",
+ "import matplotlib.pyplot as plt\n",
+ "from matplotlib.pyplot import *\n",
+ "\n",
+ "plt.close('all')\n",
+ "plt.rcParams['figure.figsize'] = [20, 10]\n",
+ "\n",
+ "fig, ax = subplots()\n",
+ "\n",
+ "atlanta_rain.to_pandas().plot(ax=ax)\n",
+ "seattle_rain.to_pandas().plot(ax=ax)\n",
+ "\n",
+ "ax.legend(['Atlanta', 'Seattle'])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Results\n",
+ "\n",
+ "It looks like I'm right (mostly)! At least for roughly the last 80 years, it rains more by volume in Atlanta than it does in Seattle. The data seems to confirm my suspicions.\n",
+ "\n",
+ "But as usual the answer raises additional questions:\n",
+ "\n",
+ "1. Without singling out Atlanta and Seattle, which city actually has the most precipitation by volume?\n",
+ "\n",
+ "2. Why is there such a large increase in observed precipitation in the last 10 years?\n",
+ "\n",
+ "3. One friend noted that it rains more frequently in Seattle, just not as hard. A contrarian was quick to point out that it mists a lot in Seattle. How often is it just \"misty\", but not really raining?\n",
+ "\n",
+ "We'll revisit these questions in a future post, and look forward to seeing what kinds of analyses YOU come up with."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Takeaways\n",
+ "\n",
+ "We just showed some of the ways you can use Dask and cuDF to parallelize typical data processing tasks on multiple GPUs. Hopefully this notebook provides useful examples to refer to while doing your own ETL & analytics work.\n",
+ "\n",
+ "For more info on what's working today with Dask and cuDF, see [our summary](https://docs.rapids.ai/api/cudf/stable/), and follow [our ongoing development](https://github.com/rapidsai/cudf).\n",
+ "\n",
+ "Also checkout out other [community contributed notebooks](https://github.com/rapidsai/notebooks-contrib), and submit your own!"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.10"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/conference_notebooks/TMLS_2020/notebooks/Taxi/Overview-Taxi.ipynb b/conference_notebooks/TMLS_2020/notebooks/Taxi/Overview-Taxi.ipynb
new file mode 100644
index 00000000..7db9dc7a
--- /dev/null
+++ b/conference_notebooks/TMLS_2020/notebooks/Taxi/Overview-Taxi.ipynb
@@ -0,0 +1,868 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Intro to RAPIDS using the New York City Yellow Taxi Data \n",
+ "light on Data Science, heavy on comparisons.\n",
+ "\n",
+ "This notebook is for the The Toronto Machine Learning Summit, Nov 16 -29, 2020\n",
+ "\n",
+ "![TMLS](./img/TMLS.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This notebook includes\n",
+ "\n",
+ "* cudf - for basic ETL and some __statistical analysis__ \n",
+ "* cuml - for __machine learning__\n",
+ "* cugraph - for some __graph analysis__\n",
+ "* cuxfilter - for __visualization__\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "----\n",
+ "# Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# load the libraries\n",
+ "import cudf\n",
+ "\n",
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import math\n",
+ "\n",
+ "import os\n",
+ "import gc\n",
+ "\n",
+ "from collections import OrderedDict\n",
+ "import argparse\n",
+ "import datetime\n",
+ "import time"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "try: \n",
+ " import tqdm\n",
+ "except ModuleNotFoundError:\n",
+ " os.system('pip install tqdm')\n",
+ " import tqdm"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Let's use Unified Memory (aka managed memory) so that we try and avoid OOM errors \n",
+ "# start by importing the RAPIDS Memory Manager and then reinitializing with managed memory turn on\n",
+ "import rmm\n",
+ "\n",
+ "rmm.reinitialize( \n",
+ " managed_memory=True, # Use managed memory, this allows for oversubscription of the GPU\n",
+ " pool_allocator=False, # default is False\n",
+ " devices=0, # GPU device IDs to register. By default, registers only GPU 0.\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Download the data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "top_dir = \"./\"\n",
+ "data_dir = \"./nyctaxi\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Download Taxi data\n",
+ "\n",
+ "if os.path.exists(data_dir) == False:\n",
+ " import nyctaxi_data\n",
+ "\n",
+ " print(\"downloading data\")\n",
+ " nyctaxi_data.download_nyctaxi_data([\"2016\"], top_dir)\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "----\n",
+ "\n",
+ "# cuDF - Accelerated Data Frame "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# get a list of files\n",
+ "data_path = top_dir + \"nyctaxi/2016\"\n",
+ "\n",
+ "files = []\n",
+ "\n",
+ "for f in sorted(os.listdir(data_path)):\n",
+ " if f[0:6] != 'yellow':\n",
+ " continue\n",
+ " \n",
+ " fname = os.path.join(data_path, f)\n",
+ " \n",
+ " files.append(fname)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "files"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!du -sh $data_path"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Loading data performance test"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def read_pandas(f):\n",
+ " start_t = time.time()\n",
+ " df = pd.read_csv(f)\n",
+ " end_t = time.time() - start_t\n",
+ "\n",
+ " return df, end_t"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def read_cudf(f):\n",
+ " start_t = time.time()\n",
+ " df = cudf.read_csv(f)\n",
+ " end_t = time.time() - start_t\n",
+ "\n",
+ " return df, end_t"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "_ = read_pandas(files[0])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Load data with Pandas\n",
+ "\n",
+ "data = []\n",
+ "\n",
+ "start_t = time.time()\n",
+ "\n",
+ "for f in files:\n",
+ " print(\"\\treading \" + f, end = '')\n",
+ " df, t = read_pandas(f)\n",
+ " print(\" ... in time of \" + str(t) + \" seconds\")\n",
+ " data.append(df)\n",
+ " \n",
+ "taxi_pdf = pd.concat(data)\n",
+ "\n",
+ "end_t = time.time()\n",
+ "\n",
+ "print(f\"loaded {len(taxi_pdf):,} records in {(end_t - start_t):2f} seconds\")\n",
+ "\n",
+ "del data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Load data with RAPIDS cuDF\n",
+ "\n",
+ "data = []\n",
+ "\n",
+ "start_t = time.time()\n",
+ "\n",
+ "for f in files:\n",
+ " print(\"\\treading \" + f, end = '')\n",
+ " df, t = read_cudf(f)\n",
+ " print(\" ... in time of \" + str(t)+ \" seconds\")\n",
+ " data.append(df)\n",
+ "\n",
+ "taxi_gdf = cudf.concat(data)\n",
+ "\n",
+ "end_t = time.time()\n",
+ "\n",
+ "print(f\"loaded {len(taxi_gdf):,} records in {(end_t - start_t):2f} seconds\")\n",
+ "\n",
+ "del data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "taxi_gdf.head(5)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Sort Comparisons - Single Field"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%time\n",
+ "sp = taxi_pdf.sort_values(by='trip_distance',ascending=False)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "sp.head(5)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%time\n",
+ "sg = taxi_gdf.sort_values(by='trip_distance',ascending=False)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "sg.head(5)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Group By - Single Column "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%time\n",
+ "gbp = taxi_pdf.groupby('passenger_count').count()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gbp.head(5)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%time\n",
+ "gbg = taxi_gdf.groupby('passenger_count').count()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gbg.head(5)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Fun with Data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%time\n",
+ "print(f\"Max fare was ${taxi_pdf['fare_amount'].max():,}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%time\n",
+ "print(f\"Max fare was ${taxi_gdf['fare_amount'].max():,}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# looking at that huge fare\n",
+ "maxf = taxi_gdf['fare_amount'].max()\n",
+ "taxi_gdf.query('fare_amount == @maxf')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(f\"Farthest trip was {taxi_gdf['trip_distance'].max():,} miles\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# How long did it take to drive that distance?\n",
+ "maxd= taxi_gdf['trip_distance'].max()\n",
+ "taxi_gdf.query('trip_distance == @maxd')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Changing data types"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# change some data types\n",
+ "taxi_gdf = taxi_gdf.astype({'tpep_pickup_datetime':'datetime64[ms]', 'tpep_dropoff_datetime':'datetime64[ms]'})"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Filtering data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# filter out records with missing or outlier values\n",
+ "query_frags = (\"(fare_amount > 0 and fare_amount < 500) \" +\n",
+ " \"and (passenger_count > 0 and passenger_count < 6) \" +\n",
+ " \"and (pickup_longitude > -75 and pickup_longitude < -73) \" +\n",
+ " \"and (dropoff_longitude > -75 and dropoff_longitude < -73) \" +\n",
+ " \"and (pickup_latitude > 40 and pickup_latitude < 42) \" +\n",
+ " \"and (dropoff_latitude > 40 and dropoff_latitude < 42)\" +\n",
+ " \"and (pickup_latitude != dropoff_latitude) \" +\n",
+ " \"and (pickup_longitude != dropoff_longitude)\"\n",
+ " )\n",
+ "\n",
+ "taxi_gdf = taxi_gdf.query(query_frags)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Add some new features"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# easier to reference time by YYYY MM DD version a time stamps\n",
+ "taxi_gdf['hour'] = taxi_gdf['tpep_pickup_datetime'].dt.hour\n",
+ "taxi_gdf['year'] = taxi_gdf['tpep_pickup_datetime'].dt.year\n",
+ "taxi_gdf['month'] = taxi_gdf['tpep_pickup_datetime'].dt.month\n",
+ "taxi_gdf['day'] = taxi_gdf['tpep_pickup_datetime'].dt.day\n",
+ "taxi_gdf['diff'] = taxi_gdf['tpep_dropoff_datetime'].astype('int64') - taxi_gdf['tpep_pickup_datetime'].astype('int64')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def day_of_the_week_kernel(day, month, year, day_of_week):\n",
+ " for i, (d_1, m_1, y_1) in enumerate(zip(day, month, year)):\n",
+ " if month[i] < 3:\n",
+ " shift = month[i]\n",
+ " else:\n",
+ " shift = 0\n",
+ " Y = year[i] - (month[i] < 3)\n",
+ " y = Y - 2000\n",
+ " c = 20\n",
+ " d = day[i]\n",
+ " m = month[i] + shift + 1\n",
+ " day_of_week[i] = (d + math.floor(m * 2.6) + y + (y // 4) + (c // 4) - 2 * c) % 7\n",
+ " \n",
+ "taxi_gdf = taxi_gdf.apply_rows(\n",
+ " day_of_the_week_kernel\n",
+ " , incols = ['day', 'month', 'year']\n",
+ " , outcols = {'day_of_week': np.int32}\n",
+ " , kwargs = {}\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "taxi_gdf.head(5)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Basic Statistical Data Science\n",
+ "\n",
+ "### Look at some feature - by Hour"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# 1) Let's look at a plot of fare by hour\n",
+ "%matplotlib inline\n",
+ "taxi_gdf.groupby('hour').fare_amount.mean().to_pandas().sort_index().plot(legend=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# 2) Tips by hour\n",
+ "%matplotlib inline\n",
+ "taxi_gdf.groupby('hour').tip_amount.mean().to_pandas().sort_index().plot(legend=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# 3) Number of taxi rides by Hour\n",
+ "%matplotlib inline\n",
+ "taxi_gdf['hour'].groupby('hour').count().to_pandas().sort_index().plot(legend=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Look at what days are the busiest\n",
+ "%matplotlib inline\n",
+ "taxi_gdf.groupby('day_of_week').day_of_week.count().to_pandas().sort_index().plot(legend=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# What days have the best tips\n",
+ "%matplotlib inline\n",
+ "taxi_gdf.groupby('day_of_week').tip_amount.mean().to_pandas().sort_index().plot(legend=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Dropping Columns"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "taxi_gdf = taxi_gdf.drop('store_and_fwd_flag', axis=1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "taxi_gdf.dtypes"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# cuML - Accelerated Machine Learning"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### In Corey's talk"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "# cuGraph - Accelerated Graph Analytics\n",
+ "\n",
+ "We need vertex IDs to be integer values but what we have are lat-long pairs (float64). There are two way that we can address the issue. The hard way and an easy way"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import cugraph"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "taxi_subset = taxi_gdf[['pickup_longitude', 'pickup_latitude','dropoff_longitude', 'dropoff_latitude', 'trip_distance']].reset_index()\n",
+ "taxi_subset['count'] = 1\n",
+ "del taxi_gdf"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Create vertices and edges the hard way"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# create node ID from lat-long combinatiuons\n",
+ "nodes = [\n",
+ " taxi_subset[['pickup_longitude', 'pickup_latitude']].drop_duplicates().rename(columns={'pickup_longitude': 'long', 'pickup_latitude': 'lat'})\n",
+ " , taxi_subset[['dropoff_longitude', 'dropoff_latitude']].drop_duplicates().rename(columns={'dropoff_longitude': 'long', 'dropoff_latitude': 'lat'})\n",
+ "]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "nodes = cudf.concat(nodes).drop_duplicates().reset_index(drop=True).reset_index().rename(columns={'index': 'id'})\n",
+ "nodes.head(5)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print('Total number of geo points in the dataset: {0:,}'.format(len(nodes)))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "edges = (\n",
+ " taxi_subset[['pickup_longitude', 'pickup_latitude','dropoff_longitude', 'dropoff_latitude', 'trip_distance']]\n",
+ " .drop_duplicates()\n",
+ " .rename(columns={'pickup_longitude': 'long', 'pickup_latitude': 'lat'})\n",
+ " .merge(nodes, on=['lat', 'long'])\n",
+ " .rename(columns={'long': 'pickup_longitude', 'lat': 'pickup_latitude', 'id': 'pickup_id', 'dropoff_longitude': 'long', 'dropoff_latitude': 'lat'})\n",
+ " .merge(nodes, on=['lat', 'long'])\n",
+ " .rename(columns={'long': 'dropoff_longitude', 'lat': 'dropoff_latitude', 'id': 'dropoff_id'})\n",
+ ")[['pickup_id', 'dropoff_id', 'trip_distance']]\n",
+ "\n",
+ "edges.head(5)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "len(edges)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "g = cugraph.Graph()\n",
+ "g.from_cudf_edgelist(edges, source='pickup_id', destination='dropoff_id')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Pagerank"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%%time\n",
+ "page = cugraph.pagerank(g, alpha=.85, max_iter=1000, tol=1.0e-05)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "page.sort_values(by='pagerank', ascending=False).head(5).to_pandas()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Now the easy way"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "g2 = cugraph.Graph()\n",
+ "g2.from_cudf_edgelist(taxi_subset, \n",
+ " source=['pickup_longitude', 'pickup_latitude'], \n",
+ " destination=['dropoff_longitude', 'dropoff_latitude'], \n",
+ " edge_attr='count',\n",
+ " renumber=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "page = cugraph.pagerank(g2, alpha=.85, max_iter=1000, tol=1.0e-05)\n",
+ "page.sort_values(by='pagerank', ascending=False).head(5).to_pandas()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "cugraph_dev",
+ "language": "python",
+ "name": "cugraph_dev"
+ },
+ "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.8.6"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/conference_notebooks/TMLS_2020/notebooks/Taxi/img/TMLS.png b/conference_notebooks/TMLS_2020/notebooks/Taxi/img/TMLS.png
new file mode 100644
index 00000000..44a8234f
Binary files /dev/null and b/conference_notebooks/TMLS_2020/notebooks/Taxi/img/TMLS.png differ
diff --git a/conference_notebooks/KDD_2020/notebooks/Taxi/nyctaxi_data.py b/conference_notebooks/TMLS_2020/notebooks/Taxi/nyctaxi_data.py
similarity index 100%
rename from conference_notebooks/KDD_2020/notebooks/Taxi/nyctaxi_data.py
rename to conference_notebooks/TMLS_2020/notebooks/Taxi/nyctaxi_data.py
diff --git a/getting_started_materials/README.md b/getting_started_materials/README.md
new file mode 100644
index 00000000..212fcd1c
--- /dev/null
+++ b/getting_started_materials/README.md
@@ -0,0 +1,199 @@
+# **Intro to RAPIDS Course for Content Creators**
+## Introduction
+
+In this intro course, we cover the basic skills you need to accelerate your data analytics and ML pipeline with RAPIDS. Get to know the RAPIDS core libraries: cuDF, cuML, cuGraph, and cuXFilter, as well as community libraries, including: XGBoost, Dask, and BlazingSQL, to accelerate how you:
+- Ingest data
+- Perform your prepare your data with ETL
+- Run modeling, inferencing, and predicting algorithms on the data in a GPU dataframe
+- Visualize your data throughout the process.
+
+Each of the three modules should take less than 2 hours to complete. When complete, you should be able to:
+1. Take an existing workflow in a data science or ML pipeline and use a RAPIDS to accelerate it with your GPU
+1. Create your own workflows from scratch
+
+This course was written with the expectation that you know Python and Jupyter Lab. It is helpful, but not necessary, to have at least some understanding of Pandas, Scikit Learn, NetworkX, and Datashader.
+
+[You should be able to run these exercises and use these libraries on any machine with these prerequisites](https://rapids.ai/start.html#PREREQUISITES), which namely are
+- OS of Ubuntu 16.04 or 18.04 or CentOS7 with gcc 5.4 & 7.3
+- an NVIDIA GPU of Pascal Architecture or better (basically 10xx series or newer)
+
+RAPIDS works on a broad range of GPUs, including NVIDIA GeForce, TITAN, Quadro, Tesla, A100, and DGX systems
+## NVIDIA Titan RTX
+- [NVIDIA Spot on Titan RTX and RAPIDS](https://www.youtube.com/watch?v=tsWPeZTLpkU)
+- [t-SNE 600x Speed up on Titan RTX](https://www.youtube.com/watch?v=_4OehmMYr44)
+
+
+
+## Questions?
+There are a few channels to ask questions or start a discussion:
+- [GoAI Slack](https://join.slack.com/t/rapids-goai/shared_invite/enQtMjE0Njg5NDQ1MDQxLTJiN2FkNTFkYmQ2YjY1OGI4NTc5Y2NlODQ3ZDdiODEwYmRiNTFhMzNlNTU5ZWJhZjA3NTg4NDZkMThkNTkxMGQ) to discuss issues and troubleshoot with the RAPIDS community
+- [RAPIDS GitHub](https://github.com/rapidsai) to submit feature requests and report bugs
+
+# **Getting Started**
+There are 3 steps to installing RAPIDS
+1. Provisioning a GPU enabled workspace
+1. Installing RAPIDS Prerequisites
+1. Installing RAPIDS libraries
+
+## 1. Provisioning a GPU-Enabled Workspace
+When installing RAPIDS, first provision a RAPIDS Compatible GPU. The GPU must be **NVIDIA Pascal™ or better with compute capability 6.0+**. Here is a list of compatible GPUs. This GPU can local, like in a workstation or server, or in the Cloud. GPUs can reside in:
+- Shared cloud
+- Dedicated cloud
+- Local workspace
+
+### Using Cloud Instance(s)
+There are two option for using Cloud Instances:
+1. Shared, **free** instances like app.blazingsql.com and Google Colab
+1. Dedicated, **paid** [usually] [GPU instances from providers like AWS, Azure, GCP, Paperspace, and more](https://rapids.ai/cloud.html)
+
+### Shared Cloud via Free Instances
+Free cloud instances have quick start capabilities or scripts to ease onboarding.
+- **Google Colab**: The installation will take about 8 minutes. First select a GPU instance from Runtime type. After, use the provided RAPIDS installation scripts, found here by copying and pasting into a code cell. Please note, RAPIDS will not run on an unsupported GPU instance like K80 - ONLY the T4, P4, and P100s (Refer to `!nvidia-smi`). If you are given a K80, please factory reset your instance and the check again.
+- **app.blazingsql.com**: these instances are preloaded with RAPIDS and you can start right away
+
+### Dedicated Cloud via Paid Instances
+There are several ways to provision a dedicated cloud GPU workspace, and our instructions are found here. Your OS will need to be **Ubuntu or RHEL/CentOS 7**. For installing RAPIDS, These instances follow the same installation process as a local instance.
+
+## 2. Installing RAPIDS Prerequisites
+### Downloads
+You can satisfy your prerequisites to install RAPIDS by:
+1. Install OS and GPU Drivers and OS
+1. Install Packaging Environment (Docker or Conda)
+
+### OS and GPU Drivers
+ Please ensure that your workstation has these installed as our prerequisites are as follows:
+- GPU: NVIDIA Pascal™ or better with compute capability 6.0+ (completed above)
+- OS: Ubuntu 16.04/18.04 or CentOS 7 with gcc/++ 7.5+
+ - See RSN 1 for details on our recent update to gcc/++ 7.5
+ - RHEL 7 support is provided through CentOS 7 builds/installs
+- CUDA & NVIDIA Drivers: One of the following supported versions:
+ - 10.0 & v410.48+ (valid option for version 0.14 and earlier only)
+ - 10.1.2 & v418.87+
+ - 10.2 & v440.33+
+ - 11.0 (valid option for version 0.16 and later)
+- Python
+ - 3.6 (valid option for version 0.14 and earlier)
+ - 3.7
+ - 3.8 (valid option for version 0.16 and later)
+
+
+### Install Packaging Environment (Docker or Conda)
+Depending on if you prefer to use RAPIDS with Docker or Conda, you will need these also installed:
+
+- If Docker: Docker CE v19.03+ and nvidia-container-toolkit
+ - Legacy Support - Docker CE v17-18 and nvidia-docker2
+
+- If Conda, please install
+ - [Miniconda](https://conda.io/miniconda.html) for a minimal conda installation
+ - [Anaconda](https://www.anaconda.com/download) for full conda installation
+ - [Mamba inside of conda](https://github.com/TheSnakePit/mamba) for a faster conda solving (untested)
+
+### 3. Install RAPIDS Libraries
+
+- Use the [Interactive RAPIDS release selector](https://rapids.ai/start.html#rapids-release-selector) to install RAPIDS as you want it. The install script at the bottom will update as you change your install parameters of **method, desired RAPIDS release, desired RAPIDS packages, Linux verison, and CUDA version**. Here is an image of it below.
+
+#
+Great! Now that you're done getting up and running, let's move on to the Data Science!
+
+## **1. The Basics of RAPIDS: cuDF and Dask**
+### Introduction
+cuDF lets you create and manipulate your dataframes on GPUs. All other RAPIDS libraries use cuDF to model, infer, regress, reduce, and predict outcomes. The cuDF API is designed to be similar to Pandas with minimal code changes.
+- [latest RAPIDS cuDF documentation](https://docs.rapids.ai/api)
+- [RAPIDS cuDF GitHub repo](https://github.com/rapidsai/cudf)
+
+There are situations where the dataframe is larger than available GPU memory. Dask is used to help RAPIDS algorithms scale up through distributed computing. Whether you have a single GPU, multiple GPUs, or clusters of multiple GPUs, Dask is used for distributed computing calculations and orchstrattion of the processing of GPU dataframe, no matter the size, just like a regular CPU cluster.
+
+Let's get started with a couple videos!
+
+### Videos
+
+| Video Title | Description |
+|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [Video- Getting Started with RAPIDS](https://www.youtube.com/watch?v=T2AU0iVbY5A). | Walks through the [01_Introduction_to_RAPIDS](intro_tutorials_and_guides/01_Introduction_to_RAPIDS.ipynb) notebook which shows, at a high level, what each of the packages in RAPIDS are as well as what they do. |
+| [Video - RAPIDS: Dask and cuDF NYCTaxi Screencast](https://www.youtube.com/watch?v=gV0cykgsTPM) | Shows you have you can use RAPIDS and Dask to easily ingest and model a large dataset (1 year's worth of NYCTaxi data) and then create a model around the question "when do you get the best tips". This same workload can be done on any GPU. |
+
+### Learning Notebooks
+
+
+| Notebook Title | Description |
+|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [01_Introduction_to_RAPIDS](intro_tutorials_and_guides/01_Introduction_to_RAPIDS.ipynb) | This notebook shows at a high level what each of the packages in RAPIDS are as well as what they do. |
+| [02_Introduction_to_cuDF](intro_tutorials_and_guides/02_Introduction_to_cuDF.ipynb) | This notebook shows how to work with cuDF DataFrames in RAPIDS. |
+| [03_Introduction_to_Dask](intro_tutorials_and_guides/03_Introduction_to_Dask.ipynb) | This notebook shows how to work with Dask using basic Python primitives like integers and strings. |
+| [04_Introduction_to_Dask_using_cuDF_DataFrames](intro_tutorials_and_guides/04_Introduction_to_Dask_using_cuDF_DataFrames.ipynb) | This notebook shows how to work with cuDF DataFrames using Dask. |
+| [Guide to UDFs](https://github.com/rapidsai/cudf/blob/branch-0.18/docs/cudf/source/guide-to-udfs.ipynb) | This notebook provides and overview of User Defined Functions with cuDF |
+
+
+
+### Extra credit and Exercises
+- [10 minute review of cuDF](https://github.com/rapidsai/cudf/blob/branch-0.18/docs/cudf/source/10min.ipynb)
+- [Extra Credit - 10 minute guide to cuDF and cuPY](https://github.com/rapidsai/cudf/blob/branch-0.18/docs/cudf/source/10min-cudf-cupy.ipynb)
+- [Extra Credit - Multi-GPU with Dask-cuDF](https://rapidsai.github.io/projects/cudf/en/0.18.0/dask-cudf.html)
+- [Review and Exercises 1- Review of cuDF](../the_archive/archived_rapids_event_notebooks/SCIPY_2019/cudf/01-Intro_to_cuDF.ipynb)
+- [Review and Exercises 2- Creating User Defined Functions (UDFs) in cuDF](../the_archive/archived_rapids_event_notebooks/SCIPY_2019/cudf/02-Intro_to_cuDF_UDFs.ipynb)
+
+## **2. Accelerating those Algorithms: cuML and XGBoost**
+### Introduction
+Congrats learning the basics of cuDF and Dask. Now let's take a look at cuML
+
+cuML runs many common scikit-learn algorithms and methods on cuDF dataframes to model, infer, regress, reduce, and predict outcomes on the data. [Among the ever growing suite of algorithms, you can perform several GPU accelerated algortihms for each of these methods:]()
+
+- Classification / Regression
+- Inference
+- Clustering
+- Decomposition & Dimensionality Reduction
+- Time Series
+
+While we look at cuML , we'll take a look at how further on how to increase your speed up with [XGBoost](https://machinelearningmastery.com/gentle-introduction-xgboost-applied-machine-learning/), scale it out with Dask XGboost, then see how to use cuML for Dimensionality Reduction and Clustering.
+- [latest RAPIDS cuML documentation](https://docs.rapids.ai/api)
+- [RAPIDS cuML GitHub repo](https://github.com/rapidsai/cuml)
+
+Let's look at a few video walkthroughs of XGBoost, as it may be an unfamiliar concept to some, and then experience how to use the above in your learning notebooks.
+
+### Videos
+
+| Video Title | Description |
+|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [Video - Introduction to XGBoost](https://www.youtube.com/watch?v=EQR3bP6XFW0) | Walks through the [07_Introduction_to_XGBoost](getting_started_notebooks/intro_tutorials/07_Introduction_to_XGBoost.ipynb) notebook and shows how to work with GPU accelerated XGBoost in RAPIDS. |
+| [Video - Introduction to Dask XGBoost](https://www.youtube.com/watch?v=q8HfEZythjM) | Walks through the [08_Introduction_to_Dask_XGBoost](getting_started_notebooks/intro_tutorials/08_Introduction_to_Dask_XGBoost.ipynb) notebook and hows how to work with Dask XGBoost in RAPIDS. This can be run on a single GPU as well and is useful when your dataset is larger than the memory size of your GPU. Will be deprecated in 0.15, and removed in 0.16 |
+
+### Learning Notebooks
+
+| Notebook Title | Description |
+|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [06_Introduction_to_Supervised_Learning](intro_tutorials_and_guides/06_Introduction_to_Supervised_Learning.ipynb) | This notebook shows how to do GPU accelerated Supervised Learning in RAPIDS. |
+| [07_Introduction_to_XGBoost](intro_tutorials_and_guides/07_Introduction_to_XGBoost.ipynb) | This notebook shows how to work with GPU accelerated XGBoost in RAPIDS. |
+| [09_Introduction_to_Dimensionality_Reduction](intro_tutorials_and_guides/09_Introduction_to_Dimensionality_Reduction.ipynb) | This notebook shows how to do GPU accelerated Dimensionality Reduction in RAPIDS. |
+| [10_Introduction_to_Clustering](intro_tutorials_and_guides/10_Introduction_to_Clustering.ipynb) | This notebook shows how to do GPU accelerated Clustering in RAPIDS. |
+
+
+### Extra credit and Exercises
+
+- [10 Review of cuML Estimators](https://github.com/rapidsai/cuml/blob/branch-0.18/docs/source/estimator_intro.ipynb)
+
+- [Review and Exercises 1 - Linear Regression](../the_archive/archived_rapids_event_notebooks/SCIPY_2019/cuml/01-Introduction-LinearRegression-Hyperparam.ipynb)
+
+- [Review and Exercises 2 - Logistic Regression](../the_archive/archived_rapids_event_notebooks/SCIPY_2019/cuml/02-LogisticRegression.ipynb)
+
+- [Review and Exercises 3- Intro to UMAP](../the_archive/archived_rapids_event_notebooks/SCIPY_2019/cuml/03-UMAP.ipynb)
+
+### RAPIDS cuML Example Notebooks
+- [Index of Notebooks](https://github.com/rapidsai/notebooks#cuml-notebooks)
+- [Direct Link to Notebooks](https://github.com/rapidsai/notebooks/tree/branch-0.18/cuml)
+
+
+### Conclusion to Sections 1 and 2
+Here ends the basics of cuDF, cuML, Dask, and XGBoost. These are libraries that everyone who uses RAPIDS will go to every day. Our next sections will cover libraries that are more niche in usage, but are powerful to accomplish your analytics.
+
+## **3. Graphs on RAPIDS: Intro to cuGraph**
+
+It is often useful to look at the relationships contained in the data, which we do that thought the use of graph analytics. Representing data as a graph is an extremely powerful techniques that has grown in popularity. Graph analytics are used to helps Netflix recommend shows, Google rank sites in their search engine, connects bits of discrete knowledge into a comprehensive corpus, schedules NFL games, and can even help you optimize seating for your wedding (and it works too!). [KDNuggests has a great in depth guide to graphs here](https://www.kdnuggets.com/2017/12/graph-analytics-using-big-data.html). Up until now, running a graph analytics was a painfully slow, particularly as the size of the graph (number of nodes and edges) grew.
+
+[RAPIDS' cuGraph library makes graph analytics effortless, as it boasts some of our best speedups](https://www.zdnet.com/article/nvidia-rapids-cugraph-making-graph-analysis-ubiquitous/), (up to 25,000x). To put it in persepctive, what can take over 20 hours, cuGraph can lets you do in less than a minute (3 seconds). In this section, we'll look at some examples of cuGraph methods for your graph analytics and look at a simple use case.
+- [latest RAPIDS cuGraph documentation](https://docs.rapids.ai/api)
+- [RAPIDS cuGraph GitHub repo](https://github.com/rapidsai/cugraph)
+
+### RAPIDS cuGraph Example Notebooks
+- [Index of Notebooks](https://github.com/rapidsai/notebooks/#cugraph-notebooks)
+- [Direct Link to Notebooks](https://github.com/rapidsai/notebooks/tree/branch-0.18/cugraph)
+"""
\ No newline at end of file
diff --git a/getting_started_notebooks/basics/Dask_Hello_World.ipynb b/getting_started_materials/hello_worlds/Dask_Hello_World.ipynb
similarity index 100%
rename from getting_started_notebooks/basics/Dask_Hello_World.ipynb
rename to getting_started_materials/hello_worlds/Dask_Hello_World.ipynb
diff --git a/getting_started_notebooks/basics/blazingsql/README.md b/getting_started_materials/hello_worlds/blazingsql/README.md
similarity index 100%
rename from getting_started_notebooks/basics/blazingsql/README.md
rename to getting_started_materials/hello_worlds/blazingsql/README.md
diff --git a/getting_started_notebooks/basics/blazingsql/federated_query_demo.ipynb b/getting_started_materials/hello_worlds/blazingsql/federated_query_demo.ipynb
similarity index 100%
rename from getting_started_notebooks/basics/blazingsql/federated_query_demo.ipynb
rename to getting_started_materials/hello_worlds/blazingsql/federated_query_demo.ipynb
diff --git a/getting_started_notebooks/basics/blazingsql/getting_started_with_blazingsql.ipynb b/getting_started_materials/hello_worlds/blazingsql/getting_started_with_blazingsql.ipynb
similarity index 100%
rename from getting_started_notebooks/basics/blazingsql/getting_started_with_blazingsql.ipynb
rename to getting_started_materials/hello_worlds/blazingsql/getting_started_with_blazingsql.ipynb
diff --git a/getting_started_notebooks/basics/hello_streamz.ipynb b/getting_started_materials/hello_worlds/hello_streamz.ipynb
similarity index 100%
rename from getting_started_notebooks/basics/hello_streamz.ipynb
rename to getting_started_materials/hello_worlds/hello_streamz.ipynb
diff --git a/getting_started_materials/intro_tutorials_and_guides/01_Introduction_to_RAPIDS.ipynb b/getting_started_materials/intro_tutorials_and_guides/01_Introduction_to_RAPIDS.ipynb
new file mode 100644
index 00000000..f08767b4
--- /dev/null
+++ b/getting_started_materials/intro_tutorials_and_guides/01_Introduction_to_RAPIDS.ipynb
@@ -0,0 +1,1101 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "## Introduction to RAPIDS\n",
+ "#### By Paul Hendricks\n",
+ "-------\n",
+ "\n",
+ "While the world’s data doubles each year, CPU computing has hit a brick wall with the end of Moore’s law. For the same reasons, scientific computing and deep learning has turned to NVIDIA GPU acceleration, data analytics and machine learning where GPU acceleration is ideal. \n",
+ "\n",
+ "NVIDIA created RAPIDS – an open-source data analytics and machine learning acceleration platform that leverages GPUs to accelerate computations. RAPIDS is based on Python, has Pandas-like and Scikit-Learn-like interfaces, is built on Apache Arrow in-memory data format, and can scale from 1 to multi-GPU to multi-nodes. RAPIDS integrates easily into the world’s most popular data science Python-based workflows. RAPIDS accelerates data science end-to-end – from data prep, to machine learning, to deep learning. And through Arrow, Spark users can easily move data into the RAPIDS platform for acceleration.\n",
+ "\n",
+ "In this notebook, we will discuss and show at a high level what each of the packages in the RAPIDS are as well as what they do. Subsequent notebooks will dive deeper into the various areas of data science and machine learning and show how you can use RAPIDS to accelerate your workflow in each of these areas.\n",
+ "\n",
+ "**Table of Contents**\n",
+ "\n",
+ "* [Introduction to RAPIDS](#introduction)\n",
+ "* [Setup](#setup)\n",
+ "* [Pandas](#pandas)\n",
+ "* [cuDF](#cudf)\n",
+ "* [Scikit-Learn](#scikitlearn)\n",
+ "* [cuML](#cuml)\n",
+ "* [Dask](#dask)\n",
+ "* [Dask cuDF](#daskcudf)\n",
+ "* [Conclusion](#conclusion)\n",
+ "\n",
+ "Before going any further, let's make sure we have access to `matplotlib`, a popular Python library for visualizing data. The Conda install of RAPIDS no longer includes it by default, but the Docker install does."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "try:\n",
+ " import matplotlib\n",
+ "except ModuleNotFoundError:\n",
+ " os.system('conda install -y matplotlib')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "## Setup\n",
+ "\n",
+ "This notebook was tested using the [RAPIDS Stable Conda channel, versions 0.17 and 0.18](https://anaconda.org/rapidsai/rapids), and the following Docker containers:\n",
+ "\n",
+ "* `rapidsai/rapidsai-dev:0.18-cuda10.2-devel-ubuntu18.04-py3.7` container from [DockerHub](https://hub.docker.com/r/rapidsai/rapidsai)\n",
+ "\n",
+ "This notebook was run on the NVIDIA GV100 GPU, the Quardo RTX8000, and the T4. Please be aware that your system may be different and you may need to modify the code or install packages to run the below examples. \n",
+ "\n",
+ "If you think you have found a bug or an error, please file an issue here: https://github.com/rapidsai/notebooks-contrib/issues\n",
+ "\n",
+ "Before we begin, let's check out our hardware setup by running the `nvidia-smi` command."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tue Apr 6 13:15:36 2021 \n",
+ "+-----------------------------------------------------------------------------+\n",
+ "| NVIDIA-SMI 440.33.01 Driver Version: 440.33.01 CUDA Version: 10.2 |\n",
+ "|-------------------------------+----------------------+----------------------+\n",
+ "| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\n",
+ "| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\n",
+ "|===============================+======================+======================|\n",
+ "| 0 Quadro RTX 8000 On | 00000000:42:00.0 Off | Off |\n",
+ "| 33% 36C P8 41W / 260W | 34515MiB / 48601MiB | 0% Default |\n",
+ "+-------------------------------+----------------------+----------------------+\n",
+ "| 1 Quadro RTX 8000 On | 00000000:43:00.0 Off | Off |\n",
+ "| 33% 42C P8 42W / 260W | 211MiB / 48598MiB | 0% Default |\n",
+ "+-------------------------------+----------------------+----------------------+\n",
+ " \n",
+ "+-----------------------------------------------------------------------------+\n",
+ "| Processes: GPU Memory |\n",
+ "| GPU PID Type Process name Usage |\n",
+ "|=============================================================================|\n",
+ "| 0 4987 C .../miniconda3/envs/rapids-0.16/bin/python 22299MiB |\n",
+ "| 0 23869 C .../miniconda3/envs/rapids-0.18/bin/python 721MiB |\n",
+ "| 0 89935 C ...an/miniconda3/envs/0.17-test/bin/python 11483MiB |\n",
+ "| 1 2156 G /usr/lib/xorg/Xorg 39MiB |\n",
+ "| 1 2313 G /usr/bin/gnome-shell 85MiB |\n",
+ "| 1 22643 G /usr/lib/xorg/Xorg 64MiB |\n",
+ "| 1 22689 G /usr/bin/gnome-shell 8MiB |\n",
+ "+-----------------------------------------------------------------------------+\n"
+ ]
+ }
+ ],
+ "source": [
+ "!nvidia-smi"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, let's see what CUDA version we have. If it's not found, that's okay, you may not have nvcc or be in a Docker container."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "nvcc: NVIDIA (R) Cuda compiler driver\n",
+ "Copyright (c) 2005-2017 NVIDIA Corporation\n",
+ "Built on Fri_Nov__3_21:07:56_CDT_2017\n",
+ "Cuda compilation tools, release 9.1, V9.1.85\n"
+ ]
+ }
+ ],
+ "source": [
+ "!nvcc --version"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, let's load some helper functions from `matplotlib` and configure the Jupyter Notebook for visualization."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from matplotlib.colors import ListedColormap\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "\n",
+ "%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's see how much GPU memory is available. Since this is a tutorial, we want to keep that data as big as possible without you running out of memory (OOM)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "your GPU has 48 GB\n"
+ ]
+ }
+ ],
+ "source": [
+ "from pynvml.smi import nvidia_smi\n",
+ "nvsmi = nvidia_smi.getInstance()\n",
+ "gpus = nvsmi.DeviceQuery()\n",
+ "\n",
+ "gpu_mem = int(gpus['gpu'][0]['fb_memory_usage']['total']/1000) #gets your memory size of your first found GPU in GB\n",
+ "print(\"your GPU has\", gpu_mem, \"GB\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "## Pandas\n",
+ "\n",
+ "Data scientists typically work with two types of data: unstructured and structured. Unstructured data often comes in the form of text, images, or videos. Structured data - as the name suggests - comes in a structured form, often represented by a table or CSV. We'll focus the majority of these tutorials on working with these types of data.\n",
+ "\n",
+ "There exist many tools in the Python ecosystem for working with structured, tabular data but few are as widely used as Pandas. Pandas represents data in a table and allows a data scientist to manipulate the data to perform a number of useful operations such as filtering, transforming, aggregating, merging, visualizing and many more. \n",
+ "\n",
+ "For more information on Pandas, check out the excellent documentation: http://pandas.pydata.org/pandas-docs/stable/\n",
+ "\n",
+ "Below we show how to create a Pandas DataFrame, an internal object for representing tabular data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Pandas Version: 1.1.4\n",
+ " key value\n",
+ "0 0 10.0\n",
+ "1 0 11.0\n",
+ "2 2 12.0\n",
+ "3 2 13.0\n",
+ "4 3 14.0\n"
+ ]
+ }
+ ],
+ "source": [
+ "import pandas as pd; print('Pandas Version:', pd.__version__)\n",
+ "\n",
+ "\n",
+ "# here we create a Pandas DataFrame with\n",
+ "# two columns named \"key\" and \"value\"\n",
+ "df = pd.DataFrame()\n",
+ "df['key'] = [0, 0, 2, 2, 3]\n",
+ "df['value'] = [float(i + 10) for i in range(5)]\n",
+ "print(df)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We can perform many operations on this data. For example, let's say we wanted to sum all values in the in the `value` column. We could accomplish this using the following syntax:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "60.0\n"
+ ]
+ }
+ ],
+ "source": [
+ "aggregation = df['value'].sum()\n",
+ "print(aggregation)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "## cuDF\n",
+ "\n",
+ "Pandas is fantastic for working with small datasets that fit into your system's memory. However, datasets are growing larger and data scientists are working with increasingly complex workloads - the need for accelerated compute arises.\n",
+ "\n",
+ "cuDF is a package within the RAPIDS ecosystem that allows data scientists to easily migrate their existing Pandas workflows from CPU to GPU, where computations can leverage the immense parallelization that GPUs provide.\n",
+ "\n",
+ "Below, we show how to create a cuDF DataFrame."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "cuDF Version: 0.17.0a+382.gbd321d1e93\n",
+ " key value\n",
+ "0 0 10.0\n",
+ "1 0 11.0\n",
+ "2 2 12.0\n",
+ "3 2 13.0\n",
+ "4 3 14.0\n"
+ ]
+ }
+ ],
+ "source": [
+ "import cudf; print('cuDF Version:', cudf.__version__)\n",
+ "\n",
+ "\n",
+ "# here we create a cuDF DataFrame with\n",
+ "# two columns named \"key\" and \"value\"\n",
+ "df = cudf.DataFrame()\n",
+ "df['key'] = [0, 0, 2, 2, 3]\n",
+ "df['value'] = [float(i + 10) for i in range(5)]\n",
+ "print(df)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "As before, we can take this cuDF DataFrame and perform a `sum` operation over the `value` column. The key difference is that any operations we perform using cuDF use the GPU instead of the CPU."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "60.0\n"
+ ]
+ }
+ ],
+ "source": [
+ "aggregation = df['value'].sum()\n",
+ "print(aggregation)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Note how the syntax for both creating and manipulating a cuDF DataFrame is identical to the syntax necessary to create and manipulate Pandas DataFrames; the cuDF API is based on the Pandas API. This design choice minimizes the cognitive burden of switching from a CPU based workflow to a GPU based workflow and allows data scientists to focus on solving problems while benefitting from the speed of a GPU!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "## Scikit-Learn\n",
+ "\n",
+ "After our data has been preprocessed, we often want to build a model so as to understand the relationships between different variables in our data. Scikit-Learn is an incredibly powerful toolkit that allows data scientists to quickly build models from their data. Below we show a simple example of how to create a Linear Regression model."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "NumPy Version: 1.19.5\n"
+ ]
+ }
+ ],
+ "source": [
+ "import numpy as np; print('NumPy Version:', np.__version__)\n",
+ "\n",
+ "\n",
+ "# create the relationship: y = 2.0 * x + 1.0\n",
+ "if(gpu_mem <= 16):\n",
+ " n_rows = 35000 # let's use 35 thousand data points. Very small GPU memory sizes will require you to reduce this number further \n",
+ "elif(gpu_mem > 17):\n",
+ " n_rows = 100000 # let's use 100 thousand data points\n",
+ "w = 2.0\n",
+ "x = np.random.normal(loc=0, scale=1, size=(n_rows,))\n",
+ "b = 1.0\n",
+ "y = w * x + b\n",
+ "\n",
+ "# add a bit of noise\n",
+ "noise = np.random.normal(loc=0, scale=2, size=(n_rows,))\n",
+ "y_noisy = y + noise"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We can now visualize our data using the `matplotlib` library."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXkAAAD4CAYAAAAJmJb0AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Il7ecAAAACXBIWXMAAAsTAAALEwEAmpwYAAA7OUlEQVR4nO2deWATZf7/30/Sgaa0NC03xXL4c0GgUKBcAgqCwH5FtoKAcomu4rGuyhereHGpC4Iuuh6LgjeoFZCK4oqcq4CA1LYoCl8vQApCpU1LD9o0eX5/pDMk6UwyOSaT4/P6B+aZycyTpHnPZz7P52CccxAEQRDRiUHvCRAEQRDaQSJPEAQRxZDIEwRBRDEk8gRBEFEMiTxBEEQUE6f3BJxp2bIl79Spk97TIAiCiCjy8/P/4Jy3ktsXViLfqVMnHDx4UO9pEARBRBSMseNK+8hdQxAEEcWQyBMEQUQxJPIEQRBRTFj55OWwWq04efIkLly4oPdUiAghPj4eHTp0gCAIek+FIHQn7EX+5MmTSEpKQqdOncAY03s6RJjDOce5c+dw8uRJdO7cWe/pEITuhL3IX7hwgQSeUA1jDC1atEBJSYneUyHCmLyCYizfchSnLDVobzYhZ0xXZPdJ03tamhD2Ig+ABJ7wCfp7ITyRV1CMhz/8FjVWGwCg2FKDhz/8FgCiUuhp4ZUgiJhi+ZajksCL1FhtWL7lqE4z0hYSeS9YLBa8/PLLek8Db775Ju655x6Px+zatQt79+6VtleuXIm33347aHM4duwYevbsKbtv/vz52LZtW9CuRRBaccpS49N4pBMR7ho9EUX+7rvvbrTPZrPBaDQG7Vr19fWIi/P/K9m1axcSExNxxRVXAADuvPPOYE3NK4sXLw7ZtQgiENqbTSiWEfT2ZpMOs9EesuS9MG/ePPz888/IzMxETk4Odu3ahREjRmDq1KnIyMhoZN0+88wzWLhwIQDg559/xtixY9GvXz8MGzYMR44caXT+hQsXYvbs2Rg9ejRmzpyJkpISTJw4Ef3790f//v2xZ8+eRq/5+OOPMXDgQPTp0wejRo3CmTNncOzYMaxcuRIrVqxAZmYmvvzySyxcuBDPPPMMAKCwsBCDBg1Cr169cP3116OsrAwAMHz4cDz00EMYMGAA/vSnP+HLL78EABw+fBgDBgxAZmYmevXqhR9//BGA48Z2++23o0ePHhg9ejRqahw/llmzZmH9+vUAHOUpxHMOGDAAP/30U5C+DYIInJwxXWESXI0zk2BEzpiuOs1IWyLKkr///vtRWFgY1HNmZmbiueeeU9y/dOlSfPfdd9J1d+3ahQMHDuC7775D586dcezYMcXXzp49GytXrsRll12G/fv34+6778aOHTsaHZefn4/du3fDZDJh6tSpmDNnDoYOHYoTJ05gzJgx+OGHH1yOHzp0KPbt2wfGGFavXo1ly5bh2WefxZ133onExEQ88MADAIDt27dLr5k5cyZeeOEFXHXVVZg/fz4WLVokve/6+nocOHAAn376KRYtWoRt27Zh5cqVuO+++zBt2jTU1dXBZrPhzJkz+PHHH/Hee+9h1apVmDx5MjZs2IDp06c3ek/NmzfHgQMH8Pbbb+P+++/HJ598ovg5EUQoERdXKbqGUGTAgAFeY7ArKyuxd+9eTJo0SRqrra2VPXb8+PEwmRyPitu2bcP3338v7auoqMD58+ddjj958iSmTJmC06dPo66uzutcysvLYbFYcNVVVwEAbr75Zpd5TZgwAQDQr18/6aY1ePBgPPXUUzh58iQmTJiAyy67DADQuXNnZGZmNjrenZtuukn6d86cOR7nRxChJrtPWtSKujsRJfKeLO5Q0qxZM+n/cXFxsNvt0raYmWu322E2m1U9eTifz26346uvvpJEX46///3v+N///V+MHz8eu3btktxD/tK0aVMAgNFoRH19PQBg6tSpGDhwIDZv3owxY8Zg9erV6NKli3SseLzornHHOYyRQhoJQj/IJ++FpKSkRpa0M23atMHZs2dx7tw51NbWSm6J5s2bo3Pnzli3bh0ARyZmUVGR1+uNHj0aL774orQtd5MoLy9HWprDCnnrrbe8zjU5ORkpKSmSv/2dd96RrHolfvnlF3Tp0gX33nsvxo8fj0OHDnmduzO5ubnSv4MHD/bptQRBBA8SeS+0aNECQ4YMQc+ePZGTk9NovyAImD9/PgYOHIhx48ahW7du0r61a9fitddeQ+/evdGjRw989NFHXq/3r3/9CwcPHkSvXr3QvXt3rFy5stExCxcuxKRJkzBs2DC0bNlSGr/uuuuwceNGaeHVmbfeegs5OTno1asXCgsLMX/+fI/zyM3NRc+ePZGZmYkjR45g5syZXufuTG1tLQYOHIjnn38eK1as8Om1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\n",
+ "text/plain": [
+ "
"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "plt.scatter(x, y_noisy, label='empirical data points')\n",
+ "plt.plot(x, y, color='black', label='true relationship')\n",
+ "plt.plot(inputs, outputs, color='red', label='predicted relationship (cpu)')\n",
+ "plt.legend()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "## cuML\n",
+ "\n",
+ "The mathematical operations underlying many machine learning algorithms are often matrix multiplications. These types of operations are highly parallelizable and can be greatly accelerated using a GPU. cuML makes it easy to build machine learning models in an accelerated fashion while still using an interface nearly identical to Scikit-Learn. The below shows how to accomplish the same Linear Regression model but on a GPU.\n",
+ "\n",
+ "First, let's convert our data from a NumPy representation to a cuDF representation."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " x y\n",
+ "0 -0.628841 -1.821668\n",
+ "1 1.490274 7.164684\n",
+ "2 -1.108334 -2.714711\n",
+ "3 -0.270642 -0.874697\n",
+ "4 1.600833 -1.727782\n"
+ ]
+ }
+ ],
+ "source": [
+ "# create a cuDF DataFrame\n",
+ "df = cudf.DataFrame({'x': x, 'y': y_noisy})\n",
+ "print(df.head())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, we'll load the GPU accelerated `LinearRegression` class from cuML, instantiate it, and fit it to our data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "cuML Version: 0.17.0a+173.g2c0aacf44\n"
+ ]
+ }
+ ],
+ "source": [
+ "import cuml; print('cuML Version:', cuml.__version__)\n",
+ "from cuml.linear_model import LinearRegression as LinearRegression_GPU\n",
+ "\n",
+ "\n",
+ "# instantiate and fit model\n",
+ "linear_regression_gpu = LinearRegression_GPU()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/opt/conda/envs/rapids/lib/python3.7/site-packages/cuml/internals/api_decorators.py:410: UserWarning: Changing solver from 'eig' to 'svd' as eig solver does not support training data with 1 column currently.\n",
+ " return func(*args, **kwargs)\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 415 ms, sys: 208 ms, total: 623 ms\n",
+ "Wall time: 2.48 s\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "LinearRegression(algorithm='eig', fit_intercept=True, normalize=False, handle=, verbose=4, output_type='input')"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%time\n",
+ "\n",
+ "linear_regression_gpu.fit(df[['x']], df['y'])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We can use this model to predict values for new data points, a step often called \"inference\" or \"scoring\". All model fitting and predicting steps are GPU accelerated."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# create new data and perform inference\n",
+ "new_data_df = cudf.DataFrame({'inputs': inputs})\n",
+ "outputs_gpu = linear_regression_gpu.predict(new_data_df[['inputs']])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Lastly, we can overlay our predicted relationship using our GPU accelerated Linear Regression model (green line) over our empirical data points (light blue circles), the true relationship (blue line), and the predicted relationship from a model built on the CPU (red line). We see that our GPU accelerated model's estimate of the true relationship (green line) is identical to the CPU based model's estimate of the true relationship (red line)!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "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\n",
+ "text/plain": [
+ "
"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "plt.scatter(x, y_noisy, label='empirical data points')\n",
+ "plt.plot(x, y, color='black', label='true relationship')\n",
+ "plt.plot(inputs, outputs, color='red', label='predicted relationship (cpu)')\n",
+ "plt.plot(inputs, outputs_gpu.to_array(), color='green', label='predicted relationship (gpu)')\n",
+ "plt.legend()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "## Dask\n",
+ "\n",
+ "Dask is a library the allows facillitates distributed computing. Written in Python, it allows one to compose complex workflows using basic Python primitives like integers or strings as well as large data structures like those found in NumPy, Pandas, and cuDF. In the following examples and notebooks, we'll show how to use Dask with cuDF to accelerate common ETL tasks and train machine learning models like Linear Regression and XGBoost.\n",
+ "\n",
+ "To learn more about Dask, check out the documentation here: http://docs.dask.org/en/latest/\n",
+ "\n",
+ "#### Client/Workers\n",
+ "\n",
+ "Dask operates by creating a cluster composed of a \"client\" and multiple \"workers\". The client is responsible for scheduling work; the workers are responsible for actually executing that work. \n",
+ "\n",
+ "Typically, we set the number of workers to be equal to the number of computing resources we have available to us. For CPU based workflows, this might be the number of cores or threads on that particlular machine. For example, we might set `n_workers = 8` if we have 8 CPU cores or threads on our machine that can each operate in parallel. This allows us to take advantage of all of our computing resources and enjoy the most benefits from parallelization.\n",
+ "\n",
+ "To get started, we'll create a local cluster of workers and client to interact with that cluster."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Dask Version: 2.30.0\n"
+ ]
+ }
+ ],
+ "source": [
+ "import dask; print('Dask Version:', dask.__version__)\n",
+ "from dask.distributed import Client, LocalCluster\n",
+ "\n",
+ "\n",
+ "# create a local cluster with 4 workers\n",
+ "n_workers = 1\n",
+ "cluster = LocalCluster(n_workers=n_workers)\n",
+ "client = Client(cluster)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's inspect the `client` object to view our current Dask status. We should see the IP Address for our Scheduler as well as the the number of workers in our Cluster. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# show current Dask status\n",
+ "client"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You can also see the status and more information at the Dashboard, found at `http:///status`. You can ignore this for now, we'll dive into this in subsequent tutorials.\n",
+ "\n",
+ "With our client and cluster of workers setup, it's time to execute our first distributed program. We'll define a function called `sleep_1` that sleeps for 1 second and returns the string \"Success!\". Executed in serial four times, this function should take around 4 seconds to execute."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import time\n",
+ "\n",
+ "\n",
+ "def sleep_1():\n",
+ " time.sleep(1)\n",
+ " return 'Success!'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 7.64 ms, sys: 7.32 ms, total: 15 ms\n",
+ "Wall time: 1 s\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%time\n",
+ "\n",
+ "for _ in range(n_workers):\n",
+ " sleep_1()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "As expected, our workflow takes about 4 seconds to run. Now let's execute this same workflow in distributed fashion using Dask."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from dask.delayed import delayed"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "['Success!']\n",
+ "CPU times: user 9.37 ms, sys: 11.2 ms, total: 20.6 ms\n",
+ "Wall time: 1.01 s\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%time\n",
+ "\n",
+ "# define delayed execution graph\n",
+ "sleep_operations = [delayed(sleep_1)() for _ in range(n_workers)]\n",
+ "\n",
+ "# use client to perform computations using execution graph\n",
+ "sleep_futures = client.compute(sleep_operations, optimize_graph=False, fifo_timeout=\"0ms\")\n",
+ "\n",
+ "# collect and print results\n",
+ "sleep_results = client.gather(sleep_futures)\n",
+ "print(sleep_results)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Using Dask, we see that this whole workflow takes a little over a second - each worker is truly executing in parallel!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "## Dask cuDF\n",
+ "\n",
+ "In the previous example, we saw how we can use Dask with very basic objects to compose a graph that can be executed in a distributed fashion. However, we aren't limited to basic data types though. \n",
+ "\n",
+ "We can use Dask with objects such as Pandas DataFrames, NumPy arrays, and cuDF DataFrames to compose more complex workflows. With larger amounts of data and embarrasingly parallel algorithms, Dask allows us to scale ETL and Machine Learning workflows to Gigabytes or Terabytes of data. In the below example, we show how we can process 100 million rows by combining cuDF with Dask.\n",
+ "\n",
+ "Before we start working with cuDF DataFrames with Dask, we need to setup a Local CUDA Cluster and Client to work with our GPUs. This is very similar to how we setup a Local Cluster and Client in vanilla Dask."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import dask; print('Dask Version:', dask.__version__)\n",
+ "from dask.distributed import Client\n",
+ "# import dask_cuda; print('Dask CUDA Version:', dask_cuda.__version__)\n",
+ "from dask_cuda import LocalCUDACluster\n",
+ "\n",
+ "\n",
+ "# create a local CUDA cluster\n",
+ "cluster = LocalCUDACluster()\n",
+ "client = Client(cluster)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's inspect our `client` object:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "plt.scatter(x, y_noisy, label='empirical data points')\n",
+ "plt.plot(x, y, color='black', label='true relationship')\n",
+ "plt.plot(inputs, outputs, color='red', label='predicted relationship (cpu)')\n",
+ "plt.legend()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The mathematical operations underlying many machine learning algorithms are often matrix multiplications. These types of operations are highly parallelizable and can be greatly accelerated using a GPU. cuML makes it easy to build machine learning models in an accelerated fashion while still using an interface nearly identical to Scikit-Learn. The below shows how to accomplish the same Linear Regression model but on a GPU.\n",
+ "\n",
+ "First, let's convert our data from a NumPy representation to a cuDF representation."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "cuDF Version: 0.17.0a+382.gbd321d1e93\n",
+ " x y\n",
+ "0 0.179561 3.154744\n",
+ "1 -0.714866 0.043070\n",
+ "2 -1.555288 -5.391598\n",
+ "3 -0.554378 -4.569417\n",
+ "4 -0.280322 1.784538\n"
+ ]
+ }
+ ],
+ "source": [
+ "import cudf; print('cuDF Version:', cudf.__version__)\n",
+ "\n",
+ "\n",
+ "# create a cuDF DataFrame\n",
+ "df = cudf.DataFrame({'x': x, 'y': y_noisy})\n",
+ "print(df.head())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, we'll load the GPU accelerated `LinearRegression` class from cuML, instantiate it, and fit it to our data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "cuML Version: 0.17.0a+173.g2c0aacf44\n"
+ ]
+ }
+ ],
+ "source": [
+ "import cuml; print('cuML Version:', cuml.__version__)\n",
+ "from cuml.linear_model import LinearRegression as LinearRegression_GPU\n",
+ "\n",
+ "\n",
+ "# instantiate and fit model\n",
+ "linear_regression_gpu = LinearRegression_GPU()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 372 ms, sys: 140 ms, total: 511 ms\n",
+ "Wall time: 508 ms\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/opt/conda/envs/rapids/lib/python3.7/site-packages/cuml/internals/api_decorators.py:410: UserWarning: Changing solver from 'eig' to 'svd' as eig solver does not support training data with 1 column currently.\n",
+ " return func(*args, **kwargs)\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "LinearRegression(algorithm='eig', fit_intercept=True, normalize=False, handle=, verbose=4, output_type='input')"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%time\n",
+ "\n",
+ "linear_regression_gpu.fit(df['x'], df['y'])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We can use this model to predict values for new data points, a step often called \"inference\" or \"scoring\". All model fitting and predicting steps are GPU accelerated."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# create new data and perform inference\n",
+ "new_data_df = cudf.DataFrame({'inputs': inputs})\n",
+ "outputs_gpu = linear_regression_gpu.predict(new_data_df[['inputs']])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Lastly, we can overlay our predicted relationship using our GPU accelerated Linear Regression model (green line) over our empirical data points (light blue circles), the true relationship (blue line), and the predicted relationship from a model built on the CPU (red line). We see that our GPU accelerated model's estimate of the true relationship (green line) is identical to the CPU based model's estimate of the true relationship (red line)!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXkAAAD4CAYAAAAJmJb0AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Il7ecAAAACXBIWXMAAAsTAAALEwEAmpwYAABPG0lEQVR4nO2dd3iTVfvHPydpuijQsqGsggoIhQJlFicylIIsRREFF4KgoojieBmKP3gFBRWUFwc4UFlSGQ5EQBFEoLQoyJBRgTIFWkZLmzbn90dJTNsnyZM2adr0fK7LS5o8ec5J03yf89znvr+3kFKiUCgUCv/E4OsJKBQKhcJ7KJFXKBQKP0aJvEKhUPgxSuQVCoXCj1Eir1AoFH5MgK8nYE+1atVkw4YNfT0NhUKhKFMkJib+I6WsrvVcqRL5hg0bsn37dl9PQ6FQKMoUQoi/HT2nwjUKhULhxyiRVygUCj9GibxCoVD4MUrkFQqFwo9RIq9QKBR+jBJ5hUKh8GOUyCsUCoUfo0ReoVAofInFAh98ACtWeOX0SuQVCoXCV+zdC7fcwq+TH2X7V7O9MoQSeYVCoShpsrPh1VdJa9+Sx6tuIe4RwX9u844clypbA4VCofB7Nm1CDn+UpezhyaeCOW3K5sn2T/LqLa96ZTgl8gqFQlESpKfD+PGkfDmXUQOC+aYetK7VjJW95xFbJ9ZrwyqRVygUCm8iJSxfTs4To5gVdYqJTwUgAo28ecubPNHhCQIM3pVhJfIKhULhLY4dg9Gj2br9a4YPCmZnZUnv625n9h2zqV+5folMQYm8QqHwCAlJqUz/fh/H0zKpEx7CuB5N6Ns60tfT8g25ufDee1yY9AIvd8pk9qOCWmERLL39Hfo3648QosSm4hGRF0J8BMQDp6WULa4+VgVYBDQEUoC7pZTnPTGeQqEoXSQkpfLCV3+Qac4FIDUtkxe++gOg/An9H38ghz/K8vTfeGJ4ECeCLIxqN4opt06hcnDlEp+Op3J2FgA9Czw2HvhRSnkt8OPVnxUKhZ+RkJTK2MU7bQJvJdOcy/Tv9/loVj4gMxNeeomjN7em73VJDBgE1eo34deHf+WdO97xicCDh1byUsqfhRANCzx8J3Dz1X9/DGwAnvfEeAqFwvPoCbcUPOaWptVZlphKrpSa5zyellkSU/c969aRM2I4s6se5OXRAVgCjbx+8+uM6TgGk9Hk06l5MyZfU0p5AkBKeUIIUUPrICHEcGA4QP36JbMRoVAo8qMn3KJ1zGdbjjg9b53wEC/OuhRw9iyMG0fid/MZPjCQHVXh9mu68W6vd2kY3tDXswNKQcWrlHKelDJWShlbvbpmH1qFQuFlpn+/z2W4ResYZ4SYjIzr0cRjcyxVSAmff86llk15+sQC2g8XHK8fwaKBi1g9eHWpEXjw7kr+lBCi9tVVfG3gtBfHUigUblAw7JLqIKxiH25xN/QyoG1kiW26lmhmz+HDMHIkK1K+Z/T9gRwNkYxoO4Kpt00lPDjcO2MWA2+u5FcAQ6/+eyjwtRfHUigUOrGGXVLTMpHgUOABKof8G092N/SyLDGVhKTUok5TN1rv54Wv/vD82Dk58MYbpHa8ngFV13LnvVC5/nVsemgT78W/VyoFHjwk8kKIL4BfgSZCiGNCiIeBaUA3IcRfQLerPysUCh/jTtjFPp17XI8mhJiMuscpqewaPaGmYpOYSG77dryz+FmaPZbDN80CmNp1Kjse20Hnep09N44X8FR2zb0OnurqifMrFArP4U7YJS3DbPu3Nfwx/ft9pKZlIgDtnJqijVVUHI3hkbEvX4YJE0j+YibD+wWwrQZ0a3QL7/V6j8ZVGhf//CWAzzdeFQpFyeJO2EUCcdPW2UIffVtHsmn8raRM68V9HetjdFG5WRLZNY7GKPbY337L5VbNGPfHm8QOh78bhLOw/0K+H/J9mRF4UCKvUJQ73A27aMW4E5JSnebHQ/7smoSkVOKmrSNq/Op8Fw1PoPV+ipXZc+oUDB7MN0/dQfP+J5gRBw+2eZg9T+xlcPTgErUk8ATKu0ahKEXYZ4mEh5qQEtIzzR7NGLEPu9gXNa3+/QTn7cIz9lhj3Pav1YrrG4XAImW++Xrb8kDr/RTpdyUlzJ/PiQnP8FSXiyy5D5pVvYafe8/jhgY3FHuevkJIJ1fikiY2NlZu377d19NQKHxCQTEsSIjJyNT+0W6Ll7vphVHjV2vG2gVweFov3cdYiZu2TjODJzI8hE3jb9X/RrzJ/v1YHhvO/y79xPieRrICjbx80394Lu45Ao2Bvp6dS4QQiVJKTVN6tZJXKEoJrrJeCq6m9VCUVbSjvHn7GHeIyUCG2VLomBBT4QiwVzdGKWaOfHY2TJ/OH3MnM/wOC1vqwK0Nb2Ju/FyurXqtR+bna1RMXqEoJegRPXeF0VF64djFOx3Gx/XEuDNzCgs8QIbZUuh8XtsYpZg58r/+Skb71ozf8DJtHs7hQKPKfNL3E9Y+sNZvBB6UyCsUpQY9oqfnGPtNTkeFTrlSOhTFvq0jmdo/msjwEAR5YZWCYSJnUd6C5/P4xqgdRcqRv3ABRo/m+wc606LrPv7bBe5vPYw9T+zj/lb3l7mNVVeocI1C4QE8UVY/rkcTxi3diTnXsYK6EkZXcX0ttMJAfVs7tiRwtUoueD5XG6PF+d25HQpKSODUuJE83eokXwyB6yKiWN/nfW5ueLOu8coiSuQVimLi0ewRJyvkiFCTy/O5ayJmRW8YyPpe3T2fo4tGcX93evYP8k6ciuWJ0Xz4dwLP3W0kI8jExBtf5IUuLxAUEORynLKMEnmFopg4Cxk4Ww0XXL1O/34fZou2ypsMgom9m7ucS1E3Mw1CEDV+tcuVtN6LiFZYydF7dvd3Z8+4Hk0K3bnkCwVZLPC///Hn6+N4rGsGv7SCm+rHMbf3/2haranL8/sDSuQVimLibsjA0erVqXjqDBM7c5R0hrWoKTUtkzGLkhmzKJlIDcHXcxExGQRpGdk0HL8agPAQE/GtarMsMVX3e9Z7sXIWCvr+zTcxPT+WdTfC6w8IKgZX5qOebzIsZpjfxd2doUReoSgmukMGV3G0ejUK4bCC1JwrbZuJzuLXWivboqIVOgkPNWkWTFmLoCqHmLiYlcPl7H/HT8s0azYXyTTnIoT2Jq47mTcFQ0Hpp07xqhDENoJHRsLhqnB/yyG80f0Nqlcofz0rVHaNQlFM3M0ecbRKzZXSqd2AVXSdpQsWzIwpLtZ0y4SkPNvgS1dyNI+rFBLAzEExVAgKINdByEkLKfNW/vYUJ/Pm44ce4s9GtdjfD+54ACx167L2/rV80u+TcinwoCpeFQqP4E6GiLMK0HE9mjBmUbJbYzurHH054Q+XLfr0EGIyEhRgIC1T2/bAekxR7iAiQk2EBgYUKzPpcGIia2NjMbaGsd3gYrDghZte4sUbXiTE5OctCFEVrwqFV3E3BdDlZqGbpKZlOtw0ndI3GoAvfjvq1EzMFZnmXJcC7irk5Ii0DDNJE7oXaV7SYuHNjh3pcHgbnw6DjQ2hfY1Y5g/8mOurX1+kc/obSuQVimJQ1BTAYJPB9hrrxuTklbsdGoQBTgXUPnxjxf7C88bdrejbOpKYyWucrsaLS66UmIzCaa5/QYpa+frb4sUcv28QF7vArSMgMCCYefFv83CbhzEIFYm2osI1CkUxcNd8S6tYyWQUIHGYPgnoatBhJSLUxBWzJf8YBkFYcIDTi4grDAJchdutISdXFywrRTFdy7p8mWl169IuPI2n4uFANbin2SBm3fEWNcNq6j6PP6HCNQqFl3A3fVIrs0bPqtedpZiWuJot0i2B17qouBJ4a8jJPttF66JmPbdWiqYrvp40iQqvTyalO0xqDXVMNfju7k/ocU0P3ecobyiRVyiKgbvpkyXRDs8ThAYaMRkNpGeaMTgIE4WHmKgQ5HzD1FNe7/8cOcKHDRpQoxU8NBrSQgTPd36OCTdPINQUWvQ3Wg5QIq8oF3jCW0YLdzdRi1qsVNJczs4lxAQzB8XwtINsn/RMM8kTXW+YOvPB0cO8gQNpvH4Zax6AdY2gTURLFgz6jOia0UU+Z3lCibzC7ymuP4qzC4S7K1Wti4KemLwvsNoLOLtb8dbFE2DfL7+w6eYbOBUHTzwORkMg7/V6i+Fth6uNVTdQIq/we4rjj+Lp1nWOLgqA2/nxJcHxtExmDorRrKK1WiDY/2z/uynqBcCSm8vrLVrQImMv00fA3uoQltWBpqFjqWXsrATeTZTIK/ye4nQmcnWB0LoIPL0ome1/n7PlqBekYPjCKoalkTrhIba56smYsfdyt7dNTk3LZNzSnYDzi+PPH33E2dEPc/A2eKEtBF0Jo0bWWEIs7TiTjkd7w5YX1CVR4fcUpzORqwuE1kVAAgu3HNHVnejlhD94elFyqYzTC/71r+/bOtJpoxB7jqdlMnnl7kJZQ+ZcyeSVuzVfk5GezsTgIA7Oepjho+Gj1lCHgdSQCwixtLMd57IhiKIQSuQVfo+Wt4zJKLicleOwBZ4VVxcIRxcBCYxdvJOXE/6wdWkqOE5CUioLtxxxKz2yJJHApBW7bXPWW0RVJzzE4Yr/fIa50O968dixrI8K59e7s3loANSscR2JI5MIzByGgeBC5ygrGUqlBRWuUfg9BePg4aEmLl3JsYmWs0rRW5pWz2eRC/mzZ5xly+RKmc83pmDMevr3+0qNwDsqtkrLNDNuyU63zuPqrsT6O+hQ4TJfXN+EK53glccBYeSd22cxMnYkRoOROuHn3EpPVWijKl4V5Q5HVarhISayciyFBH1A20jW7z3D8bRMKoeYECLPb8V6EXB3NW6thrX6rfsKA/DmoBgg78LmTJwjw0PIyM4pVsWsPTd8NYn+V7YzIR521YQ76nZj3l3ziayUf69CKz3V3QrZ8oCqeFUo7HB0u68Vjsg057J+7xk2jb9Vc5N1WWIqnRtXYfPBc7qF3jp+Ucy8PIkFeOGr3wHh0nzMmmXjqgetKyofSuTplRP5rSv0bwdVLWF8fc9C+jTpU+hYTxVSlXeUyCvKHe4WJDnbZM0055JyNpP7OtbXbekbbMrbCvOlwFvJNFt0HWefZfPM4mSXFgeFyDVzx/8eIbb+WcaPhtOhUMfSmxnxM+jT5DqHLytuIZVCbbwqyiGOmnxEhJo0j3e1yWpd0evlylVhjSxDseVxPZqQkJTK5JW73RL4iFATDZNW8d8P+pHS5ywP3wUXqU7N7FmYsh7jhWV/0fqVNboykRRFQ63kFT7Dm9WSRalSBZxaFDi6AzAK1+EOe6wa6clWfSXBuCU73arKrZB9kfjp9xLSCR4cBWYpCM96iErGPgj570X2fIZZ5b97ESXyCp/g6UpSd8/tLAzg6OLgyKemqCJtPe+kFbu96vFeXIxCMGnFbrcEPvr72dx35jumDoffa0HFi82oYnqOAKndgk9vBbLCfVS4RuETnFWS+uLcCUmpxE1bx5hFyZxMv6K5iWrfPxXcX8HbY82Z79s6kuSJ3RnSsb5HerJ6g1wpdV+EQk8fZsyseBrU+Y7Bj8BflQKpnvUiEQGvOxR4Kyr/3TuolbzCJxTHasDT5y648rduiGpZFFhXmsUNs1h9X8YsSsZwNUnd99uw2kTq2KiWFgtdFo7jlsr7mDgKTlSEh5sNZU1SL4RFnxWwyn/3Dmolr/AJxbEa8PS5tVb+VgpaFCQkpTJ28U6PxtEtMi+dsTRgNOS/nzAZBBnZOU5fU2Xfr0yc14eLN+3j0UFwgXBaBM7h/UELqBteVde4enrcWu+2XFUpK/KjRF7hExxluBS1mXVxzu1ylUqeQ2TD8asZsyi5VKQ+eotci6RCoBFBXnGYBe1OUwDCfIU737mXu069xpOj4NsoqJL5ADWDP+GV2+8EtD8L2+uv/j8yPMRlgZP1bis1LTNfP1sl9K5R4RqFT/BWoYs1qybTnGsrNnLWZk6JRGEysnO5r2N9Pv/tiMN0yQabF/HQwU95635Iqg2V0xsRHvQS1UPrMrF3c81MptS0TF2fiRbFsYsu7yhbA4XfUJR+oo4sDhTamC6eZcgHQzl7K8xpD0GZRioYxxIqb8CA4PC0Xl4ZN2r8as09CwFeG7MsoWwNFKUevTnzzo5zZPsL+TdRYxtUsZ2j9CxxSj+tVvyX2y0beW0UpFaC8AtxhAU+iUFWALy7cepuL13Fv3hd5IUQKcBFIBfIcXS1UZRf9ObMuzpOT2z9sy1HWLT1aKlrtVeaCUvdw8MrxvHz7TCyGVRMq0DN7EkEBTazHeOp/RRHuNtLV/EvJbXxeouUMkYJvEILvXntro4zCn2Z5krg9SEtFm6a/ziD/xrHhFGw+hqoeuluIoIWEmRplu/YrJxcxixK9lrWi32NgkDfZq0iDxWuUfgcvXntro7z56yXkqbGrnUMS3yT+XfCJ5EQfrYOlSq8gslYS/N463XTk5XLBXHXrMybthlliZJYyUtgjRAiUQgxvOCTQojhQojtQojtZ86cKYHpKEobevPaXR1XHMOvyPCQMmUY5i0MVy7T7+3+dDS/yRPDYXe4oNqVZ6gU+j9MUlvgC5Jp9u6qXg8q5fJfSkLk46SUbYDbgVFCiBvtn5RSzpNSxkopY6tXd172rPBP9Oa1uzrOWU62M6zdjMp7ls01G+Yz5ttBfPVYNu90hojzbahq+IIK8lZEEUwXfCms3rTNKGt4PVwjpTx+9f+nhRDLgfbAz94eV1F20Jsz7+o4reddCbejtnfliaBzJ7j3y0fZ1ROeuh0qnQ+iZtYrBIc2L/a5XeWyeyuk4k3bjLKGV/PkhRAVAIOU8uLVf/8AvCKl/E7reJUnr/D0l95RHrw1NFPeV+9tl06kdZVEpneFjAAIv9Sb0KCHEGh76xcFR7ns3mzv5+xz3zT+1mKduzTiyzz5msBykZf1EAB87kjgFQqtFMlxS3YyeeVu0jLMhIeakBLSM826LwBaqXcmo+ByVk6ptvf1NpVTkrlvw8t81Ru+qgtVzlSlRsWpmILqeHwsdzyDPFXFqlIu/8WrIi+lPAS08uYYCv9B60tvtkibd4q9h4reLI6CIZzwUBOXrpRjgc8103X+Y4S1Ps2Lj0FQJlS/9DghYbcjpOfNjp0JqzdDKqo/7L+oFEqFzygYmnE3dKJ31Wefehc3bZ1Dwy1/p86OVfQ9PJf3hkBKBFQ/1ZSgShMwUsmj4xiFwCJlvo5bcdPWFRJbb1exqv6weSiRV/gErdBMUTZBC4qE/YVDK7xTHjfejBkX6DN/MKk94fkhUPmckRqZrxBSyf2bbAPObZELxtSdVSmrkErJoERe4RMc+cwUReitHZYKCkrB8M64JTsJDTRyObts9FT1BE3WzKaj4TveGg2XA6DGma4Eh40u8saq0SioFBhgu3De0rQ66/eecRgScRZ3t26AqpCKd1Eir/AJjlbUVsfI42mZVA4xcTk7B3Ouc9mfvHI3fVtHOm3+AXnxfXM5EfiQMykMXDWadfHwSn2ofrIiFcKnYwqrW6zzmnMlFYICSJ7YXfN5a2MPVyE46+evQireR4m8wic4EoDwkH9XmBWCAohvVZv1e884jddbV+zlPR0S8vxm2i9+jtqN9zLtMQjMgprnHySocn+PbaxaBTohKTVfE/IKgUaycyw2byBnITjlHllyqM5QCp+gVZ1qMgguZ+fkK0VflpjKuB5NdFkO6DUo81eq7N/Cg8v6sOPOvbxzI1Q70YAqYiHBwQOKVLHqiDrhISQkpTJuyc58WUqXs3MLmb9ZQ3D2qLh7yaKahihKBK0iJ8gfj83IztHMfLE2/BizKLmEZ102EOYsbvvkQS7ceIHFLSHirCAo6D8EGdt7fCzrxqq105NerCE4+7i7MhDzHKppiKJEcPSldZRhMbV/dL7qw6jxqzXPezwtk76tI3lu6U6yXcTnyxv1tizmhgufMPdhuBgItVM7YKryPIJAj48lgAFt82LoT7txwdWqMtXbQ0BRfJTIKzyCsy+t3spGV3nToYEBZJfXIqYCmC6do9eiB0jsDVMbQM3jQVQPf5PAKg28Nqa16cr6vWeoHGLSVVBmDc0UXABkZOeonq0lhBJ5hUdwJuR6Kxtd5U2nK4EHoPnq/xJVfSPvjoTAbKhz6i4CIu5HlNAWW2paJiaj0MyZNxoEFYMC8tUmAIUWAI4oj3UM3kaJvMIjOBNyvZWNrkrRi1IV60+EHd/HHb+MJSEevqkKkSk1EDXfxFgpvMTnYs6VRFwtNrOu6CNCTUzs3VyzWbqz1FZ7VNaN51Eir/AIzoTcncpGZ3nTWucpD0iLhc5fjka2PcL0oVDlHNQ5P46Amjf5dF7nM8y2VnzONk31XphV1o13UCKv8AjOhLy4ZlH28dzKISYEkgyzs+J6/6H67vXceOwNPr4X0oKgXko01JyEgSBfTw0gX9clKLxpmpCU6jBXPjzERIWgAJVd42VUCqXCY3gjJU7Lc7w8YMjO5NYvhvBXjyx+joLax4yI8BmYjNf6emoO0cqiceTrLoCZg2KKdKFXF4TCqBRKRYngTom63i+tK6sCf6Thz/NpGriM+Y+AKQfqHb0dUW2k1zdWDeLfhtxFQWtfxpl9hTt/KyrdsuioildFieNOk+XylG0RdP4kd3wZz6k2y/jfLVDr70qEWz7GUG1UiWTOWCRF6pFrRWvT1NFGqjtN01W/1uKhRF5R4uj50lqNrkpPMNG7RK+cQMzJR3jvQTgTAPVOjUTU+ZwAUbXE5hAeYmJq/2hdx+q1KtDbpN0Zql9r8VDhGkWJ4+pLm5CUyrilO126T/oDFY/s5MbdL7GkF5wLgaj9DcmpNwNDcHCJzyUt08z2v88RqSNV1d4t1Fm4zRMdmrzdXMTfUSKvKHFcfWknr9zt9wIvLbl0WvwIJ288w7v9IfIY1M7+Pyz1Wvr09vqzLUcINLo2M3OnIXZx7YRVc5HiocI1ihJH6xYe4HJWDglJqX7fnq/GztXctuVOEu4/w/ZIiDoQh7Hq1xiDW/p6agAu/YFKWmD7to5kav9oIsNDbHn59t2nFM5RKZQKn5CQlMrklbsLCXqIyei32TTGzIt0+fo+tvWysKcGNNofRFbkuwQYavp6am4xpGN9pvTVF7tXlAwqhVJRqrCmT2qt2P1V4BtteJuq1dfwyYNQLQ2ijtxHbr17y+QXcP3eM76egsINyuLfmKIU4yr/vbwVNwWf+ZtOW0fxbU84EwrX7apBZuM5WKqX3U1DldVStlAir/AYeopW9BY3aTkcljVarHyWS232smAg1EuFuhkvkdW4U5nfCKscUrQm4ArfoERe4TEc5b+PWZTM5JW7mdi7ue5VoLVtXOnZMdJP+MFfiT7xGsv6gbDAdbtbcqXRqwiKXmhUmijnXRbLHErkFUVCKyzjTMDPZ5gZt3Sn7mYTZVHcyTXTLmEoe7pd4LMWcO1ewZVas8lq1MCDHVa9S3iIiUl98uyCGzro1JWWYVZeMmUIJfIKt3EUlnEl4OZciRB5DbsLNnwu69Ta/iV1gj9j2X1Q9SJcsz+e7AYjylxopkJQgE2sHRVFhYealJdMGUKJvMJtHIVlgk0GlymQ/pYDH3D5HLHrHuDn22FbBWi2syIXrp2HuV5FX0+tSNjfjTkqQpKycBZUpjmXySt3q9V9KaSsLTQUpQBHJe95TST8a4XujHpLRlLv3AMsuhsCL0HU0Se43OQLjIayKfCQ3yrAURGSozaM5zPMukznFCWLWskr3MYoBLkOiujKQzOP4JREWh6ZyOp78vYOrt9Wl0st5pBTxjdWtSpZtSwJpn+/T1e3J9WYu3SgVvIKt3Ek8P6OtFi49tN4TOETWdID6h+G6mdf43KLuWU+c8Yg0G0V4MiWQguVU+97lMgr3CYitPzlSVdK/JJWu/rw46NwMgyabGpORv2ViPBWvp6ax5i0YjdR41cTN22d0zCLVhgn3EHuvHKK9D0qXKNwi4SkVC5dyfH1NEoMQ/YVWvwwmMTu2eyqCNHb4Px1C7jSplqZSYvUg0Viy4zSky1TMIyjVcmsnCJLB0rkFTa0cp8hvxf45awcv0t/dETtrf8jqOZKVg6Ehieh0aE+pEcPLxe3v+7G0z3hG6/wDsqFUgFor8RMBgECv/d2L4gp/QTXJz3KulshxwDNEqvxT/Q8DIZAX0/NI+itJBbA4Wm9vDwbhSdQLpQKh1hX71rZEuVlxW5P1IYX+KfVH6y4HZr/BVdCJnCuVfsyv3q3ZkRZ/69H6FU83T8o63+7imJg31C7vBN6NJFmyfH83OMPTlaCmN+iuVh3JTlV2/t6asUmxGSkY6MIBP9mRlm9gSBvI91kEIVeo+Lp/oES+XKMXkfIgkSEmhxmU5Q1pCWX69YO5ULNiXzXCVrvgIpZcznfcirCT7ZWB7SNZPPBc4VW7tY+rUkTujP9rlaq85KfosI15Zii5DALoFfL2kzpG01CUiovLf+Dy9ll0xs+Ys9XhAV/xA+9Ieo0tEjqzT/NH/P1tDzO6t9POAzNWP8GituHVVF68brICyF6Am8BRuADKeU0b4+pKIxW5oyjhtrOkMCyxFRiG1QB8lLv7BFAaKCxVAu/uHKBa7cMYcutFswG6LAxlBOtF3Cxaaivp+YVnPkFhYeaiJu2TmXE+DFeza4RQhiB/UA34BiwDbhXSvmn1vEqu8Y7OMphHtA2kmWJqfke15t5YQ3XaLlOhoeYSm2qZa1tM7jcaAO76kLLg5Cd+ySZ9br7elo+o6AjaIjJqEI1ZRBn2TXejsm3Bw5IKQ9JKbOBL4E7vTymogCOXCPX7z1TqHJRryynZZod2gqnZ5oJCy5dkUDTuYM03hHP9i4bOBEBHX6qT1qdleVa4KFwBpU1P17hP3j7mxgJHLX7+RjQwf4AIcRwYDhA/fr1vTyd8omj2PvxtMxCsdi4aeuKnW1TlDCQN6n/y5McbneIdXHQKRHO1JrByfZN/WRb1fMovxn/wtsrea3vUb6lg5RynpQyVkoZW716dS9Pp3ziKN9Z6/FxPZpgMhZP/m5pWjo+x9AjG6hzMJ6N3Q5hMkPs5jiOX78Kc5Wmvp5aieLo0zQ66OOn8uP9C2+L/DGgnt3PdYHjXh5TUQAt10BnedAVAot3g/f5liPFen1xkblZNPhlEKkNZrCjKdywwYAM+JQzrV/w6bx8QWR4CPd1rK/5+d/boZ5bfxeKsom3wzXbgGuFEFFAKnAPMNjLYyoKoNdXRGuDtij40lE+fPd8cmsu4+du0PoQ5KYP4UiHe3w4I99QcAM1tkEVzc/f0eMK/8Hr3jVCiDuAWeSlUH4kpXzN0bEqu8a3eCIe7ysMl08TuechtnSGSlkQ/UsEh+LmYzCUrg3gkkAA93Wsz5S+0b6eiqKE8Kl3jZTyG+Abb4+jKD5ldcOtRuIETl6/g19ugBuS4EzF/5ByQ4dyW84tgfV7z/h6GopSQnn9Hig0KGsbboFnkqhxIJ5tXXYQYIEua5txpOkqMiM7uH6xn1NWL9gKz6NEXmGjrGy4WSw51Nr6MKer/4edzeC2DWDImcvRuOm+nlqpIbwcdu9SaFP+ApYKh/RtHcmkFbsdFjmVBkJTvkaEvs9vN0GbFDAeu4O/Ojzu62mVOqTUtrJQm6rlD7WSV+RjUp/mups0lyTSfIFaO/pz4Lr3OVEDuq8O5FzEEk63VQKvRVqm2WYjLfm3pZ+z3q0K/0SJvCIf1ibNpYlKe2ZiMQ/mt7hsbtgF9fc/zr5bv0IGlq09hJLEKISmlYWyLCh/KJFXFKJv60iH1ZAlieHiQarsj+ePNj8SIOC2lXU4cM0K0q67w9dTK9WYDMLWHKQgakO2/KFEXqGJI5EoCSSSiN+f5nzYU+xqAb1+huD0//LXbfMQhvL5JyuAIR3rF7IoMBkExgJdnRA4bOpS1jKoFMWnfH5jFC6J9JEYmE7/ROip3iR3+ItG/0Dnte3Z1W4VmbWa+2Q+pQVJXtXqzEEx+VxDw4IDyC3gJGnOlQiBsixQACVQ8eoOquLV99g39tbrLe8JLJYsqu5+iD/bpBNqhht/gN2dFpATVq2EZlD6iQwPYdP4W/M9FjV+teZnJICZg2JUdk05wacVr4qyQ0HvGmuzZ28LfXDKAi5XX0pye+j2B6RfHMTObvd7edSyh1Y83ZGtc53wENXSTwGocI3CDq3mIt4UeJl1kkr7+rCv2VKMRui9NIz9Ucs51VoJvBaOrKFVWEbhDLWSV9goqcwLiSRs32SOX7ud49HQfyMciHiB33vFlcj4ZRFHwq3XYVRRflEirwDyQjUG4Tj1zlOI9B0EZkzgzxhocxRqbL+WxJ4zvTpmWSfShXCrsIzCGUrkyxGOytytsXhvCrzETNjeURyMPk5QDgxaBckt32FPzyivjVnWiQg1kTShfPegVRQflV1TTtBqCGLdVDV6eQUfcDKBK2EfkFoduu+G7GNdOXzj014brywhgMohJi5n52DO/fczKNj0Q6FwhsquUTjdVPWWwOfmniPs8HD2Rl+hfhoM+sLAth4LyW1U0SvjlTXCQ0wkT8xbqb+c8Adf/HaUXCkxCsGAtp4JwSiTMoUS+XJCSZazSySBR2ZxIfJHTjSHQZvgkPFRtvS9s8TmUBa4nJ1jMwxblphqu9jmSsnCLUf4bMsRl/F4ZxS8e7OalAFK6MsRSuTLCeGhJs5neN9CWGbuJ+DcMxxoAq1TIerravza90OEofQ5W/oac660GYY5ussqjjBr3b1ZTcqUyJcflMiXIfTcejs6xttbL5Icgg89x9/X7SewAjzwDexoPIXE/jGF/FYU/6LnDquowuzo3MqkrHyhRL6MoOfW29kx6d5sBJK2jhzDm+xvDj32gNgTw0+3T/HeeH6EtcDJVQP1ogizs2pYRflBVbyWEZzdeus5xhtf7FzLBUyH7+dIrTcxBMOwL+Bw5ffZowReF9YCp1uaVnd5bFE+P1UNqwAl8mUGPbfejo5JTct0+IWPKEIvUInEcOpDLlsGc6jpee7dAlFb+7G+7yqyImq7fb7ygslAPgdJa4rk+r1nnL6uqMJsbQCjNaai/KDCNWUEPbfejo6xxsSn9o8uFK9/elGyW/PIyTmK6fQTHG6cQ8xxiF4SxC8DPsOiujS5RgjNfRRnoZriZNeAqoZVqJV8mUHPrfe4Hk00NzklMHbxTgA2jb+Vw9N6sWn8rfRtHak7DCDJwZA6kX+CR3KmXg7DvwPj8af5+d5lSuB1Ys6VjF28s1CfVUdduIxC2D4nhaKoKJEvI+i59e7bOtKha2SulJqNnLUuHvaYjAJj9jay0/ty+JpEbjoEPb+sz3c3rOCf5l098M78D2dfKq3PwVExmi+7cyn8BxWuKUPoufWOdBCyAe1UPHsXw4Kvs5BBzpEx/N34OLUFjFoE62NncPmupiot0glGo0BapMO01YKfg6PPzFfduRT+hVrJ+xmuVuapaZlEjV9N3LR1ttVk39aRbBp/q01UJBJL+hIumu/m78bHuW8rRK/twqo+q7hcp2mJvI+yjDlXUjnY5PRzsN8kV1kwCm+iVvJewleeIdYxnl6c7HAlKdHOsz+elolZnsJ46gmORmXQ8iR0XgRr+36CuWUVr8/dn0jPNDNzUAxjF+/UDLvY74UoT3iFN1EulF5Ay/GxpF0FYyavIU1HAZS1b2iOJYcmI3pwvPo6DMBDGyDZNISjHe/x+lx9QaUgA090iKBBuAnhheBTgEFQq3IwGdk5pGWYse+1bRB5NhOhgWqNpXCP4OBg6tati8mUP/VZuVCWMKXBM0RvhevxtEw2H9zIgBk3czLSQvf9UGddRVYNXoA0BXl5lr7jiQ4RtGlch4DQiggH2S1FxSAEkREhRIQGAnA+I5tT6VfIzrUQaDRQs3Kw7TmFQi9SSs6ePcuxY8eIitLfh0GJvBcoDZ4hjnLm7bGQgTz9Ejd88hc1KsC4xbC2yYvsG9q5hGbpOxqEm7wi8EA+gQeICA1Uoq4oNkIIqlatypkzzovnCqJE3guUhGeIq5j/uB5NCoWM7DFnfstl4xwu1If7t8PlPU1YNGA6wlA+9uIFQrfABxoNVAwO4NzlbJeNzQONBiXoCq9RlEVJ+fhGlzDezpawxvxT0zLzbaLa517b59Xbk8M/ZJ+5n+NV5lDvCoz9ELbVmE3S3W+WG4GHvCrgQKO+95udayE0KEDXF6xisFo3KUoX5edbXYJ42zNEj1lZQSS5ZF+YyznDMM7XPs+YtVBnSw+W3LeKjBoNy13hjQRqVg7WdWyg0cCp9CtYdPyOLl7J0XXOtLQ03n33XV3HepMFCxYwevRop8ds2LCBzZs3236eO3cun3zyicfmkJKSQosWLTSfmzBhAmvXrvXYWOURtezwEt70DNET87fP8MniL3LTn+NMLTO3HYCmq2HlfQvJqVDZ6TjhISbiW9VmWWKqw7BPWUWQFyvPyMrh7OVsh8cZhKBm5WCOnsvQdd7sXAvnM7JdhmysIv/4448Xei43Nxej0XNNVnJycggIKPpXfcOGDYSFhdG5c95ezYgRIzw1NZe88sorJTaWv6JEvgziKuafkJTK2MU7MctMzOemcLp2MtXC4D9L4Ydaw1k5vI/LMaz9RxOSUlm184TfiXyFoDwRjYwI5dWXn2dHUjJSShBXG5zLvPhnYICBAIMgIzsXd9KN27Zpzdw57zh8fvz48Rw8eJCYmBi6detGr169mDx5MrVr1yY5OZlvvvmG+Ph4du3aBcCMGTO4dOkSkyZN4uDBg4waNYozZ84QGhrK+++/T9Om+YvUJk2axPHjx0lJSaFatWq89dZbjBgxgiNHjgAwa9Ys4uLi8r1m5cqVTJkyhezsbKpWrcrChQvJzMxk7ty5GI1GPvvsM9555x1+/PFHwsLCePbZZ0lOTmbEiBFkZGTQuHFjPvroIyIiIrj55pvp0KED69evJy0tjQ8//JAbbriB3bt38+CDD5KdnY3FYmHZsmWYTCZyc3N59NFH2bx5M5GRkXz99deEhIQwbNgw4uPjGThwIA0bNmTQoEGsX78egM8//5xrrrlG92dSXlHhmjJAQlIqcdPW2SpVb2la3WHM37qCzzBvID1rICcjkxmSBHd8Up1Pei7nRFvXAg+Qlmm2nUtPvn1ZIq5xFcLtVtrBJiOhgUYqBAVQITCA0MAAKgQFEBpoJMCQF4cPDHDvq5KR7fyiOG3aNBo3bkxycjLTp08HYOvWrbz22mv8+eefTl87fPhw3nnnHRITE5kxY4bm3QBAYmIiX3/9NZ9//jlPPfUUTz/9NNu2bWPZsmU88sgjhY7v0qULW7ZsISkpiXvuuYfXX3+dhg0bMmLECJ5++mmSk5O54YYb8r3mgQce4L///S+///470dHRTJ482fZcTk4OW7duZdasWbbH586dy1NPPUVycjLbt2+nbt26APz111+MGjWK3bt3Ex4ezrJlyzTfU6VKldi6dSujR49mzJgxTn9PijzUSr6Uo9XtaVliKgPa5vmQ22fXADy1eB1Z557nVJ0TXH8a7v4Ivuo4hfQHY4A8Z0O98XdH1ZplnZSzmUC47edZs2a5fM35jGyOnctEusyvKTrt27d3mf986dIlNm/ezF133WV7LCsrS/PYPn36EBKSd3e3du3afBePCxcucPHixXzHHzt2jEGDBnHixAmys7NdziU9PZ20tDRuuukmAIYOHZpvXv379wegbdu2pKSkANCpUydee+01jh07Rv/+/bn22msBiIqKIiYmptDxBbn33ntt/3/66aedzk+RhxL5Uo6jTdb1e8+wafyttseW7TjCU58+z9ngL5E14Nkf4dDx1sy/91XbMSEmIwPaRuqOsfujwIN79Qr2hUxaCISm8OvN3LGnQoUKtn8HBARgsfw75pUrVwCwWCyEh4eTnJzs1vksFgu//vqrTfS1eOKJJ3jmmWfo06cPGzZsYNKkSW6/B3uCgvKK6YxGIzk5eRvSgwcPpkOHDqxevZoePXrwwQcf0KhRI9ux1uMzM7U/I/sMJ2/UOPgjXgvXCCEmCSFShRDJV/+7w1tj+TN6Nll/P/U7Iz5qRmr4l3Q+Ds+8Cyuavk/iwH8F3igEU/tHM6VvtGZqZXlCb73C+YxsUs9nOhR4uNolq4DYWDdrnVGxYsVCK2l7atasyenTpzl79ixZWVmsWrUKyAtXREVFsWTJkrzxpWTnzp0u30v37t2ZPXu27Weti0R6ejqRkXnJAh9//LHLuVauXJmIiAg2btwIwKeffmpb1Tvi0KFDNGrUiCeffJI+ffrw+++/u5y7PYsWLbL9v1OnTm69trzi7Zj8TCllzNX/vvHyWH6JI0GqEx5ChjmDB+cOpM2cVhCawatfAXv78/mjq8iq8m8bvhCTkTfubmXL9rG6TpaVdZAAQkye+1N1Vq9wPiObvScu8PuxNI6dy3SZNhloNBAZEWJbuVt/dpVdU7VqVeLi4mjRogXjxo0r9LzJZGLChAl06NCB+Pj4fBurCxcu5MMPP6RVq1Y0b96cr7/+2ulYAG+//Tbbt2+nZcuWXH/99cydO7fQMZMmTeKuu+7ihhtuoFq1arbHe/fuzfLly4mJibEJupWPP/6YcePG0bJlS5KTk5kwYYLTeSxatIgWLVoQExPD3r17eeCBB1zO3Z6srCw6dOjAW2+9xcyZM916bXnFawZlQohJwCUp5Qy9r/EXgzJP4sjsbECHk8xZ/whplXJ5YAeEbwhk5YOfYgmukO/1RiHyCbw9cdPWubQ+KC1EhJo4n+GZDeCUab3Ys2cPzZo1A1yHZBwhENSt4lrQFZ6hYcOGbN++Pd8FqDxi/7drxZlBmbdX8qOFEL8LIT4SQkR4eSy/pGBhVfXKmQRbnuW1xAepmZ3LjPnwaP8F3LLhEEEVK+V7rXUFD+TLzrFWxrryni8tCPCYwBdET0jGEUYDSuAVpZ5ibbwKIdYCtTSeegl4D3iVvOLCV4E3gIc0zjEcGA5Qv3794kzHb+nbOpI+MbV5e/0bvLDmOSwmeG49VDrXjGcO7cpnR1DQzwYolJ1T0Efe2hVKgBdzR/4l0Ci4u1093RvAnpxTRGh+i1a9laxa5Fj8c2O6tOIo40bhnGKJvJTyNj3HCSHeB1Y5OMc8YB7khWuKMx9/Zffp3fSbeSt/BZ/m5pPQYxUMWLmZawtsPGlV2cZNW+fU9tj6X0mGbsy5ktgGVYhtUEWz7aC3MBkFE3s3z/dYUVbwVoqSQaNQlDReS6EUQtSWUp64+mM/YJe3xvJXMs2ZPLt4FP/bN5/KFvhvAkQ0uZtH/1mk6/UJSakOBfR4WmY+J8uSvLpKYNKK3VQICvCqwA/pWL9QLUHBi2Cg0eBS6A1CICX5UiX1ZNAoFKUBb+bJvy6EiCHvO50CPObFsfyOHw6s4d55fThbIYshf0CDNfDw3iNUrVdP1+utG7aOCA81ObUi9jZpmWavVtJGhJqY0jc634XMauBmL/Q1KweTet55Fo1FSoQQeYVkFqkafyjKFF4TeSnl/d46tz9z+vJphs4bwHcXfuHaK/DKUqj30Kv0Xv6yy9faC5rBSWWrAK6Yc8k0O1/BhpiMbl0EwkNMXl+d6yHEZGRi7+aa1cLWC1+Tq4twq1Dbd27KlZLcAvF2KSVGg4E6VUI4lX6Fo+cyOJV+RYm9otSjgoqlBCklc7e8S4Mptfjx/C88/xPcs6gKj+zOoPfL+gTe3mPeWbWqBKcCb2+P7E7RVHqm2WsZO5HhIQzp6Hpj3t7W2VG18JhFyZxMv8L5jDz3yYjQQJrWrkTLuuE0rV2pkMBbyc615MvEsf5sPY8v2bBhA/Hx8QCsWLGCadOmOTy2qDbHkyZNYsYM3RnRRXp9QkJCPvsFT1sN2/+eCvLII4+49A0qyKxZszxquwwwe/Zs5s+f77HzKVuDUsCeM3sYOKcnf4ojdDkF966Cdu8tpd2AAbrPoSVoRcHa2NsevWGdOuEhHptHQc5fzmLRtqMOnxfAzEEx+UIxzuwLciyS1PN5zxdciTuK0wtEobCORUpOpV/x2mq+KLbDffr0oU8fx0Z0zmyOi0txbY0TEhKIj4/n+uuvB0rWaviDDz5w6/icnBw++ugjduzY4dF5PPTQQ8TFxfHggw965HxK5H3IlZwrvLzqeWbteJuK2TDjB5CBHRh5crPbXZo80T9Wq3uVVTQnrdjtNIZuMgrG9WjC04uSiz0PLTJchJbu61i/0Kaqqz63NoF+8TmwK/NvbJFkm3PzbUa7TC8NKvBViokBJ8ZnKSkp9OzZkw4dOpCUlMR1113HJ598QmhoKA0bNuShhx5izZo1jB49mipVqjBx4kSysrJo3Lgx8+fPJywsjO+++44xY8ZQrVo12rRpYzv3ggUL2L59O7Nnz+bUqVOMGDGCQ4cOAfDee+/x9ttv57M5nj59OtOnT2fx4sVkZWXRr18/m2vka6+9xieffEK9evWoXr06bdu2LfRehg0bRpUqVUhKSqJNmzY8/vjjLq2Q33//febNm0d2djbXXHMNn376KcnJyaxYsYKffvqJKVOmsGzZMl599VWb1fCPP/7Is88+S05ODu3ateO9994jKCiIhg0bMnToUFauXInZbGbJkiU0bdqUn376iaeeeirv8xOCn3/+GcgzeRs4cCC7du2ibdu2fPbZZwghuPnmm5kxYwaxsbGEhYXx2GOPsX79eiIiIvjyyy+pXr16vvewbt062rRpY7uoHThwgBEjRnDmzBmMRiNLlizh6NGjTJgwgapVq7Jv3z5uvPFG3n33XQwGA2FhYVy6dAmApUuXsmrVKhYsWGD7G9i6dSvt27d39lenCxWu8RHrD6+n4YTqvLHzbQbthpdmw8APk3j21y1FasPnyP7A6MTEKSLUpKt7Vd/WkVQoKGIFGNSuHn1bR3q0j61eTAZBbIMqhR7XEzrSWrGbDIJAk9HmSWMQ+X8uiKPHXbFv3z6GDx/O77//TqVKlfKFUIKDg/nll1+47bbbmDJlCmvXrmXHjh3Exsby5ptvcuXKFR599FFWrlzJxo0bOXnypOYYTz75JDfddBM7d+5kx44dNG/evJDN8Zo1a/jrr7/YunUrycnJJCYm8vPPP5OYmMiXX35JUlISX331Fdu2bXP4Xvbv38/atWt54403dFkh9+/fn23btrFz506aNWvGhx9+SOfOnenTpw/Tp08nOTmZxo0b246/cuUKw4YNY9GiRfzxxx/k5OTw3nvv2Z6vVq0aO3bsYOTIkbaQ0IwZM5gzZw7Jycls3LjRZs6WlJTErFmz+PPPPzl06BCbNm0qNL/Lly/Tpk0bduzYwU033ZTPQtnKpk2b8l307rvvPkaNGsXOnTvZvHkztWvnWYts3bqVN954gz/++IODBw/y1VdfOfw9WomNjS1kIVFU1Eq+hPkn4x8e/fR+Ek5+R6PL8MFiMN38MA9c0n+rqNXEW6txd4jJyNT+0UDhkIt1c1Jv9ypXdwrr9+Z1kHfVQNwbmC3SlvdvT8FiLy0CjQbNFbfp6n/2XLpaHWsfsjEIQWRECKFFCNfUq1fP1rhjyJAhvP322zz77LMADBo0CIAtW7bw559/2o7Lzs6mU6dO7N27l6ioKJtV75AhQ5g3b16hMdatW2eLGRuNRipXrsz58+fzHbNmzRrWrFlD69at897npUv89ddfXLx4kX79+hEaGgrgNAR01113YTQadVsh79q1i5dffpm0tDQuXbpEjx49nP6u9u3bR1RUFNdddx2QZ2s8Z84cm6e8va2xVUTj4uJ45plnuO++++jfv7/Nu759+/a2f8fExJCSkkKXLl3yjWcwGGyfwZAhQ2znt+fEiRM2e4GLFy+SmppKv379gLyLtJX27dvTqFEjIM8i+ZdffmHgwIFO32+NGjXYu3ev02P0okS+hJBSsiBpAaOXPkp2QC7PbYaAn+Cu46epVOA20BmOMkam9o9mQNtIvvjtKLlSYhSCAW3zF0cVvDC4057QVegjNS2Txi98w70d6jG1fzRjvBS2cYSji5C12EvLA8jdXHetTJziZNcUtMq1/9lqEyylpFu3bnzxxRf5jk1OTvaY1a6UkhdeeIHHHsuf5Txr1izdY1jnq9cKediwYSQkJNCqVSsWLFjAhg0bXM7RGVq2xuPHj6dXr1588803dOzY0baBW9DW2Hq8M7R+DyEhITYLaGfzc/Q52z9uPY/9z85sod1BhWtKgP1n99P2v9fz0MqHiDmVy4f/g67xM3nNLN0SeHDsLz955W6WJabasmpypWRZYqrNp8bqPHl4Wi82jb+1kMAX7D5lfZ0VPaGPXCn5bMsRtv99zmtWxo40x1WYqKAHUIBB6HKLLEjBTJzibLgeOXKEX3/9FYAvvvii0GoSoGPHjmzatIkDBw4AkJGRwf79+2natCmHDx/m4MGDttdr0bVrV1tYIzc3lwsXLhSyDu7RowcfffSRLT6cmprK6dOnufHGG1m+fDmZmZlcvHiRlStXunxPeq2QL168SO3atTGbzSxcuND2uCNb46ZNm5KSkmL7PeixNT548CDR0dE8//zzxMbGurUytlgsLF26FMhrM6j12TRr1sw2n0qVKlG3bl0SEhKAvLuXjIy8vsBbt27l8OHDWCwWFi1aZDtXzZo12bNnDxaLheXLl+c79/79+x02N3cXJfJeJCsni/98/yLXz2rCofS9zFwJPX+oy+CjWXQvYusyRyvW8xlmh/YFriiYfmm9O7AXeqtIFvR+0eKL345yS1P3Ll56iAg1MfPuGIetD11hf6GrVQry25s1a8bHH39My5YtOXfuHCNHjix0TPXq1VmwYAH33nsvLVu2pGPHjuzdu5fg4GDmzZtHr1696NKlCw0aNNAc46233mL9+vVER0fTtm1bdu/eXcjmuHv37gwePJhOnToRHR3NwIEDuXjxIm3atGHQoEHExMQwYMCAQq3/HKHHCvnVV1+lQ4cOdOvWLd+m7D333MP06dNp3bq17QIGeeGP+fPnc9dddxEdHY3BYHDZUHzWrFm0aNGCVq1aERISwu23365r/pB3Z7J7927atm3LunXrNC2Ub7/9dttmLuRdeN5++21atmxJ586dbfsknTp1Yvz48bRo0YKoqChbSGfatGnEx8dz66232uL3VjZt2sRtt+lyjXGJ16yGi4I/WQ3//PfPDP6oH6mGc9y9C7p+Bx2XfEvLnj2Ldd6ieMxEhoc4DdM4OqdWOiX8uyfgbB6RDsI7ekzQTIa8btrm3H+PtO4vWEMvxQk9gbZda0mSkpKSr1G3onRhn/nijH79+vH666/b9kYKsmHDBmbMmGFr+qKHpKQk3nzzTT799FPN5921GlYxeQ9zLvMcz33zNB/u+oQGF+Dj1ZDe4DaGX/zBI+d3tMEaFGBwmOJoFVstB0rQ133Kir3AOsIohMPnJfkvOrc0rc6qnSdsc48INdlMxBwJuZYRm0LhC6ZNm8aJEyccinxR+Oeff3j11VddH6gTtZL3EFJKPv/jc55eOYpz2ek88yuMrTeEwAlTiHBwK11UtFayoL9oqeAK3dFK3ijyin/cHSOucRW2HDqvWXVrP7ajhiiOUjk9ia9X8gpFUVEreR9w8NxBRn71ED+k/kz7Y/DD3mtoNeMz6NDBK+M5Wslu//scn2054vL1BVfZjtIerSJtvQMINhmcCrxRCDo2imDHkXRNgS8YO3e0iayVDqlQKIqGEvlikJ2bzYxN03l1/WRMWWZmbwhgRPwkjO8+BybXG5Sexpqr7gprJor9HUGwyYBBgKM+GJnmXKcCnzKtF6DtXw//NhK3HuPM3jg1LZO4aeuKFGtXKBT5USJfRDYd2cRjy4ax+8IBBvwJb12II/Lj+eDB2Jy76LE2sK6mC4ZKXDlSOsO+qtbRHKwFRHpDSo72DxQKhXuoFEo3OZ95nse+foQu87tw4egBVqwMY+kdC4j8ZqNPBR5c54q7cmgsKvahGUdzKIp5md4UUIVC4Rgl8jqRUvLlri9pNusaPtjxIc9shj/P3E3vbw7C0KGOq3RKEK2CJUFeh6SUAkVQ7hqahZiMhIdoh6DsC5+05mC9eyiKiZonjNfKA8pqWB9lwWoY4Nlnn2XdunUeOZcSeR0cPn+YOz7uxr3L7qVuyjm2rarNG898R9ini6BGDV9Pz0bBqs7I8BBmDophSt/oQsfqMRIzCpHPvGxSn+YuC5G05mC9e3A0ZmR4iMMKWV8YnpUmcnPdv9vq06cP48ePd/h8UUVeD3osApxRUORfeeUVjxUFueKDDz6wWRzrwWo1PHjwYI/P5YknnnB6oXYHFZN3gjnXzMxf32TSugkYs8zMWi8Y3eUZjD9PhqteHaUNvTnkeozE3ri7lea5XBUiOZqDoxx/R+mZeitZi8uY78aQfDLZo+eMqRXDrJ6zHD6vrIb9z2p427ZtPPzww1SoUIEuXbrw7bffsmvXLhYsWMDy5cvJysri8OHDDB48mIkTJxYqiJsxYwaXLl1i0qRJNGjQgLNnz3Ly5Elq1arlzp9eIdRK3gFbjm2h7ZyWPP/jeLrvyebPn1rw1JxEjK/PKLUC7w7WFbcjK+LwEJND22FnHjh6xtRa5Tt7zl9RVsP+ZTX84IMPMnfuXH799ddCjV62bt3KwoULSU5OZsmSJeipB2rTpo3m3NxFreQLkH4lnRfXvsB7iXOpcxGW/xBI32HT4NMnoBgdb0ojVgHVWkFP6tPc7fPpsRtwdqfhq0pWZytub6Kshv3HajgtLY2LFy/SuXNnAAYPHpzPyqBbt25UrVrVNtdffvmFvn37On3PNWrU4Pjx406P0YN/qVYxkFKybM8ynlz5OCczz/DEb/BqwG1U+vp9aNjQ19PzGvae68XxgnHWNNufV+PFQVkNlw+rYa3XCyEICAjAYvk3ddlbdsMqXAP8nfY3vT+7g7uW3EXNlDP8tiyCt4Z+QaUVa0qNwLuyAi4OxQnBWOc1ZlFykV0wyyvKath/rIYjIiKoWLEiW7ZsAeDLL7/Md+wPP/zAuXPnyMzMJCEhgbi4OGrWrMnp06c5e/YsWVlZhUzMPGU3XK5FPseSwxub3+D6d5qwft/3vPE9bDM/SLufDsA995SKtEjQZwXs63k5QqVAOkZZDfuX1fCHH37I8OHD6dSpE1JKKleubHuuS5cu3H///bbfZWxsLCaTiQkTJtChQwfi4+Pz/R7MZjMHDhwgNlbTjsYtyq1B2bbUbQxPeIjkf3bRaz/M+TOKBjM/gptvLpHx3cFdK+CSQo/tsa/n6AhfG5Qpq+HSTVGshi9dukRYWBjwrzvlW2+9lS/bSS/Lly9nx44dmm6U7hqUlbuV/IWsCzz17ZN0/KADp1J2s2SpgZWNXqLBr3+WSoEH96yASxJX45dUCqRC4SusYg6wevVqYmJiaNGiBRs3buTll18u8nlzcnIYO3asR+ZYrlbyCXsTGL1yBMcvn2LkNvi/i+2o/O5H4KE2W96iLK7kI4u4gVtS+Holr1AUFbWS1+Bo+lH6ftGHfov6USXlNJu/CGVO/LtU3rCl1As8OLcKKIg3N2j1zmvWoBi3N3AVCoV38OsUylxLLrO3zubltS+Sm5XJf9fD0zX6YFo3ByLLjgDpTXN0N42xuG30PJl+WdxzKBQKbfxW5Hec2MHwrx8h8VQSPf+Cd7fXIGrqXLjaRLesoadQyJ0mHJ7Ka9czL2cirvLrFQrv4nfhmkvZlxj7/VjazWvHscM7+XIpfFN5JFG/7S+zAq8XPRu0JZ3X7ir909mFSaFQFB+/EvmV+1Zy/dtNeHPLmzy63cKeNdcx6H+bEHPeBbucVX/FmZc7+Cav3ZWIl9bMobKMtbTe3eecoddm2JpC6AhvOmDao8eieMOGDWzevNnrc/E1fiHyxy8eZ+CiAfT5sg+V/j7JL5+YmNtxChG/7YQi/lGXRVxt0Opp2uFpa19XIu7qwlRaKMkN7eKiJVxWy2Jfi1pJibwei2Il8mWIs/uSWbPra177EXbs7kLct7vgpZcgMNDXUytRXDk5+iKv3ZWIu5M55Cu8VXH82Wef0b59e2JiYnjsscdsQhwWFsbzzz9P27Ztue2229i6dSs333wzjRo1YsWKFUCenfCdd95Jz549adKkST6XROtqesOGDdxyyy0MHjyY6OjofM8BvP7660RHR9OqVSub//z7779Pu3btaNWqFQMGDCAjI8Ppezh8+DCdOnWiXbt2/Oc//7E9funSJbp27UqbNm2Ijo62Vb2OHz/eZnM8btw4h8cVJCwsjLFjx9KmTRu6du3KmTN5/YyTk5Pp2LEjLVu2pF+/fjbztWHDhtlsCRo2bMjEiRNtY+zdu5eUlBTmzp3LzJkziYmJYePGjSxZssRWIXvjjTfq+QjLBlLKUvNf27ZtZZE4cECmNawt5YcfSmmxFO0c5YDOU3+UDZ5fpflf56k/yuU7jnl8zOU7jsmmL3+bb6ymL3+bb6zlO47JzlN/lA29OI+C/Pnnn7qPdfR76zz1x2KNHx8fL7Ozs6WUUo4cOVJ+/PHHUkopAfnNN99IKaXs27ev7Natm8zOzpbJycmyVatWUkop58+fL2vVqiX/+ecfmZGRIZs3by63bdsmpZSyQoUKUkop169fL0NDQ+WhQ4ds41qf++abb2SnTp3k5cuXpZRSnj17Vkop5T///GM79qWXXpJvv/22lFLKiRMnyunTpxd6H71797bNe/bs2bbzm81mmZ6eLqWU8syZM7Jx48bSYrHIw4cPy+bNm9te7+i4ggDys88+k1JKOXnyZDlq1CgppZTR0dFyw4YNUkop//Of/8innnpKSinl0KFD5ZIlS6SUUjZo0MD2PubMmSMffvhhzffUokULeexY3t/e+fPnC82htKD1twtslw501T+yaxo3pvK+lHK3cncXR007vOnbrifN0lcWw3rxxr7Bjz/+SGJiIu3atQMgMzOTGle7jAUGBtKzZ08AoqOjCQoKwmQyER0dTUpKiu0cWva1Bb1O2rdvT1RUVKHx165dy4MPPmizEa5SpQrgvgXwpk2bWLZsGQD3338/zz//PJC3eHzxxRf5+eefMRgMpKamcurUqUKvd3RcwUYZWta/6enppKWl2YzKhg4dms/i2B4tK+KCxMXFMWzYMO6++25Na+Gyin+IPCiBv4qzdEVP5bW7S2kXcVfUCQ/R3Kwuzr6BlJKhQ4cyderUQs+ZTCabNa3BYLBZ4xoMhny2uM6siq1UcNDgRkqpeby7FsCOxl24cCFnzpwhMTERk8lEw4YNC1npunOcnjGdoWVFXJC5c+fy22+/2ewJkpOTbRfRsoxfxOQVeeiJHRfHVri84o19g65du7J06VJOnz4NwLlz5/j777/dOoeWfa1eunfvzkcffWSLuZ87dw5wbAHsiLi4OJutrv3x6enp1KhRA5PJxPr1623vraCVsKPjCqJl/Vu5cmUiIiLYuHEjoM9+2J6Cczl48CAdOnTglVdeoVq1ahw9elT3uUoz/rOSV7hVDKXQjzfugK6//nqmTJlC9+7dsVgsmEwm5syZ49AyWAurfe2BAwcYPHiwW7a0PXv2JDk5mdjYWAIDA7njjjv4v//7P5sFcIMGDYiOjtb0drfnrbfeYvDgwbz11lsMGDDA9vh9991H7969iY2NJSYmxmaja29zfPvtt/P8889rHlcQe+vfypUrs2jRIgA+/vhjRowYQUZGBo0aNWL+/Pm6fwe9e/dm4MCBfP3117zzzjvMnDmTv/76CyklXbt2pVWrVrrPVZopVwZl/k7U+NVofZoCODytV0lPp1RT1g3KimJfW5bRa/1bHlAGZeWYspJzrlAoSo5iibwQ4i4hxG4hhEUIEVvguReEEAeEEPuEEM636BUeoSzknCs8w7Bhw8rNKh5Qq/hiUNyY/C6gP/A/+weFENcD9wDNgTrAWiHEdVJK5+WWimLhq+yZsoqjDBOForRSlPB6sUReSrkHNNOZ7gS+lFJmAYeFEAeA9sCvxRlP4Zqynq5YUgQHB3P27FmqVq2qhF5RJpBScvbsWYKDg916nbeyayKBLXY/H7v6WCGEEMOB4QD169f30nQUivzUrVuXY8eO2crjFYqyQHBwMHXr1nXrNS5FXgixFqil8dRLUkpto4m8hI6CaN5nSCnnAfMgL7vG1XwUCk9gMpk0K0EVCn/DpchLKZ1buWlzDKhn93Nd4HgRzqNQKBSKYuCtFMoVwD1CiCAhRBRwLbDVS2MpFAqFwgHFTaHsJ4Q4BnQCVgshvgeQUu4GFgN/At8Bo1RmjUKhUJQ8pariVQhxBnDPwKN0UA34x9eTKGHUey4flLf3XFbfbwMpZXWtJ0qVyJdVhBDbHZUU+yvqPZcPytt79sf3q2wNFAqFwo9RIq9QKBR+jBJ5zzDP1xPwAeo9lw/K23v2u/erYvIKhULhx6iVvEKhUPgxSuQVCoXCj1Ei72GEEM8KIaQQopqv5+JthBDThRB7hRC/CyGWCyHCfT0nbyCE6Hm1L8IBIcR4X8/H2wgh6gkh1gsh9lztF/GUr+dUUgghjEKIJCHEKl/PxVMokfcgQoh6QDfgiK/nUkL8ALSQUrYE9gMv+Hg+HkcIYQTmALcD1wP3Xu2X4M/kAGOllM2AjsCocvCerTwF7PH1JDyJEnnPMhN4DgeOm/6GlHKNlDLn6o9byDOi8zfaAweklIeklNnAl+T1S/BbpJQnpJQ7rv77Inmi5/dNCoQQdYFewAe+nosnUSLvIYQQfYBUKeVOX8/FRzwEfOvrSXiBSOCo3c8OeyP4I0KIhkBr4DcfT6UkmEXeIs3i43l4FG81DfFLnHnrAy8C3Ut2Rt5HTz8BIcRL5N3iLyzJuZUQunsj+BtCiDBgGTBGSnnB1/PxJkKIeOC0lDJRCHGzj6fjUZTIu4Ejb30hRDQQBey82kquLrBDCNFeSnmyBKfocVz1ExBCDAXiga7SP4suymVvBCGEiTyBXyil/MrX8ykB4oA+Qog7gGCgkhDiMynlEB/Pq9ioYigvIIRIAWKllGXRzU43QoiewJvATVJKv+yjJ4QIIG9TuSuQCmwDBl+10/ZLRN5K5WPgnJRyjI+nU+JcXck/K6WM9/FUPIKKySuKw2ygIvCDECJZCDHX1xPyNFc3lkcD35O3AbnYnwX+KnHA/cCtVz/X5KsrXEUZRK3kFQqFwo9RK3mFQqHwY5TIKxQKhR+jRF6hUCj8GCXyCoVC4ccokVcoFAo/Rom8QqFQ+DFK5BUKhcKP+X9nJ64n8oEhEgAAAABJRU5ErkJggg==\n",
+ "text/plain": [
+ "
"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "plt.scatter(x, y_noisy, label='empirical data points')\n",
+ "plt.plot(x, y, color='black', label='true relationship')\n",
+ "plt.plot(inputs, outputs, color='red', label='predicted relationship (cpu)')\n",
+ "plt.plot(inputs, outputs_gpu.to_array(), color='green', label='predicted relationship (gpu)')\n",
+ "plt.legend()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "## Ridge Regression\n",
+ "\n",
+ "Ridge extends LinearRegression by providing L2 regularization on the coefficients when predicting response y with a linear combination of the predictors in X. It can reduce the variance of the predictors, and improves the conditioning of the problem.\n",
+ "\n",
+ "Below, we instantiate and fit a Ridge Regression model to our data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from cuml.linear_model import Ridge as Ridge_GPU\n",
+ "\n",
+ "\n",
+ "# instantiate and fit model\n",
+ "ridge_regression_gpu = Ridge_GPU()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 1.13 ms, sys: 4.39 ms, total: 5.52 ms\n",
+ "Wall time: 5.07 ms\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/opt/conda/envs/rapids/lib/python3.7/site-packages/cuml/internals/api_decorators.py:410: UserWarning: Changing solver to 'svd' as 'eig' or 'cd' solvers do not support training data with 1 column currently.\n",
+ " return func(*args, **kwargs)\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Ridge(alpha=1.0, solver='eig', fit_intercept=True, normalize=False, handle=, output_type='input', verbose=4)"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%%time\n",
+ "\n",
+ "ridge_regression_gpu.fit(df[['x']], df['y'])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Similar to the `LinearRegression` model we fitted early, we can use the `predict` method to generate predictions for new data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "outputs_gpu = ridge_regression_gpu.predict(new_data_df[['inputs']])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Lastly, we can visualize our `Ridge` model's estimated relationship and overlay it our the empirical data points."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 19,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "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\n",
+ "text/plain": [
+ "
"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "plt.scatter(x, y_noisy, label='empirical data points')\n",
+ "plt.plot(x, y, color='black', label='true relationship')\n",
+ "plt.plot(inputs, outputs, color='red', label='linear regression (cpu)')\n",
+ "plt.plot(inputs, outputs_gpu.to_array(), color='green', label='ridge regression (gpu)')\n",
+ "plt.legend()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "## K Nearest Neighbors\n",
+ "\n",
+ "NearestNeighbors is a unsupervised algorithm where if one wants to find the “closest” datapoint(s) to new unseen data, one can calculate a suitable “distance” between each and every point, and return the top K datapoints which have the smallest distance to it.\n",
+ "\n",
+ "We'll generate some fake data using the `make_moons` function from the `sklearn.datasets` module. This function generates data points from two equations, each describing a half circle with a unique center. Since each data point is generated by one of these two equations, the cluster each data point belongs to is clear. The ideal classification algorithm will identify two clusters and associate each data point with the equation that generated it. \n",
+ "\n",
+ "These data points are generated using a non-linear relationship - so using a linear regression approach won't adequately solve problem. Instead, we can use a distance-based algorithm K Nearest Neighbors to classify each data point.\n",
+ "\n",
+ "First, let's generate out data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "(1000, 2)\n"
+ ]
+ }
+ ],
+ "source": [
+ "from sklearn.datasets import make_moons\n",
+ "\n",
+ "\n",
+ "X, y = make_moons(n_samples=int(1e3), noise=0.05, random_state=0)\n",
+ "print(X.shape)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's visualize our data:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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\n",
+ "text/plain": [
+ "
"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "figure = plt.figure()\n",
+ "axis = figure.add_subplot(111)\n",
+ "axis.scatter(X[y == 0, 0], X[y == 0, 1], \n",
+ " edgecolor='black',\n",
+ " c='lightblue', marker='o', s=40, label='cluster 1')\n",
+ "\n",
+ "axis.scatter(X[y == 1, 0], X[y == 1, 1], \n",
+ " edgecolor='black',\n",
+ " c='red', marker='s', s=40, label='cluster 2')\n",
+ "plt.legend()\n",
+ "plt.tight_layout()\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Before we build a KNN classification model, we first have to convert our data to a cuDF representation."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "X_df = cudf.DataFrame()\n",
+ "for column in range(X.shape[1]):\n",
+ " X_df['feature_' + str(column)] = np.ascontiguousarray(X[:, column])\n",
+ "\n",
+ "y_df = cudf.Series(y)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Next, we'll instantiate and fit a nearest neighbors model using the `NearestNeighbors` class from cuML."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from cuml.neighbors import NearestNeighbors\n",
+ "\n",
+ "\n",
+ "knn = NearestNeighbors()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "NearestNeighbors(n_neighbors=5, verbose=4, handle=, algorithm='brute', metric='euclidean', p=2, algo_params=None, metric_params=None, output_type='input')"
+ ]
+ },
+ "execution_count": 24,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "knn.fit(X_df)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Once our model has been built and fitted to the data, we can query the model for the `k` nearest neighbors to each data point. The query returns a matrix representating the distances of each data point to its nearest `k` neighbors as well as the indices of those neighbors."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "k = 3\n",
+ "\n",
+ "distances, indices = knn.kneighbors(X_df, n_neighbors=k)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We can iterate through each of our data points and do a majority vote to determine which class it belongs to."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "predictions = []\n",
+ "cp_y = cp.asarray(y_df)\n",
+ "for i in range(indices.shape[0]):\n",
+ " row = indices.iloc[i, :].values\n",
+ " vote = sum(cp_y[j] for j in row) / k\n",
+ " predictions.append(1.0 * (vote > 0.5))\n",
+ "\n",
+ "predictions = np.asarray(predictions).astype(np.float32)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Lastly, we can visualize the predictions from our K Nearest Neighbors classifier - we see that despite the non-linearity of the data, the algorithm does an excellent job of classifying the data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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\n",
+ "text/plain": [
+ "
"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "f, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 3))\n",
+ "\n",
+ "\n",
+ "ax1.scatter(X[y == 0, 0], X[y == 0, 1],\n",
+ " edgecolor='black',\n",
+ " c='lightblue', marker='o', s=40, label='cluster 1')\n",
+ "ax1.scatter(X[y == 1, 0], X[y == 1, 1],\n",
+ " edgecolor='black',\n",
+ " c='red', marker='s', s=40, label='cluster 2')\n",
+ "ax1.set_title('empirical data points')\n",
+ "\n",
+ "\n",
+ "ax2.scatter(X[predictions == 0, 0], X[predictions == 0, 1], c='lightblue',\n",
+ " edgecolor='black',\n",
+ " marker='o', s=40, label='cluster 1')\n",
+ "ax2.scatter(X[predictions == 1, 0], X[predictions == 1, 1], c='red',\n",
+ " edgecolor='black',\n",
+ " marker='s', s=40, label='cluster 2')\n",
+ "ax2.set_title('KNN predicted classes')\n",
+ "\n",
+ "plt.legend()\n",
+ "plt.tight_layout()\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "## Conclusion\n",
+ "\n",
+ "In this notebook, we showed to do GPU accelerated Supervised Learning in RAPIDS. \n",
+ "\n",
+ "To learn more about RAPIDS, be sure to check out: \n",
+ "\n",
+ "* [Open Source Website](http://rapids.ai)\n",
+ "* [GitHub](https://github.com/rapidsai/)\n",
+ "* [Press Release](https://nvidianews.nvidia.com/news/nvidia-introduces-rapids-open-source-gpu-acceleration-platform-for-large-scale-data-analytics-and-machine-learning)\n",
+ "* [NVIDIA Blog](https://blogs.nvidia.com/blog/2018/10/10/rapids-data-science-open-source-community/)\n",
+ "* [Developer Blog](https://devblogs.nvidia.com/gpu-accelerated-analytics-rapids/)\n",
+ "* [NVIDIA Data Science Webpage](https://www.nvidia.com/en-us/deep-learning-ai/solutions/data-science/)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/getting_started_notebooks/intro_tutorials/07_Introduction_to_XGBoost.ipynb b/getting_started_materials/intro_tutorials_and_guides/07_Introduction_to_XGBoost.ipynb
similarity index 100%
rename from getting_started_notebooks/intro_tutorials/07_Introduction_to_XGBoost.ipynb
rename to getting_started_materials/intro_tutorials_and_guides/07_Introduction_to_XGBoost.ipynb
diff --git a/getting_started_notebooks/intro_tutorials/08_Introduction_to_Dask_XGBoost.ipynb b/getting_started_materials/intro_tutorials_and_guides/08_Introduction_to_Dask_XGBoost.ipynb
similarity index 100%
rename from getting_started_notebooks/intro_tutorials/08_Introduction_to_Dask_XGBoost.ipynb
rename to getting_started_materials/intro_tutorials_and_guides/08_Introduction_to_Dask_XGBoost.ipynb
diff --git a/getting_started_notebooks/intro_tutorials/09_Introduction_to_Dimensionality_Reduction.ipynb b/getting_started_materials/intro_tutorials_and_guides/09_Introduction_to_Dimensionality_Reduction.ipynb
similarity index 100%
rename from getting_started_notebooks/intro_tutorials/09_Introduction_to_Dimensionality_Reduction.ipynb
rename to getting_started_materials/intro_tutorials_and_guides/09_Introduction_to_Dimensionality_Reduction.ipynb
diff --git a/getting_started_notebooks/intro_tutorials/10_Introduction_to_Clustering.ipynb b/getting_started_materials/intro_tutorials_and_guides/10_Introduction_to_Clustering.ipynb
similarity index 100%
rename from getting_started_notebooks/intro_tutorials/10_Introduction_to_Clustering.ipynb
rename to getting_started_materials/intro_tutorials_and_guides/10_Introduction_to_Clustering.ipynb
diff --git a/getting_started_materials/intro_tutorials_and_guides/11_Introduction_to_Strings.ipynb b/getting_started_materials/intro_tutorials_and_guides/11_Introduction_to_Strings.ipynb
new file mode 100644
index 00000000..f6ef887e
--- /dev/null
+++ b/getting_started_materials/intro_tutorials_and_guides/11_Introduction_to_Strings.ipynb
@@ -0,0 +1,3460 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Draft Intro into Strings \n",
+ "\n",
+ "**Authorship** \n",
+ "Original Author: Nicholas Davis \n",
+ "Last Edit: Nicholas Davis, 4/19/2021 \n",
+ "\n",
+ "**Test System Specs** \n",
+ "Test System Hardware: Tesla T4 \n",
+ "Test System Software: Ubuntu 18.04-py3.7 \n",
+ "RAPIDS Version: 0.18. - Docker Install \n",
+ "Driver: 450.80.02 \n",
+ "CUDA: 11.0 \n",
+ "\n",
+ "\n",
+ "**Known Working Systems** \n",
+ "RAPIDS Versions: 0.18"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Working with text data \n",
+ "\n",
+ "Enterprise analytics workflows commonly require processing large-scale text data. To address this need, the RAPIDS CUDA DataFrame library (cuDF) and RAPIDS CUDA Machine Learning library (cuML) now include string processing capabilities. cuDF has a fully-featured string and regular expression processing engine. With a pandas-like API, cuDF string analytics can provide data scientists with up to 90x performance improvement with minimal changes to their code. \n",
+ "\n",
+ "This notebook serves as an intro to string capabilities with cuDF. Each string functionality will have a pandas example and it's cuDF equivalent. \n",
+ "\n",
+ "For any additional information please reference: \n",
+ "[cuDF Documentation](https://docs.rapids.ai/api/cudf/stable/api.html#strings)
"
+ ],
+ "text/plain": [
+ " 0\n",
+ "0 1\n",
+ "1 2\n",
+ "2 "
+ ]
+ },
+ "execution_count": 58,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cudf.Series([\"a1\", \"b2\", \"c3\"], dtype=\"str\").str.extract(r\"[ab](\\d)\", expand=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "It returns a Series if expand=False."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 59,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0 1\n",
+ "1 2\n",
+ "2 \n",
+ "dtype: string"
+ ]
+ },
+ "execution_count": 59,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pd.Series([\"a1\", \"b2\", \"c3\"], dtype=\"string\").str.extract(r\"[ab](\\d)\", expand=False)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 60,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0 1\n",
+ "1 2\n",
+ "2 \n",
+ "dtype: object"
+ ]
+ },
+ "execution_count": 60,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cudf.Series([\"a1\", \"b2\", \"c3\"], dtype=\"str\").str.extract(r\"[ab](\\d)\", expand=False)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "When each subject string in the Series has exactly one match."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 61,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "0 a3\n",
+ "1 b3\n",
+ "2 c2\n",
+ "dtype: string\n"
+ ]
+ }
+ ],
+ "source": [
+ "pandasSeries = pd.Series([\"a3\", \"b3\", \"c2\"], dtype=\"string\")\n",
+ "print(pandasSeries)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 62,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "0 a3\n",
+ "1 b3\n",
+ "2 c2\n",
+ "dtype: object\n"
+ ]
+ }
+ ],
+ "source": [
+ "cudfSeries = cudf.Series([\"a3\", \"b3\", \"c2\"], dtype=\"str\")\n",
+ "print(cudfSeries)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "## Testing for strings that match or contain a pattern"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You can check whether elements contain a pattern:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 63,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0 False\n",
+ "1 False\n",
+ "2 True\n",
+ "3 True\n",
+ "4 True\n",
+ "5 True\n",
+ "dtype: bool"
+ ]
+ },
+ "execution_count": 63,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pattern = r\"[0-9][a-z]\"\n",
+ "\n",
+ "pd.Series([\"1\", \"2\", \"3a\", \"3b\", \"03c\", \"4dx\"],dtype=\"str\",\n",
+ " ).str.contains(pattern)\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 64,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0 False\n",
+ "1 False\n",
+ "2 True\n",
+ "3 True\n",
+ "4 True\n",
+ "5 True\n",
+ "dtype: bool"
+ ]
+ },
+ "execution_count": 64,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pattern = r\"[0-9][a-z]\"\n",
+ "\n",
+ "cudf.Series([\"1\", \"2\", \"3a\", \"3b\", \"03c\", \"4dx\"],dtype=\"str\",\n",
+ " ).str.contains(pattern)\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "Or whether elements match a pattern:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 65,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0 False\n",
+ "1 False\n",
+ "2 True\n",
+ "3 True\n",
+ "4 False\n",
+ "5 True\n",
+ "dtype: boolean"
+ ]
+ },
+ "execution_count": 65,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pd.Series([\"1\", \"2\", \"3a\", \"3b\", \"03c\", \"4dx\"],dtype=\"string\",\n",
+ " ).str.match(pattern)\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 66,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0 False\n",
+ "1 False\n",
+ "2 True\n",
+ "3 True\n",
+ "4 False\n",
+ "5 True\n",
+ "dtype: bool"
+ ]
+ },
+ "execution_count": 66,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cudf.Series([\"1\", \"2\", \"3a\", \"3b\", \"03c\", \"4dx\"],dtype=\"str\",\n",
+ " ).str.match(pattern) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "New in version 1.1.0."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 67,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0 False\n",
+ "1 False\n",
+ "2 True\n",
+ "3 True\n",
+ "4 False\n",
+ "5 False\n",
+ "dtype: boolean"
+ ]
+ },
+ "execution_count": 67,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pd.Series([\"1\", \"2\", \"3a\", \"3b\", \"03c\", \"4dx\"],dtype=\"string\",\n",
+ " ).str.fullmatch(pattern)\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 68,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0 False\n",
+ "1 False\n",
+ "2 True\n",
+ "3 True\n",
+ "4 False\n",
+ "5 True\n",
+ "dtype: bool"
+ ]
+ },
+ "execution_count": 68,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cudf.Series([\"1\", \"2\", \"3a\", \"3b\", \"03c\", \"4dx\"],dtype=\"str\",\n",
+ " ).str.match(pattern)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "Methods like match, fullmatch, contains, startswith, and endswith take an extra na argument so missing values can be considered True or False:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 69,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Strings that contain 'A':\n",
+ "0 True\n",
+ "1 False\n",
+ "2 False\n",
+ "3 True\n",
+ "4 False\n",
+ "5 False\n",
+ "6 True\n",
+ "7 False\n",
+ "8 False\n",
+ "dtype: boolean\n",
+ "\n",
+ "Strings that have swapped case:\n",
+ "0 a\n",
+ "1 b\n",
+ "2 c\n",
+ "3 aABA\n",
+ "4 bACA\n",
+ "5 \n",
+ "6 caba\n",
+ "7 DOG\n",
+ "8 CAT\n",
+ "dtype: string\n",
+ "\n",
+ "Strings that start with 'b':\n",
+ "0 False\n",
+ "1 False\n",
+ "2 False\n",
+ "3 False\n",
+ "4 False\n",
+ "5 \n",
+ "6 False\n",
+ "7 False\n",
+ "8 False\n",
+ "dtype: boolean\n",
+ "\n",
+ "Strings that ends with 'a':\n",
+ "0 False\n",
+ "1 False\n",
+ "2 False\n",
+ "3 True\n",
+ "4 True\n",
+ "5 \n",
+ "6 False\n",
+ "7 False\n",
+ "8 False\n",
+ "dtype: boolean\n"
+ ]
+ }
+ ],
+ "source": [
+ "pandasSeries5 = pd.Series([\"A\", \"B\", \"C\", \"Aaba\", \"Baca\", np.nan, \"CABA\", \"dog\", \"cat\"], dtype=\"string\") \n",
+ "print(\"Strings that contain 'A':\")\n",
+ "print(pandasSeries5.str.contains(\"A\", na=False))\n",
+ "print(\"\\nStrings that have swapped case:\")\n",
+ "print(pandasSeries5.str.swapcase())\n",
+ "print(\"\\nStrings that start with 'b':\")\n",
+ "print(pandasSeries5.str.startswith ('b'))\n",
+ "print((\"\\nStrings that ends with 'a':\"))\n",
+ "print(pandasSeries5.str.endswith ('a'))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 70,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Strings that contain 'A':\n",
+ "0 True\n",
+ "1 False\n",
+ "2 False\n",
+ "3 True\n",
+ "4 False\n",
+ "5 \n",
+ "6 True\n",
+ "7 False\n",
+ "8 False\n",
+ "dtype: bool\n",
+ "\n",
+ "Strings that have swapped case:\n",
+ "0 a\n",
+ "1 b\n",
+ "2 c\n",
+ "3 aABA\n",
+ "4 bACA\n",
+ "5 \n",
+ "6 caba\n",
+ "7 DOG\n",
+ "8 CAT\n",
+ "dtype: object\n",
+ "\n",
+ "Strings that start with 'b':\n",
+ "0 False\n",
+ "1 False\n",
+ "2 False\n",
+ "3 False\n",
+ "4 False\n",
+ "5 \n",
+ "6 False\n",
+ "7 False\n",
+ "8 False\n",
+ "dtype: bool\n",
+ "\n",
+ "Strings that ends with 'a':\n",
+ "0 False\n",
+ "1 False\n",
+ "2 False\n",
+ "3 True\n",
+ "4 True\n",
+ "5 \n",
+ "6 False\n",
+ "7 False\n",
+ "8 False\n",
+ "dtype: bool\n"
+ ]
+ }
+ ],
+ "source": [
+ "cudfSeries5 = cudf.Series([\"A\", \"B\", \"C\", \"Aaba\", \"Baca\", np.nan, \"CABA\", \"dog\", \"cat\"], dtype=\"str\") \n",
+ "print(\"Strings that contain 'A':\")\n",
+ "print(cudfSeries5.str.contains(\"A\"))\n",
+ "print(\"\\nStrings that have swapped case:\")\n",
+ "print(cudfSeries5.str.swapcase())\n",
+ "print(\"\\nStrings that start with 'b':\")\n",
+ "print(cudfSeries5.str.startswith ('b'))\n",
+ "print((\"\\nStrings that ends with 'a':\"))\n",
+ "print(cudfSeries5.str.endswith ('a'))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.7.10"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/getting_started_notebooks/intro_tutorials/README.md b/getting_started_materials/intro_tutorials_and_guides/README.md
similarity index 100%
rename from getting_started_notebooks/intro_tutorials/README.md
rename to getting_started_materials/intro_tutorials_and_guides/README.md
diff --git a/getting_started_notebooks/basics/Getting_Started_with_Dask.ipynb b/getting_started_notebooks/basics/Getting_Started_with_Dask.ipynb
deleted file mode 100644
index ea219b2c..00000000
--- a/getting_started_notebooks/basics/Getting_Started_with_Dask.ipynb
+++ /dev/null
@@ -1,415 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Introduction to Dask\n",
- "#### By Paul Hendricks\n",
- "-------\n",
- "\n",
- "In this notebook, we will show how to get started with Dask using basic Python primitives like integers and strings.\n",
- "\n",
- "**Table of Contents**\n",
- "\n",
- "* [Introduction to Dask](#introduction)\n",
- "* [Setup](#setup)\n",
- "* [Introduction to Dask](#dask)\n",
- "* [Conclusion](#conclusion)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Setup\n",
- "\n",
- "This notebook was tested using the following Docker containers:\n",
- "\n",
- "* `rapidsai/rapidsai-dev-nightly:0.10-cuda10.0-devel-ubuntu18.04-py3.7` from [DockerHub](https://hub.docker.com/r/rapidsai/rapidsai-nightly)\n",
- "\n",
- "This notebook was run on the NVIDIA GV100 GPU. Please be aware that your system may be different and you may need to modify the code or install packages to run the below examples. \n",
- "\n",
- "If you think you have found a bug or an error, please file an issue here: https://github.com/rapidsai/notebooks-contrib/issues\n",
- "\n",
- "Before we begin, let's check out our hardware setup by running the `nvidia-smi` command."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!nvidia-smi"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Next, let's see what CUDA version we have:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!nvcc --version"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!apt update\n",
- "!apt install -y graphviz\n",
- "!conda install graphviz\n",
- "!conda install python-graphviz"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Introduction to Dask\n",
- "\n",
- "Dask is a library that allows for parallelized computing. Written in Python, it allows one to compose complex workflows using large data structures like those found in NumPy, Pandas, and cuDF. In the following examples and notebooks, we'll show how to use Dask with cuDF to accelerate common ETL tasks as well as build and train machine learning models like Linear Regression and XGBoost.\n",
- "\n",
- "To learn more about Dask, check out the documentation here: http://docs.dask.org/en/latest/\n",
- "\n",
- "#### Client/Workers\n",
- "\n",
- "Dask operates by creating a cluster composed of a \"client\" and multiple \"workers\". The client is responsible for scheduling work; the workers are responsible for actually executing that work. \n",
- "\n",
- "Typically, we set the number of workers to be equal to the number of computing resources we have available to us. For CPU based workflows, this might be the number of cores or threads on that particlular machine. For example, we might set `n_workers = 8` if we have 8 CPU cores or threads on our machine that can each operate in parallel. This allows us to take advantage of all of our computing resources and enjoy the most benefits from parallelization.\n",
- "\n",
- "On a system with one or more GPUs, we usually set the number of workers equal to the number of GPUs available to us. Dask is a first class citizen in the world of General Purpose GPU computing and the RAPIDS ecosystem makes it very easy to use Dask with cuDF and XGBoost. \n",
- "\n",
- "Before we get started with Dask, we need to setup a Local Cluster of workers to execute our work and a Client to coordinate and schedule work for that cluster. As we see below, we can inititate a `cluster` and `client` using only few lines of code."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import dask; print('Dask Version:', dask.__version__)\n",
- "from dask.distributed import Client, LocalCluster\n",
- "import subprocess\n",
- "\n",
- "# parse the hostname IP address\n",
- "cmd = \"hostname --all-ip-addresses\"\n",
- "process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)\n",
- "output, error = process.communicate()\n",
- "ip_address = str(output.decode()).split()[0]\n",
- "\n",
- "# create a local cluster with 4 workers\n",
- "n_workers = 4\n",
- "cluster = LocalCluster(ip=ip_address, n_workers=n_workers)\n",
- "client = Client(cluster)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's inspect the `client` object to view our current Dask status. We should see the IP Address for our Scheduler as well as the the number of workers in our Cluster. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# show current Dask status\n",
- "client"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "You can also see the status and more information at the Dashboard, found at `http:///status`. You can ignore this for now, we'll dive into this in subsequent tutorials.\n",
- "\n",
- "With our client and workers setup, it's time to execute our first program in parallel. We'll define a function that takes some value `x` and adds 5 to it."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "def add_5_to_x(x):\n",
- " return x + 5"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Next, we'll iterate through our `n_workers` and create an execution graph, where each worker is responsible for taking its ID and passing it to the function `add_5_to_x`. For example, the worker with ID 2 will take its ID and add 5, resulting in the value 7."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from dask import delayed\n",
- "\n",
- "addition_operations = [delayed(add_5_to_x)(i) for i in range(n_workers)]\n",
- "addition_operations"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The above output shows a list of several `Delayed` objects. An important thing to note is that the workers aren't actually executing these results - we're just defining the execution graph for our client to execute later. The `delayed` function wraps our function `add_5_to_x` and returns a `Delayed` object. This ensures that this computation is in fact \"delayed\" - or lazily evaluated - and not executed on the spot i.e. when we define it.\n",
- "\n",
- "Next, let's sum each one of these intermediate results. We can accomplish this by wrapping Python's built-in `sum` function using our `delayed` function and storing this in a variable called `total`."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "total = delayed(sum)(addition_operations)\n",
- "total"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Using the `graphviz` library, we can use the `visualize` method of a `Delayed` object to visualize our current graph."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "total.visualize()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "As we mentioned before, none of these results - intermediate or final - have actually been compute. We can compute them using the `compute` method of our `client`."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import time\n",
- "\n",
- "addition_futures = client.compute(addition_operations, optimize_graph=False, fifo_timeout=\"0ms\")\n",
- "total_future = client.compute(total, optimize_graph=False, fifo_timeout=\"0ms\")\n",
- "time.sleep(1) # this will give Dask time to execute each worker"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's inspect the output of each call to `client.compute`:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "addition_futures"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We can see from the above output that our `addition_futures` variable is a list of `Future` objects - not the \"actual results\" of adding 5 to each of `[0, 1, 2, 3]`. These `Future` objects are a promise that at one point a computation will take place and we will be left with a result. Dask is responsible for ensuring that promise by delegating that task to the appropriate Dask worker and collecting the result.\n",
- "\n",
- "Let's take a look at our `total_future` object:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "print(total_future)\n",
- "print(type(total_future))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Again, we see that this is an object of type `Future` as well as metadata about the status of the request (i.e. whether it has finished or not), the type of the result, and a key associated with that operation. To collect and print the result of each of these `Future` objects, we can call the `result()` method."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "addition_results = [future.result() for future in addition_futures]\n",
- "print('Addition Results:', addition_results)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now we see the results that we want from our addition operations. We can also use the simpler syntax of the `client.gather` method to collect our results."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "addition_results = client.gather(addition_futures)\n",
- "total_result = client.gather(total_future)\n",
- "print('Addition Results:', addition_results)\n",
- "print('Total Result:', total_result)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Awesome! We just wrote our first distributed workflow.\n",
- "\n",
- "To confirm that Dask is truly executing in parallel, let's define a function that sleeps for 1 second and returns the string \"Success!\". In serial, this function should take our 4 workers around 4 seconds to execute."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "def sleep_1():\n",
- " time.sleep(1)\n",
- " return 'Success!'"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "%%time\n",
- "\n",
- "for _ in range(n_workers):\n",
- " sleep_1()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "As expected, our process takes about 4 seconds to run. Now let's execute this same workflow in parallel using Dask."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "%%time\n",
- "\n",
- "# define delayed execution graph\n",
- "sleep_operations = [delayed(sleep_1)() for _ in range(n_workers)]\n",
- "\n",
- "# use client to perform computations using execution graph\n",
- "sleep_futures = client.compute(sleep_operations, optimize_graph=False, fifo_timeout=\"0ms\")\n",
- "\n",
- "# collect and print results\n",
- "sleep_results = client.gather(sleep_futures)\n",
- "print(sleep_results)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Using Dask, we see that this whole process takes a little over a second - each worker is executing in parallel!"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Conclusion\n",
- "\n",
- "In this tutorial, we learned how to use Dask with basic Python primitives like integers and strings.\n",
- "\n",
- "To learn more about RAPIDS, be sure to check out: \n",
- "\n",
- "* [Open Source Website](http://rapids.ai)\n",
- "* [GitHub](https://github.com/rapidsai/)\n",
- "* [Press Release](https://nvidianews.nvidia.com/news/nvidia-introduces-rapids-open-source-gpu-acceleration-platform-for-large-scale-data-analytics-and-machine-learning)\n",
- "* [NVIDIA Blog](https://blogs.nvidia.com/blog/2018/10/10/rapids-data-science-open-source-community/)\n",
- "* [Developer Blog](https://devblogs.nvidia.com/gpu-accelerated-analytics-rapids/)\n",
- "* [NVIDIA Data Science Webpage](https://www.nvidia.com/en-us/deep-learning-ai/solutions/data-science/)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.3"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/getting_started_notebooks/basics/Getting_Started_with_cuDF.ipynb b/getting_started_notebooks/basics/Getting_Started_with_cuDF.ipynb
deleted file mode 100644
index 5b307586..00000000
--- a/getting_started_notebooks/basics/Getting_Started_with_cuDF.ipynb
+++ /dev/null
@@ -1,537 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Getting Started with cuDF\n",
- "#### By Yi Dong, Paul Hendricks\n",
- "-------\n",
- "\n",
- "While the world’s data doubles each year, CPU computing has hit a brick wall with the end of Moore’s law. For the same reasons, scientific computing and deep learning has turned to NVIDIA GPU acceleration, data analytics and machine learning where GPU acceleration is ideal. \n",
- "\n",
- "NVIDIA created RAPIDS – an open-source data analytics and machine learning acceleration platform that leverages GPUs to accelerate computations. RAPIDS is based on Python, has pandas-like and Scikit-Learn-like interfaces, is built on Apache Arrow in-memory data format, and can scale from 1 to multi-GPU to multi-nodes. RAPIDS integrates easily into the world’s most popular data science Python-based workflows. RAPIDS accelerates data science end-to-end – from data prep, to machine learning, to deep learning. And through Arrow, Spark users can easily move data into the RAPIDS platform for acceleration.\n",
- "\n",
- "In this notebook, we will also show how to get started with GPU DataFrames using cuDF in RAPIDS.\n",
- "\n",
- "**Table of Contents**\n",
- "\n",
- "* Setup\n",
- "* Loading data into a GPU DataFrame (GDF)\n",
- " * Loading data into a Pandas DataFrame\n",
- " * Converting a Pandas DataFrame to a GDF\n",
- "* Working with the GDF\n",
- " * Take a look at the columns and their data types\n",
- " * Slice the GDF\n",
- " * Modify data types\n",
- " * Manipulate data with a user-defined function (UDF)\n",
- " * Sort the data\n",
- " * Filter the data\n",
- " * One-hot encode categorical columns\n",
- " * Split the data into training and validation sets\n",
- " * Turn the GDFs into matrices\n",
- "* Conclusion"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Setup\n",
- "\n",
- "This notebook was tested using the `rapidsai/rapidsai-dev-nightly:0.10-cuda10.0-devel-ubuntu18.04-py3.7` container from [DockerHub](https://hub.docker.com/r/rapidsai/rapidsai-nightly) and run on the NVIDIA GV100 GPU. Please be aware that your system may be different and you may need to modify the code or install packages to run the below examples. \n",
- "\n",
- "If you think you have found a bug or an error, please file an issue here: https://github.com/rapidsai/notebooks-contrib/issues\n",
- "\n",
- "Before we begin, let's check out our hardware setup by running the `nvidia-smi` command."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!nvidia-smi"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Next, let's see what CUDA version we have:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!nvcc --version"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Last, let's ensure that we have graphviz installed"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 17,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "done\n",
- "\n",
- "\n",
- "==> WARNING: A newer version of conda exists. <==\n",
- " current version: 4.6.14\n",
- " latest version: 4.7.12\n",
- "\n",
- "Please update conda by running\n",
- "\n",
- " $ conda update -n base -c defaults conda\n",
- "\n",
- "\n",
- "\n",
- "## Package Plan ##\n",
- "\n",
- " environment location: /opt/conda/envs/rapids\n",
- "\n",
- " added / updated specs:\n",
- " - python-graphviz\n",
- "\n",
- "\n",
- "The following packages will be downloaded:\n",
- "\n",
- " package | build\n",
- " ---------------------------|-----------------\n",
- " python-graphviz-0.10.1 | py_0 22 KB\n",
- " ------------------------------------------------------------\n",
- " Total: 22 KB\n",
- "\n",
- "The following packages will be SUPERSEDED by a higher-priority channel:\n",
- "\n",
- " python-graphviz conda-forge::python-graphviz-0.13-py_0 --> pkgs/main::python-graphviz-0.10.1-py_0\n",
- "\n",
- "\n",
- "\n",
- "Downloading and Extracting Packages\n",
- "python-graphviz-0.10 | 22 KB | ##################################### | 100% \n",
- "Preparing transaction: done\n",
- "Verifying transaction: done\n",
- "Executing transaction: done\n"
- ]
- }
- ],
- "source": [
- "!apt update\n",
- "!apt install -y graphviz\n",
- "!conda install -y graphviz\n",
- "!conda install -y python-graphviz"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Loading data into a GPU DataFrame (GDF)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Loading data into a Pandas DataFrame\n",
- "\n",
- "It's easy to load almost any sort of data (json, csv, etc) into a Pandas DataFrame. \n",
- "For example, let's import some census data from a compressed CSV file on disk:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import pandas as pd; print('pandas Version:', pd.__version__)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# read data from csv file into pandas dataframe\n",
- "df = pd.read_csv('../../data/ipums/ipums_easy.csv')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "[Read more on using a Pandas DataFrame](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.html)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Converting a Pandas DataFrame to a GDF\n",
- "\n",
- "Next, we use our `pandas.DataFrame` and to create a `cudf.dataframe.DataFrame` object using the `from_pandas` method."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import cudf\n",
- "\n",
- "# convert the Panda dataframe into a GPU dataframe\n",
- "gdf = cudf.DataFrame.from_pandas(df)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "And that's it! For the most part, working with GPU DataFrames will be the same as working with Pandas DataFrames. See the [cuDF documentation](https://cudf.readthedocs.io/en/latest/index.html) for more information.\n",
- "\n",
- "## Working with the GDF"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Take a look at the columns and their data types"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# print the columns and their datatypes in this gdf\n",
- "gdf.dtypes"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Slice the GDF\n",
- "\n",
- "Woah! This GDF has a lot of columns, let's make it more manageable..."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# only select certain columns (and overwrite the gdf)\n",
- "column_names = [\n",
- " 'INCEARN', 'PERWT', 'ADJUST', 'STATEICP', 'ROOMS', 'BEDROOMS',\n",
- " 'PHONE', 'VEHICLES', 'RACE', 'SEX', 'AGE', 'VETSTAT'\n",
- "]\n",
- "gdf = gdf.loc[:, column_names]\n",
- "\n",
- "# show the first 5 records of each column\n",
- "print(gdf.head(5))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Modify data types"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "gdf.dtypes"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Looks like `INCEARN` and `PERWT` are integers when they should be floats. Let's fix that..."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import numpy as np\n",
- "\n",
- "# convert the following two int64 columns to float64 data type\n",
- "gdf['INCEARN'] = gdf['INCEARN'].astype(np.float64)\n",
- "gdf['PERWT'] = gdf['PERWT'].astype(np.float64)\n",
- "\n",
- "# take another look\n",
- "gdf.dtypes"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Manipulate data with a user-defined function (UDF)\n",
- "\n",
- "`INCEARN` is a column in our dataset that supposedly represents income earned; however, it does not truly represent the amount of income earned when adjusted for inflation. The `ADJUST` column represents the dollar inflation factor, which we can use to adjust `INCEARN` to the amount that the individual would have earned during the calender year. In our dataset, `ADJUST` is constant over all rows.\n",
- "\n",
- "Below, we will define a simple function `adjust_incearn` that takes `INCEARN` and and multiplies it by a constant - in this case, the dollar inflation factor. We'll use the `applymap` method in our `cudf.dataframe.DataFrame` object to apply an element-wise function to transform the values in the Column."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# define a function to adjust the incearn column\n",
- "# so it more accurately represents income earned\n",
- "adjust = gdf['ADJUST'][0] # take constant from first row\n",
- "print('adjustment factor: {}'.format(adjust))\n",
- "def adjust_incearn(incearn):\n",
- " return adjust * incearn;\n",
- "\n",
- "# apply it to the 'population' column\n",
- "gdf['INCEARN'] = gdf['INCEARN'].applymap(adjust_incearn)\n",
- "\n",
- "# drop the ADJUST column\n",
- "gdf.drop_column('ADJUST')\n",
- "\n",
- "# compute the mean\n",
- "print('mean adjusted income: {}'.format(gdf['INCEARN'].mean()))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Sort the data\n",
- "\n",
- "Next, let's sort out data to do some light exploration."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# sort the gdf by the INCEARN column\n",
- "gdf = gdf.sort_values(by='INCEARN', ascending=True)\n",
- "# reset the index so we can use loc slicing later\n",
- "gdf = gdf.reset_index()\n",
- "print(gdf.head(5))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Looks like we have some negative income values. Let's filter those out...\n",
- "\n",
- "### Filter the data\n",
- "\n",
- "We'll use the `query` method to filter our dataset. The `query` method takes as argument a boolean expression very similar to the `query` method for the `pandas.DataFrame` class. However, the `cudf.dataframe.DataFrame` implementation uses Numba to compile a GPU kernel. \n",
- "\n",
- "For more information on the syntax for arguments into `query`, see the Pandas documentation: \n",
- "\n",
- "https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.query.html"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# how many records do we have?\n",
- "print(\"{} = Original # of records\".format(len(gdf)))\n",
- "\n",
- "# filter out\n",
- "gdf = gdf.query('INCEARN >= 0')\n",
- "\n",
- "# how many records do we have left?\n",
- "print(\"{} = New # of records\".format(len(gdf)))\n",
- "\n",
- "# sanity check...\n",
- "print(gdf.head(5))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### One-hot encode categorical columns\n",
- "\n",
- "Next, let's prepare our categorical columns. Machine learning models won't take strings as inputs, so we need to go to each column and convert its string representations to a numerical representation. The most common way to convert a Column with `n` elements and `k` unique categories to a numerical representation is to create a matrix of shape `n` by `k` and impute a 1 in cell `(i, j)` if the `ith` element is of category `j` and 0 otherwise, where $j \\in k$. This is known as one-hot encoding."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# define the categorical columns\n",
- "cat_cols = set(['STATEICP', 'RACE', 'SEX', 'VETSTAT'])\n",
- "# store the unique values for each category column\n",
- "uniques = {}\n",
- "\n",
- "# iterate through each categorical column and one-hot\n",
- "# encode it using the unique values it has\n",
- "for k in cat_cols:\n",
- " uniques[k] = gdf[k].unique_k(k=1000)\n",
- " cats = uniques[k][1:] # drop first\n",
- " gdf = gdf.one_hot_encoding(k, prefix=k, cats=cats)\n",
- " del gdf[k]\n",
- " \n",
- "# we should see many more columns since the categorical\n",
- "# columns will get expanded due to one-hot encoding\n",
- "gdf.dtypes"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Split the data into training and validation sets\n",
- "\n",
- "Next, let's split out data into an 80% train dataset and a 20% validation dataset."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# enforce float64 data type on ALL columns\n",
- "for k in gdf.columns:\n",
- " gdf[k] = gdf[k].astype(np.float64)\n",
- "\n",
- "# set the fractions for training and validation\n",
- "fractions = {\n",
- " \"train\": 0.8,\n",
- " \"valid\": 0.2\n",
- "}\n",
- "\n",
- "# validation splitpoint\n",
- "splitpoint = int(len(gdf) * fractions[\"train\"])\n",
- "print('splitpoint: {} of {} is {}'.format(fractions[\"train\"], len(gdf), splitpoint))\n",
- "\n",
- "# break the gdf up into training and validation sets\n",
- "gdfs = {\n",
- " \"train\": gdf.loc[:splitpoint],\n",
- " \"valid\": gdf.loc[splitpoint:]\n",
- "}\n",
- "print('gdfs[\"train\"] has {} rows'.format(len(gdfs[\"train\"])))\n",
- "print('gdfs[\"valid\"] has {} rows'.format(len(gdfs[\"valid\"])))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Turn the GDFs into matrices\n",
- "\n",
- "Lastly, we want to convert our GPU DataFrame to a GPU Matrix for usage as input to other machine learning libraries such as cuML and XGBoost. We can use the `as_gpu_matrix` method to facillitate this conversion."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# produce gpu matrices (to input to ML libraries, etc.)\n",
- "# this step should not be necessary in the near future\n",
- "# (should be able to use gdf as input)\n",
- "matrices = {\n",
- " \"train\": {\n",
- " \"x\": gdfs[\"train\"].as_gpu_matrix(columns=gdf.columns[1:]),\n",
- " \"y\": gdfs[\"train\"].as_gpu_matrix(columns=[gdf.columns[0]])\n",
- " },\n",
- " \"valid\": {\n",
- " \"x\": gdfs[\"valid\"].as_gpu_matrix(columns=gdf.columns[1:]),\n",
- " \"y\": gdfs[\"valid\"].as_gpu_matrix(columns=[gdf.columns[0]])\n",
- " }\n",
- "}\n",
- "\n",
- "# check the matrix shapes (sanity check)\n",
- "print('matrices[\"train\"][\"x\"] shape:', matrices[\"train\"][\"x\"].shape)\n",
- "print('matrices[\"train\"][\"y\"] shape:', matrices[\"train\"][\"y\"].shape)\n",
- "print('matrices[\"valid\"][\"x\"] shape:', matrices[\"valid\"][\"x\"].shape)\n",
- "print('matrices[\"valid\"][\"y\"] shape:', matrices[\"valid\"][\"y\"].shape)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Conclusion\n",
- "\n",
- "To learn more about RAPIDS, be sure to check out: \n",
- "\n",
- "* [Open Source Website](http://rapids.ai)\n",
- "* [GitHub](https://github.com/rapidsai/)\n",
- "* [Press Release](https://nvidianews.nvidia.com/news/nvidia-introduces-rapids-open-source-gpu-acceleration-platform-for-large-scale-data-analytics-and-machine-learning)\n",
- "* [NVIDIA Blog](https://blogs.nvidia.com/blog/2018/10/10/rapids-data-science-open-source-community/)\n",
- "* [Developer Blog](https://devblogs.nvidia.com/gpu-accelerated-analytics-rapids/)\n",
- "* [NVIDIA Data Science Webpage](https://www.nvidia.com/en-us/deep-learning-ai/solutions/data-science/)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.3"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/getting_started_notebooks/intro_tutorials/01_Introduction_to_RAPIDS.ipynb b/getting_started_notebooks/intro_tutorials/01_Introduction_to_RAPIDS.ipynb
deleted file mode 100644
index b0124b36..00000000
--- a/getting_started_notebooks/intro_tutorials/01_Introduction_to_RAPIDS.ipynb
+++ /dev/null
@@ -1,708 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Introduction to RAPIDS\n",
- "#### By Paul Hendricks\n",
- "-------\n",
- "\n",
- "While the world’s data doubles each year, CPU computing has hit a brick wall with the end of Moore’s law. For the same reasons, scientific computing and deep learning has turned to NVIDIA GPU acceleration, data analytics and machine learning where GPU acceleration is ideal. \n",
- "\n",
- "NVIDIA created RAPIDS – an open-source data analytics and machine learning acceleration platform that leverages GPUs to accelerate computations. RAPIDS is based on Python, has Pandas-like and Scikit-Learn-like interfaces, is built on Apache Arrow in-memory data format, and can scale from 1 to multi-GPU to multi-nodes. RAPIDS integrates easily into the world’s most popular data science Python-based workflows. RAPIDS accelerates data science end-to-end – from data prep, to machine learning, to deep learning. And through Arrow, Spark users can easily move data into the RAPIDS platform for acceleration.\n",
- "\n",
- "In this notebook, we will discuss and show at a high level what each of the packages in the RAPIDS are as well as what they do. Subsequent notebooks will dive deeper into the various areas of data science and machine learning and show how you can use RAPIDS to accelerate your workflow in each of these areas.\n",
- "\n",
- "**Table of Contents**\n",
- "\n",
- "* [Introduction to RAPIDS](#introduction)\n",
- "* [Setup](#setup)\n",
- "* [Pandas](#pandas)\n",
- "* [cuDF](#cudf)\n",
- "* [Scikit-Learn](#scikitlearn)\n",
- "* [cuML](#cuml)\n",
- "* [Dask](#dask)\n",
- "* [Dask cuDF](#daskcudf)\n",
- "* [Conclusion](#conclusion)\n",
- "\n",
- "Before going any further, let's make sure we have access to `matplotlib`, a popular Python library for visualizing data."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import os\n",
- "\n",
- "try:\n",
- " import matplotlib\n",
- "except ModuleNotFoundError:\n",
- " os.system('conda install -y matplotlib')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Setup\n",
- "\n",
- "This notebook was tested using the following Docker containers:\n",
- "\n",
- "* `rapidsai/rapidsai-dev-nightly:0.10-cuda10.0-devel-ubuntu18.04-py3.7` container from [DockerHub](https://hub.docker.com/r/rapidsai/rapidsai-nightly)\n",
- "\n",
- "This notebook was run on the NVIDIA GV100 GPU. Please be aware that your system may be different and you may need to modify the code or install packages to run the below examples. \n",
- "\n",
- "If you think you have found a bug or an error, please file an issue here: https://github.com/rapidsai/notebooks-contrib/issues\n",
- "\n",
- "Before we begin, let's check out our hardware setup by running the `nvidia-smi` command."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!nvidia-smi"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Next, let's see what CUDA version we have:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!nvcc --version"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Next, let's load some helper functions from `matplotlib` and configure the Jupyter Notebook for visualization."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from matplotlib.colors import ListedColormap\n",
- "import matplotlib.pyplot as plt\n",
- "\n",
- "\n",
- "%matplotlib inline"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Pandas\n",
- "\n",
- "Data scientists typically work with two types of data: unstructured and structured. Unstructured data often comes in the form of text, images, or videos. Structured data - as the name suggests - comes in a structured form, often represented by a table or CSV. We'll focus the majority of these tutorials on working with these types of data.\n",
- "\n",
- "There exist many tools in the Python ecosystem for working with structured, tabular data but few are as widely used as Pandas. Pandas represents data in a table and allows a data scientist to manipulate the data to perform a number of useful operations such as filtering, transforming, aggregating, merging, visualizing and many more. \n",
- "\n",
- "For more information on Pandas, check out the excellent documentation: http://pandas.pydata.org/pandas-docs/stable/\n",
- "\n",
- "Below we show how to create a Pandas DataFrame, an internal object for representing tabular data."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import pandas as pd; print('Pandas Version:', pd.__version__)\n",
- "\n",
- "\n",
- "# here we create a Pandas DataFrame with\n",
- "# two columns named \"key\" and \"value\"\n",
- "df = pd.DataFrame()\n",
- "df['key'] = [0, 0, 2, 2, 3]\n",
- "df['value'] = [float(i + 10) for i in range(5)]\n",
- "print(df)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We can perform many operations on this data. For example, let's say we wanted to sum all values in the in the `value` column. We could accomplish this using the following syntax:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "aggregation = df['value'].sum()\n",
- "print(aggregation)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## cuDF\n",
- "\n",
- "Pandas is fantastic for working with small datasets that fit into your system's memory. However, datasets are growing larger and data scientists are working with increasingly complex workloads - the need for accelerated compute arises.\n",
- "\n",
- "cuDF is a package within the RAPIDS ecosystem that allows data scientists to easily migrate their existing Pandas workflows from CPU to GPU, where computations can leverage the immense parallelization that GPUs provide.\n",
- "\n",
- "Below, we show how to create a cuDF DataFrame."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import cudf; print('cuDF Version:', cudf.__version__)\n",
- "\n",
- "\n",
- "# here we create a cuDF DataFrame with\n",
- "# two columns named \"key\" and \"value\"\n",
- "df = cudf.DataFrame()\n",
- "df['key'] = [0, 0, 2, 2, 3]\n",
- "df['value'] = [float(i + 10) for i in range(5)]\n",
- "print(df)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "As before, we can take this cuDF DataFrame and perform a `sum` operation over the `value` column. The key difference is that any operations we perform using cuDF use the GPU instead of the CPU."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "aggregation = df['value'].sum()\n",
- "print(aggregation)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Note how the syntax for both creating and manipulating a cuDF DataFrame is identical to the syntax necessary to create and manipulate Pandas DataFrames; the cuDF API is based on the Pandas API. This design choice minimizes the cognitive burden of switching from a CPU based workflow to a GPU based workflow and allows data scientists to focus on solving problems while benefitting from the speed of a GPU!"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Scikit-Learn\n",
- "\n",
- "After our data has been preprocessed, we often want to build a model so as to understand the relationships between different variables in our data. Scikit-Learn is an incredibly powerful toolkit that allows data scientists to quickly build models from their data. Below we show a simple example of how to create a Linear Regression model."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import numpy as np; print('NumPy Version:', np.__version__)\n",
- "\n",
- "\n",
- "# create the relationship: y = 2.0 * x + 1.0\n",
- "n_rows = 100000 # let's use 100 thousand data points\n",
- "w = 2.0\n",
- "x = np.random.normal(loc=0, scale=1, size=(n_rows,))\n",
- "b = 1.0\n",
- "y = w * x + b\n",
- "\n",
- "# add a bit of noise\n",
- "noise = np.random.normal(loc=0, scale=2, size=(n_rows,))\n",
- "y_noisy = y + noise"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We can now visualize our data using the `matplotlib` library."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "plt.scatter(x, y_noisy, label='empirical data points')\n",
- "plt.plot(x, y, color='black', label='true relationship')\n",
- "plt.legend()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We'll use the `LinearRegression` class from Scikit-Learn to instantiate a model and fit it to our data."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import sklearn; print('Scikit-Learn Version:', sklearn.__version__)\n",
- "from sklearn.linear_model import LinearRegression\n",
- "\n",
- "\n",
- "# instantiate and fit model\n",
- "linear_regression = LinearRegression()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "%%time\n",
- "\n",
- "linear_regression.fit(np.expand_dims(x, 1), y)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# create new data and perform inference\n",
- "inputs = np.linspace(start=-5, stop=5, num=1000)\n",
- "outputs = linear_regression.predict(np.expand_dims(inputs, 1))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's now visualize our empirical data points, the true relationship of the data, and the relationship estimated by the model. Looks pretty close!"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "plt.scatter(x, y_noisy, label='empirical data points')\n",
- "plt.plot(x, y, color='black', label='true relationship')\n",
- "plt.plot(inputs, outputs, color='red', label='predicted relationship (cpu)')\n",
- "plt.legend()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## cuML\n",
- "\n",
- "The mathematical operations underlying many machine learning algorithms are often matrix multiplications. These types of operations are highly parallelizable and can be greatly accelerated using a GPU. cuML makes it easy to build machine learning models in an accelerated fashion while still using an interface nearly identical to Scikit-Learn. The below shows how to accomplish the same Linear Regression model but on a GPU.\n",
- "\n",
- "First, let's convert our data from a NumPy representation to a cuDF representation."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# create a cuDF DataFrame\n",
- "df = cudf.DataFrame({'x': x, 'y': y_noisy})\n",
- "print(df.head())"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Next, we'll load the GPU accelerated `LinearRegression` class from cuML, instantiate it, and fit it to our data."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import cuml; print('cuML Version:', cuml.__version__)\n",
- "from cuml.linear_model import LinearRegression as LinearRegression_GPU\n",
- "\n",
- "\n",
- "# instantiate and fit model\n",
- "linear_regression_gpu = LinearRegression_GPU()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "%%time\n",
- "\n",
- "linear_regression_gpu.fit(df[['x']], df['y'])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We can use this model to predict values for new data points, a step often called \"inference\" or \"scoring\". All model fitting and predicting steps are GPU accelerated."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# create new data and perform inference\n",
- "new_data_df = cudf.DataFrame({'inputs': inputs})\n",
- "outputs_gpu = linear_regression_gpu.predict(new_data_df[['inputs']])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Lastly, we can overlay our predicted relationship using our GPU accelerated Linear Regression model (green line) over our empirical data points (light blue circles), the true relationship (blue line), and the predicted relationship from a model built on the CPU (red line). We see that our GPU accelerated model's estimate of the true relationship (green line) is identical to the CPU based model's estimate of the true relationship (red line)!"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "plt.scatter(x, y_noisy, label='empirical data points')\n",
- "plt.plot(x, y, color='black', label='true relationship')\n",
- "plt.plot(inputs, outputs, color='red', label='predicted relationship (cpu)')\n",
- "plt.plot(inputs, outputs_gpu.to_array(), color='green', label='predicted relationship (gpu)')\n",
- "plt.legend()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Dask\n",
- "\n",
- "Dask is a library the allows facillitates distributed computing. Written in Python, it allows one to compose complex workflows using basic Python primitives like integers or strings as well as large data structures like those found in NumPy, Pandas, and cuDF. In the following examples and notebooks, we'll show how to use Dask with cuDF to accelerate common ETL tasks and train machine learning models like Linear Regression and XGBoost.\n",
- "\n",
- "To learn more about Dask, check out the documentation here: http://docs.dask.org/en/latest/\n",
- "\n",
- "#### Client/Workers\n",
- "\n",
- "Dask operates by creating a cluster composed of a \"client\" and multiple \"workers\". The client is responsible for scheduling work; the workers are responsible for actually executing that work. \n",
- "\n",
- "Typically, we set the number of workers to be equal to the number of computing resources we have available to us. For CPU based workflows, this might be the number of cores or threads on that particlular machine. For example, we might set `n_workers = 8` if we have 8 CPU cores or threads on our machine that can each operate in parallel. This allows us to take advantage of all of our computing resources and enjoy the most benefits from parallelization.\n",
- "\n",
- "To get started, we'll create a local cluster of workers and client to interact with that cluster."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import dask; print('Dask Version:', dask.__version__)\n",
- "from dask.distributed import Client, LocalCluster\n",
- "\n",
- "\n",
- "# create a local cluster with 4 workers\n",
- "n_workers = 4\n",
- "cluster = LocalCluster(n_workers=n_workers)\n",
- "client = Client(cluster)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's inspect the `client` object to view our current Dask status. We should see the IP Address for our Scheduler as well as the the number of workers in our Cluster. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# show current Dask status\n",
- "client"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "You can also see the status and more information at the Dashboard, found at `http:///status`. You can ignore this for now, we'll dive into this in subsequent tutorials.\n",
- "\n",
- "With our client and cluster of workers setup, it's time to execute our first distributed program. We'll define a function called `sleep_1` that sleeps for 1 second and returns the string \"Success!\". Executed in serial four times, this function should take around 4 seconds to execute."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import time\n",
- "\n",
- "\n",
- "def sleep_1():\n",
- " time.sleep(1)\n",
- " return 'Success!'"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "%%time\n",
- "\n",
- "for _ in range(n_workers):\n",
- " sleep_1()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "As expected, our workflow takes about 4 seconds to run. Now let's execute this same workflow in distributed fashion using Dask."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from dask.delayed import delayed"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "%%time\n",
- "\n",
- "# define delayed execution graph\n",
- "sleep_operations = [delayed(sleep_1)() for _ in range(n_workers)]\n",
- "\n",
- "# use client to perform computations using execution graph\n",
- "sleep_futures = client.compute(sleep_operations, optimize_graph=False, fifo_timeout=\"0ms\")\n",
- "\n",
- "# collect and print results\n",
- "sleep_results = client.gather(sleep_futures)\n",
- "print(sleep_results)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Using Dask, we see that this whole workflow takes a little over a second - each worker is truly executing in parallel!"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Dask cuDF\n",
- "\n",
- "In the previous example, we saw how we can use Dask with very basic objects to compose a graph that can be executed in a distributed fashion. However, we aren't limited to basic data types though. \n",
- "\n",
- "We can use Dask with objects such as Pandas DataFrames, NumPy arrays, and cuDF DataFrames to compose more complex workflows. With larger amounts of data and embarrasingly parallel algorithms, Dask allows us to scale ETL and Machine Learning workflows to Gigabytes or Terabytes of data. In the below example, we show how we can process 100 million rows by combining cuDF with Dask.\n",
- "\n",
- "Before we start working with cuDF DataFrames with Dask, we need to setup a Local CUDA Cluster and Client to work with our GPUs. This is very similar to how we setup a Local Cluster and Client in vanilla Dask."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import dask; print('Dask Version:', dask.__version__)\n",
- "from dask.distributed import Client\n",
- "# import dask_cuda; print('Dask CUDA Version:', dask_cuda.__version__)\n",
- "from dask_cuda import LocalCUDACluster\n",
- "\n",
- "\n",
- "# create a local CUDA cluster\n",
- "cluster = LocalCUDACluster()\n",
- "client = Client(cluster)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's inspect our `client` object:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "client"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "As before, you can also see the status of the Client along with information on the Scheduler and Dashboard.\n",
- "\n",
- "With our client and workers setup, let's create our first distributed cuDF DataFrame using Dask. We'll instantiate our cuDF DataFrame in the same manner as the previous sections but instead we'll use significantly more data. Lastly, we'll pass the cuDF DataFrame to `dask_cudf.from_cudf` and create an object of type `dask_cudf.core.DataFrame`."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import dask_cudf; print('Dask cuDF Version:', dask_cudf.__version__)\n",
- "\n",
- "\n",
- "# identify number of workers\n",
- "workers = client.has_what().keys()\n",
- "n_workers = len(workers)\n",
- "\n",
- "# create a cuDF DataFrame with two columns named \"key\" and \"value\"\n",
- "df = cudf.DataFrame()\n",
- "n_rows = 100000000 # let's process 100 million rows in a distributed parallel fashion\n",
- "df['key'] = np.random.binomial(1, 0.2, size=(n_rows))\n",
- "df['value'] = np.random.normal(size=(n_rows))\n",
- "\n",
- "# create a distributed cuDF DataFrame using Dask\n",
- "distributed_df = dask_cudf.from_cudf(df, npartitions=n_workers)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# inspect our distributed cuDF DataFrame using Dask\n",
- "print('-' * 15)\n",
- "print('Type of our Dask cuDF DataFrame:', type(distributed_df))\n",
- "print('-' * 15)\n",
- "print(distributed_df.head())"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The above output shows the first several rows of our distributed cuDF DataFrame.\n",
- "\n",
- "With our Dask cuDF DataFrame defined, we can now perform the same `sum` operation as we did with our cuDF DataFrame. The key difference is that this operation is now distributed - meaning we can perform this operation using multiple GPUs or even multiple nodes, each of which may have multiple GPUs. This allows us to scale to larger and larger amounts of data!"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "aggregation = distributed_df['value'].sum()\n",
- "print(aggregation.compute())"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Conclusion\n",
- "\n",
- "In this notebook, we showed at a high level what each of the packages in the RAPIDS are as well as what they do.\n",
- "\n",
- "To learn more about RAPIDS, be sure to check out: \n",
- "\n",
- "* [Open Source Website](http://rapids.ai)\n",
- "* [GitHub](https://github.com/rapidsai/)\n",
- "* [Press Release](https://nvidianews.nvidia.com/news/nvidia-introduces-rapids-open-source-gpu-acceleration-platform-for-large-scale-data-analytics-and-machine-learning)\n",
- "* [NVIDIA Blog](https://blogs.nvidia.com/blog/2018/10/10/rapids-data-science-open-source-community/)\n",
- "* [Developer Blog](https://devblogs.nvidia.com/gpu-accelerated-analytics-rapids/)\n",
- "* [NVIDIA Data Science Webpage](https://www.nvidia.com/en-us/deep-learning-ai/solutions/data-science/)\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.3"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/getting_started_notebooks/intro_tutorials/06_Introduction_to_Supervised_Learning.ipynb b/getting_started_notebooks/intro_tutorials/06_Introduction_to_Supervised_Learning.ipynb
deleted file mode 100644
index 5844e3f6..00000000
--- a/getting_started_notebooks/intro_tutorials/06_Introduction_to_Supervised_Learning.ipynb
+++ /dev/null
@@ -1,838 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Introduction to Supervised Learning\n",
- "#### By Paul Hendricks\n",
- "-------\n",
- "\n",
- "In this notebook, we will show to do GPU accelerated Supervised Learning in RAPIDS. We will not cover SGD Regression at this time.\n",
- "\n",
- "**Table of Contents**\n",
- "\n",
- "* [Introduction to Supervised Learning](#introduction)\n",
- "* [Linear Regression](#linear)\n",
- "* [Ridge Regression](#ridge)\n",
- "* [K Nearest Neighbors](#knn)\n",
- "* [Setup](#setup)\n",
- "* [Conclusion](#conclusion)\n",
- "\n",
- "Before going any further, let's make sure we have access to `matplotlib`, a popular Python library for visualizing data."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 1,
- "metadata": {},
- "outputs": [],
- "source": [
- "import os\n",
- "import subprocess\n",
- "\n",
- "try:\n",
- " import matplotlib\n",
- "except ModuleNotFoundError:\n",
- " os.system('conda install -y matplotlib')\n",
- " import matplotlib\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Setup\n",
- "\n",
- "This notebook was tested using the following Docker containers:\n",
- "\n",
- "* `rapidsai/rapidsai-dev-nightly:0.12-cuda10.1-runtime-ubuntu18.04-py3.7` container from [DockerHub](https://hub.docker.com/r/rapidsai/rapidsai-nightly)\n",
- "\n",
- "This notebook was run on the NVIDIA GV100 GPU. Please be aware that your system may be different and you may need to modify the code or install packages to run the below examples. \n",
- "\n",
- "If you think you have found a bug or an error, please file an issue here: https://github.com/rapidsai/notebooks-contrib/issues\n",
- "\n",
- "Before we begin, let's check out our hardware setup by running the `nvidia-smi` command."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!nvidia-smi"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Next, let's see what CUDA version we have:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!nvcc --version"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Next, let's load some helper functions from `matplotlib` and configure the Jupyter Notebook for visualization."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {},
- "outputs": [],
- "source": [
- "from matplotlib.colors import ListedColormap\n",
- "import matplotlib.pyplot as plt\n",
- "\n",
- "\n",
- "%matplotlib inline"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Linear Regression\n",
- "\n",
- "After our data has been preprocessed, we often want to build a model so as to understand the relationships between different variables in our data. Scikit-Learn is an incredibly powerful toolkit that allows data scientists to quickly build models from their data. Below we show a simple example of how to create a Linear Regression model."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "NumPy Version: 1.17.5\n"
- ]
- }
- ],
- "source": [
- "import numpy as np; print('NumPy Version:', np.__version__)\n",
- "\n",
- "\n",
- "# create the relationship: y = 2.0 * x + 1.0\n",
- "\n",
- "n_rows = 46000\n",
- "w = 2.0\n",
- "x = np.random.normal(loc=0, scale=1, size=(n_rows,))\n",
- "b = 1.0\n",
- "y = w * x + b\n",
- "\n",
- "# add a bit of noise\n",
- "noise = np.random.normal(loc=0, scale=2, size=(n_rows,))\n",
- "y_noisy = y + noise"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We can now visualize our data using the `matplotlib` library."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- ""
- ]
- },
- "execution_count": 4,
- "metadata": {},
- "output_type": "execute_result"
- },
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- "
"
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "plt.scatter(x, y_noisy, label='empirical data points')\n",
- "plt.plot(x, y, color='black', label='true relationship')\n",
- "plt.plot(inputs, outputs, color='red', label='predicted relationship (cpu)')\n",
- "plt.legend()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The mathematical operations underlying many machine learning algorithms are often matrix multiplications. These types of operations are highly parallelizable and can be greatly accelerated using a GPU. cuML makes it easy to build machine learning models in an accelerated fashion while still using an interface nearly identical to Scikit-Learn. The below shows how to accomplish the same Linear Regression model but on a GPU.\n",
- "\n",
- "First, let's convert our data from a NumPy representation to a cuDF representation."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "cuDF Version: 0.12.0b+1877.g8b04eb7\n",
- " x y\n",
- "0 -0.445580 2.929034\n",
- "1 1.065418 -0.256664\n",
- "2 -1.133438 -1.950435\n",
- "3 1.977738 6.854074\n",
- "4 3.121144 6.280575\n"
- ]
- }
- ],
- "source": [
- "import cudf; print('cuDF Version:', cudf.__version__)\n",
- "\n",
- "\n",
- "# create a cuDF DataFrame\n",
- "df = cudf.DataFrame({'x': x, 'y': y_noisy})\n",
- "print(df.head())"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Next, we'll load the GPU accelerated `LinearRegression` class from cuML, instantiate it, and fit it to our data."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 10,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "cuML Version: 0.12.0a+752.g564916e\n"
- ]
- }
- ],
- "source": [
- "import cuml; print('cuML Version:', cuml.__version__)\n",
- "from cuml.linear_model import LinearRegression as LinearRegression_GPU\n",
- "\n",
- "\n",
- "# instantiate and fit model\n",
- "linear_regression_gpu = LinearRegression_GPU()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 11,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "CPU times: user 535 ms, sys: 186 ms, total: 721 ms\n",
- "Wall time: 717 ms\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "LinearRegression(algorithm='eig', fit_intercept=True, normalize=False, handle=)"
- ]
- },
- "execution_count": 11,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "%%time\n",
- "\n",
- "linear_regression_gpu.fit(df['x'], df['y'])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We can use this model to predict values for new data points, a step often called \"inference\" or \"scoring\". All model fitting and predicting steps are GPU accelerated."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 12,
- "metadata": {},
- "outputs": [],
- "source": [
- "# create new data and perform inference\n",
- "new_data_df = cudf.DataFrame({'inputs': inputs})\n",
- "outputs_gpu = linear_regression_gpu.predict(new_data_df[['inputs']])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Lastly, we can overlay our predicted relationship using our GPU accelerated Linear Regression model (green line) over our empirical data points (light blue circles), the true relationship (blue line), and the predicted relationship from a model built on the CPU (red line). We see that our GPU accelerated model's estimate of the true relationship (green line) is identical to the CPU based model's estimate of the true relationship (red line)!"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 13,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- ""
- ]
- },
- "execution_count": 13,
- "metadata": {},
- "output_type": "execute_result"
- },
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- "
"
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "plt.scatter(x, y_noisy, label='empirical data points')\n",
- "plt.plot(x, y, color='black', label='true relationship')\n",
- "plt.plot(inputs, outputs, color='red', label='predicted relationship (cpu)')\n",
- "plt.plot(inputs, outputs_gpu.to_array(), color='green', label='predicted relationship (gpu)')\n",
- "plt.legend()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Ridge Regression\n",
- "\n",
- "Ridge extends LinearRegression by providing L2 regularization on the coefficients when predicting response y with a linear combination of the predictors in X. It can reduce the variance of the predictors, and improves the conditioning of the problem.\n",
- "\n",
- "Below, we instantiate and fit a Ridge Regression model to our data."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 14,
- "metadata": {},
- "outputs": [],
- "source": [
- "from cuml.linear_model import Ridge as Ridge_GPU\n",
- "\n",
- "\n",
- "# instantiate and fit model\n",
- "ridge_regression_gpu = Ridge_GPU()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 15,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "CPU times: user 20.7 ms, sys: 20.5 ms, total: 41.2 ms\n",
- "Wall time: 40.4 ms\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "Ridge(alpha=1.0, solver='eig', fit_intercept=True, normalize=False, handle=)"
- ]
- },
- "execution_count": 15,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "%%time\n",
- "\n",
- "ridge_regression_gpu.fit(df[['x']], df['y'])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Similar to the `LinearRegression` model we fitted early, we can use the `predict` method to generate predictions for new data."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 16,
- "metadata": {},
- "outputs": [],
- "source": [
- "outputs_gpu = ridge_regression_gpu.predict(new_data_df[['inputs']])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Lastly, we can visualize our `Ridge` model's estimated relationship and overlay it our the empirical data points."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 17,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- ""
- ]
- },
- "execution_count": 17,
- "metadata": {},
- "output_type": "execute_result"
- },
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- "
"
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "plt.scatter(x, y_noisy, label='empirical data points')\n",
- "plt.plot(x, y, color='black', label='true relationship')\n",
- "plt.plot(inputs, outputs, color='red', label='linear regression (cpu)')\n",
- "plt.plot(inputs, outputs_gpu.to_array(), color='green', label='ridge regression (gpu)')\n",
- "plt.legend()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## K Nearest Neighbors\n",
- "\n",
- "NearestNeighbors is a unsupervised algorithm where if one wants to find the “closest” datapoint(s) to new unseen data, one can calculate a suitable “distance” between each and every point, and return the top K datapoints which have the smallest distance to it.\n",
- "\n",
- "We'll generate some fake data using the `make_moons` function from the `sklearn.datasets` module. This function generates data points from two equations, each describing a half circle with a unique center. Since each data point is generated by one of these two equations, the cluster each data point belongs to is clear. The ideal classification algorithm will identify two clusters and associate each data point with the equation that generated it. \n",
- "\n",
- "These data points are generated using a non-linear relationship - so using a linear regression approach won't adequately solve problem. Instead, we can use a distance-based algorithm K Nearest Neighbors to classify each data point.\n",
- "\n",
- "First, let's generate out data."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 18,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "(1000, 2)\n"
- ]
- }
- ],
- "source": [
- "from sklearn.datasets import make_moons\n",
- "\n",
- "\n",
- "X, y = make_moons(n_samples=int(1e3), noise=0.05, random_state=0)\n",
- "print(X.shape)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's visualize our data:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 19,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- "
"
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "figure = plt.figure()\n",
- "axis = figure.add_subplot(111)\n",
- "axis.scatter(X[y == 0, 0], X[y == 0, 1], \n",
- " edgecolor='black',\n",
- " c='lightblue', marker='o', s=40, label='cluster 1')\n",
- "\n",
- "axis.scatter(X[y == 1, 0], X[y == 1, 1], \n",
- " edgecolor='black',\n",
- " c='red', marker='s', s=40, label='cluster 2')\n",
- "plt.legend()\n",
- "plt.tight_layout()\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Before we build a KNN classification model, we first have to convert our data to a cuDF representation."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 20,
- "metadata": {},
- "outputs": [],
- "source": [
- "X_df = cudf.DataFrame()\n",
- "for column in range(X.shape[1]):\n",
- " X_df['feature_' + str(column)] = np.ascontiguousarray(X[:, column])\n",
- "\n",
- "y_df = cudf.Series(y)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Next, we'll instantiate and fit a nearest neighbors model using the `NearestNeighbors` class from cuML."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 21,
- "metadata": {},
- "outputs": [],
- "source": [
- "from cuml.neighbors import NearestNeighbors\n",
- "\n",
- "\n",
- "knn = NearestNeighbors()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 22,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "NearestNeighbors(n_neighbors=5, verbose=False, handle=, algorithm='brute', metric='euclidean')"
- ]
- },
- "execution_count": 22,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "knn.fit(X_df)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Once our model has been built and fitted to the data, we can query the model for the `k` nearest neighbors to each data point. The query returns a matrix representating the distances of each data point to its nearest `k` neighbors as well as the indices of those neighbors."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 23,
- "metadata": {},
- "outputs": [],
- "source": [
- "k = 3\n",
- "\n",
- "distances, indices = knn.kneighbors(X_df, n_neighbors=k)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We can iterate through each of our data points and do a majority vote to determine which class it belongs to."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 24,
- "metadata": {},
- "outputs": [],
- "source": [
- "predictions = []\n",
- "\n",
- "for i in range(indices.shape[0]):\n",
- " row = indices.iloc[i, :]\n",
- " vote = sum(y_df[j] for j in row) / k\n",
- " predictions.append(1.0 * (vote > 0.5))\n",
- "\n",
- "predictions = np.asarray(predictions).astype(np.float32)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Lastly, we can visualize the predictions from our K Nearest Neighbors classifier - we see that despite the non-linearity of the data, the algorithm does an excellent job of classifying the data."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 25,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- "
"
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "f, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 3))\n",
- "\n",
- "\n",
- "ax1.scatter(X[y == 0, 0], X[y == 0, 1],\n",
- " edgecolor='black',\n",
- " c='lightblue', marker='o', s=40, label='cluster 1')\n",
- "ax1.scatter(X[y == 1, 0], X[y == 1, 1],\n",
- " edgecolor='black',\n",
- " c='red', marker='s', s=40, label='cluster 2')\n",
- "ax1.set_title('empirical data points')\n",
- "\n",
- "\n",
- "ax2.scatter(X[predictions == 0, 0], X[predictions == 0, 1], c='lightblue',\n",
- " edgecolor='black',\n",
- " marker='o', s=40, label='cluster 1')\n",
- "ax2.scatter(X[predictions == 1, 0], X[predictions == 1, 1], c='red',\n",
- " edgecolor='black',\n",
- " marker='s', s=40, label='cluster 2')\n",
- "ax2.set_title('KNN predicted classes')\n",
- "\n",
- "plt.legend()\n",
- "plt.tight_layout()\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "\n",
- "## Conclusion\n",
- "\n",
- "In this notebook, we showed to do GPU accelerated Supervised Learning in RAPIDS. \n",
- "\n",
- "To learn more about RAPIDS, be sure to check out: \n",
- "\n",
- "* [Open Source Website](http://rapids.ai)\n",
- "* [GitHub](https://github.com/rapidsai/)\n",
- "* [Press Release](https://nvidianews.nvidia.com/news/nvidia-introduces-rapids-open-source-gpu-acceleration-platform-for-large-scale-data-analytics-and-machine-learning)\n",
- "* [NVIDIA Blog](https://blogs.nvidia.com/blog/2018/10/10/rapids-data-science-open-source-community/)\n",
- "* [Developer Blog](https://devblogs.nvidia.com/gpu-accelerated-analytics-rapids/)\n",
- "* [NVIDIA Data Science Webpage](https://www.nvidia.com/en-us/deep-learning-ai/solutions/data-science/)\n"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.6.7"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/intermediate_notebooks/E2E/census/census_education2income_demo.ipynb b/intermediate_notebooks/E2E/census/census_education2income_demo.ipynb
deleted file mode 100644
index 4ee35863..00000000
--- a/intermediate_notebooks/E2E/census/census_education2income_demo.ipynb
+++ /dev/null
@@ -1,519 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Census Notebook\n",
- "**Authorship** \n",
- "Original Author: Taurean Dyer \n",
- "Last Edit: Taurean Dyer, 9/26/2019 \n",
- "\n",
- "**Test System Specs** \n",
- "Test System Hardware: GV100 \n",
- "Test System Software: Ubuntu 18.04 \n",
- "RAPIDS Version: 0.10.0a - Docker Install \n",
- "Driver: 410.79 \n",
- "CUDA: 10.0 \n",
- "\n",
- "\n",
- "**Known Working Systems** \n",
- "RAPIDS Versions:0.8, 0.9, 0.10\n",
- "\n",
- "# Intro\n",
- "Held every 10 years, the US census gives a detailed snapshot in time about the makeup of the country. The last census in 2010 surveyed nearly 309 million people. IPUMS.org provides researchers an open source data set with 1% to 10% of the census data set. In this notebook, we want to see how education affects total income earned in the US based on data from each census from the 1970 to 2010 and see if we can predict some results if the census was held today, according to the national average. We will go through the ETL, training the model, and then testing the prediction. We'll make every effort to get as balanced of a dataset as we can. We'll also pull some extra variables to allow for further self-exploration of gender based education and income breakdowns. On a single Titan RTX, you can run the whole notebook workflow on the 4GB dataset of 14 million rows by 44 columns in less than 3 minutes. "
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "**Let's begin!**"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Imports"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import pandas as pd\n",
- "import numpy as np\n",
- "import cuml\n",
- "import cudf\n",
- "import dask_cudf\n",
- "import sys\n",
- "import os\n",
- "from pprint import pprint\n",
- "import warnings\n",
- "warnings.filterwarnings('ignore')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Get your data!"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The ipums dataset is in our S3 bucket and zipped. \n",
- "1. We'll need to create a folder for our data in the `/data` folder\n",
- "1. Download the zipped data into that folder from S3\n",
- "1. Load the zipped data quickly into cudf using it's read_csv() parameters"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import urllib.request\n",
- "\n",
- "data_dir = '../../../data/census/'\n",
- "if not os.path.exists(data_dir):\n",
- " print('creating census data directory')\n",
- " os.system('mkdir ../../../data/census')"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# download the IPUMS dataset\n",
- "base_url = 'https://rapidsai-data.s3.us-east-2.amazonaws.com/datasets/'\n",
- "fn = 'ipums_education2income_1970-2010.csv.gz'\n",
- "if not os.path.isfile(data_dir+fn):\n",
- " print(f'Downloading {base_url+fn} to {data_dir+fn}')\n",
- " urllib.request.urlretrieve(base_url+fn, data_dir+fn)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "def load_data(cached = data_dir+fn):\n",
- " if os.path.exists(cached):\n",
- " print('use ipums data')\n",
- " X = cudf.read_csv(cached, compression='infer')\n",
- " else:\n",
- " print(\"No data found! Please check your that your data directory is ../../../data/census/ and that you downloaded the data. If you did, please delete the `../../../data/census/` directory and try the above 2 cells again\")\n",
- " X = null\n",
- " return X"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "df = load_data(data_dir+fn)\n",
- "print('data',df.shape)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "print(df.head(5).to_pandas())"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "df.dtypes"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "original_counts = df.YEAR.value_counts()\n",
- "print(original_counts) ### Remember these numbers!"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## ETL"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Cleaning Income data\n",
- "First, let's focus on cleaning out the bad values for Total Income `INCTOT`. First, let's see if there are an `N/A` values, as when we did `head()`, we saw some in other columns, like CBSERIAL"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "df['INCTOT_NA'] = df['INCTOT'].isna()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "print(df.INCTOT_NA.value_counts())"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Okay, great, there are no `N/A`s...or are there? Let's drop `INCTOT_NA` and see what our value counts look like"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "df=df.drop('INCTOT_NA')\n",
- "print(df.INCTOT.value_counts().to_pandas()) ### Wow, look how many people in America make $10,000,000! Wait a minutes... "
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Not that many people make $10M a year. Checking https://usa.ipums.org/usa-action/variables/INCTOT#codes_section, `9999999`is INCTOT's code for `N/A`. That was why when we ran `isna`, RAPIDS won't find any. Let's first create a new dataframe that is only NA values, then let's pull those encoded `N/A`s out of our working dataframe!"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "print('data',df.shape)\n",
- "tdf = df.query('INCTOT == 9999999')\n",
- "df = df.query('INCTOT != 9999999')"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "print('working data',df.shape)\n",
- "print('junk count data',tdf.shape)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We're down by nearly 1/4 of our original dataset size. For the curious, now we should be able to get accurate Total Income data, by year, not taking into account inflation"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "print(df.groupby('YEAR')['INCTOT'].mean()) # without that cleanup, the average would have bene in the millions...."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "#### Normalize Income for inflation\n",
- "Now that we have reduced our dataframe to a baseline clean data to answer our question, we should normalize the amounts for inflation. `CPI99`is the value that IPUMS uses to contian the inflation factor. All we have to do is multipy by year. Let's see how that changes the Total Income values from just above!"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "print(df.groupby('YEAR')['CPI99'].mean()) ## it just returns the CPI99\n",
- "df['INCTOT'] = df['INCTOT'] * df['CPI99']\n",
- "print(df.groupby('YEAR')['INCTOT'].mean()) ## let's see what we got!"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Cleaning Education Data\n",
- "Okay, great! Now we have income cleaned up, it should also have cleaned much of our next sets of values of interes, namely Education and Education Detailed. However, there are still some `N/A`s in key variables to worry about, which can cause problmes later. Let's create a list of them..."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "suspect = ['CBSERIAL','EDUC', 'EDUCD', 'EDUC_HEAD', 'EDUC_POP', 'EDUC_MOM','EDUCD_MOM2','EDUCD_POP2', 'INCTOT_MOM','INCTOT_POP','INCTOT_MOM2','INCTOT_POP2', 'INCTOT_HEAD']"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "for i in range(0, len(suspect)):\n",
- " df[suspect[i]] = df[suspect[i]].fillna(-1)\n",
- " print(suspect[i], df[suspect[i]].value_counts())"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Let's get drop any rows of any `-1`s in Education and Education Detailed."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "totincome = ['EDUC','EDUCD']\n",
- "for i in range(0, len(totincome)):\n",
- " query = totincome[i] + ' != -1'\n",
- " df = df.query(query)\n",
- " print(totincome[i])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "print(df.shape)\n",
- "df.head().to_pandas().head()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Well, the good news is that we lost no further rows, start to normalize the data so when we do our OLS, one year doesn't unfairly dominate the data"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Normalize the Data\n",
- "The in the last step, need to keep our data at about the same ratio as we when started (1% of the population), with the exception of 1980, which was a 5% and needs to be reduced. This is why we kept the temp dataframe `tdf` - to get the counts per year. we will find out just how many have to realize"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "print('Working data: \\n', df.YEAR.value_counts())\n",
- "print('junk count data: \\n', tdf.YEAR.value_counts())"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "And now, so that we can do MSE, let's make all the dtypes the same. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "df.dtypes"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "\n",
- "keep_cols = ['YEAR', 'DATANUM', 'SERIAL', 'CBSERIAL', 'HHWT', 'GQ', 'PERNUM', 'SEX', 'AGE', 'INCTOT', 'EDUC', 'EDUCD', 'EDUC_HEAD', 'EDUC_POP', 'EDUC_MOM','EDUCD_MOM2','EDUCD_POP2', 'INCTOT_MOM','INCTOT_POP','INCTOT_MOM2','INCTOT_POP2', 'INCTOT_HEAD', 'SEX_HEAD']\n",
- "df = df.loc[:, keep_cols]\n",
- "#df = df.drop(col for col in df.columns if col not in keep_cols)\n",
- "for i in range(0, len(keep_cols)):\n",
- " df[keep_cols[i]] = df[keep_cols[i]].fillna(-1)\n",
- " print(keep_cols[i], df[keep_cols[i]].value_counts())\n",
- " df[keep_cols[i]]= df[keep_cols[i]].astype('float64')"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "## I WANTED TO REDUCE THE 1980 SAMPLE HERE, BUT .SAMPLE() IS NEEDED AND NOT WORKING, UNLESS THERE IS A WORK AROUND..."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "With the important data now clean and normalized, let's start doing the regression"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Ridge Regression\n",
- "We have 44 variables. The other variables may provide important predictive information. The Ridge Regression technique with cross validation to identify the best hyperparamters may be the best way to get the most accurate model. We'll have to \n",
- "\n",
- "* define our performance metrics\n",
- "* split our data into train and test sets\n",
- "* train and test our model\n",
- "\n",
- "Let's begin and see what we get!"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# As our performance metrics we'll use a basic mean squared error and coefficient of determination implementation\n",
- "def mse(y_test, y_pred):\n",
- " return ((y_test.reset_index(drop=True) - y_pred.reset_index(drop=True)) ** 2).mean()\n",
- "\n",
- "def cod(y_test, y_pred):\n",
- " y_bar = y_test.mean()\n",
- " total = ((y_test - y_bar) ** 2).sum()\n",
- " residuals = ((y_test.reset_index(drop=True) - y_pred.reset_index(drop=True)) ** 2).sum()\n",
- " return 1 - (residuals / total)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from cuml.preprocessing.model_selection import train_test_split\n",
- "trainsize = .9\n",
- "yCol = \"EDUC\"\n",
- "from cuml.preprocessing.model_selection import train_test_split\n",
- "from cuml.linear_model.ridge import Ridge\n",
- "\n",
- "def train_and_score(data, clf, train_frac=0.8, n_runs=20):\n",
- " mse_scores, cod_scores = [], []\n",
- " for _ in range(n_runs):\n",
- " X_train, X_test, y_train, y_test = cuml.preprocessing.model_selection.train_test_split(df, yCol, train_size=.9)\n",
- " y_pred = clf.fit(X_train, y_train).predict(X_test)\n",
- " mse_scores.append(mse(y_test, y_pred))\n",
- " cod_scores.append(cod(y_test, y_pred))\n",
- " return mse_scores, cod_scores"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- " ## Results\n",
- " **Moment of truth! Let's see how our regression training does!**"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import numpy as np\n",
- "n_runs = 20\n",
- "clf = Ridge()\n",
- "mse_scores, cod_scores = train_and_score(df, clf, n_runs=n_runs)\n",
- "print(f\"median MSE ({n_runs} runs): {np.median(mse_scores)}\")\n",
- "print(f\"median COD ({n_runs} runs): {np.median(cod_scores)}\")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "**Fun fact:** if you made INCTOT the y axis, your prediction results would not be so pretty! It just shows that your education level can be an indicator for your income, but your income is NOT a great predictor for your education level. You have better odds flipping a coin!\n",
- "\n",
- "* median MSE (50 runs): 518189521.07548225\n",
- "* median COD (50 runs): 0.425769113846303"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Next Steps/Self Study\n",
- "* You can pickle the model and use it in another workflow\n",
- "* You can redo the workflow with based on head of household using `EDUC`, `SEX`, and `INCTOT` for X in `X`_HEAD\n",
- "* You can see the growing role of education with women in their changing role in the workforce and income with \"EDUC_MOM\" and \"EDUC_POP"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.3"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/intermediate_notebooks/E2E/mortgage/mortgage_e2e.ipynb b/intermediate_notebooks/E2E/mortgage/mortgage_e2e.ipynb
deleted file mode 100644
index 8fb2de06..00000000
--- a/intermediate_notebooks/E2E/mortgage/mortgage_e2e.ipynb
+++ /dev/null
@@ -1,893 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Mortgage Workflow\n",
- "\n",
- "## The Dataset\n",
- "The dataset used with this workflow is derived from [Fannie Mae’s Single-Family Loan Performance Data](http://www.fanniemae.com/portal/funding-the-market/data/loan-performance-data.html) with all rights reserved by Fannie Mae. This processed dataset is redistributed with permission and consent from Fannie Mae.\n",
- "\n",
- "To acquire this dataset, please visit [RAPIDS Datasets Homepage](https://docs.rapids.ai/datasets/mortgage-data)\n",
- "\n",
- "## Introduction\n",
- "The Mortgage workflow is composed of three core phases:\n",
- "\n",
- "1. ETL - Extract, Transform, Load\n",
- "2. Data Conversion\n",
- "3. ML - Training\n",
- "\n",
- "### ETL\n",
- "Data is \n",
- "1. Read in from storage\n",
- "2. Transformed to emphasize key features\n",
- "3. Loaded into volatile memory for conversion\n",
- "\n",
- "### Data Conversion\n",
- "Features are\n",
- "1. Broken into (labels, data) pairs\n",
- "2. Distributed across many workers\n",
- "3. Converted into compressed sparse row (CSR) matrix format for XGBoost\n",
- "\n",
- "### Machine Learning\n",
- "The CSR data is fed into a distributed training session with `xgboost.dask`"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "---\n",
- "If required, the notebook can be converted to a python script for execution using tools like `nbconvert`\n",
- "\n",
- "```sh\n",
- "$ jupyter nbconvert --to python mortgage_e2e.ipynb\n",
- "$ python mortgage_e2e.py\n",
- "```\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "#### Imports statements"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 1,
- "metadata": {},
- "outputs": [],
- "source": [
- "from utils.utils import (\n",
- " determine_dataset,\n",
- " get_data,\n",
- " memory_info,\n",
- ")\n",
- "\n",
- "from dask_cuda import LocalCUDACluster\n",
- "from dask.delayed import delayed\n",
- "from dask.distributed import Client, wait\n",
- "import rmm\n",
- "\n",
- "import numpy as np\n",
- "\n",
- "from collections import OrderedDict\n",
- "import argparse\n",
- "import gc\n",
- "from glob import glob\n",
- "import os\n",
- "import subprocess\n",
- "import time"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "#### Define functions to encapsulate the workflow into a single call"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {},
- "outputs": [],
- "source": [
- "def run_dask_task(func, **kwargs):\n",
- " task = func(**kwargs)\n",
- " return task\n",
- "\n",
- "\n",
- "def process_quarter_gpu(\n",
- " year=2000, quarter=1, perf_file=\"\", data_dir=\"\", client=None, **kwargs\n",
- "):\n",
- " ml_arrays = run_dask_task(\n",
- " delayed(run_gpu_workflow), quarter=quarter, year=year, perf_file=perf_file\n",
- " )\n",
- " return client.compute(ml_arrays, optimize_graph=False, fifo_timeout=\"0ms\")\n",
- "\n",
- "\n",
- "def run_gpu_workflow(\n",
- " quarter=1, year=2000, perf_file=\"\", acq_file=\"\", names_file=\"\", **kwargs\n",
- "):\n",
- " names = gpu_load_names(col_names_path=data_dir + \"names.csv\")\n",
- " names = hash_df_string_columns(names)\n",
- " acq_gdf = gpu_load_acquisition_csv(\n",
- " acquisition_path=data_dir\n",
- " + \"acq\"\n",
- " + \"/Acquisition_\"\n",
- " + str(year)\n",
- " + \"Q\"\n",
- " + str(quarter)\n",
- " + \".txt\"\n",
- " )\n",
- " acq_gdf = hash_df_string_columns(acq_gdf)\n",
- " acq_gdf = acq_gdf.merge(names, how=\"left\", on=[\"seller_name\"])\n",
- " acq_gdf.drop_column(\"seller_name\")\n",
- " acq_gdf[\"seller_name\"] = acq_gdf[\"new\"]\n",
- " acq_gdf.drop_column(\"new\")\n",
- " perf_df_tmp = gpu_load_performance_csv(perf_file)\n",
- " perf_df_tmp = hash_df_string_columns(perf_df_tmp)\n",
- " gdf = perf_df_tmp\n",
- " everdf = create_ever_features(gdf)\n",
- " delinq_merge = create_delinq_features(gdf)\n",
- " everdf = join_ever_delinq_features(everdf, delinq_merge)\n",
- " del delinq_merge\n",
- " joined_df = create_joined_df(gdf, everdf)\n",
- " testdf = create_12_mon_features(joined_df)\n",
- " joined_df = combine_joined_12_mon(joined_df, testdf)\n",
- " del testdf\n",
- " perf_df = final_performance_delinquency(gdf, joined_df)\n",
- " del (gdf, joined_df)\n",
- " final_gdf = join_perf_acq_gdfs(perf_df, acq_gdf)\n",
- " del perf_df\n",
- " del acq_gdf\n",
- " final_gdf = last_mile_cleaning(final_gdf)\n",
- " return final_gdf\n",
- "\n",
- "\n",
- "def gpu_load_performance_csv(performance_path, **kwargs):\n",
- " \"\"\" \n",
- " Loads performance data\n",
- "\n",
- " Returns\n",
- " -------\n",
- " GPU DataFrame\n",
- " \"\"\"\n",
- "\n",
- " cols = [\n",
- " \"loan_id\",\n",
- " \"monthly_reporting_period\",\n",
- " \"servicer\",\n",
- " \"interest_rate\",\n",
- " \"current_actual_upb\",\n",
- " \"loan_age\",\n",
- " \"remaining_months_to_legal_maturity\",\n",
- " \"adj_remaining_months_to_maturity\",\n",
- " \"maturity_date\",\n",
- " \"msa\",\n",
- " \"current_loan_delinquency_status\",\n",
- " \"mod_flag\",\n",
- " \"zero_balance_code\",\n",
- " \"zero_balance_effective_date\",\n",
- " \"last_paid_installment_date\",\n",
- " \"foreclosed_after\",\n",
- " \"disposition_date\",\n",
- " \"foreclosure_costs\",\n",
- " \"prop_preservation_and_repair_costs\",\n",
- " \"asset_recovery_costs\",\n",
- " \"misc_holding_expenses\",\n",
- " \"holding_taxes\",\n",
- " \"net_sale_proceeds\",\n",
- " \"credit_enhancement_proceeds\",\n",
- " \"repurchase_make_whole_proceeds\",\n",
- " \"other_foreclosure_proceeds\",\n",
- " \"non_interest_bearing_upb\",\n",
- " \"principal_forgiveness_upb\",\n",
- " \"repurchase_make_whole_proceeds_flag\",\n",
- " \"foreclosure_principal_write_off_amount\",\n",
- " \"servicing_activity_indicator\",\n",
- " ]\n",
- "\n",
- " dtypes = OrderedDict(\n",
- " [\n",
- " (\"loan_id\", \"int64\"),\n",
- " (\"monthly_reporting_period\", \"date\"),\n",
- " (\"servicer\", \"str\"),\n",
- " (\"interest_rate\", \"float64\"),\n",
- " (\"current_actual_upb\", \"float64\"),\n",
- " (\"loan_age\", \"float64\"),\n",
- " (\"remaining_months_to_legal_maturity\", \"float64\"),\n",
- " (\"adj_remaining_months_to_maturity\", \"float64\"),\n",
- " (\"maturity_date\", \"date\"),\n",
- " (\"msa\", \"float64\"),\n",
- " (\"current_loan_delinquency_status\", \"int32\"),\n",
- " (\"mod_flag\", \"str\"),\n",
- " (\"zero_balance_code\", \"str\"),\n",
- " (\"zero_balance_effective_date\", \"date\"),\n",
- " (\"last_paid_installment_date\", \"date\"),\n",
- " (\"foreclosed_after\", \"date\"),\n",
- " (\"disposition_date\", \"date\"),\n",
- " (\"foreclosure_costs\", \"float64\"),\n",
- " (\"prop_preservation_and_repair_costs\", \"float64\"),\n",
- " (\"asset_recovery_costs\", \"float64\"),\n",
- " (\"misc_holding_expenses\", \"float64\"),\n",
- " (\"holding_taxes\", \"float64\"),\n",
- " (\"net_sale_proceeds\", \"float64\"),\n",
- " (\"credit_enhancement_proceeds\", \"float64\"),\n",
- " (\"repurchase_make_whole_proceeds\", \"float64\"),\n",
- " (\"other_foreclosure_proceeds\", \"float64\"),\n",
- " (\"non_interest_bearing_upb\", \"float64\"),\n",
- " (\"principal_forgiveness_upb\", \"float64\"),\n",
- " (\"repurchase_make_whole_proceeds_flag\", \"str\"),\n",
- " (\"foreclosure_principal_write_off_amount\", \"float64\"),\n",
- " (\"servicing_activity_indicator\", \"str\"),\n",
- " ]\n",
- " )\n",
- "\n",
- " return cudf.read_csv(\n",
- " performance_path,\n",
- " names=cols,\n",
- " delimiter=\"|\",\n",
- " dtype=list(dtypes.values()),\n",
- " skiprows=1,\n",
- " )\n",
- "\n",
- "\n",
- "def gpu_load_acquisition_csv(acquisition_path, **kwargs):\n",
- " \"\"\" \n",
- " Loads acquisition data\n",
- "\n",
- " Returns\n",
- " -------\n",
- " GPU DataFrame\n",
- " \"\"\"\n",
- "\n",
- " cols = [\n",
- " \"loan_id\",\n",
- " \"orig_channel\",\n",
- " \"seller_name\",\n",
- " \"orig_interest_rate\",\n",
- " \"orig_upb\",\n",
- " \"orig_loan_term\",\n",
- " \"orig_date\",\n",
- " \"first_pay_date\",\n",
- " \"orig_ltv\",\n",
- " \"orig_cltv\",\n",
- " \"num_borrowers\",\n",
- " \"dti\",\n",
- " \"borrower_credit_score\",\n",
- " \"first_home_buyer\",\n",
- " \"loan_purpose\",\n",
- " \"property_type\",\n",
- " \"num_units\",\n",
- " \"occupancy_status\",\n",
- " \"property_state\",\n",
- " \"zip\",\n",
- " \"mortgage_insurance_percent\",\n",
- " \"product_type\",\n",
- " \"coborrow_credit_score\",\n",
- " \"mortgage_insurance_type\",\n",
- " \"relocation_mortgage_indicator\",\n",
- " ]\n",
- "\n",
- " dtypes = OrderedDict(\n",
- " [\n",
- " (\"loan_id\", \"int64\"),\n",
- " (\"orig_channel\", \"str\"),\n",
- " (\"seller_name\", \"str\"),\n",
- " (\"orig_interest_rate\", \"float64\"),\n",
- " (\"orig_upb\", \"int64\"),\n",
- " (\"orig_loan_term\", \"int64\"),\n",
- " (\"orig_date\", \"date\"),\n",
- " (\"first_pay_date\", \"date\"),\n",
- " (\"orig_ltv\", \"float64\"),\n",
- " (\"orig_cltv\", \"float64\"),\n",
- " (\"num_borrowers\", \"float64\"),\n",
- " (\"dti\", \"float64\"),\n",
- " (\"borrower_credit_score\", \"float64\"),\n",
- " (\"first_home_buyer\", \"str\"),\n",
- " (\"loan_purpose\", \"str\"),\n",
- " (\"property_type\", \"str\"),\n",
- " (\"num_units\", \"int64\"),\n",
- " (\"occupancy_status\", \"str\"),\n",
- " (\"property_state\", \"str\"),\n",
- " (\"zip\", \"int64\"),\n",
- " (\"mortgage_insurance_percent\", \"float64\"),\n",
- " (\"product_type\", \"str\"),\n",
- " (\"coborrow_credit_score\", \"float64\"),\n",
- " (\"mortgage_insurance_type\", \"float64\"),\n",
- " (\"relocation_mortgage_indicator\", \"str\"),\n",
- " ]\n",
- " )\n",
- "\n",
- " return cudf.read_csv(\n",
- " acquisition_path,\n",
- " names=cols,\n",
- " delimiter=\"|\",\n",
- " dtype=list(dtypes.values()),\n",
- " skiprows=1,\n",
- " )\n",
- "\n",
- "\n",
- "def gpu_load_names(col_names_path=\"\", **kwargs):\n",
- " \"\"\" \n",
- " Loads names used for renaming the banks\n",
- "\n",
- " Returns\n",
- " -------\n",
- " GPU DataFrame\n",
- " \"\"\"\n",
- "\n",
- " cols = [\"seller_name\", \"new\"]\n",
- "\n",
- " dtypes = OrderedDict([(\"seller_name\", \"str\"), (\"new\", \"str\"),])\n",
- "\n",
- " return cudf.read_csv(\n",
- " col_names_path,\n",
- " names=cols,\n",
- " delimiter=\"|\",\n",
- " dtype=list(dtypes.values()),\n",
- " skiprows=1,\n",
- " )\n",
- "\n",
- "\n",
- "def hash_df_string_columns(gdf):\n",
- " \"\"\"\n",
- " Hash all string columns in a cudf dataframe\n",
- "\n",
- " Returns\n",
- " -------\n",
- " Dataframe with all string columns replaced by hashed values for the strings\n",
- " \"\"\"\n",
- " for col in gdf.columns:\n",
- " if cudf.utils.dtypes.is_string_dtype(gdf[col]):\n",
- " gdf[col] = gdf[col].hash_values()\n",
- " return gdf\n",
- "\n",
- "\n",
- "def create_ever_features(gdf, **kwargs):\n",
- " \"\"\"\n",
- " Creates features denoting whether a loan_id has ever been delinquent\n",
- " for over 30, 90 and 180 days.\n",
- " \"\"\"\n",
- " everdf = gdf[[\"loan_id\", \"current_loan_delinquency_status\"]]\n",
- " everdf = everdf.groupby(\"loan_id\", method=\"hash\", as_index=False).max()\n",
- " del gdf\n",
- " everdf[\"ever_30\"] = (everdf[\"current_loan_delinquency_status\"] >= 1).astype(\"int8\")\n",
- " everdf[\"ever_90\"] = (everdf[\"current_loan_delinquency_status\"] >= 3).astype(\"int8\")\n",
- " everdf[\"ever_180\"] = (everdf[\"current_loan_delinquency_status\"] >= 6).astype(\"int8\")\n",
- " everdf.drop_column(\"current_loan_delinquency_status\")\n",
- " return everdf\n",
- "\n",
- "\n",
- "def create_delinq_features(gdf, **kwargs):\n",
- " \"\"\"\n",
- " Computes features denoting the earliest reported date when a loan_id\n",
- " became delinquent for more than 30, 90 and 180 days.\n",
- " \"\"\"\n",
- " delinq_gdf = gdf[\n",
- " [\"loan_id\", \"monthly_reporting_period\", \"current_loan_delinquency_status\",]\n",
- " ]\n",
- " del gdf\n",
- " delinq_30 = (\n",
- " delinq_gdf.query(\"current_loan_delinquency_status >= 1\")[\n",
- " [\"loan_id\", \"monthly_reporting_period\"]\n",
- " ]\n",
- " .groupby(\"loan_id\", method=\"hash\", as_index=False)\n",
- " .min()\n",
- " )\n",
- " delinq_30[\"delinquency_30\"] = delinq_30[\"monthly_reporting_period\"]\n",
- " delinq_30.drop_column(\"monthly_reporting_period\")\n",
- " delinq_90 = (\n",
- " delinq_gdf.query(\"current_loan_delinquency_status >= 3\")[\n",
- " [\"loan_id\", \"monthly_reporting_period\"]\n",
- " ]\n",
- " .groupby(\"loan_id\", method=\"hash\", as_index=False)\n",
- " .min()\n",
- " )\n",
- " delinq_90[\"delinquency_90\"] = delinq_90[\"monthly_reporting_period\"]\n",
- " delinq_90.drop_column(\"monthly_reporting_period\")\n",
- " delinq_180 = (\n",
- " delinq_gdf.query(\"current_loan_delinquency_status >= 6\")[\n",
- " [\"loan_id\", \"monthly_reporting_period\"]\n",
- " ]\n",
- " .groupby(\"loan_id\", method=\"hash\", as_index=False)\n",
- " .min()\n",
- " )\n",
- " delinq_180[\"delinquency_180\"] = delinq_180[\"monthly_reporting_period\"]\n",
- " delinq_180.drop_column(\"monthly_reporting_period\")\n",
- " del delinq_gdf\n",
- " delinq_merge = delinq_30.merge(delinq_90, how=\"left\", on=[\"loan_id\"], type=\"hash\")\n",
- " delinq_merge = delinq_merge.merge(\n",
- " delinq_180, how=\"left\", on=[\"loan_id\"], type=\"hash\"\n",
- " )\n",
- " del delinq_30\n",
- " del delinq_90\n",
- " del delinq_180\n",
- " return delinq_merge\n",
- "\n",
- "\n",
- "def join_ever_delinq_features(everdf_tmp, delinq_merge, **kwargs):\n",
- " \"\"\"\n",
- " Merges the ever and delinq features table on loan_id\n",
- " \"\"\"\n",
- " everdf = everdf_tmp.merge(delinq_merge, on=[\"loan_id\"], how=\"left\", type=\"hash\")\n",
- " del everdf_tmp\n",
- " del delinq_merge\n",
- " return everdf\n",
- "\n",
- "\n",
- "def create_joined_df(gdf, everdf, **kwargs):\n",
- " \"\"\"\n",
- " Join the performance table with the features table. (delinq and ever features)\n",
- " \"\"\"\n",
- " test = gdf[\n",
- " [\n",
- " \"loan_id\",\n",
- " \"monthly_reporting_period\",\n",
- " \"current_loan_delinquency_status\",\n",
- " \"current_actual_upb\",\n",
- " ]\n",
- " ]\n",
- " del gdf\n",
- " test[\"timestamp\"] = test[\"monthly_reporting_period\"]\n",
- " test.drop_column(\"monthly_reporting_period\")\n",
- " test[\"timestamp_month\"] = test[\"timestamp\"].dt.month\n",
- " test[\"timestamp_year\"] = test[\"timestamp\"].dt.year\n",
- " test[\"delinquency_12\"] = test[\"current_loan_delinquency_status\"]\n",
- " test.drop_column(\"current_loan_delinquency_status\")\n",
- " test[\"upb_12\"] = test[\"current_actual_upb\"]\n",
- " test.drop_column(\"current_actual_upb\")\n",
- "\n",
- " joined_df = test.merge(everdf, how=\"left\", on=[\"loan_id\"], type=\"hash\")\n",
- " del everdf\n",
- " del test\n",
- "\n",
- " joined_df[\"timestamp_year\"] = joined_df[\"timestamp_year\"].astype(\"int32\")\n",
- " joined_df[\"timestamp_month\"] = joined_df[\"timestamp_month\"].astype(\"int32\")\n",
- "\n",
- " return joined_df\n",
- "\n",
- "\n",
- "def create_12_mon_features(joined_df, **kwargs):\n",
- " \"\"\"\n",
- " For every loan_id in a 12 month window compute a feature denoting\n",
- " whether it has been delinquent for over 3 months or had an unpaid principal balance.\n",
- " The 12 month window moves by a month to span across all months of the year.\n",
- " \n",
- " The computations windows for each loan_id follows the pattern below\n",
- " Window 1: Jan 2000 - Jan 2001, Jan 2001 - Jan 2002\n",
- " Window 2: Feb 2000- Feb 2001, Feb 2001 - Feb 2002\n",
- " \"\"\"\n",
- " testdfs = []\n",
- " n_months = 12\n",
- " for y in range(1, n_months + 1):\n",
- " tmpdf = joined_df[\n",
- " [\"loan_id\", \"timestamp_year\", \"timestamp_month\", \"delinquency_12\", \"upb_12\"]\n",
- " ]\n",
- " tmpdf[\"josh_months\"] = tmpdf[\"timestamp_year\"] * 12 + tmpdf[\"timestamp_month\"]\n",
- " tmpdf[\"josh_mody_n\"] = (\n",
- " (tmpdf[\"josh_months\"].astype(\"float64\") - 24000 - y) / 12\n",
- " ).floor()\n",
- " tmpdf = tmpdf.groupby(\n",
- " [\"loan_id\", \"josh_mody_n\"], method=\"hash\", as_index=False\n",
- " ).agg({\"delinquency_12\": \"max\", \"upb_12\": \"min\"})\n",
- " tmpdf[\"delinquency_12\"] = (tmpdf[\"delinquency_12\"] > 3).astype(\"int32\")\n",
- " tmpdf[\"delinquency_12\"] += (tmpdf[\"upb_12\"] == 0).astype(\"int32\")\n",
- " tmpdf[\"timestamp_year\"] = (\n",
- " (((tmpdf[\"josh_mody_n\"] * n_months) + 24000 + (y - 1)) / 12)\n",
- " .floor()\n",
- " .astype(\"int16\")\n",
- " )\n",
- " tmpdf[\"timestamp_month\"] = np.int8(y)\n",
- " tmpdf.drop_column(\"josh_mody_n\")\n",
- " testdfs.append(tmpdf)\n",
- " del tmpdf\n",
- " del joined_df\n",
- "\n",
- " return cudf.concat(testdfs)\n",
- "\n",
- "\n",
- "def combine_joined_12_mon(joined_df, testdf, **kwargs):\n",
- " \"\"\"\n",
- " Combines the 12_mon features table with the ever_delinq features tables\n",
- " \"\"\"\n",
- " joined_df.drop_column(\"delinquency_12\")\n",
- " joined_df.drop_column(\"upb_12\")\n",
- " joined_df[\"timestamp_year\"] = joined_df[\"timestamp_year\"].astype(\"int16\")\n",
- " joined_df[\"timestamp_month\"] = joined_df[\"timestamp_month\"].astype(\"int8\")\n",
- " return joined_df.merge(\n",
- " testdf,\n",
- " how=\"left\",\n",
- " on=[\"loan_id\", \"timestamp_year\", \"timestamp_month\"],\n",
- " type=\"hash\",\n",
- " )\n",
- "\n",
- "\n",
- "def final_performance_delinquency(gdf, joined_df, **kwargs):\n",
- " \"\"\"\n",
- " Combines the grouped table with all features with the original Performance table\n",
- " \"\"\"\n",
- " merged = gdf\n",
- " joined_df[\"timestamp_month\"] = joined_df[\"timestamp_month\"].astype(\"int8\")\n",
- " joined_df[\"timestamp_year\"] = joined_df[\"timestamp_year\"].astype(\"int16\")\n",
- " merged[\"timestamp_month\"] = merged[\"monthly_reporting_period\"].dt.month\n",
- " merged[\"timestamp_month\"] = merged[\"timestamp_month\"].astype(\"int8\")\n",
- " merged[\"timestamp_year\"] = merged[\"monthly_reporting_period\"].dt.year\n",
- " merged[\"timestamp_year\"] = merged[\"timestamp_year\"].astype(\"int16\")\n",
- " merged = merged.merge(\n",
- " joined_df,\n",
- " how=\"left\",\n",
- " on=[\"loan_id\", \"timestamp_year\", \"timestamp_month\"],\n",
- " type=\"hash\",\n",
- " )\n",
- " merged.drop_column(\"timestamp_year\")\n",
- " merged.drop_column(\"timestamp_month\")\n",
- " return merged\n",
- "\n",
- "\n",
- "def join_perf_acq_gdfs(perf, acq, **kwargs):\n",
- " \"\"\"\n",
- " Combines the Acquisition and Performance tables on loan_id\n",
- " \"\"\"\n",
- " return perf.merge(acq, how=\"left\", on=[\"loan_id\"], type=\"hash\")\n",
- "\n",
- "\n",
- "def last_mile_cleaning(df, **kwargs):\n",
- " \"\"\"\n",
- " Final cleanup to drop columns not passed to the XGBoost model for training.\n",
- " Convert all string/categorical features to numeric features.\n",
- "\n",
- " Returns\n",
- " ------\n",
- " Arrow Table (Host memory)\n",
- " \"\"\"\n",
- " drop_list = [\n",
- " \"loan_id\",\n",
- " \"orig_date\",\n",
- " \"first_pay_date\",\n",
- " \"seller_name\",\n",
- " \"monthly_reporting_period\",\n",
- " \"last_paid_installment_date\",\n",
- " \"maturity_date\",\n",
- " \"ever_30\",\n",
- " \"ever_90\",\n",
- " \"ever_180\",\n",
- " \"delinquency_30\",\n",
- " \"delinquency_90\",\n",
- " \"delinquency_180\",\n",
- " \"upb_12\",\n",
- " \"zero_balance_effective_date\",\n",
- " \"foreclosed_after\",\n",
- " \"disposition_date\",\n",
- " \"timestamp\",\n",
- " ]\n",
- " for column in drop_list:\n",
- " df.drop_column(column)\n",
- " for col, dtype in df.dtypes.iteritems():\n",
- " if str(dtype) == \"category\":\n",
- " df[col] = df[col].cat.codes\n",
- " df[col] = df[col].astype(\"float32\")\n",
- " df[\"delinquency_12\"] = df[\"delinquency_12\"] > 0\n",
- " df[\"delinquency_12\"] = df[\"delinquency_12\"].fillna(False).astype(\"int32\")\n",
- " for column in df.columns:\n",
- " df[column] = df[column].fillna(np.dtype(str(df[column].dtype)).type(-1))\n",
- " return df.to_arrow(preserve_index=False)\n",
- "\n",
- "\n",
- "def prepare_data(arrow_input):\n",
- " \"\"\"\n",
- " Convert a list of arrow tables to a single GPU dataframe\n",
- " \n",
- " Returns\n",
- " -------\n",
- " GPU Dataframe\n",
- " \"\"\"\n",
- " gpu_dataframes = []\n",
- " for arrow_df in arrow_input:\n",
- " gpu_dataframes.append(cudf.DataFrame.from_arrow(arrow_df))\n",
- "\n",
- " concat_df = cudf.concat(gpu_dataframes)\n",
- " del gpu_dataframes\n",
- " return concat_df\n",
- "\n",
- "\n",
- "def xgb_training(arrow_dfs, client=None):\n",
- " \"\"\"\n",
- " Convert the post ETL data to Dmatrix format for XGBoost training input.\n",
- " Train the XGBoost model.\n",
- " \n",
- " Returns\n",
- " -------\n",
- " The trained model and time taken for preparing, training data.\n",
- " \"\"\"\n",
- " dxgb_gpu_params = {\n",
- " \"max_depth\": 8,\n",
- " \"max_leaves\": 2 ** 8,\n",
- " \"alpha\": 0.9,\n",
- " \"eta\": 0.1,\n",
- " \"gamma\": 0.1,\n",
- " \"learning_rate\": 0.1,\n",
- " \"subsample\": 1,\n",
- " \"reg_lambda\": 1,\n",
- " \"scale_pos_weight\": 2,\n",
- " \"min_child_weight\": 30,\n",
- " \"tree_method\": \"gpu_hist\",\n",
- " \"objective\": \"binary:logistic\",\n",
- " \"grow_policy\": \"lossguide\",\n",
- " }\n",
- " NUM_BOOST_ROUND = 100\n",
- "\n",
- " part_count = len(arrow_dfs)\n",
- " print(f\"Preparing data for training with part count: {part_count}\")\n",
- " t1 = time.time()\n",
- " tmp_map = [\n",
- " (arrow_df, list(client.who_has(arrow_df).values())[0][0])\n",
- " for arrow_df in arrow_dfs\n",
- " ]\n",
- " new_map = OrderedDict()\n",
- " for key, value in tmp_map:\n",
- " if value not in new_map:\n",
- " new_map[value] = [key]\n",
- " else:\n",
- " new_map[value].append(key)\n",
- "\n",
- " del (tmp_map, key, value)\n",
- "\n",
- " train_x_y = []\n",
- " for list_delayed in new_map.values():\n",
- " train_x_y.append(delayed(prepare_data)(list_delayed))\n",
- "\n",
- " del (new_map, list_delayed)\n",
- "\n",
- " worker_list = OrderedDict()\n",
- " for task in train_x_y:\n",
- " worker_list[task] = list(client.who_has(task).values())[0][0]\n",
- "\n",
- " del task\n",
- "\n",
- " persisted_train_x_y = []\n",
- " for task in train_x_y:\n",
- " persisted_train_x_y.append(\n",
- " client.persist(\n",
- " collections=task,\n",
- " workers=worker_list[task],\n",
- " optimize_graph=False,\n",
- " fifo_timeout=\"0ms\",\n",
- " )\n",
- " )\n",
- "\n",
- " del (arrow_dfs, train_x_y, worker_list, task)\n",
- "\n",
- " wait(persisted_train_x_y)\n",
- " persisted_train_x_y = dask_cudf.from_delayed(persisted_train_x_y)\n",
- "\n",
- " dmat = xgb.dask.DaskDMatrix(\n",
- " client=client,\n",
- " data=persisted_train_x_y[\n",
- " persisted_train_x_y.columns.difference([\"delinquency_12\"])\n",
- " ],\n",
- " label=persisted_train_x_y[[\"delinquency_12\"]],\n",
- " missing=-1,\n",
- " )\n",
- "\n",
- " del persisted_train_x_y\n",
- " gc.collect()\n",
- "\n",
- " dmat_time = time.time() - t1\n",
- " print(\"Prepared data for XGB training\")\n",
- "\n",
- " print(\"Training model\")\n",
- " t1 = time.time()\n",
- "\n",
- " print(\"XGB training for part_count:{}\".format(part_count))\n",
- " bst = xgb.dask.train(\n",
- " client, dxgb_gpu_params, dmat, num_boost_round=NUM_BOOST_ROUND,\n",
- " )\n",
- "\n",
- " train_time = time.time() - t1\n",
- " print(\"Training complete\")\n",
- " return (bst, dmat_time, train_time)\n",
- "\n",
- "\n",
- "def run_etl(start_year, end_year, data_dir, client):\n",
- " \"\"\"\n",
- " Driver function for the ETL step\n",
- " \n",
- " Iterates through all files in `data_dir` between `start_year` \n",
- " and `end_year` and calls the ETL function for each file.\n",
- " \n",
- " Returns\n",
- " -------\n",
- " Dask futures to arrow tables containing post ETL data for all processed files.\n",
- " \"\"\"\n",
- " print(\"Starting ETL\")\n",
- " t1 = time.time()\n",
- "\n",
- " perf_data_path = data_dir + \"perf/\"\n",
- "\n",
- " gpu_dfs = []\n",
- " quarter = 1\n",
- " year = start_year\n",
- " count = 0\n",
- " while year <= end_year:\n",
- " for file in glob(\n",
- " os.path.join(\n",
- " perf_data_path + \"/Performance_\" + str(year) + \"Q\" + str(quarter) + \"*\"\n",
- " )\n",
- " ):\n",
- " gpu_dfs.append(\n",
- " process_quarter_gpu(\n",
- " year=year, quarter=quarter, perf_file=file, client=client\n",
- " )\n",
- " )\n",
- " count += 1\n",
- " quarter += 1\n",
- " if quarter == 5:\n",
- " year += 1\n",
- " quarter = 1\n",
- " print(\"ETL for start_year:{} and end_year:{}\".format(start_year, end_year))\n",
- " wait(gpu_dfs)\n",
- "\n",
- " etl_time = time.time() - t1\n",
- "\n",
- " print(\"ETL done!\")\n",
- " return (gpu_dfs, etl_time)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "#### The cell below runs the workflow end to end including the ETL and XGBoost model training step\n",
- "\n",
- "**Notes** \n",
- "\n",
- "The mortgage dataset for years 2000-2016 is about 200GB of data. There are two key factors that determine the `start_year`, `end_year`, `part_count` and `use_1GB_splits` params used in the notebook for processing this data. \n",
- "\n",
- "_Total GPU memory_: Determines the amount of data that can be trained using XGBoost (`part_count`). The ETL is performed on one part file at a time (per GPU) whereas XGBoost training requires all the training data to be loaded in GPU memory.\n",
- "\n",
- "_Memory per GPU_: Determines the variation of the dataset to use (1GB vs 2GB splits). The 2GB splits version of the data results in larger partitions being processed per task resulting in better utilization of the GPU, with the tradeoff of increased memory usage that can be handled by GPUs cards with greater than `32GB` of memory.\n",
- "\n",
- "The `determine_dataset` utility used below automatically queries these two parameters based on the machine and decides suitable values for `part_count` and consequently `start_year`, `end_year`(to ensure ETL is performed on enough parts for training), as well as the variation of the dataset (1GB split part files vs 2GB split part files) that should work on such systems.\n",
- "\n",
- "If you'd like to use existing data that has already been downloaded to your own location, or manually adjust these parameters based on the amount of data needed for processing, you can change these parameters provided in the notebook, by assigning new values to the variables or setting enivronment variables for `MORTGAGE_DATA_DIR` and `part_count`. You can visit the [RAPIDS Datasets Homepage](https://docs.rapids.ai/datasets/mortgage-data) for more information on downloading the data manually."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Downloading data for year 2000\n",
- "Download complete\n",
- "Decompressing and extracting data\n",
- "Done extracting year 2000\n",
- "Downloading data for year 2001\n",
- "Download complete\n",
- "Decompressing and extracting data\n",
- "Done extracting year 2001\n",
- "Downloading data for year 2002\n",
- "Download complete\n",
- "Decompressing and extracting data\n",
- "Done extracting year 2002\n",
- "Downloading data for year 2003\n",
- "Download complete\n",
- "Decompressing and extracting data\n",
- "Done extracting year 2003\n",
- "Downloading data for year 2004\n",
- "Download complete\n",
- "Decompressing and extracting data\n",
- "Done extracting year 2004\n",
- "Starting ETL\n",
- "ETL for start_year:2000 and end_year:2004\n",
- "ETL done!\n",
- "Preparing data for training with part count: 12\n",
- "Prepared data for XGB training\n",
- "Training model\n",
- "XGB training for part_count:12\n",
- "Training complete\n",
- "\n",
- "Time taken to run ETL from 2000 to 2004 (108 parts) was 68.7227 s\n",
- "Time taken to prepare 12 parts for XGB training 3.3915 s\n",
- "Time taken to train XGB model 87.521 s\n",
- "Total E2E time: 159.6352 s\n"
- ]
- }
- ],
- "source": [
- "if __name__ == \"__main__\":\n",
- "\n",
- " import cudf\n",
- " import xgboost as xgb\n",
- " import dask_cudf\n",
- "\n",
- " cmd = \"hostname --all-ip-addresses\"\n",
- " process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)\n",
- " output, error = process.communicate()\n",
- " IPADDR = str(output.decode()).split()[0]\n",
- "\n",
- " cluster = LocalCUDACluster(ip=IPADDR)\n",
- " client = Client(cluster)\n",
- "\n",
- " data_dir = os.environ.get(\"MORTGAGE_DATA_DIR\", \"\") # Default to current working directory\n",
- " res = client.run(memory_info)\n",
- " # Total GPU memory on the system\n",
- " total_mem = sum(res.values()) \n",
- " # Memory of a single GPU on the machine\n",
- " # If the machine has multiple GPUs of different sizes, this is the size of the smallest GPU\n",
- " min_mem = min(res.values()) \n",
- " \n",
- " # Start year for processing mortgage data\n",
- " start_year = None\n",
- " # End year for processing mortgage data\n",
- " end_year = None\n",
- " # The number of part files to train against. \n",
- " # If not provided, default to auto selection based on GPU memory available on the system\n",
- " part_count = os.environ.get(\"part_count\")\n",
- "\n",
- " start_year, end_year, part_count, use_1GB_splits = determine_dataset(\n",
- " total_mem=total_mem, min_mem=min_mem, part_count=part_count\n",
- " )\n",
- "\n",
- " # Download data based on these parameters\n",
- " # The 2GB split mortgage performance files are used if the system has 32GB GPUs.\n",
- " # On machines with GPUs less than 32GB we use the 1GB split files (to help reduce memory load)\n",
- " get_data(data_dir, start_year, end_year, use_1GB_splits)\n",
- "\n",
- " # Initialize a GPU pool allocating 90% of GPU memory for each worker\n",
- " client.run(rmm.reinitialize, pool_allocator=True, initial_pool_size=0.9 * min_mem)\n",
- " etl_result, etl_time = run_etl(start_year, end_year, data_dir, client)\n",
- "\n",
- " # Clear the existing RMM pool post-ETL to make space for GPU accelerated XGBoost\n",
- " # This makes space for XGBoost to operate since it doesn't have visibility into the cuDF memory pool\n",
- " client.run(rmm.reinitialize, pool_allocator=False)\n",
- "\n",
- " total_file_count = len(etl_result)\n",
- " etl_result = etl_result[:part_count] # Select subset for training\n",
- " model, dmat_time, train_time = xgb_training(etl_result, client)\n",
- "\n",
- " print(\n",
- " f\"\\nTime taken to run ETL from {start_year} to {end_year}\"\n",
- " f\" ({total_file_count} parts) was {round(etl_time,4)} s\"\n",
- " )\n",
- " print(\n",
- " f\"Time taken to prepare {len(etl_result)} parts\"\n",
- " f\" for XGB training {round(dmat_time,4)} s\"\n",
- " )\n",
- " print(f\"Time taken to train XGB model {round(train_time, 4)} s\")\n",
- " print(f\"Total E2E time: {round(etl_time+dmat_time+train_time, 4)} s\")\n",
- " client.close()\n",
- " cluster.close()"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "rapids-14-may6",
- "language": "python",
- "name": "rapids-14-may6"
- },
- "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.6"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/intermediate_notebooks/E2E/mortgage/utils/Data_Spec.json b/intermediate_notebooks/E2E/mortgage/utils/Data_Spec.json
deleted file mode 100644
index d69e0463..00000000
--- a/intermediate_notebooks/E2E/mortgage/utils/Data_Spec.json
+++ /dev/null
@@ -1,40 +0,0 @@
-{
- "SpecInfo":
- [
- {
- "Total_Mem" : 511e9,
- "Start_Year" : 2000,
- "End_Year" : 2016,
- "Part_Count" : [48, 96]
- },
-
- {
- "Total_Mem" : 255e9,
- "Start_Year" : 2000,
- "End_Year" : 2016,
- "Part_Count" : [24, 48]
- },
-
- {
- "Total_Mem" : 127e9,
- "Start_Year" : 2000,
- "End_Year" : 2007,
- "Part_Count" : [16, 24]
- },
-
- {
- "Total_Mem" : 47e9,
- "Start_Year" : 2000,
- "End_Year" : 2004,
- "Part_Count" : [8, 12]
- },
-
- {
- "Total_Mem" : 15e9,
- "Start_Year" : 2000,
- "End_Year" : 2000,
- "Part_Count" : [2, 3]
- }
-
- ]
-}
\ No newline at end of file
diff --git a/intermediate_notebooks/E2E/mortgage/utils/utils.py b/intermediate_notebooks/E2E/mortgage/utils/utils.py
deleted file mode 100644
index a0745185..00000000
--- a/intermediate_notebooks/E2E/mortgage/utils/utils.py
+++ /dev/null
@@ -1,127 +0,0 @@
-from packaging import version
-import json
-import glob
-import multiprocessing
-import pynvml
-import os
-import tarfile
-import urllib
-
-# Global variables
-
-# Links to mortgage data files
-MORTGAGE_YEARLY_1GB_SPLITS_URL = "https://rapidsai-data.s3.us-east-2.amazonaws.com/notebook-mortgage-data/mortgage_yearly/"
-MORTGAGE_YEARLY_2GB_SPLITS_URL = "https://rapidsai-data.s3.us-east-2.amazonaws.com/notebook-mortgage-data/mortgage_yearly_2gb/"
-
-
-def get_data(data_dir, start_year, end_year, use_1GB_splits):
- """
- Utility to download and extract mortgage data to specied data_dir.
- Only specific years of data between `start_year` and `end_year` will be downloaded
- to the specified directory
- """
- if use_1GB_splits:
- data_url = MORTGAGE_YEARLY_1GB_SPLITS_URL
- else:
- data_url = MORTGAGE_YEARLY_2GB_SPLITS_URL
- for year in range(start_year, end_year + 1):
- if not os.path.isfile(data_dir + "acq/Acquisition_" + str(year) + "Q4.txt"):
- print(f"Downloading data for year {year}")
- filename = "mortgage_" + str(year)
- filename += "_1gb.tgz" if use_1GB_splits else "_2GB.tgz"
- urllib.request.urlretrieve(data_url + filename, data_dir + filename)
- print(f"Download complete")
- print(f"Decompressing and extracting data")
-
- tar = tarfile.open(data_dir + filename, mode="r:gz")
- tar.extractall(path=data_dir)
- tar.close()
- print(f"Done extracting year {year}")
-
- if not os.path.isfile(data_dir + "names.csv"):
- urllib.request.urlretrieve(data_url + "names.csv", data_dir + "names.csv")
-
-
-def _read_data_spec(filename=os.path.dirname(__file__) + "/Data_Spec.json"):
- """
- Read the Data_Spec json
- """
- with open(filename) as f:
- data_spec = json.load(f)
-
- try:
- spec_list = data_spec["SpecInfo"]
- except KeyError:
- raise ValueError(f"SpecInfo missing in Data spec file: {filename}")
- return spec_list
-
-
-def determine_dataset(total_mem, min_mem, part_count=None):
- """
- Determine params and dataset to use
- based on Data spec sheet and available memory
- """
- start_year = None # start year for etl proessing
- end_year = None # end year for etl processing (inclusive)
-
- use_1GB_splits = True
- if min_mem >= 31.5e9:
- use_1GB_splits = False
-
- spec_list = _read_data_spec()
- # Assumption that spec_list has elements with mem_requirement
- # in Descending order
-
- # TODO: Code duplication. Consolidate into one
- if part_count:
- part_count = int(part_count)
- for i, spec in enumerate(spec_list):
- spec_part_count = (
- spec["Part_Count"][1] if use_1GB_splits else spec["Part_Count"][0]
- )
- if part_count > spec_part_count:
- start_year = spec_list[i-1]["Start_Year"] if i>0 else spec["Start_Year"]
- end_year = spec_list[i-1]["End_Year"] if i>0 else spec["End_Year"]
- break
- if not start_year:
- start_year = spec_list[-1]["Start_Year"]
- end_year = spec_list[-1]["End_Year"]
-
- else:
- for spec in spec_list:
- spec_part_count = (
- spec["Part_Count"][1] if use_1GB_splits else spec["Part_Count"][0]
- )
- if total_mem >= spec["Total_Mem"]:
- start_year = spec["Start_Year"]
- end_year = spec["End_Year"]
- part_count = spec_part_count
- break
-
- return (start_year, end_year, part_count, use_1GB_splits)
-
-
-def memory_info():
- """
- Assumes identical GPUs in a node
- """
- pynvml.nvmlInit()
- handle = pynvml.nvmlDeviceGetHandleByIndex(0)
- gpu_mem = pynvml.nvmlDeviceGetMemoryInfo(handle).total
- pynvml.nvmlShutdown()
- return gpu_mem
-
-
-def get_num_files(start_year, end_year, perf_dir):
- """
- Get number of files to read given start_year
- end_year and path to performance files
- """
- count = 0
- for year in range(start_year, end_year + 1):
- count += len(glob.glob(perf_dir + f"/*{year}*"))
- return count
-
-
-def get_cpu_cores():
- return multiprocessing.cpu_count()
diff --git a/intermediate_notebooks/E2E/synthetic_3D/rapids_ml_workflow_demo.ipynb b/intermediate_notebooks/E2E/synthetic_3D/rapids_ml_workflow_demo.ipynb
deleted file mode 100644
index ebfb40cb..00000000
--- a/intermediate_notebooks/E2E/synthetic_3D/rapids_ml_workflow_demo.ipynb
+++ /dev/null
@@ -1,1513 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
"
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "fig = plt.figure(figsize=(100,50))\n",
- "plot_tree(xgBoostModelGPU, num_trees=0, ax=plt.subplot(1,1,1))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Visualize Class Predictions"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 51,
- "metadata": {},
- "outputs": [],
- "source": [
- "def map_colors_to_clusters_topK ( dataset, labels, topK=None, cmapName = 'tab10'):\n",
- " if topK == None:\n",
- " topK = dataset.shape[0]\n",
- " \n",
- " colorStack = np.zeros((topK, 3), dtype=np.float32)\n",
- " \n",
- " cMap = plt.get_cmap(cmapName)\n",
- " for iColor in range ( topK ):\n",
- " colorStack[iColor] = cMap.colors[ labels[iColor] ]\n",
- " \n",
- " return colorStack "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 52,
- "metadata": {},
- "outputs": [],
- "source": [
- "colorStackClassifier = map_colors_to_clusters_topK ( pd_X_test, yPredTestGPU.astype(np.int), topK=None )"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "plot_data( pd_X_test, colorStack= colorStackClassifier)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "-------\n",
- "# Extensions\n",
- "-------\n",
- "For extensions to this work visit github.com/miroenev/rapids"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "-----\n",
- "# End [ thanks! ]"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.3"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/intermediate_notebooks/benchmarks/cugraph_benchmarks/README.md b/intermediate_notebooks/benchmarks/cugraph_benchmarks/README.md
deleted file mode 100644
index 80d5a3d5..00000000
--- a/intermediate_notebooks/benchmarks/cugraph_benchmarks/README.md
+++ /dev/null
@@ -1,108 +0,0 @@
-# cuGraph Benchmarking
-
-This folder contains a collection of graph algorithm benchmarking notebooks. Each notebook will compare one cuGraph algorithm against the equivalent NetworkX version. In some cases, additional popular implementations are also tested.
-
-Before any benchmarking can be done, it is important to fir download the test data sets.
-
-
-## Getting the Data Sets
-
-Run the data prep script.
-
-```bash
-sh ./dataPrep.sh
-```
-
-## Benchmarks
-
-1. Louvain
-2. PageRank
-3. BSF
-4. SSSP
-
-
-
-The benchmark does not include data reading time, but does include:
-
-- Creating the Graph object
-- Running the analytic
-
-
-
-
-
-
-#### The data prep script
-By default, each files would be created in its own directory. The goal here is to have all the MTX files in a single directory.
-
-
-```bash
-#!/bin/bash
-
-mkdir data
-cd data
-mkdir tmp
-cd tmp
-
-wget https://sparse.tamu.edu/MM/DIMACS10/preferentialAttachment.tar.gz
-wget https://sparse.tamu.edu/MM/DIMACS10/caidaRouterLevel.tar.gz
-wget https://sparse.tamu.edu/MM/DIMACS10/coAuthorsDBLP.tar.gz
-wget https://sparse.tamu.edu/MM/LAW/dblp-2010.tar.gz
-wget https://sparse.tamu.edu/MM/DIMACS10/citationCiteseer.tar.gz
-wget https://sparse.tamu.edu/MM/DIMACS10/coPapersDBLP.tar.gz
-wget https://sparse.tamu.edu/MM/DIMACS10/coPapersCiteseer.tar.gz
-wget https://sparse.tamu.edu/MM/SNAP/as-Skitter.tar.gz
-
-tar xvzf preferentialAttachment.tar.gz
-tar xvzf caidaRouterLevel.tar.gz
-tar xvzf coAuthorsDBLP.tar.gz
-tar xvzf dblp-2010.tar.gz
-tar xvzf citationCiteseer.tar.gz
-tar xvzf coPapersDBLP.tar.gz
-tar xvzf coPapersCiteseer.tar.gz
-tar xvzf as-Skitter.tar.gz
-
-cd ..
-
-find ./tmp -name *.mtx -exec mv {} . \;
-
-rm -rf tmp
-```
-
-
-
-**About the Test files**
-
-| File Name | Num of Vertices | Num of Edges | Format | Graph Type | Symmetric |
-| ---------------------- | --------------: | -----------: |--------|---------------------------|-------------|
-| preferentialAttachment | 100,000 | 999,970 | MTX | Random Undirected Graph | Yes |
-| caidaRouterLevel | 192,244 | 1,218,132 | MTX | Undirected Graph | Yes |
-| coAuthorsDBLP | 299,067 | 1,955,352 |MTX | Undirected Graph | Yes |
-| dblp-2010 | 326,186 | 1,615,400 | MTX | Undirected Graph | Yes |
-| citationCiteseer | 268,495 | 2,313,294 | MTX | Undirected Graph | Yes |
-| coPapersDBLP | 540,486 | 30,491,458 | MTX | Undirected Graph | Yes |
-| coPapersCiteseer | 434,102 | 32,073,440 | MTX | Undirected Graph | Yes |
-| as-Skitter | 1,696,415 | 22,190,596 | MTX | Undirected Graph | Yes |
-
-
-
-### Dataset Acknowlegments
-
-The dataset are downloaded from the Texas A&M SuiteSparse Matrix Collection
-
-```
-The SuiteSparse Matrix Collection (formerly known as the University of Florida Sparse Matrix Collection), is a large and actively growing set of sparse matrices that arise in real applications.
-...
-The Collection is hosted here, and also mirrored at the University of Florida at www.cise.ufl.edu/research/sparse/matrices. The Collection is maintained by Tim Davis, Texas A&M University (email: davis@tamu.edu), Yifan Hu, Yahoo! Labs, and Scott Kolodziej, Texas A&M University.
-```
-
-| File Name | Author |
-| ---------------------- |----------------|
-| preferentialAttachment | H. Meyerhenke |
-| caidaRouterLevel | Unknown |
-| coAuthorsDBLP | R. Geisberger, P. Sanders, and D. Schultes |
-| dblp-2010 | Laboratory for Web Algorithmics (LAW), |
-| citationCiteseer | R. Geisberger, P. Sanders, and D. Schultes |
-| coPapersDBLP | R. Geisberger, P. Sanders, and D. Schultes |
-| coPapersCiteseer | R. Geisberger, P. Sanders, and D. Schultes |
-| as-Skitter | J. Leskovec, J. Kleinberg and C. Faloutsos |
diff --git a/intermediate_notebooks/benchmarks/cugraph_benchmarks/bfs_benchmark.ipynb b/intermediate_notebooks/benchmarks/cugraph_benchmarks/bfs_benchmark.ipynb
deleted file mode 100644
index 366c6b65..00000000
--- a/intermediate_notebooks/benchmarks/cugraph_benchmarks/bfs_benchmark.ipynb
+++ /dev/null
@@ -1,329 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# BFS Performance Benchmarking\n",
- "\n",
- "This notebook benchmarks performance of running BFS within cuGraph against NetworkX. \n",
- "\n",
- "Notebook Credits\n",
- "\n",
- " Original Authors: Bradley Rees\n",
- " Last Edit: 10/30/2019\n",
- " \n",
- "RAPIDS Versions: 0.10.0\n",
- "\n",
- "Test Hardware\n",
- "\n",
- " GV100 32G, CUDA 10,0\n",
- " Intel(R) Core(TM) CPU i7-7800X @ 3.50GHz\n",
- " 32GB system memory\n",
- " \n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Test Data\n",
- "\n",
- "| File Name | Num of Vertices | Num of Edges |\n",
- "|:---------------------- | --------------: | -----------: |\n",
- "| preferentialAttachment | 100,000 | 999,970 |\n",
- "| caidaRouterLevel | 192,244 | 1,218,132 |\n",
- "| coAuthorsDBLP | 299,067 | 1,955,352 |\n",
- "| dblp-2010 | 326,186 | 1,615,400 |\n",
- "| citationCiteseer | 268,495 | 2,313,294 |\n",
- "| coPapersDBLP | 540,486 | 30,491,458 |\n",
- "| coPapersCiteseer | 434,102 | 32,073,440 |\n",
- "| as-Skitter | 1,696,415 | 22,190,596 |\n",
- "\n",
- "\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Timing \n",
- "What is not timed: Reading the data\n",
- "What is timmed: (1) creating a Graph, (2) running BSF\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## NOTICE:\n",
- "You must have run the dataPrep script prior to running this notebook so that the data is downloaded\n",
- "\n",
- "See the README file in this folder for a discription of how to get the data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Import needed libraries\n",
- "import gc\n",
- "import time\n",
- "import rmm\n",
- "import cugraph\n",
- "import cudf"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# NetworkX libraries\n",
- "import networkx as nx\n",
- "from scipy.io import mmread"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import matplotlib.pyplot as plt; plt.rcdefaults()\n",
- "import numpy as np"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Get Data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!bash dataPrep.sh"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Define the test data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Test File\n",
- "data = {\n",
- " 'preferentialAttachment' : './data/preferentialAttachment.mtx',\n",
- " 'caidaRouterLevel' : './data/caidaRouterLevel.mtx',\n",
- " 'coAuthorsDBLP' : './data/coAuthorsDBLP.mtx',\n",
- " 'dblp' : './data/dblp-2010.mtx',\n",
- " 'citationCiteseer' : './data/citationCiteseer.mtx',\n",
- " 'coPapersDBLP' : './data/coPapersDBLP.mtx',\n",
- " 'coPapersCiteseer' : './data/coPapersCiteseer.mtx',\n",
- " 'as-Skitter' : './data/as-Skitter.mtx'\n",
- "}"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Define the testing functions"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Data reader - the file format is MTX, so we will use the reader from SciPy\n",
- "def read_mtx_file(mm_file):\n",
- " print('Reading ' + str(mm_file) + '...')\n",
- " M = mmread(mm_file).asfptype()\n",
- " \n",
- " return M"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "metadata": {},
- "outputs": [],
- "source": [
- "# CuGraph BFS\n",
- "\n",
- "def cugraph_call(M):\n",
- "\n",
- " gdf = cudf.DataFrame()\n",
- " gdf['src'] = M.row\n",
- " gdf['dst'] = M.col\n",
- " \n",
- " print('\\tcuGraph Solving... ')\n",
- " \n",
- " t1 = time.time()\n",
- " \n",
- " # cugraph Pagerank Call\n",
- " G = cugraph.Graph()\n",
- " G.from_cudf_edgelist(gdf, source='src', destination='dst')\n",
- " \n",
- " df = cugraph.bfs(G, 1)\n",
- " t2 = time.time() - t1\n",
- " \n",
- " return t2\n",
- " "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Basic NetworkX BFS\n",
- "\n",
- "def networkx_call(M):\n",
- " nnz_per_row = {r: 0 for r in range(M.get_shape()[0])}\n",
- " for nnz in range(M.getnnz()):\n",
- " nnz_per_row[M.row[nnz]] = 1 + nnz_per_row[M.row[nnz]]\n",
- " for nnz in range(M.getnnz()):\n",
- " M.data[nnz] = 1.0/float(nnz_per_row[M.row[nnz]])\n",
- "\n",
- " M = M.tocsr()\n",
- " if M is None:\n",
- " raise TypeError('Could not read the input graph')\n",
- " if M.shape[0] != M.shape[1]:\n",
- " raise TypeError('Shape is not square')\n",
- "\n",
- " # should be autosorted, but check just to make sure\n",
- " if not M.has_sorted_indices:\n",
- " print('sort_indices ... ')\n",
- " M.sort_indices()\n",
- "\n",
- " z = {k: 1.0/M.shape[0] for k in range(M.shape[0])}\n",
- " \n",
- " print('\\tNetworkX Solving... ')\n",
- " \n",
- " # start timer\n",
- " t1 = time.time()\n",
- " \n",
- " Gnx = nx.DiGraph(M)\n",
- "\n",
- " pr = nx.bfs_edges(Gnx, 1)\n",
- " \n",
- " t2 = time.time() - t1\n",
- "\n",
- " return t2"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Run the benchmarks"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# arrays to capture performance gains\n",
- "perf_nx = []\n",
- "names = []\n",
- "\n",
- "for k,v in data.items():\n",
- " gc.collect()\n",
- "\n",
- " rmm.reinitialize(\n",
- " managed_memory=False,\n",
- " pool_allocator=False,\n",
- " initial_pool_size=2 << 27\n",
- " ) \n",
- " \n",
- " # Saved the file Name\n",
- " names.append(k)\n",
- " \n",
- " # read the data\n",
- " M = read_mtx_file(v)\n",
- " \n",
- " \n",
- " # call cuGraph - this will be the baseline\n",
- " trapids = cugraph_call(M)\n",
- " \n",
- " # Now call NetworkX\n",
- " tn = networkx_call(M)\n",
- " speedUp = (tn / trapids)\n",
- " perf_nx.append(speedUp)\n",
- " \n",
- " print(\"\\tcuGraph (\" + str(trapids) + \") Nx (\" + str(tn) + \")\" )"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "%matplotlib inline\n",
- "\n",
- "plt.figure(figsize=(10,8))\n",
- "\n",
- "bar_width = 0.4\n",
- "index = np.arange(len(names))\n",
- "\n",
- "_ = plt.bar(index, perf_nx, bar_width, color='g', label='vs Nx')\n",
- "\n",
- "plt.xlabel('Datasets')\n",
- "plt.ylabel('Speedup')\n",
- "plt.title('BFS Performance Speedup')\n",
- "plt.xticks(index + (bar_width / 2), names)\n",
- "plt.xticks(rotation=90) \n",
- "\n",
- "# Text on the top of each barplot\n",
- "for i in range(len(perf_nx)):\n",
- " plt.text(x = (i - .5) + bar_width, y = perf_nx[i] + 25, s = round(perf_nx[i], 1), size = 12)\n",
- "\n",
- "plt.legend()\n",
- "plt.show()"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.3"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/intermediate_notebooks/benchmarks/cugraph_benchmarks/dataPrep.sh b/intermediate_notebooks/benchmarks/cugraph_benchmarks/dataPrep.sh
deleted file mode 100755
index 34b15efa..00000000
--- a/intermediate_notebooks/benchmarks/cugraph_benchmarks/dataPrep.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-
-mkdir data
-cd data
-mkdir tmp
-cd tmp
-
-wget https://sparse.tamu.edu/MM/DIMACS10/preferentialAttachment.tar.gz
-wget https://sparse.tamu.edu/MM/DIMACS10/caidaRouterLevel.tar.gz
-wget https://sparse.tamu.edu/MM/DIMACS10/coAuthorsDBLP.tar.gz
-wget https://sparse.tamu.edu/MM/LAW/dblp-2010.tar.gz
-wget https://sparse.tamu.edu/MM/DIMACS10/citationCiteseer.tar.gz
-wget https://sparse.tamu.edu/MM/DIMACS10/coPapersDBLP.tar.gz
-wget https://sparse.tamu.edu/MM/DIMACS10/coPapersCiteseer.tar.gz
-wget https://sparse.tamu.edu/MM/SNAP/as-Skitter.tar.gz
-
-tar xvzf preferentialAttachment.tar.gz
-tar xvzf caidaRouterLevel.tar.gz
-tar xvzf coAuthorsDBLP.tar.gz
-tar xvzf dblp-2010.tar.gz
-tar xvzf citationCiteseer.tar.gz
-tar xvzf coPapersDBLP.tar.gz
-tar xvzf coPapersCiteseer.tar.gz
-tar xvzf as-Skitter.tar.gz
-
-cd ..
-
-find ./tmp -name *.mtx -exec mv {} . \;
-
-rm -rf tmp
diff --git a/intermediate_notebooks/benchmarks/cugraph_benchmarks/louvain_benchmark.ipynb b/intermediate_notebooks/benchmarks/cugraph_benchmarks/louvain_benchmark.ipynb
deleted file mode 100644
index 2a860e35..00000000
--- a/intermediate_notebooks/benchmarks/cugraph_benchmarks/louvain_benchmark.ipynb
+++ /dev/null
@@ -1,449 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Louvain Performance Benchmarking\n",
- "\n",
- "This notebook benchmarks performance improvement of running the Louvain clustering algorithm within cuGraph against NetworkX. The test is run over eight test networks (graphs) and then results plotted. \n",
- "
\n",
- "\n",
- "\n",
- "#### Notebook Credits\n",
- "\n",
- " Original Authors: Bradley Rees\n",
- " Last Edit: 08/06/2019\n",
- "\n",
- "\n",
- "#### Test Environment\n",
- "\n",
- " RAPIDS Versions: 0.9.0\n",
- "\n",
- " Test Hardware:\n",
- " GV100 32G, CUDA 10,0\n",
- " Intel(R) Core(TM) CPU i7-7800X @ 3.50GHz\n",
- " 32GB system memory\n",
- "\n",
- "\n",
- "\n",
- "#### Updates\n",
- "- moved loading ploting libraries to front so that dependencies can be checked before running algorithms\n",
- "- added edge values \n",
- "- changed timing to including Graph creation for both cuGraph and NetworkX. This will better represent end-to-end times\n",
- "\n",
- "\n",
- "\n",
- "#### Dependencies\n",
- "- RAPIDS cuDF and cuGraph version 0.6.0 \n",
- "- NetworkX \n",
- "- Matplotlib \n",
- "- Scipy \n",
- "- data prep script run\n",
- "\n",
- "\n",
- "\n",
- "#### Note: Comparison against published results\n",
- "\n",
- "\n",
- "The cuGraph blog post included performance numbers that were collected over a year ago. For the test graphs, int32 values are now used. That improves GPUs performance. Additionally, the initial benchamrks were measured on a P100 GPU. \n",
- "\n",
- "This test only comparse the modularity scores and a success is if the scores are within 15% of each other. That comparison is done by adjusting the NetworkX modularity score and then verifying that the cuGraph score is higher.\n",
- "\n",
- "cuGraph did a full validation of NetworkX results against cuGraph results. That included cross-validation of every cluster. That test is very slow and not included here"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Import needed libraries\n",
- "import time\n",
- "import cugraph\n",
- "import cudf\n",
- "import os"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [],
- "source": [
- "# NetworkX libraries\n",
- "try: \n",
- " import community\n",
- "except ModuleNotFoundError:\n",
- " os.system('pip install python-louvain')\n",
- " import community\n",
- "import networkx as nx\n",
- "from scipy.io import mmread"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Loading plotting libraries\n",
- "import matplotlib.pyplot as plt; plt.rcdefaults()\n",
- "import numpy as np\n",
- "import matplotlib.pyplot as plt"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "mkdir: cannot create directory 'data': File exists\n",
- "--2019-11-01 20:49:03-- https://sparse.tamu.edu/MM/DIMACS10/preferentialAttachment.tar.gz\n",
- "Resolving sparse.tamu.edu (sparse.tamu.edu)... 128.194.136.136\n",
- "Connecting to sparse.tamu.edu (sparse.tamu.edu)|128.194.136.136|:443... connected.\n",
- "HTTP request sent, awaiting response... 200 OK\n",
- "Length: 2027782 (1.9M) [application/x-gzip]\n",
- "Saving to: 'preferentialAttachment.tar.gz'\n",
- "\n",
- "preferentialAttachm 100%[===================>] 1.93M 3.48MB/s in 0.6s \n",
- "\n",
- "2019-11-01 20:49:04 (3.48 MB/s) - 'preferentialAttachment.tar.gz' saved [2027782/2027782]\n",
- "\n",
- "--2019-11-01 20:49:04-- https://sparse.tamu.edu/MM/DIMACS10/caidaRouterLevel.tar.gz\n",
- "Resolving sparse.tamu.edu (sparse.tamu.edu)... 128.194.136.136\n",
- "Connecting to sparse.tamu.edu (sparse.tamu.edu)|128.194.136.136|:443... connected.\n",
- "HTTP request sent, awaiting response... 200 OK\n",
- "Length: 2418742 (2.3M) [application/x-gzip]\n",
- "Saving to: 'caidaRouterLevel.tar.gz'\n",
- "\n",
- "caidaRouterLevel.ta 100%[===================>] 2.31M 3.76MB/s in 0.6s \n",
- "\n",
- "2019-11-01 20:49:05 (3.76 MB/s) - 'caidaRouterLevel.tar.gz' saved [2418742/2418742]\n",
- "\n",
- "--2019-11-01 20:49:05-- https://sparse.tamu.edu/MM/DIMACS10/coAuthorsDBLP.tar.gz\n",
- "Resolving sparse.tamu.edu (sparse.tamu.edu)... 128.194.136.136\n",
- "Connecting to sparse.tamu.edu (sparse.tamu.edu)|128.194.136.136|:443... connected.\n",
- "HTTP request sent, awaiting response... 200 OK\n",
- "Length: 3206075 (3.1M) [application/x-gzip]\n",
- "Saving to: 'coAuthorsDBLP.tar.gz'\n",
- "\n",
- "coAuthorsDBLP.tar.g 100%[===================>] 3.06M 3.99MB/s in 0.8s \n",
- "\n",
- "2019-11-01 20:49:06 (3.99 MB/s) - 'coAuthorsDBLP.tar.gz' saved [3206075/3206075]\n",
- "\n",
- "--2019-11-01 20:49:06-- https://sparse.tamu.edu/MM/LAW/dblp-2010.tar.gz\n",
- "Resolving sparse.tamu.edu (sparse.tamu.edu)... 128.194.136.136\n",
- "Connecting to sparse.tamu.edu (sparse.tamu.edu)|128.194.136.136|:443... connected.\n",
- "HTTP request sent, awaiting response... 200 OK\n",
- "Length: 2235407 (2.1M) [application/x-gzip]\n",
- "Saving to: 'dblp-2010.tar.gz'\n",
- "\n",
- "dblp-2010.tar.gz 100%[===================>] 2.13M 3.75MB/s in 0.6s \n",
- "\n",
- "2019-11-01 20:49:07 (3.75 MB/s) - 'dblp-2010.tar.gz' saved [2235407/2235407]\n",
- "\n",
- "--2019-11-01 20:49:07-- https://sparse.tamu.edu/MM/DIMACS10/citationCiteseer.tar.gz\n",
- "Resolving sparse.tamu.edu (sparse.tamu.edu)... 128.194.136.136\n",
- "Connecting to sparse.tamu.edu (sparse.tamu.edu)|128.194.136.136|:443... connected.\n",
- "HTTP request sent, awaiting response... 200 OK\n",
- "Length: 5082095 (4.8M) [application/x-gzip]\n",
- "Saving to: 'citationCiteseer.tar.gz'\n",
- "\n",
- "citationCiteseer.ta 100%[===================>] 4.85M 4.23MB/s in 1.1s \n",
- "\n",
- "2019-11-01 20:49:08 (4.23 MB/s) - 'citationCiteseer.tar.gz' saved [5082095/5082095]\n",
- "\n",
- "--2019-11-01 20:49:08-- https://sparse.tamu.edu/MM/DIMACS10/coPapersDBLP.tar.gz\n",
- "Resolving sparse.tamu.edu (sparse.tamu.edu)... 128.194.136.136\n",
- "Connecting to sparse.tamu.edu (sparse.tamu.edu)|128.194.136.136|:443... connected.\n",
- "HTTP request sent, awaiting response... 200 OK\n",
- "Length: 36298718 (35M) [application/x-gzip]\n",
- "Saving to: 'coPapersDBLP.tar.gz'\n",
- "\n",
- "coPapersDBLP.tar.gz 100%[===================>] 34.62M 4.93MB/s in 7.2s \n",
- "\n",
- "2019-11-01 20:49:16 (4.79 MB/s) - 'coPapersDBLP.tar.gz' saved [36298718/36298718]\n",
- "\n",
- "--2019-11-01 20:49:16-- https://sparse.tamu.edu/MM/DIMACS10/coPapersCiteseer.tar.gz\n",
- "Resolving sparse.tamu.edu (sparse.tamu.edu)... 128.194.136.136\n",
- "Connecting to sparse.tamu.edu (sparse.tamu.edu)|128.194.136.136|:443... connected.\n",
- "HTTP request sent, awaiting response... 200 OK\n",
- "Length: 36652888 (35M) [application/x-gzip]\n",
- "Saving to: 'coPapersCiteseer.tar.gz'\n",
- "\n",
- "coPapersCiteseer.ta 100%[===================>] 34.95M 4.93MB/s in 7.2s \n",
- "\n",
- "2019-11-01 20:49:23 (4.82 MB/s) - 'coPapersCiteseer.tar.gz' saved [36652888/36652888]\n",
- "\n",
- "--2019-11-01 20:49:23-- https://sparse.tamu.edu/MM/SNAP/as-Skitter.tar.gz\n",
- "Resolving sparse.tamu.edu (sparse.tamu.edu)... 128.194.136.136\n",
- "Connecting to sparse.tamu.edu (sparse.tamu.edu)|128.194.136.136|:443... connected.\n",
- "HTTP request sent, awaiting response... 200 OK\n",
- "Length: 33172905 (32M) [application/x-gzip]\n",
- "Saving to: 'as-Skitter.tar.gz'\n",
- "\n",
- "as-Skitter.tar.gz 100%[===================>] 31.64M 4.92MB/s in 6.6s \n",
- "\n",
- "2019-11-01 20:49:30 (4.79 MB/s) - 'as-Skitter.tar.gz' saved [33172905/33172905]\n",
- "\n",
- "preferentialAttachment/preferentialAttachment.mtx\n",
- "caidaRouterLevel/caidaRouterLevel.mtx\n",
- "coAuthorsDBLP/coAuthorsDBLP.mtx\n",
- "dblp-2010/dblp-2010.mtx\n",
- "citationCiteseer/citationCiteseer.mtx\n",
- "coPapersDBLP/coPapersDBLP.mtx\n",
- "coPapersCiteseer/coPapersCiteseer.mtx\n",
- "as-Skitter/as-Skitter.mtx\n",
- "find: paths must precede expression: caidaRouterLevel.mtx\n",
- "Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec|time] [path...] [expression]\n"
- ]
- }
- ],
- "source": [
- "!bash dataPrep.sh"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Define the test data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Test File\n",
- "data = {\n",
- " 'preferentialAttachment' : './data/preferentialAttachment.mtx',\n",
- " 'caidaRouterLevel' : './data/caidaRouterLevel.mtx',\n",
- " 'coAuthorsDBLP' : './data/coAuthorsDBLP.mtx',\n",
- " 'dblp' : './data/dblp-2010.mtx',\n",
- " 'citationCiteseer' : './data/citationCiteseer.mtx',\n",
- " 'coPapersDBLP' : './data/coPapersDBLP.mtx',\n",
- " 'coPapersCiteseer' : './data/coPapersCiteseer.mtx',\n",
- " 'as-Skitter' : './data/as-Skitter.mtx'\n",
- "}"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Define the testing functions"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Read in a dataset in MTX format \n",
- "def read_mtx_file(mm_file):\n",
- " print('Reading ' + str(mm_file) + '...')\n",
- " d = mmread(mm_file).asfptype()\n",
- " M = d.tocsr()\n",
- " \n",
- " if M is None:\n",
- " raise TypeError('Could not read the input graph')\n",
- " if M.shape[0] != M.shape[1]:\n",
- " raise TypeError('Shape is not square')\n",
- " \n",
- " return M"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 8,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Run the cuGraph Louvain analytic (using nvGRAPH function)\n",
- "def cugraph_call(M):\n",
- "\n",
- " t1 = time.time()\n",
- "\n",
- " # data\n",
- " row_offsets = cudf.Series(M.indptr)\n",
- " col_indices = cudf.Series(M.indices)\n",
- " data = cudf.Series(M.data)\n",
- " \n",
- " # create graph \n",
- " G = cugraph.Graph()\n",
- " G.add_adj_list(row_offsets, col_indices, data)\n",
- "\n",
- " # cugraph Louvain Call\n",
- " print(' cuGraph Solving... ')\n",
- " df, mod = cugraph.louvain(G) \n",
- " \n",
- " t2 = time.time() - t1\n",
- " return t2, mod\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Run the NetworkX Louvain analytic. THis is done in two parts since the modularity score is not returned \n",
- "def networkx_call(M):\n",
- " \n",
- " t1 = time.time()\n",
- "\n",
- " # Directed NetworkX graph\n",
- " Gnx = nx.Graph(M)\n",
- "\n",
- " # Networkx \n",
- " print(' NetworkX Solving... ')\n",
- " parts = community.best_partition(Gnx)\n",
- " \n",
- " # Calculating modularity scores for comparison \n",
- " mod = community.modularity(parts, Gnx) \n",
- " \n",
- " t2 = time.time() - t1\n",
- " \n",
- " return t2, mod"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Run the benchmarks"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "scrolled": true
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Reading ./data/preferentialAttachment.mtx...\n",
- " cuGraph Solving... \n",
- " NetworkX Solving... \n",
- "3509.4500202625027x faster => cugraph 0.8648371696472168 vs 3035.1028225421906\n",
- "Modularity => cugraph 0.19461682219817675 should be greater than 0.21973558127621454\n",
- "Reading ./data/caidaRouterLevel.mtx...\n",
- " cuGraph Solving... \n",
- " NetworkX Solving... \n",
- "7076.7607431556x faster => cugraph 0.04834103584289551 vs 342.0979447364807\n",
- "Modularity => cugraph 0.7872923202092253 should be greater than 0.7289947349239256\n",
- "Reading ./data/coAuthorsDBLP.mtx...\n",
- " cuGraph Solving... \n",
- " NetworkX Solving... \n",
- "11893.139026724633x faster => cugraph 0.06750750541687012 vs 802.8761472702026\n",
- "Modularity => cugraph 0.7648739273488195 should be greater than 0.7026254024456955\n",
- "Reading ./data/dblp-2010.mtx...\n",
- " cuGraph Solving... \n",
- " NetworkX Solving... \n",
- "12969.744546806074x faster => cugraph 0.07826042175292969 vs 1015.0176782608032\n",
- "Modularity => cugraph 0.7506256512679915 should be greater than 0.7450002914515801\n",
- "Reading ./data/citationCiteseer.mtx...\n",
- " cuGraph Solving... \n",
- " NetworkX Solving... \n",
- "16875.667838933237x faster => cugraph 0.07159066200256348 vs 1208.1402323246002\n",
- "Modularity => cugraph 0.6726575224227932 should be greater than 0.6845554405196591\n",
- "Reading ./data/coPapersDBLP.mtx...\n",
- " cuGraph Solving... \n",
- " NetworkX Solving... \n"
- ]
- }
- ],
- "source": [
- "# Loop through each test file and compute the speedup\n",
- "perf = []\n",
- "names = []\n",
- "\n",
- "for k,v in data.items():\n",
- " M = read_mtx_file(v)\n",
- " tr, modc = cugraph_call(M)\n",
- " tn, modx = networkx_call(M)\n",
- " \n",
- " speedUp = (tn / tr)\n",
- " names.append(k)\n",
- " perf.append(speedUp)\n",
- " \n",
- " mod_delta = (0.85 * modx)\n",
- " \n",
- " print(str(speedUp) + \"x faster => cugraph \" + str(tr) + \" vs \" + str(tn))\n",
- " print(\"Modularity => cugraph \" + str(modc) + \" should be greater than \" + str(mod_delta))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### plot the output"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "%matplotlib inline\n",
- "\n",
- "y_pos = np.arange(len(names))\n",
- " \n",
- "plt.bar(y_pos, perf, align='center', alpha=0.5)\n",
- "plt.xticks(y_pos, names)\n",
- "plt.ylabel('Speed Up')\n",
- "plt.title('Performance Speedup: cuGraph vs NetworkX')\n",
- "plt.xticks(rotation=90) \n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.6.7"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/intermediate_notebooks/benchmarks/cugraph_benchmarks/pagerank_benchmark.ipynb b/intermediate_notebooks/benchmarks/cugraph_benchmarks/pagerank_benchmark.ipynb
deleted file mode 100644
index 3697fcce..00000000
--- a/intermediate_notebooks/benchmarks/cugraph_benchmarks/pagerank_benchmark.ipynb
+++ /dev/null
@@ -1,398 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# PageRank Performance Benchmarking\n",
- "\n",
- "This notebook benchmarks performance of running PageRank within cuGraph against NetworkX. NetworkX contains several implementations of PageRank. This benchmark will compare cuGraph versus the defaukt Nx implementation as well as the SciPy version\n",
- "\n",
- "Notebook Credits\n",
- "\n",
- " Original Authors: Bradley Rees\n",
- " Last Edit: 12/23/2019\n",
- " \n",
- "RAPIDS Versions: 0.12.0\n",
- "\n",
- "Test Hardware\n",
- "\n",
- " GV100 32G, CUDA 10,0\n",
- " Intel(R) Core(TM) CPU i7-7800X @ 3.50GHz\n",
- " 32GB system memory\n",
- " \n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Test Data\n",
- "\n",
- "| File Name | Num of Vertices | Num of Edges |\n",
- "|:---------------------- | --------------: | -----------: |\n",
- "| preferentialAttachment | 100,000 | 999,970 |\n",
- "| caidaRouterLevel | 192,244 | 1,218,132 |\n",
- "| coAuthorsDBLP | 299,067 | 1,955,352 |\n",
- "| dblp-2010 | 326,186 | 1,615,400 |\n",
- "| citationCiteseer | 268,495 | 2,313,294 |\n",
- "| coPapersDBLP | 540,486 | 30,491,458 |\n",
- "| coPapersCiteseer | 434,102 | 32,073,440 |\n",
- "| as-Skitter | 1,696,415 | 22,190,596 |\n",
- "\n",
- "\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Timing \n",
- "What is not timed: Reading the data\n",
- "What is timmed: (1) creating a Graph, (2) running PageRank\n",
- "\n",
- "The data file is read in once for all flavors of PageRank. Each timed block will craete a Graph and then execute the algorithm. The results of the algorithm are not compared. If you are interested in seeing the comparison of results, then please see PageRank in the __notebooks__ repo. "
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## NOTICE\n",
- "You must have run the dataPrep script prior to running this notebook so that the data is downloaded\n",
- "\n",
- "See the README file in this folder for a discription of how to get the data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Import needed libraries\n",
- "import gc\n",
- "import time\n",
- "import rmm\n",
- "import cugraph\n",
- "import cudf"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# NetworkX libraries\n",
- "import networkx as nx\n",
- "from scipy.io import mmread"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import matplotlib.pyplot as plt; plt.rcdefaults()\n",
- "import numpy as np"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Get Data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!bash dataPrep.sh"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Define the test data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Test File\n",
- "data = {\n",
- " 'preferentialAttachment' : './data/preferentialAttachment.mtx',\n",
- " 'caidaRouterLevel' : './data/caidaRouterLevel.mtx',\n",
- " 'coAuthorsDBLP' : './data/coAuthorsDBLP.mtx',\n",
- " 'dblp' : './data/dblp-2010.mtx',\n",
- " 'citationCiteseer' : './data/citationCiteseer.mtx',\n",
- " 'coPapersDBLP' : './data/coPapersDBLP.mtx',\n",
- " 'coPapersCiteseer' : './data/coPapersCiteseer.mtx',\n",
- " 'as-Skitter' : './data/as-Skitter.mtx'\n",
- "}"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Define the testing functions"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Data reader - the file format is MTX, so we will use the reader from SciPy\n",
- "def read_mtx_file(mm_file):\n",
- " print('Reading ' + str(mm_file) + '...')\n",
- " M = mmread(mm_file).asfptype()\n",
- " \n",
- " return M"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# CuGraph PageRank\n",
- "\n",
- "def cugraph_call(M, max_iter, tol, alpha):\n",
- "\n",
- " gdf = cudf.DataFrame()\n",
- " gdf['src'] = M.row\n",
- " gdf['dst'] = M.col\n",
- " \n",
- " print('\\tcuGraph Solving... ')\n",
- " \n",
- " t1 = time.time()\n",
- " \n",
- " # cugraph Pagerank Call\n",
- " G = cugraph.Graph()\n",
- " G.from_cudf_edgelist(gdf, source='src', destination='dst')\n",
- " \n",
- " df = cugraph.pagerank(G, alpha=alpha, max_iter=max_iter, tol=tol)\n",
- " t2 = time.time() - t1\n",
- " \n",
- " return t2\n",
- " "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Basic NetworkX PageRank\n",
- "\n",
- "def networkx_call(M, max_iter, tol, alpha):\n",
- " nnz_per_row = {r: 0 for r in range(M.get_shape()[0])}\n",
- " for nnz in range(M.getnnz()):\n",
- " nnz_per_row[M.row[nnz]] = 1 + nnz_per_row[M.row[nnz]]\n",
- " for nnz in range(M.getnnz()):\n",
- " M.data[nnz] = 1.0/float(nnz_per_row[M.row[nnz]])\n",
- "\n",
- " M = M.tocsr()\n",
- " if M is None:\n",
- " raise TypeError('Could not read the input graph')\n",
- " if M.shape[0] != M.shape[1]:\n",
- " raise TypeError('Shape is not square')\n",
- "\n",
- " # should be autosorted, but check just to make sure\n",
- " if not M.has_sorted_indices:\n",
- " print('sort_indices ... ')\n",
- " M.sort_indices()\n",
- "\n",
- " z = {k: 1.0/M.shape[0] for k in range(M.shape[0])}\n",
- " \n",
- " print('\\tNetworkX Solving... ')\n",
- " \n",
- " # start timer\n",
- " t1 = time.time()\n",
- " \n",
- " Gnx = nx.DiGraph(M)\n",
- "\n",
- " pr = nx.pagerank(Gnx, alpha, z, max_iter, tol)\n",
- " \n",
- " t2 = time.time() - t1\n",
- "\n",
- " return t2"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# SciPy PageRank\n",
- "\n",
- "def networkx_scipy_call(M, max_iter, tol, alpha):\n",
- " nnz_per_row = {r: 0 for r in range(M.get_shape()[0])}\n",
- " for nnz in range(M.getnnz()):\n",
- " nnz_per_row[M.row[nnz]] = 1 + nnz_per_row[M.row[nnz]]\n",
- " for nnz in range(M.getnnz()):\n",
- " M.data[nnz] = 1.0/float(nnz_per_row[M.row[nnz]])\n",
- "\n",
- " M = M.tocsr()\n",
- " if M is None:\n",
- " raise TypeError('Could not read the input graph')\n",
- " if M.shape[0] != M.shape[1]:\n",
- " raise TypeError('Shape is not square')\n",
- "\n",
- " # should be autosorted, but check just to make sure\n",
- " if not M.has_sorted_indices:\n",
- " print('sort_indices ... ')\n",
- " M.sort_indices()\n",
- "\n",
- " z = {k: 1.0/M.shape[0] for k in range(M.shape[0])}\n",
- "\n",
- " # SciPy Pagerank Call\n",
- " print('\\tSciPy Solving... ')\n",
- " t1 = time.time()\n",
- " \n",
- " Gnx = nx.DiGraph(M) \n",
- " \n",
- " pr = nx.pagerank_scipy(Gnx, alpha, z, max_iter, tol)\n",
- " t2 = time.time() - t1\n",
- "\n",
- " return t2"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Run the benchmarks"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# arrays to capture performance gains\n",
- "perf_nx = []\n",
- "perf_sp = []\n",
- "names = []\n",
- "\n",
- "for k,v in data.items():\n",
- " gc.collect()\n",
- "\n",
- " rmm.reinitialize(\n",
- " managed_memory=False,\n",
- " pool_allocator=False,\n",
- " initial_pool_size=2 << 27\n",
- " )\n",
- " \n",
- " # Saved the file Name\n",
- " names.append(k)\n",
- " \n",
- " # read the data\n",
- " M = read_mtx_file(v)\n",
- " \n",
- " # call cuGraph - this will be the baseline\n",
- " trapids = cugraph_call(M, 100, 0.00001, 0.85)\n",
- " \n",
- " # Now call NetworkX\n",
- " tn = networkx_call(M, 100, 0.00001, 0.85)\n",
- " speedUp = (tn / trapids)\n",
- " perf_nx.append(speedUp)\n",
- " \n",
- " # Now call SciPy\n",
- " tsp = networkx_scipy_call(M, 100, 0.00001, 0.85)\n",
- " speedUp = (tsp / trapids)\n",
- " perf_sp.append(speedUp) \n",
- " \n",
- " print(\"cuGraph (\" + str(trapids) + \") Nx (\" + str(tn) + \") SciPy (\" + str(tsp) + \")\" )"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "### plot the output"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
-
- "%matplotlib inline\n",
- "\n",
- "plt.figure(figsize=(10,8))\n",
- "\n",
- "bar_width = 0.35\n",
- "index = np.arange(len(names))\n",
- "\n",
- "_ = plt.bar(index, perf_nx, bar_width, color='g', label='vs Nx')\n",
- "_ = plt.bar(index + bar_width, perf_sp, bar_width, color='b', label='vs SciPy')\n",
- "\n",
- "plt.xlabel('Datasets')\n",
- "plt.ylabel('Speedup')\n",
- "plt.title('PageRank Performance Speedup')\n",
- "plt.xticks(index + (bar_width / 2), names)\n",
- "plt.xticks(rotation=90) \n",
- "\n",
- "# Text on the top of each barplot\n",
- "for i in range(len(perf_nx)):\n",
- " plt.text(x = (i - 0.55) + bar_width, y = perf_nx[i] + 25, s = round(perf_nx[i], 1), size = 12)\n",
- "\n",
- "for i in range(len(perf_sp)):\n",
- " plt.text(x = (i - 0.1) + bar_width, y = perf_sp[i] + 25, s = round(perf_sp[i], 1), size = 12)\n",
- "\n",
- "\n",
- "plt.legend()\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.6.7"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/intermediate_notebooks/benchmarks/cugraph_benchmarks/sssp_benchmark.ipynb b/intermediate_notebooks/benchmarks/cugraph_benchmarks/sssp_benchmark.ipynb
deleted file mode 100644
index 170c72c0..00000000
--- a/intermediate_notebooks/benchmarks/cugraph_benchmarks/sssp_benchmark.ipynb
+++ /dev/null
@@ -1,331 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# SSSP Performance Benchmarking\n",
- "\n",
- "This notebook benchmarks performance of running SSSP within cuGraph against NetworkX. \n",
- "\n",
- "Notebook Credits\n",
- "\n",
- " Original Authors: Bradley Rees\n",
- " Last Edit: 12/24/2019\n",
- " \n",
- "RAPIDS Versions: 0.12.0\n",
- "\n",
- "Test Hardware\n",
- "\n",
- " GV100 32G, CUDA 10,0\n",
- " Intel(R) Core(TM) CPU i7-7800X @ 3.50GHz\n",
- " 32GB system memory\n",
- " \n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Test Data\n",
- "\n",
- "| File Name | Num of Vertices | Num of Edges |\n",
- "|:---------------------- | --------------: | -----------: |\n",
- "| preferentialAttachment | 100,000 | 999,970 |\n",
- "| caidaRouterLevel | 192,244 | 1,218,132 |\n",
- "| coAuthorsDBLP | 299,067 | 1,955,352 |\n",
- "| dblp-2010 | 326,186 | 1,615,400 |\n",
- "| citationCiteseer | 268,495 | 2,313,294 |\n",
- "| coPapersDBLP | 540,486 | 30,491,458 |\n",
- "| coPapersCiteseer | 434,102 | 32,073,440 |\n",
- "| as-Skitter | 1,696,415 | 22,190,596 |\n",
- "\n",
- "\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Timing \n",
- "What is not timed: Reading the data\n",
- "What is timmed: (1) creating a Graph, (2) running SSSP\n",
- "\n",
- "The data file is read and used for both cuGraph and NetworkX. Each timed block will craete a Graph and then execute the algorithm. The results of the algorithm are not compared. "
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## NOTICE\n",
- "You must have run the dataPrep script prior to running this notebook so that the data is downloaded\n",
- "\n",
- "See the README file in this folder for a discription of how to get the data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Import needed libraries\n",
- "import gc\n",
- "import time\n",
- "import rmm\n",
- "import cugraph\n",
- "import cudf"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# NetworkX libraries\n",
- "import networkx as nx\n",
- "from scipy.io import mmread"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import matplotlib.pyplot as plt; plt.rcdefaults()\n",
- "import numpy as np"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Get Data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!bash dataPrep.sh"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Define the test data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Test File\n",
- "data = {\n",
- " 'preferentialAttachment' : './data/preferentialAttachment.mtx',\n",
- " 'caidaRouterLevel' : './data/caidaRouterLevel.mtx',\n",
- " 'coAuthorsDBLP' : './data/coAuthorsDBLP.mtx',\n",
- " 'dblp' : './data/dblp-2010.mtx',\n",
- " 'citationCiteseer' : './data/citationCiteseer.mtx',\n",
- " 'coPapersDBLP' : './data/coPapersDBLP.mtx',\n",
- " 'coPapersCiteseer' : './data/coPapersCiteseer.mtx',\n",
- " 'as-Skitter' : './data/as-Skitter.mtx'\n",
- "}"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Define the testing functions"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Data reader - the file format is MTX, so we will use the reader from SciPy\n",
- "def read_mtx_file(mm_file):\n",
- " print('Reading ' + str(mm_file) + '...')\n",
- " M = mmread(mm_file).asfptype()\n",
- " \n",
- " return M"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# CuGraph SSSP\n",
- "\n",
- "def cugraph_call(M, max_iter, tol, alpha):\n",
- "\n",
- " gdf = cudf.DataFrame()\n",
- " gdf['src'] = M.row\n",
- " gdf['dst'] = M.col\n",
- " \n",
- " print('\\tcuGraph Solving... ')\n",
- " \n",
- " t1 = time.time()\n",
- " \n",
- " # cugraph SSSP Call\n",
- " G = cugraph.Graph()\n",
- " G.from_cudf_edgelist(gdf, source='src', destination='dst')\n",
- " \n",
- " df = cugraph.sssp(G, 1)\n",
- " t2 = time.time() - t1\n",
- " \n",
- " return t2\n",
- " "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Basic NetworkX SSSP\n",
- "\n",
- "def networkx_call(M, max_iter, tol, alpha):\n",
- " nnz_per_row = {r: 0 for r in range(M.get_shape()[0])}\n",
- " for nnz in range(M.getnnz()):\n",
- " nnz_per_row[M.row[nnz]] = 1 + nnz_per_row[M.row[nnz]]\n",
- " for nnz in range(M.getnnz()):\n",
- " M.data[nnz] = 1.0/float(nnz_per_row[M.row[nnz]])\n",
- "\n",
- " M = M.tocsr()\n",
- " if M is None:\n",
- " raise TypeError('Could not read the input graph')\n",
- " if M.shape[0] != M.shape[1]:\n",
- " raise TypeError('Shape is not square')\n",
- "\n",
- " # should be autosorted, but check just to make sure\n",
- " if not M.has_sorted_indices:\n",
- " print('sort_indices ... ')\n",
- " M.sort_indices()\n",
- "\n",
- " z = {k: 1.0/M.shape[0] for k in range(M.shape[0])}\n",
- " \n",
- " print('\\tNetworkX Solving... ')\n",
- " \n",
- " # start timer\n",
- " t1 = time.time()\n",
- " \n",
- " Gnx = nx.DiGraph(M)\n",
- "\n",
- " pr = nx.shortest_path(Gnx, 1)\n",
- " \n",
- " t2 = time.time() - t1\n",
- "\n",
- " return t2"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Run the benchmarks"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# arrays to capture performance gains\n",
- "perf_nx = []\n",
- "names = []\n",
- "\n",
- "for k,v in data.items():\n",
- " gc.collect()\n",
- "\n",
- " rmm.reinitialize(\n",
- " managed_memory=False,\n",
- " pool_allocator=False,\n",
- " initial_pool_size=2 << 27\n",
- " ) \n",
- " \n",
- " # Saved the file Name\n",
- " names.append(k)\n",
- " \n",
- " # read the data\n",
- " M = read_mtx_file(v)\n",
- " \n",
- " # call cuGraph - this will be the baseline\n",
- " trapids = cugraph_call(M, 100, 0.00001, 0.85)\n",
- " \n",
- " # Now call NetworkX\n",
- " tn = networkx_call(M, 100, 0.00001, 0.85)\n",
- " speedUp = (tn / trapids)\n",
- " perf_nx.append(speedUp)\n",
- " \n",
- " print(\"\\tcuGraph (\" + str(trapids) + \") Nx (\" + str(tn) + \")\" )"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "%matplotlib inline\n",
- "\n",
- "plt.figure(figsize=(10,8))\n",
- "\n",
- "bar_width = 0.4\n",
- "index = np.arange(len(names))\n",
- "\n",
- "_ = plt.bar(index, perf_nx, bar_width, color='g', label='vs Nx')\n",
- "\n",
- "plt.xlabel('Datasets')\n",
- "plt.ylabel('Speedup')\n",
- "plt.title('SSSP Performance Speedup of cuGraph vs NetworkX')\n",
- "plt.xticks(index, names)\n",
- "plt.xticks(rotation=90) \n",
- "\n",
- "# Text on the top of each barplot\n",
- "for i in range(len(perf_nx)):\n",
- " #plt.text(x = (i - 0.6) + bar_width, y = perf_nx[i] + 25, s = round(perf_nx[i], 1), size = 12)\n",
- " plt.text(x = i - (bar_width/2), y = perf_nx[i] + 25, s = round(perf_nx[i], 1), size = 12)\n",
- "\n",
- "#plt.legend()\n",
- "plt.show()"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.3"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/intermediate_notebooks/benchmarks/cuml_benchmarks.ipynb b/intermediate_notebooks/benchmarks/cuml_benchmarks.ipynb
deleted file mode 100644
index 2f56d21d..00000000
--- a/intermediate_notebooks/benchmarks/cuml_benchmarks.ipynb
+++ /dev/null
@@ -1,488 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Benchmark and Bounds Tests\n",
- "\n",
- "The purpose of this notebook is to benchmark all of the single GPU cuML algorithms against their skLearn counterparts, while also providing the ability to find and verify upper bounds.\n",
- "\n",
- "Each benchmark returns a Panda with the results, which can then be analyzed, manipulated, and stored to disk. "
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Notebook Credits\n",
- "**Authorship**\n",
- "Original Author: Corey Nolet \n",
- "Last Edit: Taurean Dyer, 9/25/2019 \n",
- "\n",
- "Last Edit: Corey Nolet, 10/04/2019\n",
- " \n",
- "### Test System Specs\n",
- "Test System Hardware: DGX-1 \n",
- "Test System Software: Ubuntu 16.04 \n",
- "RAPIDS Version: 0.10.0pre - Conda Install \n",
- "Driver: 410.48\n",
- "CUDA: 10.0 \n",
- "\n",
- "### Known Working Systems\n",
- "RAPIDS Versions: 0.10+"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import cuml\n",
- "\n",
- "from cuml.benchmark.runners import SpeedupComparisonRunner\n",
- "from cuml.benchmark.algorithms import algorithm_by_name\n",
- "\n",
- "\n",
- "print(cuml.__version__)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Neighbors"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Nearest Neighbors"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(11, 24)], \n",
- " bench_dims=[64, 128, 256],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"NearestNeighbors\"), verbose=True)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Clustering"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### DBSCAN"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(11, 24)], \n",
- " bench_dims=[64, 128, 256],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"DBSCAN\"), verbose=True)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### K-means Clustering"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(12, 22)], \n",
- " bench_dims=[64, 128, 256],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"KMeans\"), verbose=True)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Manifold Learning"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### UMAP"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(11, 24)], \n",
- " bench_dims=[64, 128, 256],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"UMAP\"), verbose=True)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### T-SNE"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(11, 24)], \n",
- " bench_dims=[64, 128, 256],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"TSNE\"), verbose=True)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Linear Models"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Linear Regression"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(11, 24)], \n",
- " bench_dims=[64, 128, 256],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"LinearRegression\"), verbose=True)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Logistic Regression"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(11, 24)], \n",
- " bench_dims=[64, 128, 256],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"LogisticRegression\"), verbose=True)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Ridge Regression"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(11, 24)], \n",
- " bench_dims=[64, 128, 256],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"Ridge\"), verbose=True)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Lasso Regression"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(11, 24)], \n",
- " bench_dims=[64, 128, 256],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"Lasso\"), verbose=True)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### ElasticNet Regression"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(11, 24)], \n",
- " bench_dims=[64, 128, 256],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"ElasticNet\"), verbose=True)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Mini-batch SGD Classifier"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(11, 24)], \n",
- " bench_dims=[64, 128, 256],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"MBSGDClassifier\"))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Decomposition"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### PCA"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(11, 24)], \n",
- " bench_dims=[64, 128, 256],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"PCA\"), verbose=True)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### TSVD"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(11, 24)], \n",
- " bench_dims=[64, 128, 256],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"TSVD\"), verbose=True)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Ensemble"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Random Forest Classifier"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(11, 24)], \n",
- " bench_dims=[64, 128, 256],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"RandomForestClassifier\"), verbose=True)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Random Forest Regressor"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(11, 24)], \n",
- " bench_dims=[64, 128, 256],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"RandomForestClassifier\"), verbose=True)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Random Projection"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Gaussian Random Projection"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "runner = cuml.benchmark.runners.SpeedupComparisonRunner(\n",
- " bench_rows=[2**x for x in range(17, 24)], \n",
- " bench_dims=[100, 500, 1000, 10000],\n",
- " dataset_name=\"blobs\",\n",
- " input_type=\"numpy\")\n",
- "\n",
- "results = runner.run(algorithm_by_name(\"GaussianRandomProjection\"), verbose=True)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python (cuml_dev)",
- "language": "python",
- "name": "other-env"
- },
- "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": 4
-}
diff --git a/intermediate_notebooks/examples/linear_regression_demo.ipynb b/intermediate_notebooks/examples/linear_regression_demo.ipynb
deleted file mode 100644
index 1e6b1d98..00000000
--- a/intermediate_notebooks/examples/linear_regression_demo.ipynb
+++ /dev/null
@@ -1,826 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "2tZ3RLnlkrkg"
- },
- "source": [
- "# Intro to Linear Regression with cuML\n",
- "Corresponding notebook to [*Beginner’s Guide to Linear Regression in Python with cuML*](http://bit.ly/cuml_lin_reg_friend) story on Medium\n",
- "\n",
- "Linear Regression is a simple machine learning model where the response `y` is modelled by a linear combination of the predictors in `X`. The `LinearRegression` function implemented in the `cuML` library allows users to change the `fit_intercept`, `normalize`, and `algorithm` parameters. \n",
- "\n",
- "Here is a brief on RAPIDS' Linear Regression parameters:\n",
- "\n",
- "- `algorithm`: 'eig' or 'svd' (default = 'eig')\n",
- " - `Eig` uses a eigen decomposition of the covariance matrix, and is much faster\n",
- " - `SVD` is slower, but guaranteed to be stable\n",
- "- `fit_intercept`: boolean (default = True)\n",
- " - If `True`, `LinearRegresssion` tries to correct for the global mean of `y`\n",
- " - If `False`, the model expects that you have centered the data.\n",
- "- `normalize`: boolean (default = False)\n",
- " - If True, the predictors in X will be normalized by dividing by it’s L2 norm\n",
- " - If False, no scaling will be done\n",
- "\n",
- "Methods that can be used with `LinearRegression` are:\n",
- "\n",
- "- `fit`: Fit the model with `X` and `y`\n",
- "- `get_params`: Sklearn style return parameter state\n",
- "- `predict`: Predicts the `y` for `X`\n",
- "- `set_params`: Sklearn style set parameter state to dictionary of params\n",
- "\n",
- "`cuML`'s `LinearRegression` expects expects either `cuDF` DataFrame or `NumPy` matrix inputs\n",
- "\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "-tG6ezqKh1Z0"
- },
- "source": [
- "Note: `CuPy` is not installed by default with RAPIDS `Conda` or `Docker` packages, but is needed for visualizing results in this notebook.\n",
- "- install with `pip` via the cell below "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 1,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "pxBcXor_0-Jd"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Requirement already satisfied: cupy in /opt/conda/envs/rapids/lib/python3.6/site-packages (7.4.0)\n",
- "Requirement already satisfied: six>=1.9.0 in /opt/conda/envs/rapids/lib/python3.6/site-packages (from cupy) (1.14.0)\n",
- "Requirement already satisfied: numpy>=1.9.0 in /opt/conda/envs/rapids/lib/python3.6/site-packages (from cupy) (1.18.4)\n",
- "Requirement already satisfied: fastrlock>=0.3 in /opt/conda/envs/rapids/lib/python3.6/site-packages (from cupy) (0.4)\n"
- ]
- }
- ],
- "source": [
- "# install cupy\n",
- "!pip install cupy"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "N20le3_KlP3O"
- },
- "source": [
- "## Load data\n",
- "- for this demo, we will be utilizing the Boston housing dataset from `sklearn`\n",
- " - start by loading in the set and printing a map of the contents"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/",
- "height": 34
- },
- "colab_type": "code",
- "id": "RFE-nxxlTajg",
- "outputId": "04f89e88-61a3-4dd2-9088-123b410e508c"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "dict_keys(['data', 'target', 'feature_names', 'DESCR', 'filename'])\n"
- ]
- }
- ],
- "source": [
- "from sklearn.datasets import load_boston\n",
- "\n",
- "# load Boston dataset\n",
- "boston = load_boston()\n",
- "\n",
- "# let's see what's inside\n",
- "print(boston.keys())"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "wmcO8dxO0uOB"
- },
- "source": [
- "#### Boston house prices dataset\n",
- "- a description of the dataset is provided in `DESCR`\n",
- " - let's explore "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/",
- "height": 923
- },
- "colab_type": "code",
- "id": "c3kLHAsP-Al2",
- "outputId": "02518c3c-7767-42a7-b6f4-6756ace741cc"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- ".. _boston_dataset:\n",
- "\n",
- "Boston house prices dataset\n",
- "---------------------------\n",
- "\n",
- "**Data Set Characteristics:** \n",
- "\n",
- " :Number of Instances: 506 \n",
- "\n",
- " :Number of Attributes: 13 numeric/categorical predictive. Median Value (attribute 14) is usually the target.\n",
- "\n",
- " :Attribute Information (in order):\n",
- " - CRIM per capita crime rate by town\n",
- " - ZN proportion of residential land zoned for lots over 25,000 sq.ft.\n",
- " - INDUS proportion of non-retail business acres per town\n",
- " - CHAS Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)\n",
- " - NOX nitric oxides concentration (parts per 10 million)\n",
- " - RM average number of rooms per dwelling\n",
- " - AGE proportion of owner-occupied units built prior to 1940\n",
- " - DIS weighted distances to five Boston employment centres\n",
- " - RAD index of accessibility to radial highways\n",
- " - TAX full-value property-tax rate per $10,000\n",
- " - PTRATIO pupil-teacher ratio by town\n",
- " - B 1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town\n",
- " - LSTAT % lower status of the population\n",
- " - MEDV Median value of owner-occupied homes in $1000's\n",
- "\n",
- " :Missing Attribute Values: None\n",
- "\n",
- " :Creator: Harrison, D. and Rubinfeld, D.L.\n",
- "\n",
- "This is a copy of UCI ML housing dataset.\n",
- "https://archive.ics.uci.edu/ml/machine-learning-databases/housing/\n",
- "\n",
- "\n",
- "This dataset was taken from the StatLib library which is maintained at Carnegie Mellon University.\n",
- "\n",
- "The Boston house-price data of Harrison, D. and Rubinfeld, D.L. 'Hedonic\n",
- "prices and the demand for clean air', J. Environ. Economics & Management,\n",
- "vol.5, 81-102, 1978. Used in Belsley, Kuh & Welsch, 'Regression diagnostics\n",
- "...', Wiley, 1980. N.B. Various transformations are used in the table on\n",
- "pages 244-261 of the latter.\n",
- "\n",
- "The Boston house-price data has been used in many machine learning papers that address regression\n",
- "problems. \n",
- " \n",
- ".. topic:: References\n",
- "\n",
- " - Belsley, Kuh & Welsch, 'Regression diagnostics: Identifying Influential Data and Sources of Collinearity', Wiley, 1980. 244-261.\n",
- " - Quinlan,R. (1993). Combining Instance-Based and Model-Based Learning. In Proceedings on the Tenth International Conference of Machine Learning, 236-243, University of Massachusetts, Amherst. Morgan Kaufmann.\n",
- "\n"
- ]
- }
- ],
- "source": [
- "# what do we know about this dataset?\n",
- "print(boston.DESCR)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "wI_sB78vE297"
- },
- "source": [
- "### Build Dataframe\n",
- "- Import `cuDF` and input the data into a DataFrame \n",
- " - Then add a `PRICE` column equal to the `target` key"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/",
- "height": 206
- },
- "colab_type": "code",
- "id": "xiMmIZ8O5scJ",
- "outputId": "fd09db1f-fb41-4494-bb8b-eab6e18c258f"
- },
- "outputs": [
- {
- "data": {
- "text/html": [
- "
\n",
- "\n",
- "
\n",
- " \n",
- "
\n",
- "
\n",
- "
CRIM
\n",
- "
ZN
\n",
- "
INDUS
\n",
- "
CHAS
\n",
- "
NOX
\n",
- "
RM
\n",
- "
AGE
\n",
- "
DIS
\n",
- "
RAD
\n",
- "
TAX
\n",
- "
PTRATIO
\n",
- "
B
\n",
- "
LSTAT
\n",
- "
PRICE
\n",
- "
\n",
- " \n",
- " \n",
- "
\n",
- "
0
\n",
- "
0.00632
\n",
- "
18.0
\n",
- "
2.31
\n",
- "
0.0
\n",
- "
0.538
\n",
- "
6.575
\n",
- "
65.2
\n",
- "
4.0900
\n",
- "
1.0
\n",
- "
296.0
\n",
- "
15.3
\n",
- "
396.90
\n",
- "
4.98
\n",
- "
24.0
\n",
- "
\n",
- "
\n",
- "
1
\n",
- "
0.02731
\n",
- "
0.0
\n",
- "
7.07
\n",
- "
0.0
\n",
- "
0.469
\n",
- "
6.421
\n",
- "
78.9
\n",
- "
4.9671
\n",
- "
2.0
\n",
- "
242.0
\n",
- "
17.8
\n",
- "
396.90
\n",
- "
9.14
\n",
- "
21.6
\n",
- "
\n",
- "
\n",
- "
2
\n",
- "
0.02729
\n",
- "
0.0
\n",
- "
7.07
\n",
- "
0.0
\n",
- "
0.469
\n",
- "
7.185
\n",
- "
61.1
\n",
- "
4.9671
\n",
- "
2.0
\n",
- "
242.0
\n",
- "
17.8
\n",
- "
392.83
\n",
- "
4.03
\n",
- "
34.7
\n",
- "
\n",
- "
\n",
- "
3
\n",
- "
0.03237
\n",
- "
0.0
\n",
- "
2.18
\n",
- "
0.0
\n",
- "
0.458
\n",
- "
6.998
\n",
- "
45.8
\n",
- "
6.0622
\n",
- "
3.0
\n",
- "
222.0
\n",
- "
18.7
\n",
- "
394.63
\n",
- "
2.94
\n",
- "
33.4
\n",
- "
\n",
- "
\n",
- "
4
\n",
- "
0.06905
\n",
- "
0.0
\n",
- "
2.18
\n",
- "
0.0
\n",
- "
0.458
\n",
- "
7.147
\n",
- "
54.2
\n",
- "
6.0622
\n",
- "
3.0
\n",
- "
222.0
\n",
- "
18.7
\n",
- "
396.90
\n",
- "
5.33
\n",
- "
36.2
\n",
- "
\n",
- " \n",
- "
\n",
- "
"
- ],
- "text/plain": [
- " CRIM ZN INDUS CHAS NOX RM AGE DIS RAD TAX \\\n",
- "0 0.00632 18.0 2.31 0.0 0.538 6.575 65.2 4.0900 1.0 296.0 \n",
- "1 0.02731 0.0 7.07 0.0 0.469 6.421 78.9 4.9671 2.0 242.0 \n",
- "2 0.02729 0.0 7.07 0.0 0.469 7.185 61.1 4.9671 2.0 242.0 \n",
- "3 0.03237 0.0 2.18 0.0 0.458 6.998 45.8 6.0622 3.0 222.0 \n",
- "4 0.06905 0.0 2.18 0.0 0.458 7.147 54.2 6.0622 3.0 222.0 \n",
- "\n",
- " PTRATIO B LSTAT PRICE \n",
- "0 15.3 396.90 4.98 24.0 \n",
- "1 17.8 396.90 9.14 21.6 \n",
- "2 17.8 392.83 4.03 34.7 \n",
- "3 18.7 394.63 2.94 33.4 \n",
- "4 18.7 396.90 5.33 36.2 "
- ]
- },
- "execution_count": 4,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "import cudf\n",
- "\n",
- "# build dataframe from data key\n",
- "bos = cudf.DataFrame(list(boston.data))\n",
- "# set column names to feature_names\n",
- "bos.columns = boston.feature_names\n",
- "\n",
- "# add PRICE column from target\n",
- "bos['PRICE'] = boston.target\n",
- "\n",
- "# let's see what we're working with\n",
- "bos.head()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "r2qrTxo4ljZp"
- },
- "source": [
- "### Split Train from Test\n",
- "- For basic Linear Regression, we will predict `PRICE` (Median value of owner-occupied homes) based on `TAX` (full-value property-tax rate per $10,000)\n",
- " - Go ahead and trim data to just these columns"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "spaDB10E3okF"
- },
- "outputs": [],
- "source": [
- "# simple linear regression X and Y\n",
- "X = bos['TAX']\n",
- "Y = bos['PRICE']"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "4TKLv8FjIBuI"
- },
- "source": [
- "We can now set training and testing sets for our model\n",
- "- Use `cuML`'s `train_test_split` to do this\n",
- " - Train on 70% of data\n",
- " - Test on 30% of data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/",
- "height": 86
- },
- "colab_type": "code",
- "id": "1DC6FHsNIKH_",
- "outputId": "4c932268-7a82-4ac3-c7b9-9966ffc2b12e"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "(354,)\n",
- "(152,)\n",
- "(354,)\n",
- "(152,)\n"
- ]
- }
- ],
- "source": [
- "from cuml.preprocessing.model_selection import train_test_split\n",
- "\n",
- "# train/test split (70:30)\n",
- "sX_train, sX_test, sY_train, sY_test = train_test_split(X, Y, train_size = 0.7)\n",
- "\n",
- "# see what it looks like\n",
- "print(sX_train.shape)\n",
- "print(sX_test.shape)\n",
- "print(sY_train.shape)\n",
- "print(sY_test.shape)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "ZLVg44gAmJG7"
- },
- "source": [
- "### Predict Values\n",
- "1. fit the model with `TAX` (*X_train*) and corresponding `PRICE` (*y_train*) values \n",
- " - so it can build an understanding of their relationship \n",
- "2. predict `PRICE` (*y_test*) for a test set of `TAX` (*X_test*) values\n",
- " - and compare `PRICE` predictions to actual median house (*y_test*) values\n",
- " - use `sklearn`'s `mean_squared_error` to do this"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "0 666.0\n",
- "1 403.0\n",
- "2 193.0\n",
- "3 307.0\n",
- "4 264.0\n",
- "Name: TAX, dtype: float64"
- ]
- },
- "execution_count": 7,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "sX_train.head()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 8,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/",
- "height": 34
- },
- "colab_type": "code",
- "id": "ZGMPloJxGtK3",
- "outputId": "664b54fe-16d5-4140-a657-3dc782574da9"
- },
- "outputs": [
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "/opt/conda/envs/rapids/lib/python3.6/site-packages/ipykernel_launcher.py:8: UserWarning: Changing solver from 'eig' to 'svd' as eig solver does not support training data with 1 column currently.\n",
- " \n"
- ]
- },
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "53.207501007491125\n"
- ]
- }
- ],
- "source": [
- "from cuml import LinearRegression\n",
- "from sklearn.metrics import mean_squared_error\n",
- "\n",
- "# call Linear Regression model\n",
- "slr = LinearRegression()\n",
- "\n",
- "# train the model\n",
- "slr.fit(sX_train, sY_train)\n",
- "\n",
- "# make predictions for test X values\n",
- "sY_pred = slr.predict(sX_test)\n",
- "\n",
- "# calculate error\n",
- "mse = mean_squared_error(sY_test.to_array(), \n",
- " sY_pred.to_array())\n",
- "print(mse)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "T7BXjkPSGwqd"
- },
- "source": [
- "3. visualize prediction accuracy with `matplotlib`"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/",
- "height": 305
- },
- "colab_type": "code",
- "id": "pp9RNPt_Iemk",
- "outputId": "22a22472-50ad-4bb3-d104-35e9e100b8b6"
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "
"
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "import cupy\n",
- "import matplotlib.pyplot as plt\n",
- "\n",
- "# scatter actual and predicted results\n",
- "plt.scatter(sY_test.to_array(), sY_pred.to_array())\n",
- "\n",
- "# label graph\n",
- "plt.xlabel(\"Actual Prices: $Y_i$\")\n",
- "plt.ylabel(\"Predicted prices: $\\hat{Y}_i$\")\n",
- "plt.title(\"Prices vs Predicted prices: $Y_i$ vs $\\hat{Y}_i$\")\n",
- "\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "8MqX73B4s5tv"
- },
- "source": [
- "## Multiple Linear Regression \n",
- "- Our mean squared error for Simple Linear Regression looks kinda high.\n",
- " - Let's try Multiple Linear Regression (predicting based on multiple variables rather than just `TAX`) and see if that produces more accurate predictions\n",
- "\n",
- "1. Set X to contain all values that are not `PRICE` from the unsplit data\n",
- " - i.e. `CRIM`, `ZN`, `INDUS`, `CHAS`, `NOX`, `RM`, `AGE`, `DIS`, `RAD`, `TAX`, `PTRATIO`, `B`, `LSTAT`\n",
- " - Y to still represent just 1 target value (`PRICE`)\n",
- " - also from the unsplit data\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 10,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "ZtQK5-f4M0Vg"
- },
- "outputs": [],
- "source": [
- "# set X to all variables except price\n",
- "mX = bos.drop('PRICE', axis=1)\n",
- "# and, like in the simple Linear Regression, set Y to price\n",
- "mY = bos['PRICE']"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "RTYG4-UwNDsK"
- },
- "source": [
- "2. Split the data into `multi_X_train`, `multi_X_test`, `Y_train`, and `Y_test`\n",
- " - Use `cuML`'s `train_test_split`\n",
- " - And the same 70:30 train:test ratio"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 11,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/",
- "height": 86
- },
- "colab_type": "code",
- "id": "EsKxK8u_F7t8",
- "outputId": "673a1a44-4d2f-4a45-8333-8f29782eaf65"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "(354, 13)\n",
- "(152, 13)\n",
- "(354,)\n",
- "(152,)\n"
- ]
- }
- ],
- "source": [
- "# train/test split (70:30)\n",
- "mX_train, mX_test, mY_train, mY_test = train_test_split(mX, mY, train_size = 0.7)\n",
- "\n",
- "# see what it looks like\n",
- "print(mX_train.shape)\n",
- "print(mX_test.shape)\n",
- "print(mY_train.shape)\n",
- "print(mY_test.shape)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "_Y40R17LGHsI"
- },
- "source": [
- "3. fit the model with `multi_X_train` and corresponding `PRICE` (*y_train*) values \n",
- " - so it can build an understanding of their relationships \n",
- "4. predict `PRICE` (*y_test*) for the test set of independent (*multi_X_test*) values\n",
- " - and compare `PRICE` predictions to actual median house (*y_test*) values\n",
- " - use `sklearn`'s `mean_squared_error` to do this"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 13,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/",
- "height": 34
- },
- "colab_type": "code",
- "id": "N7qm1HuVO-1k",
- "outputId": "7e291cec-e602-4ad9-a5b3-b70d7261f63d"
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "28.312087834147203\n"
- ]
- }
- ],
- "source": [
- "# call Linear Regression model\n",
- "mlr = LinearRegression()\n",
- "\n",
- "# train the model for multiple regression\n",
- "mlr.fit(mX_train, mY_train)\n",
- "\n",
- "# make predictions for test X values\n",
- "mY_pred = mlr.predict(mX_test)\n",
- "\n",
- "# calculate error\n",
- "mmse = mean_squared_error(mY_test.to_array(), mY_pred.to_array())\n",
- "print(mmse)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "jTdmleXCM_Xb"
- },
- "source": [
- "5. visualize with `matplotlib`"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 14,
- "metadata": {
- "colab": {
- "base_uri": "https://localhost:8080/",
- "height": 305
- },
- "colab_type": "code",
- "id": "Q83NFMK1JKvL",
- "outputId": "569cfa77-a66e-4b1b-9d70-ae4ef8e7936e"
- },
- "outputs": [
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- "
"
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "# scatter actual and predicted results\n",
- "plt.scatter(mY_test.to_array(), mY_pred.to_array())\n",
- "\n",
- "# label graph\n",
- "plt.xlabel(\"Actual Prices: $Y_i$\")\n",
- "plt.ylabel(\"Predicted prices: $\\hat{Y}_i$\")\n",
- "plt.title(\"Prices vs Predicted prices: $Y_i$ vs $\\hat{Y}_i$\")\n",
- "\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "2X1RA6sgtZQ6"
- },
- "source": [
- "## Conclusion\n",
- "- looks like the multiple regression we ran does provide more accurate predictions than the simple linear regression\n",
- " - this will not always be the case, so always be sure to check and confirm if the extra computing is worth it\n",
- "\n",
- "Anyways, that's how you implement both Simple and Multiple Linear Regression with `cuML`. Go forth and do great things. Thanks for stopping by!"
- ]
- }
- ],
- "metadata": {
- "accelerator": "GPU",
- "colab": {
- "collapsed_sections": [],
- "name": "LOCAL_intro_lin_reg_cuml",
- "provenance": []
- },
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.6.10"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/intermediate_notebooks/examples/weather.ipynb b/intermediate_notebooks/examples/weather.ipynb
deleted file mode 100644
index 604628ce..00000000
--- a/intermediate_notebooks/examples/weather.ipynb
+++ /dev/null
@@ -1,701 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Simpler Multi-GPU ETL using Dask ##\n",
- "\n",
- "A major focus of the last several RAPIDS releases is easier scaling: up *and* out.\n",
- "\n",
- "While we introduced examples of multi-gpu/multi-node data processing using Dask in our first release, it was difficult to install, configure, and launch.\n",
- "\n",
- "Running our main example, the [Mortgage Workflow](https://github.com/rapidsai/notebooks-contrib/blob/master/intermediate_notebooks/E2E/mortgage/mortgage_e2e.ipynb) required:\n",
- "\n",
- "1. Pre-splitting or downloading pre-split datasets\n",
- "2. Using a [custom shell script](https://github.com/rapidsai/notebooks/blob/master/utils/dask-setup.sh) to:\n",
- " * Check for and force shut-down of existing dask clusters\n",
- " * Set environment variables\n",
- " * Launch dask-scheduler and dask-worker processes\n",
- "3. Make limited use of Dask, only via the [`delayed` interface](http://docs.dask.org/en/latest/delayed.html)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Since our first release, we've created the [dask-cuda project](https://github.com/rapidsai/dask-cuda), which automatically handles configuring Dask worker processes to make use of available GPUs.\n",
- "\n",
- "We also improved [dask-cudf](https://github.com/rapidsai/cudf/tree/branch-0.10/python/dask_cudf) to support a variety of common ETL operations. While joins and groupbys received the most attention, dask-cudf now also supports friendlier parallel IO.\n",
- "\n",
- "The rest of this notebook demonstrates how we've addressed the above pains, and generally made scaling RAPIDS out to multiple-GPUs easier.\n",
- "\n",
- "First, let's see what GPUs we have available..."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from dask.distributed import Client, wait\n",
- "from dask_cuda import LocalCUDACluster\n",
- "import dask, dask_cudf\n",
- "from dask.diagnostics import ProgressBar\n",
- "\n",
- "# Use dask-cuda to start one worker per GPU on a single-node system\n",
- "# When you shutdown this notebook kernel, the Dask cluster also shuts down.\n",
- "cluster = LocalCUDACluster(ip='0.0.0.0')\n",
- "client = Client(cluster)\n",
- "# print client info\n",
- "client"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Ok, we've got a cluster of GPU workers. Notice also the link to the Dask status dashboard. It provides lots of useful information while running data processing tasks.\n",
- "\n",
- "## Accessing Data\n",
- "\n",
- "Now, let's download a dataset.\n",
- "\n",
- "If you're working on a local machine, you'd normally use wget, Python's `urllib` package, or another tool to pull down the data you want to analyze.\n",
- "\n",
- "For the sake of not making you wait for 200+ files to download, the cell below uses urllib to download just 20 years of weather records, and a metadata file about the stations that recorded it. You can update the `years` list if you want to download more, but it wont change the logic in the notebook either way, it'll just process more data.\n",
- "\n",
- "*Note*: The rest of the markdown commentary in this notebook assumes you're operating on all 232 years of data."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Make and set a home for your data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import os\n",
- "import urllib.request\n",
- "\n",
- "data_dir = '../../data/weather/'\n",
- "if not os.path.exists(data_dir):\n",
- " print('creating weather directory')\n",
- " os.system('mkdir ../../data/weather')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Choose and Download your data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# download weather observations\n",
- "base_url = 'ftp://ftp.ncdc.noaa.gov/pub/data/ghcn/daily/by_year/'\n",
- "years = list(range(2000, 2020))\n",
- "for year in years:\n",
- " fn = str(year) + '.csv.gz'\n",
- " if not os.path.isfile(data_dir+fn):\n",
- " print(f'Downloading {base_url+fn} to {data_dir+fn}')\n",
- " urllib.request.urlretrieve(base_url+fn, data_dir+fn)\n",
- " \n",
- "# download weather station metadata\n",
- "station_meta_url = 'https://www1.ncdc.noaa.gov/pub/data/ghcn/daily/ghcnd-stations.txt'\n",
- "if not os.path.isfile(data_dir+'ghcnd-stations.txt'):\n",
- " print('Downloading station meta..')\n",
- " urllib.request.urlretrieve(station_meta_url, data_dir+'ghcnd-stations.txt')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Alternatives to Pre-Downloading Data\n",
- "\n",
- "While downloading or copying data to your local environment is a good way to get started, many users will want other options:\n",
- "\n",
- "1. Reading directly from distributed storage, like HDFS\n",
- "2. Reading from cloud storage (S3, GCS, ADLS, etc)\n",
- "\n",
- "See [Dask Remote Data Services](http://docs.dask.org/en/latest/remote-data-services.html) for more details on supported providers, authentication, and other storage configuration options.\n",
- "\n",
- "Here's an example of reading the same weather data, conveniently available in a public Amazon S3 bucket.\n",
- "\n",
- "But first make sure your Python environment has the right packages to read from your storage system of choice.\n",
- "\n",
- "For this example: ```conda install -y s3fs```"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# these CSV files don't have headers, we specify column names manually\n",
- "names = [\"station_id\", \"date\", \"type\", \"val\"]\n",
- "# there are more fields, but only the first 4 are relevant in this notebook\n",
- "usecols = names[0:4]\n",
- "\n",
- "url = 's3://noaa-ghcn-pds/csv/1788.csv'\n",
- "dask_cudf.read_csv(url, names=names, usecols=usecols, storage_options={'anon': True})"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Reading Large & Multi-File DataSets\n",
- "\n",
- "Wait... there are many weather files: one for each year going back to the 1780s.\n",
- "\n",
- "Before RAPIDS 0.6, if you wanted to read all these files in, you'd need to either use a for-loop, manually concatenating dataframes, or use [`dask.delayed`](http://docs.dask.org/en/latest/delayed.html) functions that invoke cuDF.read_csv.\n",
- "\n",
- "Fortunately, now there's `dask_cudf.read_csv`, which supports file globs, _and_ automatically splits files into chunks that can be processed serially when needed, so you're less likely to run out of memory.\n",
- "\n",
- "When you call `dask_cudf.read_csv`, Dask reads metadata for each CSV file and tasks workers with lists of filenames & byte-ranges that they're responsible for loading with cuDF's GPU CSV reader.\n",
- "\n",
- "*Note*: compressed files are not splittable on read, but you can [repartition](https://docs.dask.org/en/latest/dataframe-best-practices.html#repartition-to-reduce-overhead) them downstream."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "weather_ddf = dask_cudf.read_csv(data_dir+'*.csv.gz', names=names, usecols=usecols, compression='gzip')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Let's Process Some Data\n",
- "\n",
- "Per the [readme](https://docs.opendata.aws/noaa-ghcn-pds/readme.html) for this dataset, multiple types of weather observations are in the same files, and each carries a different units of measure:\n",
- "\n",
- "| Observation Type | Existing Units | Action |\n",
- "| ------------- | ------------- | ------------- |\n",
- "| PRCP | Precipitation (tenths of mm) | convert to inches |\n",
- "| SNWD | Snow depth (mm) | convert to inches |\n",
- "| TMAX | tenths of degrees C | convert to fahrenheit |\n",
- "| TMIN | tenths of degrees C | convert to fahrenheit |\n",
- "\n",
- "There are more even more observation types, each with their own units of measure, but I wont list them all. In this notebook, I'm going to focus specifically on precipitation.\n",
- "\n",
- "The `type` column tells us what kind of weather observation each record represents. Ordinarily, you might use `query` to filter out subsets of records and apply different logic to each subset. However, [query doesn't support string datatypes yet](https://github.com/rapidsai/cudf/issues/111). Instead, you can use boolean indexing.\n",
- "\n",
- "For numeric types, Dask with cuDF works mostly like regular Dask. For instance, you can define new columns as combinations of other columns:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "precip_index = weather_ddf['type'] == 'PRCP'\n",
- "precip_ddf = weather_ddf[precip_index]\n",
- "\n",
- "# convert 10ths of mm to inches\n",
- "mm_to_inches = 0.0393701\n",
- "precip_ddf['val'] = precip_ddf['val'] * 1/10 * mm_to_inches"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Note: Calling .head() will read the first few rows, usually from the first partition.\n",
- "\n",
- "In our case, the first partition represents weather data from 1788. Apparently, there wasn't _any_ precipitation data collected that year:\n",
- "\n",
- "Beware in your own analyes, that you .head() from partitions that you haven't already filtered everything out of!"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "precip_ddf.get_partition(1).head()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Ok, we have a lot of weather observations. Now what?\n",
- "\n",
- "# Answering Questions With Data ##\n",
- "\n",
- "For some reason, residents of particular cities like to lay claim to having the best, or the worst of something. For Los Angeles, it's having the worst traffic. New Yorkers and Chicagoans argue over who has the best pizza. [West Coasters argue about who has the most rain](https://twitter.com/MikeNiccoABC7/status/1105184947663396864).\n",
- "\n",
- "Well... as a longtime Atlanta resident suffering from humidity exhaustion, I like to joke that with all the spring showers, _Atlanta_ is the new Seattle.\n",
- "\n",
- "Does my theory hold water? Or will the data rain on my bad pun parade?"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# How Can I Test My Theory?\n",
- "\n",
- "We've already created `precip_df`, which is only the precipitation observations, but it's for all 100k weather stations, most of them no-where near Atlanta, and this is time-series data, so we'll need to aggregate over time ranges.\n",
- "\n",
- "To get down to just Atlanta and Seattle precipitation records, we have to...\n",
- "\n",
- "1. Extract year, month, and day from the compound \"date\" column, so that we can compare total rainfall across time.\n",
- "\n",
- "2. Load up the station metadata file.\n",
- "\n",
- "3. There's no \"city\" in the station metadata, so we'll do some geo-math and keep only stations near Atlanta and Seattle.\n",
- "\n",
- "4. Use a Groupby to compare changing precipitation patterns across time\n",
- "\n",
- "5. Use inner joins to filter the precipitation dataframe down to just Atlanta & Seattle data."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## 1. Extracting Finer Grained Date Fields\n",
- "\n",
- "We _can_ do a bit of math to separate date parts.."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "precip_ddf['year'] = precip_ddf['date']/10000\n",
- "precip_ddf['year'] = precip_ddf['year'].astype('int')\n",
- "\n",
- "precip_ddf['month'] = (precip_ddf['date'] - precip_ddf['year']*10000)/100\n",
- "precip_ddf['month'] = precip_ddf['month'].astype('int')\n",
- "\n",
- "precip_ddf['day'] = (precip_ddf['date'] - precip_ddf['year']*10000 - precip_ddf['month']*100)\n",
- "precip_ddf['day'] = precip_ddf['day'].astype('int')\n",
- "\n",
- "precip_ddf.get_partition(1).head()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "For this dataset, getting date parts is easier with string slicing. However, as is sometimes the case, Dask expects some aspect of cuDF's Python API to match Pandas in a way that [isn't fully compatible yet](https://github.com/rapidsai/cudf/issues/2367).\n",
- "\n",
- "That bug will likely be resolved quickly. But, this example is a good chance to show how to workaround similar problems.\n",
- "\n",
- "Dask has a [map_partitions](https://docs.dask.org/en/latest/dataframe-api.html#dask.dataframe.Series.map_partitions) function which will apply a given Python function to all partitions of a distributed DataFrame. When you do this on a dask_cudf df, your input is a cuDF object:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "def get_date_parts(df):\n",
- " date_str = df['date'].astype('str')\n",
- " df['year'] = date_str.str.slice(0, 4).astype('int')\n",
- " df['month'] = date_str.str.slice(4, 6).astype('int')\n",
- " df['day'] = date_str.str.slice(6, 8).astype('int')\n",
- " return df\n",
- "\n",
- "# any single-GPU function that works in cuDF may be called via dask.map_partitions\n",
- "precip_ddf = precip_ddf.map_partitions(get_date_parts)\n",
- "precip_ddf.get_partition(1).head()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The map_partitions pattern is also useful whenever there are cuDF specific functions without a direct mapping into Dask."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## 2. Loading Station Metadata ##"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "!head -n 5 /data/weather/ghcnd-stations.txt"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Wait... That's no CSV file! It's fixed-width!\n",
- "\n",
- "That's annoying because we don't have a reader for it. We could use CPU code to pre-process the file, making it friendlier for loading into a DataFrame, but, RAPIDS is about end-to-end data processing without leaving the GPU.\n",
- "\n",
- "This file is small enough that we can handle it directly with cuDF on a single GPU.\n",
- "\n",
- "*Warning*: Make sure you [create your dask-cuda cluster _before_ importing cudf](https://github.com/rapidsai/dask-cuda/issues/32).\n",
- "\n",
- "Here's how to cleanup this metadata using cuDF and string operations:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import cudf\n",
- "\n",
- "fn = data_dir+'ghcnd-stations.txt'\n",
- "# There are no '|' chars in the file. Use that to read the file as a single column per line\n",
- "# quoting=3 handles misplaced quotes in the `name` field \n",
- "station_df = cudf.read_csv(fn, sep='|', quoting=3, names=['lines'], header=None)\n",
- "\n",
- "# you can use normal DataFrame .str accessor, and chain operators together\n",
- "station_df['station_id'] = station_df['lines'].str.slice(0, 11).str.strip()\n",
- "station_df['latitude'] = station_df['lines'].str.slice(12, 20).str.strip()\n",
- "station_df['longitude'] = station_df['lines'].str.slice(21, 30).str.strip()\n",
- "station_df = station_df.drop('lines')\n",
- "\n",
- "station_df.head()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Managing Memory\n",
- "\n",
- "While GPU memory is very fast, there's less of it than host RAM. It's a good idea to avoid storing lots of columns that aren't useful for what you're trying to do, especially when they're strings.\n",
- "\n",
- "For example, for the station metadata, there are more columns than we parsed out above. In this workflow we only need `station_id`, `latitude`, and `longitude`, so we skipped parsing the rest of the columns.\n",
- "\n",
- "We also need to convert latitude and longitude from strings to floats, and convert the single-GPU DataFrame to a Dask DataFrame that can be distributed across workers."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# you can cast string columns to numerics\n",
- "station_df['latitude'] = station_df['latitude'].astype('float')\n",
- "station_df['longitude'] = station_df['longitude'].astype('float')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## 3. Filtering Weather Stations by Distance\n",
- "\n",
- "Initially we planned to use our [existing Haversine Distance user defined function](https://medium.com/rapids-ai/user-defined-functions-in-rapids-cudf-2d7c3fc2728d) to figure out which stations are within a given distance from a city. However, that relies on a [numba CUDA JIT'ed kernel](https://numba.pydata.org/numba-doc/dev/cuda/index.html), which would be slower and would incur compilation time the first time you call it.\n",
- "\n",
- "Now that [cuSpatial](https://github.com/rapidsai/cuspatial) is available as [a nightly conda package](https://anaconda.org/rapidsai-nightly/cuspatial), we can use it without having to build from source:\n",
- "\n",
- "```\n",
- "conda install -c conda-forge -c rapidsai-nightly cuspatial\n",
- "```\n",
- "\n",
- "For this scenario, we've manually looked up Atlanta and Seattle's city centers and will fill `cudf.Series` with their latitude and longitude values. Then we can call a cuSpatial function to compute the distance between each station and each city."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "import cuspatial\n",
- "\n",
- "# fill new Series with Atlanta lat/lng\n",
- "station_df['atlanta_lat'] = 33.7490\n",
- "station_df['atlanta_lng'] = -84.3880\n",
- "# compute distance from each station to Atlanta\n",
- "station_df['atlanta_dist'] = cuspatial.haversine_distance(\n",
- " station_df['longitude'], station_df['latitude'],\n",
- " station_df['atlanta_lng'], station_df['atlanta_lat']\n",
- ")\n",
- "\n",
- "# fill new Series with Seattle lat/lng\n",
- "station_df['seattle_lat'] = 47.6219\n",
- "station_df['seattle_lng'] = -122.3517\n",
- "# compute distance from each station to Seattle\n",
- "station_df['seattle_dist'] = cuspatial.haversine_distance(\n",
- " station_df['longitude'], station_df['latitude'],\n",
- " station_df['seattle_lng'], station_df['seattle_lat']\n",
- ")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Checking the Results"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "# Inspect the results:\n",
- "atlanta_stations_df = station_df.query('atlanta_dist <= 25')\n",
- "seattle_stations_df = station_df.query('seattle_dist <= 25')\n",
- "\n",
- "print(f'Atlanta Stations: {len(atlanta_stations_df)}')\n",
- "print(f'Seattle Stations: {len(seattle_stations_df)}')\n",
- "\n",
- "atlanta_stations_df.head()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "[Google tells me those station ids are from Smyrna](https://geographic.org/global_weather/georgia/smyrna_23_ne_002.html), a town just outside of Atlanta's perimeter. Our distance calculation worked!"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## 4. Grouping & Aggregating by Time Range\n",
- "\n",
- "Before using an inner join to filter down to city-specific precipitation data, we can use a groupby to sum the precipitation for station and year. That'll allow the join to proceed faster and use less memory.\n",
- "\n",
- "One total precipitation record per station per year is relatively small, and we're going to need to graph this data, so we'll go ahead and `compute()` the result, asking Dask to aggregate across the 200+ years worth of data, bringing the results back to the client as a single GPU cuDF DataFrame.\n",
- "\n",
- "Note that with Dask, data is partitioned and distributed across multiple workers. Some operations require that workers \"[shuffle](http://docs.dask.org/en/latest/dataframe-groupby.html#)\" data from their partitions back and forth across the network, which has major performance implications. Today join, groupby, and sort operations can be fairly network constrained.\n",
- "\n",
- "See the [slides](https://www.slideshare.net/MatthewRocklin/ucxpython-a-flexible-communication-library-for-python-applications) from a recent talk at GTC San Jose to learn more about [ongoing efforts to integrate Dask with UCX](https://github.com/rapidsai/ucx-py/) and allow it to use accelerated networking hardware like Infiniband and [nvlink](https://www.nvidia.com/en-us/data-center/nvlink/).\n",
- "\n",
- "In the meantime, distributed operators that require shuffling like joins, groupbys, and sorts work, albeit not as fast as we'd like."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "precip_year_ddf = precip_ddf.groupby(by=['station_id', 'year']).val.sum()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Note that we're calling `compute` again here. This tells Dask to actually start computing the full set of processing logic defined thus far:\n",
- "\n",
- "1. Read and decompress 232 gzipped files (about 100 GB decompressed)\n",
- "2. Send to the GPU and parse\n",
- "3. Filter down to precipitation records\n",
- "4. Apply a conversion to inches\n",
- "5. Sum total inches of rain per year per each of the 108k weather stations\n",
- "6. Combine and pull results a single GPU DataFrame on the client host\n",
- "\n",
- "To wit.. this will take time."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "%time precip_year_df = precip_year_ddf.compute()\n",
- "\n",
- "# Convert from the groupby multi-indexed DataFrame back to a normal DF which we can use with merge\n",
- "precip_year_df = precip_year_df.reset_index()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## 5. Using Inner Joins to Filter Weather Observations\n",
- "\n",
- "We have separate DataFrames containing Atlanta and Seattle stations, and we have our total precipitation grouped by `station_id` and `year`. Computing inner joins can let us compute total precipitation by year for just Atlanta and Seattle."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "%time atlanta_precip_df = precip_year_df.merge(atlanta_stations_df, on=['station_id'], how='inner')"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "atlanta_precip_df.head()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "%time seattle_precip_df = precip_year_df.merge(seattle_stations_df, on=['station_id'], how='inner')"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "seattle_precip_df.head()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Lastly, we need to normalize the total amount of rain in each city by the number of stations which collected rainfall: Seattle had twice as many stations collecting, but that doesn't mean more total rain fell! "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "atlanta_rain = atlanta_precip_df.groupby(['year']).val.sum()/len(atlanta_stations_df)\n",
- "atlanta_rain.head()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "seattle_rain = seattle_precip_df.groupby(['year']).val.sum()/len(seattle_stations_df)\n",
- "\n",
- "seattle_rain.head()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Visualizing the Answer\n",
- "\n",
- "To generate the graphs in the cells below, first you'll need to ```conda install -y python-graphviz matplotlib```"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "%matplotlib inline\n",
- "import matplotlib.pyplot as plt\n",
- "from matplotlib.pyplot import *\n",
- "\n",
- "plt.close('all')\n",
- "plt.rcParams['figure.figsize'] = [20, 10]\n",
- "\n",
- "fig, ax = subplots()\n",
- "\n",
- "atlanta_rain.to_pandas().plot(ax=ax)\n",
- "seattle_rain.to_pandas().plot(ax=ax)\n",
- "\n",
- "ax.legend(['Atlanta', 'Seattle'])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Results\n",
- "\n",
- "It looks like I'm right (mostly)! At least for roughly the last 80 years, it rains more by volume in Atlanta than it does in Seattle. The data seems to confirm my suspicions.\n",
- "\n",
- "But as usual the answer raises additional questions:\n",
- "\n",
- "1. Without singling out Atlanta and Seattle, which city actually has the most precipitation by volume?\n",
- "\n",
- "2. Why is there such a large increase in observed precipitation in the last 10 years?\n",
- "\n",
- "3. One friend noted that it rains more frequently in Seattle, just not as hard. A contrarian was quick to point out that it mists a lot in Seattle. How often is it just \"misty\", but not really raining?\n",
- "\n",
- "We'll revisit these questions in a future post, and look forward to seeing what kinds of analyses YOU come up with."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Takeaways\n",
- "\n",
- "We just showed some of the ways you can use Dask and cuDF to parallelize typical data processing tasks on multiple GPUs. Hopefully this notebook provides useful examples to refer to while doing your own ETL & analytics work.\n",
- "\n",
- "For more info on what's working today with Dask and cuDF, see [our summary](https://docs.rapids.ai/api/cudf/stable/), and follow [our ongoing development](https://github.com/rapidsai/cudf).\n",
- "\n",
- "Also checkout out other [community contributed notebooks](https://github.com/rapidsai/notebooks-contrib), and submit your own!"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.7.3"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/the_archive/archived_competition_notebooks/kaggle/README.md b/the_archive/archived_competition_notebooks/kaggle/README.md
new file mode 100644
index 00000000..28091c87
--- /dev/null
+++ b/the_archive/archived_competition_notebooks/kaggle/README.md
@@ -0,0 +1,9 @@
+## Open GPU Data Science
+
+# Introduction
+This repo contains rapids solutions for kaggle competitions and other real world, end to end (E2E) problems.
+1. plasticc: 8th place [Rapids.ai](https://rapids.ai) solution of [PLAsTiCC Astronomical Classification](https://www.kaggle.com/c/PLAsTiCC-2018).
+2. malware: explorative analysis of [microsoft malware prediction](https://www.kaggle.com/c/microsoft-malware-prediction).
+
+# Build and Run with Docker or bare-metal
+please find readme file in each folder.
diff --git a/conference_notebooks/KDD_2019/img/rapids_logo.png b/the_archive/archived_competition_notebooks/kaggle/img/rapids_logo.png
similarity index 100%
rename from conference_notebooks/KDD_2019/img/rapids_logo.png
rename to the_archive/archived_competition_notebooks/kaggle/img/rapids_logo.png
diff --git a/the_archive/archived_competition_notebooks/kaggle/img/solution.png b/the_archive/archived_competition_notebooks/kaggle/img/solution.png
new file mode 100644
index 00000000..2722ca3d
Binary files /dev/null and b/the_archive/archived_competition_notebooks/kaggle/img/solution.png differ
diff --git a/the_archive/archived_competition_notebooks/kaggle/landmark/cudf_stratifiedKfold_1000x_speedup.ipynb b/the_archive/archived_competition_notebooks/kaggle/landmark/cudf_stratifiedKfold_1000x_speedup.ipynb
new file mode 100644
index 00000000..7a270ede
--- /dev/null
+++ b/the_archive/archived_competition_notebooks/kaggle/landmark/cudf_stratifiedKfold_1000x_speedup.ipynb
@@ -0,0 +1,773 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "cudf version 0.7.2+0.g3ebd286.dirty\n"
+ ]
+ }
+ ],
+ "source": [
+ "import pandas as pd\n",
+ "import cudf as gd\n",
+ "import time\n",
+ "from sklearn.model_selection import StratifiedKFold\n",
+ "import numpy as np\n",
+ "import warnings\n",
+ "from numba import cuda\n",
+ "warnings.filterwarnings(\"ignore\")\n",
+ "print('cudf version',gd.__version__)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### I didn't expect to use rapids at all for the __[Google Landmark Recognition 2019](https://www.kaggle.com/c/landmark-recognition-2019)__ but it turned out that stratified kfold operation could be painfully slow. In this notebook, a cudf based implementation is shown to achieve 1000+ speedup."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Table of contents\n",
+ "[1. Implementation of cudf based stratified kfold split](#imp) \n",
+ "[2. Sanity Check with toy data](#san) \n",
+ "[3. The google landmark dataset](#land) \n",
+ "[4. Measure the runtime](#runtime) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Implementation of cudf based stratified kfold split\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class StratifiedKFold_cudf_gpu:\n",
+ " \"\"\"Stratified K-Folds cross-validator using cudf on gpu.\n",
+ " Functionality is the same as \n",
+ " https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedKFold.html\n",
+ " Parameters\n",
+ " ----------\n",
+ " n_splits : int, default=3\n",
+ " Number of folds. Must be at least 2.\n",
+ " .. versionchanged:: 0.20\n",
+ " ``n_splits`` default value will change from 3 to 5 in v0.22.\n",
+ " shuffle : boolean, optional\n",
+ " Whether to shuffle each stratification of the data before splitting\n",
+ " into batches.\n",
+ " random_state : int, default=42, RandomState instance, which is the seed used \n",
+ " by the random number generator;\n",
+ " tpb: int, default=32, number of threads per thread block. A thread block is a group of threads \n",
+ " to process the group of samples with same value of y. If the number of unique values of \n",
+ " y is small,the group size is large and tpb should increase accordingly. The largest value\n",
+ " of tpb is 1024 and it should be multiples of 32.\n",
+ " mode: str, default = 'relax', how to deal with class with fewer samples than n_splits\n",
+ " The possible options are 'relax' and 'sklearn'. \n",
+ " With 'sklearn' mode, it will assert that n_splits must be less or equal to the number of samples\n",
+ " in the smallest class.\n",
+ " With 'relax', class with fewer samples than n_splits will be only in either train or valid part\n",
+ " of a given fold.\n",
+ " Examples\n",
+ " --------\n",
+ " >>> X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])\n",
+ " >>> y = np.array([0, 0, 1, 1])\n",
+ " >>> skf = StratifiedKFold_cudf_gpu(n_splits=2,random_state=None, shuffle=False)\n",
+ " >>> for train_index, test_index in skf.split(X, y):\n",
+ " ... print(\"TRAIN:\", train_index, \"TEST:\", test_index)\n",
+ " ... X_train, X_test = X[train_index], X[test_index]\n",
+ " ... y_train, y_test = y[train_index], y[test_index]\n",
+ " TRAIN: [1 3] TEST: [0 2]\n",
+ " TRAIN: [0 2] TEST: [1 3]\n",
+ " Notes\n",
+ " -----\n",
+ " Train and test sizes may be different in each fold, with a difference of at most ``n_classes``.\n",
+ " \"\"\"\n",
+ " def __init__(self,n_splits=3,shuffle=True,random_state=42,tpb=32,mode='relax'):\n",
+ " self.n_splits = n_splits\n",
+ " self.shuffle = shuffle\n",
+ " self.seed = random_state\n",
+ " self.tpb = tpb # threads per thread block\n",
+ " self.mode = mode\n",
+ " \n",
+ " def get_n_splits(self, X=None, y=None):\n",
+ " return self.n_splits\n",
+ " \n",
+ " def split(self,x,y):\n",
+ " \"\"\"Generate indices to split data into training and test set.\n",
+ " Parameters\n",
+ " ----------\n",
+ " X : array-like, shape (n_samples, n_features)\n",
+ " y : array-like, shape (n_samples,)\n",
+ " The target variable for supervised learning problems.\n",
+ " Stratification is done based on the y labels.\n",
+ " Yields\n",
+ " ------\n",
+ " train : ndarray\n",
+ " The training set indices for that split.\n",
+ " test : ndarray\n",
+ " The testing set indices for that split.\n",
+ " Notes\n",
+ " -----\n",
+ " Randomized CV splitters may return different results for each call of\n",
+ " split. You can make the results identical by setting ``random_state``\n",
+ " to an integer.\n",
+ " \"\"\"\n",
+ " assert x.shape[0] == y.shape[0]\n",
+ " df = gd.DataFrame()\n",
+ " x = np.array(x)\n",
+ " y = np.array(y)\n",
+ " ids = np.arange(x.shape[0])\n",
+ " \n",
+ " if self.shuffle:\n",
+ " np.random.seed(self.seed)\n",
+ " np.random.shuffle(ids)\n",
+ " x = x[ids]\n",
+ " y = y[ids]\n",
+ " \n",
+ " cols = []\n",
+ " df['y'] = np.ascontiguousarray(y)\n",
+ " df['ids'] = ids\n",
+ " \n",
+ " grpby = df.groupby(['y'], method=\"cudf\")\n",
+ " if self.mode == 'sklearn':\n",
+ " dg = grpby.agg({'y':'count'})\n",
+ " #print(dg.columns)\n",
+ " col = dg.columns[0]\n",
+ " msg = 'n_splits=%d cannot be greater than the number of members in each class.'%self.n_splits\n",
+ " assert dg[col].min()>=self.n_splits,msg\n",
+ "\n",
+ " def get_order_in_group(y,ids,order):\n",
+ " for i in range(cuda.threadIdx.x, len(y), cuda.blockDim.x):\n",
+ " order[i] = i\n",
+ "\n",
+ " got = grpby.apply_grouped(get_order_in_group,incols=['y','ids'],\n",
+ " outcols={'order': np.int32},\n",
+ " tpb=self.tpb)\n",
+ "\n",
+ " got = got.sort_values('ids')\n",
+ " \n",
+ " dx = got.to_pandas()\n",
+ " del got,df\n",
+ " \n",
+ " for i in range(self.n_splits):\n",
+ " mask = dx['order']%self.n_splits==i\n",
+ " train = dx.loc[~mask,'ids'].values\n",
+ " test = dx.loc[mask,'ids'].values\n",
+ " if len(test)==0:\n",
+ " break\n",
+ " yield train,test "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Sanity check\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "TRAIN: [1 3] TEST: [0 2]\n",
+ "TRAIN: [0 2] TEST: [1 3]\n"
+ ]
+ }
+ ],
+ "source": [
+ "X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])\n",
+ "y = np.array([0, 0, 1, 1])\n",
+ "skf = StratifiedKFold_cudf_gpu(n_splits=2,random_state=None, shuffle=False)\n",
+ "for train_index, test_index in skf.split(X, y):\n",
+ " print(\"TRAIN:\", train_index, \"TEST:\", test_index)\n",
+ " X_train, X_test = X[train_index], X[test_index]\n",
+ " y_train, y_test = y[train_index], y[test_index]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### We compare relax and sklearn mode of StratifiedKFold_cudf_gpu"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "TRAIN: [1 3] TEST: [0 2]\n",
+ "TRAIN: [0 2] TEST: [1 3]\n"
+ ]
+ }
+ ],
+ "source": [
+ "skf = StratifiedKFold_cudf_gpu(n_splits=4,random_state=None, shuffle=False,mode='relax')\n",
+ "for train_index, test_index in skf.split(X, y):\n",
+ " print(\"TRAIN:\", train_index, \"TEST:\", test_index)\n",
+ " X_train, X_test = X[train_index], X[test_index]\n",
+ " y_train, y_test = y[train_index], y[test_index]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This examples shows that when the number of samples is too small for a certain `n_splits`, the `relax` mode stops making more splits without reporting an error. Please refer to [the google landmark dataset example](#land) for more behavior analysis of `relax` mode."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Errors are intended in the following example for both sklearn and cudf. The `sklearn` mode of cudf is designed to catch the same error as sklearn version."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "ename": "AssertionError",
+ "evalue": "n_splits=4 cannot be greater than the number of members in each class.",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mskf\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mStratifiedKFold_cudf_gpu\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn_splits\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m4\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mrandom_state\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mshuffle\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mmode\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'sklearn'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mtrain_index\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtest_index\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mskf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"TRAIN:\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtrain_index\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"TEST:\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtest_index\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mX_train\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX_test\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mtrain_index\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mtest_index\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0my_train\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my_test\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mtrain_index\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mtest_index\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36msplit\u001b[0;34m(self, x, y)\u001b[0m\n\u001b[1;32m 91\u001b[0m \u001b[0mcol\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdg\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcolumns\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 92\u001b[0m \u001b[0mmsg\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'n_splits=%d cannot be greater than the number of members in each class.'\u001b[0m\u001b[0;34m%\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_splits\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 93\u001b[0;31m \u001b[0;32massert\u001b[0m \u001b[0mdg\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mcol\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m>=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_splits\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 94\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 95\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mget_order_in_group\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mids\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0morder\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mAssertionError\u001b[0m: n_splits=4 cannot be greater than the number of members in each class."
+ ]
+ }
+ ],
+ "source": [
+ "skf = StratifiedKFold_cudf_gpu(n_splits=4,random_state=None, shuffle=False,mode='sklearn')\n",
+ "for train_index, test_index in skf.split(X, y):\n",
+ " print(\"TRAIN:\", train_index, \"TEST:\", test_index)\n",
+ " X_train, X_test = X[train_index], X[test_index]\n",
+ " y_train, y_test = y[train_index], y[test_index]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "ename": "ValueError",
+ "evalue": "n_splits=4 cannot be greater than the number of members in each class.",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mskf\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mStratifiedKFold\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn_splits\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m4\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mrandom_state\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mshuffle\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mtrain_index\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtest_index\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mskf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"TRAIN:\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtrain_index\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"TEST:\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtest_index\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mX_train\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX_test\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mtrain_index\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mtest_index\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0my_train\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my_test\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mtrain_index\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mtest_index\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m~/anaconda3/envs/cudf0.7/lib/python3.6/site-packages/sklearn/model_selection/_split.py\u001b[0m in \u001b[0;36msplit\u001b[0;34m(self, X, y, groups)\u001b[0m\n\u001b[1;32m 333\u001b[0m .format(self.n_splits, n_samples))\n\u001b[1;32m 334\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 335\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mtrain\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtest\u001b[0m \u001b[0;32min\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgroups\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 336\u001b[0m \u001b[0;32myield\u001b[0m \u001b[0mtrain\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtest\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 337\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m~/anaconda3/envs/cudf0.7/lib/python3.6/site-packages/sklearn/model_selection/_split.py\u001b[0m in \u001b[0;36msplit\u001b[0;34m(self, X, y, groups)\u001b[0m\n\u001b[1;32m 87\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgroups\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mindexable\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgroups\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 88\u001b[0m \u001b[0mindices\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_num_samples\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 89\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mtest_index\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_iter_test_masks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgroups\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 90\u001b[0m \u001b[0mtrain_index\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mindices\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlogical_not\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtest_index\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 91\u001b[0m \u001b[0mtest_index\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mindices\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mtest_index\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m~/anaconda3/envs/cudf0.7/lib/python3.6/site-packages/sklearn/model_selection/_split.py\u001b[0m in \u001b[0;36m_iter_test_masks\u001b[0;34m(self, X, y, groups)\u001b[0m\n\u001b[1;32m 684\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 685\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_iter_test_masks\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgroups\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 686\u001b[0;31m \u001b[0mtest_folds\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_make_test_folds\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 687\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_splits\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 688\u001b[0m \u001b[0;32myield\u001b[0m \u001b[0mtest_folds\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mi\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m~/anaconda3/envs/cudf0.7/lib/python3.6/site-packages/sklearn/model_selection/_split.py\u001b[0m in \u001b[0;36m_make_test_folds\u001b[0;34m(self, X, y)\u001b[0m\n\u001b[1;32m 649\u001b[0m raise ValueError(\"n_splits=%d cannot be greater than the\"\n\u001b[1;32m 650\u001b[0m \u001b[0;34m\" number of members in each class.\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 651\u001b[0;31m % (self.n_splits))\n\u001b[0m\u001b[1;32m 652\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_splits\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0mmin_groups\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 653\u001b[0m warnings.warn((\"The least populated class in y has only %d\"\n",
+ "\u001b[0;31mValueError\u001b[0m: n_splits=4 cannot be greater than the number of members in each class."
+ ]
+ }
+ ],
+ "source": [
+ "skf = StratifiedKFold(n_splits=4,random_state=None, shuffle=False)\n",
+ "for train_index, test_index in skf.split(X, y):\n",
+ " print(\"TRAIN:\", train_index, \"TEST:\", test_index)\n",
+ " X_train, X_test = X[train_index], X[test_index]\n",
+ " y_train, y_test = y[train_index], y[test_index]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Examples above show that the `sklearn` mode of cudf can catch the same error as sklearn stratified kfold split."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### A real world example with the google landmark dataset\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Please download the train.csv from https://s3.amazonaws.com/google-landmark/metadata/train.csv"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 472 ms, sys: 160 ms, total: 632 ms\n",
+ "Wall time: 633 ms\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%time\n",
+ "path = 'train.csv'\n",
+ "cols = ['id','url','landmark_id']\n",
+ "dtypes = ['str','str','int32']\n",
+ "train = gd.read_csv(path,names=cols,dtype=dtypes,skiprows=1) # skip the header"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
id
\n",
+ "
url
\n",
+ "
landmark_id
\n",
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ "
0
\n",
+ "
6e158a47eb2ca3f6
\n",
+ "
https://upload.wikimedia.org/wikipedia/commons...
\n",
+ "
142820
\n",
+ "
\n",
+ "
\n",
+ "
1
\n",
+ "
202cd79556f30760
\n",
+ "
http://upload.wikimedia.org/wikipedia/commons/...
\n",
+ "
104169
\n",
+ "
\n",
+ "
\n",
+ "
2
\n",
+ "
3ad87684c99c06e1
\n",
+ "
http://upload.wikimedia.org/wikipedia/commons/...
\n",
+ "
37914
\n",
+ "
\n",
+ "
\n",
+ "
3
\n",
+ "
e7f70e9c61e66af3
\n",
+ "
https://upload.wikimedia.org/wikipedia/commons...
\n",
+ "
102140
\n",
+ "
\n",
+ "
\n",
+ "
4
\n",
+ "
4072182eddd0100e
\n",
+ "
https://upload.wikimedia.org/wikipedia/commons...
\n",
+ "
2474
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " id url \\\n",
+ "0 6e158a47eb2ca3f6 https://upload.wikimedia.org/wikipedia/commons... \n",
+ "1 202cd79556f30760 http://upload.wikimedia.org/wikipedia/commons/... \n",
+ "2 3ad87684c99c06e1 http://upload.wikimedia.org/wikipedia/commons/... \n",
+ "3 e7f70e9c61e66af3 https://upload.wikimedia.org/wikipedia/commons... \n",
+ "4 4072182eddd0100e https://upload.wikimedia.org/wikipedia/commons... \n",
+ "\n",
+ " landmark_id \n",
+ "0 142820 \n",
+ "1 104169 \n",
+ "2 37914 \n",
+ "3 102140 \n",
+ "4 2474 "
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df = train.head().to_pandas()\n",
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "number of samples 4132914 number of classes 203094\n"
+ ]
+ }
+ ],
+ "source": [
+ "y = train['landmark_id'].to_pandas().values\n",
+ "print('number of samples %d number of classes %d'%(y.shape[0],np.unique(y).shape[0]))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[samples, classes] in each fold:\n",
+ "train [3610971,184200] valid [521943,203094]\n",
+ "train [3641533,203094] valid [491381,184200]\n",
+ "train [3670149,203094] valid [462765,166463]\n",
+ "train [3695903,203094] valid [437011,150659]\n",
+ "train [3718707,203094] valid [414207,137133]\n",
+ "train [3738707,203094] valid [394207,125731]\n",
+ "train [3756907,203094] valid [376007,115755]\n",
+ "train [3773431,203094] valid [359483,106996]\n",
+ "train [3788254,203094] valid [344660,99342]\n",
+ "train [3801664,203094] valid [331250,92740]\n",
+ "CPU times: user 1min 29s, sys: 5.24 s, total: 1min 34s\n",
+ "Wall time: 4.26 s\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%time\n",
+ "skf = StratifiedKFold_cudf_gpu(n_splits=10,random_state=42, shuffle=True, mode='relax')\n",
+ "print('[samples, classes] in each fold:')\n",
+ "for train_index, test_index in skf.split(y, y):\n",
+ " print('train [%d,%d] valid [%d,%d]'%(y[train_index].shape[0],\n",
+ " np.unique(y[train_index]).shape[0],\n",
+ " y[test_index].shape[0],\n",
+ " np.unique(y[test_index]).shape[0],))\n",
+ " assert y[train_index].shape[0]+y[test_index].shape[0] == y.shape[0]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Unlike sklearn's version, where n_splits cannot be greater than the number of members in the smallest class, we use an approximate approach to allow minority class samples to be only in train part or valid part. The downside is the number of samples in each fold is not even. Actually the number of samples in valid is monotonically decreasing from fold 0 to fold n-1, and the size difference between largest fold and smallest fold increases with n_splits. Based on my limited experience, this is acceptable."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### I don't even want to run sklean's version on this dataset since it runs forever. Please feel free to try."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\"\\n%%time\\nskf = StratifiedKFold(n_splits=4,random_state=None, shuffle=False)\\nprint('[samples, classes] in each fold:')\\nfor train_index, test_index in skf.split(y, y):\\n print('train [%d,%d] valid [%d,%d]'%(y[train_index].shape[0],\\n np.unique(y[train_index]).shape[0],\\n y[test_index].shape[0],\\n np.unique(y[test_index]).shape[0],))\\n\""
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "\"\"\"\n",
+ "%%time\n",
+ "skf = StratifiedKFold(n_splits=4,random_state=None, shuffle=False)\n",
+ "print('[samples, classes] in each fold:')\n",
+ "for train_index, test_index in skf.split(y, y):\n",
+ " print('train [%d,%d] valid [%d,%d]'%(y[train_index].shape[0],\n",
+ " np.unique(y[train_index]).shape[0],\n",
+ " y[test_index].shape[0],\n",
+ " np.unique(y[test_index]).shape[0],))\n",
+ "\"\"\" "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Measure the run time of sklearn's stratified kfold using random data\n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def stratified_kfold_timing(n_splits,classes,samples,gpu=False):\n",
+ " \"\"\"measure the run time of stratified kfold split using random synthetic data.\n",
+ " Parameters\n",
+ " ----------\n",
+ " n_splits : int, \n",
+ " number of splits\n",
+ " classes : int, \n",
+ " number of classes for the synthetic data\n",
+ " samples: int, \n",
+ " number of samples for the synthetic data\n",
+ " gpu: boolean, default False,\n",
+ " use gpu based stratified split or not\n",
+ " \n",
+ " Returns\n",
+ " ------\n",
+ " samples: int, \n",
+ " number of samples for the synthetic data\n",
+ " classes : int, \n",
+ " number of classes for the synthetic data\n",
+ " duration: float,\n",
+ " run time of the stratified kfold split operation\n",
+ " \"\"\"\n",
+ " y = np.random.randint(0,classes,samples)\n",
+ " start = time.time()\n",
+ " if gpu:\n",
+ " skf = StratifiedKFold_cudf_gpu(n_splits=n_splits)\n",
+ " else:\n",
+ " skf = StratifiedKFold(n_splits=n_splits)\n",
+ " try:\n",
+ " for train_index, test_index in skf.split(y, y):\n",
+ " break # only measure the time of one fold\n",
+ " except:\n",
+ " return None,None,None\n",
+ " duration = time.time()-start\n",
+ " return samples,classes,duration"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "samples: 10000 classes: 10 time:0.0108 seconds\n",
+ "samples: 10000 classes: 100 time:0.0353 seconds\n",
+ "samples: 10000 classes: 1000 time:0.2877 seconds\n",
+ "samples: 100000 classes: 10 time:0.0643 seconds\n",
+ "samples: 100000 classes: 100 time:0.1900 seconds\n",
+ "samples: 100000 classes: 1000 time:1.2166 seconds\n",
+ "samples: 100000 classes: 10000 time:11.4666 seconds\n",
+ "samples: 1000000 classes: 10 time:0.6816 seconds\n",
+ "samples: 1000000 classes: 100 time:1.6286 seconds\n",
+ "samples: 1000000 classes: 1000 time:9.7290 seconds\n",
+ "samples: 1000000 classes: 10000 time:87.1364 seconds\n",
+ "samples: 1000000 classes: 100000 time:867.2177 seconds\n"
+ ]
+ }
+ ],
+ "source": [
+ "sklearn_split_time = []\n",
+ "for i in range(4,7):\n",
+ " for j in range(1,6):\n",
+ " samples,classes,t = stratified_kfold_timing(n_splits=10,classes=10**j,samples=10**i,gpu=False)\n",
+ " if t is None:\n",
+ " continue\n",
+ " print('samples: %d classes: %d time:%.4f seconds'%(samples,classes,t))\n",
+ " sklearn_split_time.append([samples,classes,t])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Measure the run time of cudf's stratified kfold using random data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "samples: 10000 classes: 10 time:0.2129 seconds\n",
+ "samples: 10000 classes: 100 time:0.2013 seconds\n",
+ "samples: 10000 classes: 1000 time:0.1998 seconds\n",
+ "samples: 10000 classes: 10000 time:0.2702 seconds\n",
+ "samples: 10000 classes: 100000 time:0.1997 seconds\n",
+ "samples: 100000 classes: 10 time:0.2067 seconds\n",
+ "samples: 100000 classes: 100 time:0.2078 seconds\n",
+ "samples: 100000 classes: 1000 time:0.2079 seconds\n",
+ "samples: 100000 classes: 10000 time:0.2139 seconds\n",
+ "samples: 100000 classes: 100000 time:0.2746 seconds\n",
+ "samples: 1000000 classes: 10 time:0.3496 seconds\n",
+ "samples: 1000000 classes: 100 time:0.3753 seconds\n",
+ "samples: 1000000 classes: 1000 time:0.3382 seconds\n",
+ "samples: 1000000 classes: 10000 time:0.3291 seconds\n",
+ "samples: 1000000 classes: 100000 time:0.3089 seconds\n"
+ ]
+ }
+ ],
+ "source": [
+ "cudf_split_time = []\n",
+ "for i in range(4,7):\n",
+ " for j in range(1,6):\n",
+ " samples,classes,t = stratified_kfold_timing(n_splits=10,classes=10**j,samples=10**i,gpu=True)\n",
+ " if t is None:\n",
+ " continue\n",
+ " print('samples: %d classes: %d time:%.4f seconds'%(samples,classes,t))\n",
+ " cudf_split_time.append([samples,classes,t])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import matplotlib.pyplot as plt\n",
+ "%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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hUAhQM+ePSkrK4vGHwHwNnAvL/dW/+zdezv3319GWpqXBEbxY5LS9u3bo/p3kexUX5FRfUVG9SUiIiLgz5zAw4C1dZ6vA45p5vwpwOlAtpnlOeceb+wk59xMYCbAqFGj3NixY6MTbVBJiTc3cMqUMh55ZCxFRXDuuVH9iKRUVlZGtP8ukpnqKzKqr8iovkRERAQSYGEY59zDwMPhnGtmk4BJeXl5UY+joMB7lJXB6tVRv7yIiEjMxLJ9FBGRxOPHrnVfAofXed47WLbfnHNznHOF2dnZ0biciIhIUlD7KCIidfmRBC4EBphZrpm1By4CXo7Ghc1skpnNrKioiMblREREREREkk6st4iYBbwDDDKzdWZ2pXMuAPwYKAW+AF5wzi2JxufpTqeIiIiIiEjzYr066OQmyl8DXovlZ4uIiIiIiMi+/BgOGjMaDioiIiIiItK8pEoCNRxURERERESkeUmVBIqIiIiIiEjzkioJ1HBQERERERGR5iVVEqjhoCIiIiIiIs1LqiRQREREREREmpdUSaCGg4qIiIiIiDQvqZJADQcVERERERFpXlIlgSIiIiIiItI8JYEiIiIiIiIpREmgiIiIiIhICkmqJFALw4iIiOxL7aOIiNSVVEmgFoYRERHZl9pHERGpK6mSQBEREREREWmekkAREREREZEUoiRQREREREQkhSRVEqiJ7yIiIiIiIs1LqiRQE99FRERERESal1RJoIiIiIiIiDRPSaCIiIiIiEgKURIoIiIiIiKSQpQEioiIiIiIpBAlgSIiIiIiIilESaCIiIiIiEgKSaokUPsEioiIiIiINC+pkkDtEygiIiIiItK8pEoCRUREREREpHlKAkVERERERFKIkkAREREREZEUoiRQREREREQkhSgJFBERERERSSFKAkVERERERFKIkkAREREREZEUoiRQREREREQkhSgJFBERERERSSEZfgfQEjM7ApgKdAPmOed+73NIIiIiccHMvgdMBDoDTznn3vA5JBERSQAx7Qk0s6fNbIOZLW5Qnm9my8xsuZnd2tw1nHNfOOeuAS4ETohlvCIiIm0lSm3kS865q4BrgO/HMl4REUkese4JLAYeBf5QU2Bm6cBjwBnAOmChmb0MpAPTG7z/CufcBjM7G7gW+J8YxysiItJWiolCGxk8viP4PhERkRbFNAl0zs03s74NiscA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\n",
+ "text/plain": [
+ "
"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "plt.figure(figsize=(15,5))\n",
+ "colors = ['b','g','r']\n",
+ "\n",
+ "plt.subplot(1,2,1)\n",
+ "seq = {}\n",
+ "for samples,classes,t in sklearn_split_time:\n",
+ " if samples not in seq:\n",
+ " seq[samples] = [[],[]]\n",
+ " seq[samples][0].append(classes)\n",
+ " seq[samples][1].append(t)\n",
+ "plt.yscale('log')\n",
+ "plt.xlim(5,5*10**5)\n",
+ "plt.ylim(10**(-3),10**3)\n",
+ "plt.xscale('log')\n",
+ "plt.xlabel('number of classes')\n",
+ "plt.ylabel('run time: seconds')\n",
+ "plt.grid()\n",
+ "for samples,color in zip(seq,colors):\n",
+ " plt.scatter(seq[samples][0],seq[samples][1],c=color,label='%d samples'%samples) \n",
+ " plt.plot(seq[samples][0],seq[samples][1],c=color)\n",
+ " plt.legend(loc='upper left')\n",
+ " plt.title('sklearn stratified split')\n",
+ " \n",
+ "plt.subplot(1,2,2)\n",
+ "seq = {}\n",
+ "for samples,classes,t in cudf_split_time:\n",
+ " if samples not in seq:\n",
+ " seq[samples] = [[],[]]\n",
+ " seq[samples][0].append(classes)\n",
+ " seq[samples][1].append(t)\n",
+ "plt.yscale('log')\n",
+ "plt.xscale('log')\n",
+ "plt.xlim(5,5*10**5)\n",
+ "plt.ylim(10**(-2),10**1)\n",
+ "plt.xlabel('number of classes')\n",
+ "plt.ylabel('run time: seconds')\n",
+ "plt.grid()\n",
+ "for samples,color in zip(seq,colors):\n",
+ " plt.scatter(seq[samples][0],seq[samples][1],c=color,label='%d samples'%samples) \n",
+ " plt.plot(seq[samples][0],seq[samples][1],c=color)\n",
+ " plt.legend(loc='upper left')\n",
+ " plt.title('cudf stratified split')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The real landmark data has more than 200K classes and 4 million samples, hence rapids can get more than **1000x speedup**."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/blog_notebooks/plasticc/notebooks/cudf_workaround.py b/the_archive/archived_competition_notebooks/kaggle/malware/cudf_workaround.py
similarity index 100%
rename from blog_notebooks/plasticc/notebooks/cudf_workaround.py
rename to the_archive/archived_competition_notebooks/kaggle/malware/cudf_workaround.py
diff --git a/the_archive/archived_competition_notebooks/kaggle/malware/draw.py b/the_archive/archived_competition_notebooks/kaggle/malware/draw.py
new file mode 100644
index 00000000..f29a8200
--- /dev/null
+++ b/the_archive/archived_competition_notebooks/kaggle/malware/draw.py
@@ -0,0 +1,19 @@
+import pandas as pd
+import seaborn as sns
+
+
+def pie_chart(data,tags,title=None,transpose=False,figsize=(16,8)):
+ sns.set()
+ dic = {}
+ values = set()
+ for i in data:
+ for k in i:
+ values.add(k)
+ values = list(values)
+ for i,tag in zip(data,tags):
+ dic[tag] = [i.get(k,0) for k in values]
+ df = pd.DataFrame(dic, index=values)
+ if transpose:
+ df = df.transpose()
+ df.plot(kind='pie', subplots=True, figsize=figsize,title=title)
+
diff --git a/the_archive/archived_competition_notebooks/kaggle/malware/malware_time_column_explore.ipynb b/the_archive/archived_competition_notebooks/kaggle/malware/malware_time_column_explore.ipynb
new file mode 100644
index 00000000..f3c066c6
--- /dev/null
+++ b/the_archive/archived_competition_notebooks/kaggle/malware/malware_time_column_explore.ipynb
@@ -0,0 +1,742 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "GPU_id = 0\n",
+ "os.environ['CUDA_VISIBLE_DEVICES'] = str(GPU_id)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "import cudf as gd\n",
+ "import numpy as np\n",
+ "from collections import OrderedDict,Counter\n",
+ "import re\n",
+ "from librmm_cffi import librmm\n",
+ "import nvstrings\n",
+ "import time\n",
+ "import draw\n",
+ "from termcolor import colored\n",
+ "from nvstring_workaround import get_unique_tokens,on_gpu,get_token_counts,is_in\n",
+ "import warnings\n",
+ "\n",
+ "warnings.filterwarnings(\"ignore\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "PATH = '/raid/data/ml/malware/input'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**The purpose of this notebook is to study the difference between train and test datasets in order to develop a robust validation scheme. This is important to the generalization capability of models to unseen dataset (test data on private leaderboard).** I'm also trying to use cudf and nvstring as much as possible."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Table of contents\n",
+ "[1. Previous CV schemes](#prev) \n",
+ "[2. Functions](#func) \n",
+ "[3. Visualizations](#vis) \n",
+ "[4. Conclusions](#conclusions) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 1. Previous CV schemes\n",
+ "\n",
+ "Previously, I used the naive __[K-Fold cross validation](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.KFold.html)__ with random shuffling and observed some discrepencies between cross validation AUC (CV) and leaderboard AUC (LB) as follows: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
CV
\n",
+ "
LB
\n",
+ "
description
\n",
+ "
\n",
+ "
\n",
+ "
models
\n",
+ "
\n",
+ "
\n",
+ "
\n",
+ "
\n",
+ " \n",
+ " \n",
+ "
\n",
+ "
lgb1
\n",
+ "
0.730
\n",
+ "
0.675
\n",
+ "
lightGBM
\n",
+ "
\n",
+ "
\n",
+ "
lgb2
\n",
+ "
0.732
\n",
+ "
0.672
\n",
+ "
lightGBM with mean target features
\n",
+ "
\n",
+ "
\n",
+ "
ffm
\n",
+ "
0.727
\n",
+ "
0.680
\n",
+ "
Field aware factorization machine
\n",
+ "
\n",
+ "
\n",
+ "
nn
\n",
+ "
0.729
\n",
+ "
0.678
\n",
+ "
Neural network
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " CV LB description\n",
+ "models \n",
+ "lgb1 0.730 0.675 lightGBM\n",
+ "lgb2 0.732 0.672 lightGBM with mean target features\n",
+ "ffm 0.727 0.680 Field aware factorization machine\n",
+ "nn 0.729 0.678 Neural network"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "scores = pd.DataFrame({'models':['lgb1','lgb2','ffm','nn'],'CV':[0.730,0.732,0.727,0.729],'LB':[0.675,0.672,0.680,0.678]})\n",
+ "scores['description'] = ['lightGBM','lightGBM with mean target features','Field aware factorization machine', 'Neural network']\n",
+ "scores = scores.set_index('models')\n",
+ "scores"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Two points to be noted from the table above:\n",
+ "1. **CV AUC is higher than LB** which means there is some overfitting.\n",
+ "2. **Improvement of CV doesn't lead to improvement of LB** which means the split between train and test dataset is *not* the same as the KFOLD random split.\n",
+ "\n",
+ "Actually, the dataset provided here has been roughly __[split by time](https://www.kaggle.com/c/microsoft-malware-prediction/data)__. And random split for time series is __[not a good idea](https://www.datapred.com/blog/advanced-cross-validation-tips)__. \n",
+ "\n",
+ "I believe a time-based split will 1) reduce the gap between CV and LB and more importantly 2) align the improvement of CV to LB so that we can evaluate ETL, feature/model selection locally without submitting to kaggle."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "What's annoying is the dataset **doesn't include an explicit timestamp column**, so we have to infer the timing information from other columns. My intuitive assumption is that the version number of the defender software contains timing information: higher version number means more recent observations. Hence, in this notebook, I'll study these 4 columns:\n",
+ "1. *ProductName* - Defender state information e.g. win8defender\n",
+ "2. *EngineVersion* - Defender state information e.g. 1.1.12603.0\n",
+ "3. *AppVersion* - Defender state information e.g. 4.9.10586.0\n",
+ "4. *AvSigVersion* - Defender state information e.g. 1.217.1014.0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "### 2. Functions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def rmse(a,b):\n",
+ " \"\"\"compute root mean square error of two numpy arrays\n",
+ " \"\"\"\n",
+ " return np.mean((a-b)**2)**0.5"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def get_topk_token_count(nvs,k=5):\n",
+ " \"\"\"get top-k token counts of a nvstring object\n",
+ " \n",
+ " Parameters\n",
+ " ----------\n",
+ " nvs : a nvstring object, \n",
+ " k : integer, for top-k\n",
+ " \n",
+ " Returns\n",
+ " ----------\n",
+ " nvs_count : a dictionary (collections.Counter) token (str) => count (int)\n",
+ " including all tokens of that nvstring\n",
+ " nvs_count_topk : a dictionary (collections.Counter) token (str) => count (int)\n",
+ " including top-k frequent tokens of that nvstring\n",
+ " \"\"\"\n",
+ " nvs_count = get_token_counts(nvs)\n",
+ " nvs_count_topk = dict(nvs_count.most_common(k)) \n",
+ " sum_top = sum([j for i,j in nvs_count_topk.items()])\n",
+ " ratio = '%.4f'%(sum_top/nvs.size())\n",
+ " ratio = colored(ratio,'red')\n",
+ " print('# of unique values: %d, top%d %s, top%d percentage:'%(len(nvs_count),k,str(nvs_count_topk),k),ratio) \n",
+ " return nvs_count,nvs_count_topk"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def overlap_piechart(train_count,test_count,title):\n",
+ " \"\"\"draw a pie chart of overlapped ratio between two datasets\n",
+ " Parameters\n",
+ " ----------\n",
+ " train_count : a dictionary (collections.Counter) token (str) => count (int)\n",
+ " for train data\n",
+ " test_count : a dictionary (collections.Counter) token (str) => count (int)\n",
+ " for test data\n",
+ " \n",
+ " \"\"\"\n",
+ " train_in_test = is_in(train_count,test_count)\n",
+ " test_in_train = is_in(test_count,train_count)\n",
+ " t1 = colored('%.3f'%train_in_test,'red')\n",
+ " t2 = colored('%.3f'%test_in_train,'red')\n",
+ " print('train_in_test ratio',t1,' test_in_train ratio',t2)\n",
+ " data = []\n",
+ " data.append({'in test':train_in_test,'not in test':1-train_in_test})\n",
+ " data.append({'in train':test_in_train,'not in train':1-test_in_train})\n",
+ " tags = ['train data','test data']\n",
+ " draw.pie_chart(data,tags,title=title,figsize=(16,4))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "### 3. Visualizations"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 5.09 s, sys: 1.22 s, total: 6.31 s\n",
+ "Wall time: 6.3 s\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%time\n",
+ "# peak gpu memory usage is 17GB!\n",
+ "cols = ['ProductName', 'EngineVersion', 'AppVersion', 'AvSigVersion']\n",
+ "train = gd.read_csv('%s/train.csv'%PATH,usecols=cols)\n",
+ "test = gd.read_csv('%s/test.csv'%PATH,usecols=cols)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**ProductName: name of the defender software**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "train Counter({'win8defender': 8826520, 'mse': 94873, 'mseprerelease': 53, 'scep': 22, 'windowsintune': 8, 'fep': 7})\n",
+ "test Counter({'win8defender': 7797245, 'mse': 55946, 'mseprerelease': 34, 'scep': 16, 'fep': 7, 'windowsintune': 5})\n",
+ "CPU times: user 1.96 s, sys: 232 ms, total: 2.19 s\n",
+ "Wall time: 2.24 s\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%time\n",
+ "col = 'ProductName'\n",
+ "train_count = get_token_counts(train[col].data)\n",
+ "print('train',train_count)\n",
+ "test_count = get_token_counts(test[col].data)\n",
+ "print('test',test_count)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**The following pie charts show that**:\n",
+ "1. there are 6 difference product names in both train and test\n",
+ "2. most samples are from product *win8defender*\n",
+ "3. the distribution is similar in train and test"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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90PTdcgyt1AlIiLzczefgqwoOPTNHuzZthHf7PoSjcd+EB3rsiiqhoycfDgLS5CZ2x/w+6GyNlMnYjNFHc5wNUOSFZxsPIZ927dg39dbcHD/7pC4M2aLS0T3LCechaVIy+4Nr9cL3WKFJEmioxEREQXN7WqGLMs4eeIY9v7PZuzbvgUNdXvh83lFR7uoGHs80rL7IHdAGbpn5cLr9bA2kzBspqhT+bxeGIYLiqrh8IFa1O/dju8PHsD3DQdw4qejQhfKahYrUtJ6olt6NtIcfZDcIxOKosLn9XKaABERhS2fzwvD7YYsK/iuvhYH9+/CkYN1+P5gHY7/9L3Q2myxRqNrWhZS0nqhh6M3krtnQlU1+Lw+6FbuxkfisZkioU43V7KsQJJkHPvhML6r34fDB/bh+4YDOP7DETQ3NbbbnTJZURBjT0BsfBJscYmIjU9El9QMdMtwIDrWDo/hhqrqXKRKREQRy+fzwXC7IMsSJEnGT0cP43BdLQ7X1+JoQz2ajv+EU03H4fW0zwwTWVERY4+HLS4BtrhE2OISkZrhQEp6L1ijbS21WdehKKzNZD5spsiUDLcLPq8XsqJAUTV4PQbcrma4TjbiZNMJnDxxDI3HfkBzUyP8ft///0BJgqKq0DQLVN0C3WJFjD0esfFJiI6Ng6Zb4TXc8Pl8kCQJqqZDVjivmoiI6GIMdzN8Xl+gNvu8XribT+LUyUacPHEMTSd+womfjsLjcsEPf8uRKX5/YCc9VdOhajp0axRiE5Jgsyci2maHqlta1WZF1XhTk0IGmykKaT6vF360/hFuecrFedNERESdze/3t9zk9KNVfWZtpnDFZoqIiIiIiCgIsugAREREREREoYjNFBERERERURDYTBEREREREQWBzRQREREREVEQ2EwREREREREFgc0UERERERFRENhMERERERERBYHNFBERERERURDYTBEREREREQWBzRQREREREVEQ2EwREREREREFgc0UERERERFRENhMERERERERBYHNFBERERERURDYTBEREREREQWBzRQREREREVEQ2EwREREREREFgc0UERERERFRENhMERERERERBYHNFBERERERURDYTBEREREREQWBzRQREREREVEQ2EwREREREREFgc0UERERERFRENhMERERERERBYHNFBERERERURDYTBEREREREQXhfwEEaT7q6UmWYQAAAABJRU5ErkJggg==\n",
+ "text/plain": [
+ "
"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "draw.pie_chart([train_count_topk,test_count_topk],['train','test'],title=col+' top 5',figsize=(16,4))\n",
+ "draw.pie_chart([train_count_topk,test_count_topk],['train','test'],title=col+' top 5',figsize=(18,2),transpose=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Observations**\n",
+ "1. The top-5 AvSigVersion of train and test are completely different and they have no overlap. \n",
+ "2. For example, `1.277.1102.0` can only be found in test data but not in train data. Similarly, `1.251.42.0` can only be found in train data but not in test data.\n",
+ "3. the top 5 of train are 1.251.x ~ 1.275.x and 4 of the top 5 of test is above 1.277.x. This may indicate that higher version number is from more recent observations. \n",
+ "4. we could use AvSigVersion as a timestamp to split data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "train_in_test ratio \u001b[31m0.966\u001b[0m test_in_train ratio \u001b[31m0.154\u001b[0m\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAvMAAAEECAYAAAClervrAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjAsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+17YcXAAAgAElEQVR4nOzdd3hUZdrH8e+UZJJMCgEC0ksCAQxVQFAX1qCiApsAsqCI+7KKu+xaVmXBxQaoICAgoKKIIqgoIr3ZAEWko9J7CQkhAQIhbeo55/0jEo20lEnOlPtzXV6GmTPP+SWQOfc85ykGTdM0hBBCCCGEED7HqHcAIYQQQgghRNlIMS+EEEIIIYSPkmJeCCGEEEIIHyXFvBBCCCGEED5KinkhhBBCCCF8lBTzQgghhBBC+Cgp5oUQooKlpaURHx/P9u3b9Y5SKsOGDePhhx/WO4YQQohrMMg680KIQJeZmUm3bt2oUqUK3333HWazuVSvX7NmDbNmzeLo0aM4nU5iYmJo1aoVo0ePJjw8HEVROH/+PFWqVCEoKOi67TmdTrp27Urv3r0ZPnz4Zc/v2rWLfv36MXv2bG655ZZSZS2N3NxcVFUlKiqqws4hhBCifKRnXggR8BYsWMDtt99OZGQk69atK9VrN23axOOPP86tt97KvHnzWLZsGS+++CLh4eE4nU4ATCYTMTExJSrkAYKDg0lOTmbJkiW4XK7Lnv/888+pX78+nTt3LlXW37tSu38UEREhhbwQQng5KeaFEAFNVVUWLlxI7969SU5OZv78+UXPTZkyhe7du1/2mpdeeon7778fgLVr19KsWTMee+wx4uLiqF+/Pn/6058YPXo0VatWBa48zGbfvn389a9/JSEhgbvuuovVq1eTmJjI22+/DUD//v05f/48a9asKXbu/Px8Vq5cSb9+/TAYDACcPXuW4cOH06lTJ9q2bcv9999f7FwbN24kPj6e77//ngEDBtCyZUsWL15MTk4OI0aM4JZbbiEhIYE///nPjB8/vuh1fxxmo2kaM2fOJDExkYSEBO644w4++uijYvm6dOnCm2++ycsvv0yHDh245ZZbGD9+PIqilO4vRgghRIlIMS+ECGjr16/H6XTSpUsXkpKS2Lx5M2lpaQAkJydz4sQJdu7cWXS80+lk9erVJCcnAxATE8PJkyfZtWtXic9ps9l49NFHqVq1Kl988QUTJkxgzpw5ZGVlFR3TsGFDOnbsyIIFC4q9duXKlbhcLvr27VvU1oMPPojD4WDWrFksWbKEW2+9lcGDB3P8+PFirx0/fjz/+Mc/WLVqFV27dmXy5MkcPHiQGTNm8NVXXzFp0iQaN2581dxz587lrbfeYujQoaxYsYLBgwczfvx4Fi9eXOy4OXPmUKtWLRYsWMBzzz3HnDlzWLZsWYl/PkIIIUpOinkhRECbP38+vXr1wmw2U7NmTW6++eaiArpRo0a0bt2aJUuWFB2/du1a7HY799xzDwCDBg2iffv29OvXj9tuu42hQ4cyZ84cLly4cNVzLl++nPz8fCZOnEizZs1o06YNr776Kna7vdhxAwYM4Mcffyz6cAGFQ4ISExOpVq0aACtWrMDpdDJ58mQSEhJo0KABjz32GK1ateLzzz8v1t6//vUvbr/9durVq0fNmjVJT0/nxhtvpHXr1tSpU4ebbrqJfv36XTX3e++9x9/+9jf69etHw4YNGThwIP379+edd94pdtzNN9/MI488QsOGDenRowc333wzGzduvNZfgxBCiDKSYl4IEbAyMzP5/vvv6d27d9FjvXv3ZuHChbjdbqCwd37VqlVFY8yXLFlCYmIikZGRAISGhvLOO++wZs0ann76aWrWrMnMmTO5++67OXr06BXPe+TIERo3bkxERETRY7GxsUVtXnLHHXcQHR3NF198AcCBAwfYtWsXAwYMKDpm9+7dZGZm0r59e9q2bVv03y+//MKJEyeKtdeqVatifx44cCArV66kV69evPrqq6xfv56rrYmQnZ3N2bNnad++fbHHO3TowMmTJ4vmBwA0a9as2DE1atQodtdBCCGE55RuyQYhhPAjCxYsQFGUYsU8gKIorFu3jjvvvJMePXowduxYvv/+e9q1a8eGDRt46623Lmurbt261K1blz59+vDUU0/RvXt3Zs2axbhx46547kvj3a/l0kTYhQsX8vjjj7NgwYLLJr6qqkqTJk2YNm3aZa8PDQ295p+7du3KunXr2LBhA1u3bmXYsGE0b96cDz74AJPJdN1818r9ewaDAVVVy9yeEEKIq5NiXggRkC5NfP3nP/9Jjx49ij337rvvMn/+fO68806ioqJITExk6dKlpKenExUVxW233XbNtqOiooiJiblqb3RcXBwLFiwg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cZBVFZKqQhZ8p09ERJHP5zchZAmH607jnc3V+PDLGhw74bM7VodIEpCfHY+RxRmYOKIH4jwqVEWGEBwvGWlY5h3MMgxIsgxfTS1OfLYFjVXb0LhjB6zmyL/lp2dmIn5AfyQOHwZvcX/ILhckReF8eCIicjR/wIQkSag74cN7fzmMqn312HXwBIyAZXe0i0qI1VBSkIIrijMwvG86ZFmCIiS4FF5ci2Qs891EyLJgGQZkVUWgoQGntn6Jk198icbt2xE4bvPmF5IEd1YmvMXFSBw+DPHF/SB9Xdo5F56IiLor0wrCH7CgqQJHG1qwdfcxbN1zHF/tb0BDo2FrNs0lkJfpRX52PPrmJWJg7xQkxGkwrSCX0DgMy3w3FQqFYPl8kBUFlt8P42gdfIcPo+VgNYyaWhi1Z/5YvvD9MFFiY6FnZcKdnQVPbg5ievWCOzsbalIiQtaZKxIs70REFK2CwRB8ARMuISPQaqG2oQXVtU04UHMKfz3WjCPHTqO2vgWmFQzL+SQJiI/RkByvIyleR256HPr3TELvnAQkxGnwBywIIUFXeVfcyVjmo0zQNBEMBAAAsqoi1NoKf309rOYWWIYBy2fA8rXAavHBbG6GZRgI+v2QZBmypkHWNAi3Gy5vHFxeLxRvHJSYGLi88ZAUcea1ZRlC0yDJXFdHRER0Ma2mhUBrELIEqC6BZqMVp32taGoO4NTpABqb/TjtM9HsC8AIWGeWv8gyFEWCS5GhKuLM3y6BRK+OlAQ3EmJVxLhdMK0QTPPMmwPVJXPJTDfEMk8XFLIsBC0LkiRBEoIFnYiIyCbBUAiWFYQkSZAlCbLMSW/EMk9ERERE5Fi8zEpERERE5FAs80REREREDsUyT0RERETkUCzzREREREQOxTJPRERERORQLPNERERERA7FMk9ERERE5FAs80REREREDsUyT0RERETkUCzzREREREQOxTJPRERERORQLPNERERERA7FMk9ERERE5FAs80REREREDsUyT0RERETkUCzzREREREQOxTJPRERERORQLPNERERERA7FMk9ERERE5FAs80REREREDsUyT0RERETkUCzzREREREQOxTJPRERERORQLPNERERERA7FMk9ERERE5FAs80REREREDsUyT0RERETkUP8P7QjMb6BSt9wAAAAASUVORK5CYII=\n",
+ "text/plain": [
+ "
"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "overlap_piechart(train_count,test_count,col)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**We also look at overall token counts instead of just top-5 tokens**\n",
+ "1. for test data, there are only 15.3% of samples whose AvSigVersion present in train data. This indicates that most of test data are with new AvSigVersions.\n",
+ "2. for train data, there are 97.5% samples whose AvSigVersion present in test data. This indicates the 15.3% test data (the red slice of the left pie chart) actually contain most of the AvSigVersions of train. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**The similar analysis is also done to EngineVersion and AppVersion**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "==============Train==============\n",
+ "# of unique values: 68, top5 {'1.1.15000.2': 265218, '1.1.15200.1': 212408, '1.1.14600.4': 160585, '1.1.14800.3': 136476, '1.1.15300.6': 120295}, top5 percentage: \u001b[31m0.1003\u001b[0m\n",
+ "==============Test==============\n",
+ "# of unique values: 69, top5 {'1.1.15300.6': 3101305, '1.1.15400.4': 2106236, '1.1.15200.1': 366085, '1.1.15100.1': 158036, '1.1.14600.4': 138514}, top5 percentage: \u001b[31m0.7475\u001b[0m\n",
+ "CPU times: user 708 ms, sys: 232 ms, total: 940 ms\n",
+ "Wall time: 941 ms\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%time\n",
+ "col = 'EngineVersion'\n",
+ "k = 5\n",
+ "print(\"==============Train==============\")\n",
+ "train_count,train_count_topk = get_topk_token_count(train[col].data,k)\n",
+ "print(\"==============Test==============\")\n",
+ "test_count,test_count_topk = get_topk_token_count(test[col].data,k)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAyoAAAEECAYAAADH8MCoAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjAsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+17YcXAAAgAElEQVR4nOzdeZxN9f/A8de9d/Z9NYPsfsguYwmhlMrImhQtJBWRLSqiUiqVCKVsSXwxFUMI2ZeQrexkG2PG7JuZufs5vz+GmzErhnvHvJ+Phwdz3p97zvsenM993/M5n49GVVUVIYQQQgghhHAgWnsnIIQQQgghhBA3kkJFCCGEEEII4XCkUBFCCCGEEEI4HClUhBBCCCGEEA5HChUhhBBCCCGEw5FCRQghhBBCCOFwpFARQggHtXz5curWrWvvNEqcxWKhdu3arFmzxt6pCCGEcGAaWUdFCCFuzjvvvMOKFSvybPfw8ODQoUMldhyDwUBmZiZBQUElts8//viDIUOGsGbNGmrWrJkn/sEHH7Bt2zY2bdqEVnvnvstKTEzEx8cHV1fXO3YMIYQQpZuTvRMQQojSKCwsjGnTpuXaVtIf7N3c3HBzcyvRfT788MMEBwcTERHB2LFjc8X0ej2rV6+mX79+t/xezGYzzs7ORbYLDg6+pf0LIYQoO2TolxBC3AJnZ2eCg4Nz/QoMDATghRdeYNy4cXzzzTe0bt2a5s2bM2bMGLKysmyvVxSFr776ipYtW9KkSRNGjBjBggULcg31unHo17WfDxw4QPfu3WnUqBE9evTg8OHDuXKLiopi6NChhIWF0axZM15++WVOnToFgJOTEz179mTlypWYTKZcr/v999/Jzs6mV69etm07duygd+/eNGzYkIceeoixY8eSlpZmi7/11lsMGDCABQsW8PDDD9OgQQPMZjP79u3j2WefpUmTJjzwwAN07dqVP//8E8h/6Fd8fDzDhg0jLCyMhg0b8sILL3D8+HFb/M8//6R27drs3r2b5557joYNGxIeHs6OHTtu/i9PCCFEqSCFihBC3AHr168nPT2dhQsX8tVXX7F161bmzJlji//444/89NNPtmFkDRs25Ntvvy1yv9cKnHHjxrF8+XICAgIYPnw4FosFgKSkJPr06UNAQACLFy9m2bJlVKtWjRdffJGUlBQAevXqRUZGBhs2bMi174iICNq1a0dISAgAO3fuZMiQIXTp0oXffvuNb775hqioKN58881crzt48CAHDx5k1qxZREZGoqoqr7/+Ok2aNCEyMpLly5fzxhtvFDjMS1VVBg0axMWLF5k9ezYRERH4+/vTv3//XEURwOTJk3njjTdYtWoV9erVY8SIEVy5cqXI8yaEEKL0kUJFCCFuwV9//UWTJk1y/Xr99ddt8QoVKjB27Fhq1KhBmzZtePLJJ9m9e7ctPn/+fF566SW6detG1apV6d+/P61bty7yuKqqMnbsWMLCwqhRowZDhw4lJiaGixcvArBkyRIqVqzIhx9+SO3atalevTrvvfce3t7erFq1CoD77ruP1q1bExERYdvv2bNnOXToEL1797Zt++abb+jXrx99+/alSpUqNGzYkM8++4y9e/dy+vRpWztnZ2cmT55MnTp1qFOnDpmZmWRmZtKhQweqVKlC1apV6dixI02bNs33Pe3cuZNjx44xZcoUHnjgAerUqcPnn3+OTqdj6dKludoOHTqUNm3aULVqVUaNGsWVK1c4evRokedNCCFE6SPPqAghxC1o2LAhkydPzrXt+udJ6tSpkytWrlw5du7cCcCVK1dISEigcePGudo0btyY9evXF3pcjUaTa9/lypUDIDk5merVq3PkyBGOHTtGkyZNcr3OYDAQFRVl+7l3794MHTqUqKgoqlSpQkREBBUqVKBt27a2NkePHuXo0aMsXLgwTx5RUVHUqlULgJo1a+Lu7m6LBQQE0KNHD/r370/Lli1p1qwZHTt2pGrVqvm+pzNnzhAUFET16tVt29zc3GjQoAH//vtvrrb3339/nveelJSU/8kSQghRqkmhIoQQt8DNzY0qVaoUGL/xgXKNRsONkyxqNJqbPq5Wq0Wn0+XZh6Iott9btmzJhAkT8rzW29vb9ueHH36YoKAgIiIiGDZsGJGRkbzwwgu5HqK/NiSrc+fOefZ1/Uxk1xcp13z66af069ePXbt2sWvXLqZPn87777+f6/mXW3H9eb3xvQshhLi3SKEihBB3mbe3N+XKlePQoUO0a9fOtv2ff/657X3Xr1+fFStWEBoaWujUv9ceqv/555+pVasWV65cyVNE1KtXjzNnzhRakBWmdu3a1K5dm5dffplx48YRERGRb6FSs2ZNkpKSOHfunO2uisFg4MiRI7z00ku3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\n",
+ "text/plain": [
+ "