├── .gitignore ├── README.md ├── Report ├── Report_Shravan_Kuchkula.ipynb ├── Report_Shravan_Kuchkula.md ├── Report_Shravan_Kuchkula_files │ ├── Report_Shravan_Kuchkula_11_0.png │ ├── Report_Shravan_Kuchkula_13_0.png │ ├── Report_Shravan_Kuchkula_8_0.png │ ├── airflow_tree_view.png │ ├── clicks.png │ ├── dag.png │ ├── dataframe.png │ ├── operators.png │ ├── rawdata.png │ ├── redshift.png │ └── validsearches.png └── dwh-streeteasy.cfg ├── docker-compose.yml ├── images ├── Report_Shravan_Kuchkula_11_0.png ├── Report_Shravan_Kuchkula_13_0.png ├── Report_Shravan_Kuchkula_8_0.png ├── airflow_tree_view.png ├── clicks.png ├── connections.png ├── dag.png ├── dataframe.png ├── operators.png ├── rawdata.png ├── redshift.png ├── validsearches.png └── variables.png └── street-easy ├── dags ├── create_postgres_table.py └── street_easy.py ├── plugins ├── __init__.py ├── helpers │ ├── __init__.py │ └── transforms.py └── operators │ ├── __init__.py │ ├── extract_and_transform_streeteasy.py │ └── valid_search_stats.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## ETL data pipeline to process StreetEasy data 3 | 4 | **Project Description**: 5 | 6 | An online real-estate company is interested in understanding `user enagagement` by analyzing user search patterns to send targeted emails to the users with valid searches. A valid search is termed as one where the search metadata contains `enabled:true` and number of clicks is atleast `3`. 7 | 8 | A daily snapshot of user search history and related data is saved to S3. Each file represents a single date, as noted by the filename: `inferred_users.20180330.csv.gz`. Each line in each file represents a *unique user*, as identified by `id` column. Information on each user's searches and engagement is stored in `searches` column. An example of this is shown below: 9 | 10 | ![rawdata](images/rawdata.png) 11 | 12 | 13 | **Data Description**: The source data resides in S3 `s3://` for each day from **2018-01-20** till **2018-03-30**, as shown: 14 | ```bash 15 | s3:/// 16 | inferred_users.20180120.csv.gz 17 | inferred_users.20180121.csv.gz 18 | inferred_users.20180122.csv.gz 19 | inferred_users.20180123.csv.gz 20 | inferred_users.20180124.csv.gz 21 | .. 22 | inferred_users.20180325.csv.gz 23 | inferred_users.20180326.csv.gz 24 | inferred_users.20180327.csv.gz 25 | inferred_users.20180328.csv.gz 26 | inferred_users.20180329.csv.gz 27 | inferred_users.20180330.csv.gz 28 | ``` 29 | 30 | All this data needs to be processed using a data pipeline to answer the following **business questions:** 31 | 1. Produce a list of **unique "valid searches"**. 32 | 2. Produce, for each date, the **total number of valid searches** that existed on that date. 33 | 3. Produce, for each date, the **total number of users** who had valid searches on that date. 34 | 4. Given this data, determine which is the **most engaging search.** 35 | 5. What would the email traffic look like if the definition of a valid search is changed from **3 clicks to 2 clicks**? 36 | 6. Report any interesting **trends over the timespan** of the data available. 37 | 38 | 39 | **Data Pipeline design**: 40 | The design of the pipeline can be summarized as: 41 | - Extract data from source S3 location. 42 | - Process and Transform it using python and custom **Airflow operators**. 43 | - Load a clean dataset and intermediate artifacts to **destination S3 location**. 44 | - Calculate summary statistics and load the summary stats into **Amazon Redshift**. 45 | 46 | > Figure showns the structure of the data pipeline as represented by a Airflow DAG 47 | ![dag](images/dag.png) 48 | 49 | Finally, I have made use of `Jupyter Notebook` to connect to the `Redshift` cluster and answer the questions of interest. 50 | 51 | **Design Goals**: 52 | As the data is stored in S3, we need a way to incrementally load each file, then process it and store that particular day's results back into S3. Doing so will allow us to perform further analysis later-on, on the cleaned dataset. Secondly, we need a way to aggregate the data and store it in a table to facilitate time-based analysis. Keeping these two goals in mind, the following tools were chosen: 53 | - Apache Airflow will incrementally extract the data from S3 and process it *in-memory* and store the results back into a destination S3 bucket. The reason we need to process this in-memory is because, we don't want to download the file from S3 to airflow worker's disk, as this might fill-up the worker's disk and crash the worker process. 54 | - Amazon Redshift is a simple cloud-managed data warehouse that can be integrated into pipelines without much effort. Airflow will then read the intermediate dataset created in the first step and aggregate the data per day and store it into a Redshift table. 55 | 56 | **Pipeline Implementation**: 57 | Apache Airflow is a Python framework for programmatically creating workflows in DAGs, e.g. ETL processes, generating reports, and retraining models on a daily basis. The Airflow UI automatically parses our DAG and creates a natural representation for the movement and transformation of data. A DAG simply is a collection of all the tasks you want to run, organized in a way that reflects their relationships and dependencies. A **DAG** describes *how* you want to carry out your workflow, and **Operators** determine *what* actually gets done. 58 | 59 | By default, airflow comes with some simple built-in operators like `PythonOperator`, `BashOperator`, `DummyOperator` etc., however, airflow lets you extend the features of a `BaseOperator` and create custom operators. For this project, I developed two custom operators: 60 | 61 | ![operators](images/operators.png) 62 | 63 | - **StreetEasyOperator**: Extract data from **source S3 bucket**, processes the data in-memory by applying a series of transformations found inside `transforms.py`, then loads it to destination S3 bucket. Please see the code here: [StreetEasyOperator](https://github.com/nn/batch-etl/blob/master/street-easy/plugins/operators/extract_and_transform_streeteasy.py) 64 | - **ValidSearchStatsOperator**: Takes data from **destination S3 bucket**, aggregates the data on a per-day basis, and uploads it to Redshift table `search_stats`. Please see the code here: [ValidSearchStatsOperator](https://github.com/nn/batch-etl/blob/master/street-easy/plugins/operators/valid_search_stats.py) 65 | 66 | Here's the directory organization: 67 | 68 | ```bash 69 | ├── README.md 70 | ├── Report 71 | │   ├── Report.ipynb 72 | │   └── dwh-streeteasy.cfg 73 | ├── docker-compose.yml 74 | ├── images 75 | └── street-easy 76 | ├── dags 77 | │   ├── create_postgres_table.py 78 | │   └── street_easy.py 79 | ├── plugins 80 | │   ├── __init__.py 81 | │   ├── helpers 82 | │   │   ├── __init__.py 83 | │   │   └── transforms.py 84 | │   └── operators 85 | │   ├── __init__.py 86 | │   ├── extract_and_transform_streeteasy.py 87 | │   └── valid_search_stats.py 88 | └── requirements.txt 89 | ``` 90 | 91 | 92 | **Pipeline Schedule**: Our pipeline is required to adhere to the following guidelines: 93 | * The DAG should run *daily* from `2018-01-20` to `2018-03-30` 94 | * The DAG should not have any dependencies on past runs. 95 | * On failure, the task is retried for 3 times. 96 | * Retries happen every 5 minutes. 97 | * Do not email on retry. 98 | 99 | > Shown below is the data pipeline (street_easy DAG) execution starting on **2018-01-20** and ending on **2018-03-30**. 100 | ![airflow_tree_view](images/airflow_tree_view.png) 101 | > Note: The data for *2018-01-29 and 2018-01-30* is not available, thus we are skipping over that. 102 | 103 | **Destination S3 datasets and Redshift Table**: 104 | After each successful run of the DAG, two files are stored in the destination bucket: 105 | * `s3://skuchkula-etl/unique_valid_searches_.csv`: Contains a list of unique valid searches for each day. 106 | * `s3://skuchkula-etl/valid_searches_.csv`: Contains a dataset with the following fields: 107 | * user_id: Unique id of the user 108 | * num_valid_searches: Number of valid searches 109 | * avg_listings: Avg number of listings for that user 110 | * type_of_search: Did the user search for: 111 | * Only Rental 112 | * Only Sale 113 | * Both Rental and Sale 114 | * Neither 115 | * list_of_valid_searches: A list of valid searches for that user 116 | 117 | 118 | **unique_valid_searches_{date}.csv** contains unique valid searches per day: 119 | ```bash 120 | s3://skuchkula-etl/ 121 | unique_valid_searches_20180120.csv 122 | unique_valid_searches_20180121.csv 123 | unique_valid_searches_20180122.csv 124 | unique_valid_searches_20180123.csv 125 | unique_valid_searches_20180214.csv 126 | ... 127 | ``` 128 | 129 | **valid_searches_{date}.csv** contains the valid searches dataset per day: 130 | ```bash 131 | s3://skuchkula-etl/ 132 | valid_searches_20180120.csv 133 | valid_searches_20180121.csv 134 | valid_searches_20180122.csv 135 | valid_searches_20180123.csv 136 | valid_searches_20180214.csv 137 | ... 138 | ``` 139 | **Amazon Redshift table:** 140 | 141 | The `ValidSearchesStatsOperator` then takes each of datasets `valid_searches_{date}.csv` and calcuates summary stats and loads the results to **search_stats** table, as shown: 142 | 143 | ![redshift](images/redshift.png) 144 | 145 | 146 | ## Answering business questions using data 147 | 148 | ### Business question: Produce a list of all unique "valid searches" given the above requirements. 149 | The list of all unique searches is stored in the destination S3 bucket: `s3://skuchkula-etl/unique_valid_searches_{date}.csv`. An example of the output is shown here 150 | 151 | ```bash 152 | $ head -10 unique_valid_searches_20180330.csv 153 | searches 154 | 38436711 155 | 56011095 156 | 3161333 157 | 43841677 158 | 42719934 159 | 40166212 160 | 44847718 161 | 36981443 162 | 13923552 163 | ``` 164 | The code used to calculate the unique valid searches can be found here: [transforms.py](https://github.com/nn/batch-etl/blob/16986034763616f330d27febf22c92efa007d1db/street-easy/plugins/operators/extract_and_transform_streeteasy.py#L112) 165 | 166 | We will be making using of `pandas`, `psycopg2` and `matplotlib` to use the data we gathered to answer the next set of business questions. 167 | 168 | 169 | ```python 170 | import pandas as pd 171 | import pandas.io.sql as sqlio 172 | import configparser 173 | import psycopg2 174 | 175 | import matplotlib.pyplot as plt 176 | plt.style.use('fivethirtyeight') 177 | ``` 178 | 179 | ### Business question: Produce, for each date, the total number of valid searches that existed on that date. 180 | To answer this we need to connect to the Redshift cluster and query the `search_stats` table. First, we obtain a connection to Redshift cluster. The secrets are stored in the `dwh-streeteasy.cfg` file. Next, we execute the SQL query and store the result as a pandas dataframe. 181 | 182 | 183 | ```python 184 | config = configparser.ConfigParser() 185 | config.read('dwh-streeteasy.cfg') 186 | 187 | # connect to redshift cluster 188 | conn = psycopg2.connect("host={} dbname={} user={} password={} port={}".format(*config['CLUSTER'].values())) 189 | cur = conn.cursor() 190 | 191 | sql_query = "SELECT * FROM search_stats" 192 | df = sqlio.read_sql_query(sql_query, conn) 193 | df['day'] = pd.to_datetime(df['day']) 194 | df = df.set_index('day') 195 | print(df.shape) 196 | df.head() 197 | ``` 198 | ``` 199 | (68, 6) 200 | ``` 201 | ![dataframe](images/dataframe.png) 202 | 203 | From this dataframe, for this question, we are interested in finding out the **total number of valid searches** on a given day. This is captured in the `num_searches` column. Shown below is a plot showing the num_searches per day for the entire time-period. 204 | 205 | 206 | ```python 207 | ax = df['num_searches'].plot(figsize=(12, 8), fontsize=12, linewidth=3, linestyle='--') 208 | ax.set_xlabel('Date', fontsize=16) 209 | ax.set_ylabel('Valid Searches', fontsize=16) 210 | ax.set_title('Total number of valid searches on each day') 211 | ax.axvspan('2018-03-21', '2018-03-24', color='red', alpha=0.3) 212 | plt.show() 213 | ``` 214 | 215 | 216 | ![png](images/Report__8_0.png) 217 | 218 | 219 | **Observation**: The red band indicates a sharp drop in the number of valid searches on `2018-03-24`. 220 | 221 | ### Business Question: Produce, for each date, the total number of valid searches that existed on that date. 222 | The **total number of users with valid searches per day** is captured in the `num_users` column of the dataframe. A similar trend can be observed for the num_users indicated by the red band. 223 | 224 | 225 | ```python 226 | ax = df['num_users'].plot(figsize=(12, 8), fontsize=12, linewidth=3, linestyle='--') 227 | ax.set_xlabel('Date', fontsize=16) 228 | ax.set_ylabel('Number of users', fontsize=16) 229 | ax.set_title('Total number of users on each day') 230 | ax.axvspan('2018-03-21', '2018-03-24', color='red', alpha=0.3) 231 | plt.show() 232 | ``` 233 | 234 | 235 | ![png](images/Report__11_0.png) 236 | 237 | 238 | ### Business question: Most engaging search 239 | From the data that is available, it appears that `Rental` searches are the most engaging ones. I am assuming that the number of valid searches is a good indicator to guage user engagement. It is evident from the below plot, that Rental Searches are consistently producing more valid searches than `Sale` type searches. 240 | 241 | 242 | ```python 243 | ax = df[['num_rental_searches', 244 | 'num_sales_searches', 245 | 'num_rental_and_sales_searches', 246 | 'num_none_type_searches']].plot(figsize=(12, 8), fontsize=12, linewidth=2, linestyle='--') 247 | ax.set_xlabel('Date', fontsize=16) 248 | ax.set_ylabel('Valid Searches', fontsize=16) 249 | ax.set_title('Types of searches every day') 250 | ax.legend(fontsize=10) 251 | plt.show() 252 | ``` 253 | 254 | 255 | ![png](images/Report__13_0.png) 256 | 257 | 258 | ### Business question: What would the email traffic look like if we changed the definition of a valid search from 3 clicks to 2? 259 | When the defintion of valid search is changed from `clicks >= 3` to `clicks >= 2` the number of searches and its corresponding stats increase in size. Shown below is a comparison for the first 3 days: 260 | 261 | ![clicks](images/clicks.png) 262 | 263 | This means that the **email traffic would increase**. 264 | 265 | ### Business question: Report any interesting trends over the timespan of the data available. 266 | Mainly there are two trends observed with this timeseries data: 267 | - One is that there is a steady increase in the number of searches made and also in the number of users. The stats corresponding to individual search type shows that Rental searches are growing faster than Sales searches. 268 | - Second interesting thing that I found was a sharp dip in the number of searches and users on 2018-03-23, which could be something interesting to investigate. 269 | 270 | ## Recommendations 271 | 272 | ### Recommendations in data storage: 273 | In terms of storing data, using CSV files comes with some problems down the line. Here are some difficulties with CSV files: 274 | - No defined schema: There are no data types included and column names beyond a header row. 275 | - Nested data requires special handling. 276 | In addition to these issues with using CSV file format, **Spark** has some **specific problems** when working with CSV data: 277 | - CSV files are quite **slow to import** and parse. 278 | - The files cannot be shared between workers during the import process. 279 | - If no schema is defined, then all data must be read before a schema can be inferred. 280 | - Spark has a feature known as **predicate pushdown** - which is an idea of ordering tasks to do the least amount of work. Example, *filtering* data prior to processing is one of the primary optimizations of predicate pushdown, this drastically reduces the amount of information that must be processed in large data sets. Unfortunately, we cannot filter the CSV data via predicate pushdown. 281 | - Finally, Spark processes are often multi-step and may utilize an intermediate file representation. These representations allow data to be used later without regenerating the data from source. 282 | 283 | Instead of using CSV, when possible use **parquet file format**. 284 | 285 | **Parquet Format**: Parquet is a compressed columnar data format and is structured with data accessible in chunks that allows efficient read/write operations without processing the entire file. This structured format supports Spark's predicate pushdown functionality, thus providing significant performance improvement. Finally, parquet files automatically include schema information and handle data encoding. This is perfect for intermediary or on-disk representation of processed data. Note that parquet files are binary file format and can only be used with proper tools. 286 | 287 | ### Recommendations for downstream processing: 288 | The search field coming through from the application appears to be `YAML` format. I found that writing regular expression to parse out the search field is prone to errors if the schema evolves. A better way to capture the search field is using JSON or AVRO, as this has some form of schema tied to it, so that downstream applications can know when the schema evolves. 289 | 290 | ## How to run this project? 291 | **pre-requisites**: 292 | - Docker and docker-compose must be running on your laptop. 293 | - You have credentials for source and destination S3 buckets. (Both are private buckets) 294 | - You need to have AWS Redshift cluster endpoint. [guide to create Redshift cluster using IaC](https://nn.github.io/create-aws-redshift-cluster/) 295 | 296 | **Step 1:** Once the requirements are met, launch Airflow on your laptop by running: `docker-compose up` from the location where `docker-compose.yml` is located. 297 | ```bash 298 | : batch-etl$ docker-compose up 299 | Creating network "batch-etl_default" with the default driver 300 | Creating batch-etl_postgres_1 ... done 301 | Creating batch-etl_webserver_1 ... done 302 | 303 | webserver_1 | ____________ _____________ 304 | webserver_1 | ____ |__( )_________ __/__ /________ __ 305 | webserver_1 | ____ /| |_ /__ ___/_ /_ __ /_ __ \_ | /| / / 306 | webserver_1 | ___ ___ | / _ / _ __/ _ / / /_/ /_ |/ |/ / 307 | webserver_1 | _/_/ |_/_/ /_/ /_/ /_/ \____/____/|__/ 308 | ``` 309 | Inside the `docker-compose.yml` we have the **volumes** section, which maps our dags directory to airflow's dag-bag: `/usr/local/airflow/dags`. Next, we map the custom Airflow Plugin that we created to extend Airflow's functionality by adding two custom operators, this is mapped to the airflow's plugin directory. Lastly, inside both my operators, I have made use of `s3fs` python package, which is essentially a wrapper around `boto3` package, but provides more simpler interface. Add `s3fs` to `requirements.txt` and map that to `/requirements.txt`. The reason we need to map this to way is because the entrypoint docker script runs `pip install -r requirements.txt` from `/` within the docker container. 310 | 311 | ``` 312 | volumes: 313 | - ./street-easy/dags:/usr/local/airflow/dags 314 | # Uncomment to include custom plugins 315 | - ./street-easy/plugins:/usr/local/airflow/plugins 316 | # Additional python packages used inside airflow operators 317 | - ./street-easy/requirements.txt:/requirements.txt 318 | ``` 319 | 320 | **Step 2:**: Configure Airflow Variables 321 | Login to Airflow Console: http://localhost:8080/admin , and create two `Variables`. Our code uses these variables to reference the source and destination buckets. 322 | ![variables](images/variables.png) 323 | 324 | Next, create the following connections: 325 | - *aws_credentials*: (Type: Amazon Web Services, Login:, Password:) 326 | - *aws_dest_credentials*: (Type: Amazon Web Services, Login:, Password:) 327 | - *redshift*: Shown below is the configuration 328 | ![connections](images/connections.png) 329 | 330 | **Step 3**: There are two dags in our dag-bag: `create_postgres_table` and `street_easy`. The first is used to create a table in Redshift. Turn on the `create_postgres_table` DAG and trigger it manually. Once the dag finishes running, it will create the tables in Redshift. After that, turn on the `street_easy` dag. This will trigger the execution automatically since the start date is in the past. 331 | 332 | **Step 4**: Launch the jupyter notebook provided here: [notebook](https://github.com/nn/batch-etl/blob/16986034763616f330d27febf22c92efa007d1db/Report/Report.ipynb) . Navigate to "Answering Business questions using data" section. Run the code cells. 333 | -------------------------------------------------------------------------------- /Report/Report_Shravan_Kuchkula.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## ETL data pipeline to process StreetEasy data\n", 8 | "### Author: Shravan Kuchkula (email: shravan.kuchkula@gmail.com)\n", 9 | "**Project Description**: \n", 10 | "\n", 11 | "An online real-estate company is interested in understanding `user enagagement` by analyzing user search patterns to send targeted emails to the users with valid searches. A valid search is termed as one where the search metadata contains `enabled:true` and number of clicks is atleast `3`.\n", 12 | "\n", 13 | " A daily snapshot of user search history and related data is saved to S3. Each file represents a single date, as noted by the filename: `inferred_users.20180330.csv.gz`. Each line in each file represents a *unique user*, as identified by `id` column. Information on each user's searches and engagement is stored in `searches` column. An example of this is shown below:\n", 14 | "\n", 15 | "![rawdata](streeteasy-images/rawdata.png)\n", 16 | " \n", 17 | "\n", 18 | "**Data Description**: The source data resides in S3 `s3://` for each day from **2018-01-20** till **2018-03-30**, as shown:\n", 19 | "```bash\n", 20 | "s3:///\n", 21 | "inferred_users.20180120.csv.gz\n", 22 | "inferred_users.20180121.csv.gz\n", 23 | "inferred_users.20180122.csv.gz\n", 24 | "inferred_users.20180123.csv.gz\n", 25 | "inferred_users.20180124.csv.gz\n", 26 | "..\n", 27 | "inferred_users.20180325.csv.gz\n", 28 | "inferred_users.20180326.csv.gz\n", 29 | "inferred_users.20180327.csv.gz\n", 30 | "inferred_users.20180328.csv.gz\n", 31 | "inferred_users.20180329.csv.gz\n", 32 | "inferred_users.20180330.csv.gz\n", 33 | "```\n", 34 | "\n", 35 | "All this data needs to be processed using a data pipeline to answer the following **business questions:**\n", 36 | "1. Produce a list of **unique \"valid searches\"**. \n", 37 | "2. Produce, for each date, the **total number of valid searches** that existed on that date.\n", 38 | "3. Produce, for each date, the **total number of users** who had valid searches on that date.\n", 39 | "4. Given this data, determine which is the **most engaging search.**\n", 40 | "5. What would the email traffic look like if the definition of a valid search is changed from **3 clicks to 2 clicks**?\n", 41 | "6. Report any interesting **trends over the timespan** of the data available.\n", 42 | "\n", 43 | "\n", 44 | "**Data Pipeline design**:\n", 45 | "The design of the pipeline can be summarized as:\n", 46 | "- Extract data from source S3 location.\n", 47 | "- Process and Transform it using python and custom **Airflow operators**.\n", 48 | "- Load a clean dataset and intermediate artifacts to **destination S3 location**.\n", 49 | "- Calculate summary statistics and load the summary stats into **Amazon Redshift**.\n", 50 | "\n", 51 | "> Figure showns the structure of the data pipeline as represented by a Airflow DAG\n", 52 | "![dag](streeteasy-images/dag.png)\n", 53 | "\n", 54 | "Finally, I have made use of `Jupyter Notebook` to connect to the `Redshift` cluster and answer the questions of interest.\n", 55 | "\n", 56 | "**Design Goals**:\n", 57 | "As the data is stored in S3, we need a way to incrementally load each file, then process it and store that particular day's results back into S3. Doing so will allow us to perform further analysis later-on, on the cleaned dataset. Secondly, we need a way to aggregate the data and store it in a table to facilitate time-based analysis. Keeping these two goals in mind, the following tools were chosen:\n", 58 | "- Apache Airflow will incrementally extract the data from S3 and process it *in-memory* and store the results back into a destination S3 bucket. The reason we need to process this in-memory is because, we don't want to download the file from S3 to airflow worker's disk, as this might fill-up the worker's disk and crash the worker process.\n", 59 | "- Amazon Redshift is a simple cloud-managed data warehouse that can be integrated into pipelines without much effort. Airflow will then read the intermediate dataset created in the first step and aggregate the data per day and store it into a Redshift table.\n", 60 | "\n", 61 | "**Pipeline Implementation**:\n", 62 | "Apache Airflow is a Python framework for programmatically creating workflows in DAGs, e.g. ETL processes, generating reports, and retraining models on a daily basis. The Airflow UI automatically parses our DAG and creates a natural representation for the movement and transformation of data. A DAG simply is a collection of all the tasks you want to run, organized in a way that reflects their relationships and dependencies. A **DAG** describes *how* you want to carry out your workflow, and **Operators** determine *what* actually gets done.\n", 63 | "\n", 64 | "By default, airflow comes with some simple built-in operators like `PythonOperator`, `BashOperator`, `DummyOperator` etc., however, airflow lets you extend the features of a `BaseOperator` and create custom operators. For this project, I developed two custom operators:\n", 65 | "\n", 66 | "![operators](streeteasy-images/operators.png)\n", 67 | "\n", 68 | "- **StreetEasyOperator**: Extract data from **source S3 bucket**, processes the data in-memory by applying a series of transformations found inside `transforms.py`, then loads it to destination S3 bucket. Please see the code here: \n", 69 | "- **ValidSearchStatsOperator**: Takes data from **destination S3 bucket**, aggregates the data on a per-day basis, and uploads it to Reshift table `search_stats`. Please see the code here:\n", 70 | "\n", 71 | "Here's the directory organization:\n", 72 | "\n", 73 | "```bash\n", 74 | "├── README.md\n", 75 | "├── docker-compose.yml\n", 76 | "└── street-easy\n", 77 | " ├── dags\n", 78 | " │   ├── create_postgres_table.py\n", 79 | " │   └── street_easy.py\n", 80 | " ├── plugins\n", 81 | " │   ├── __init__.py\n", 82 | " │   ├── helpers\n", 83 | " │   │   ├── __init__.py\n", 84 | " │   │   └── transforms.py\n", 85 | " │   └── operators\n", 86 | " │   ├── __init__.py\n", 87 | " │   ├── extract_and_transform_streeteasy.py\n", 88 | " │   └── valid_search_stats.py\n", 89 | " └── requirements.txt\n", 90 | "```\n", 91 | "\n", 92 | "\n", 93 | "**Pipeline Schedule**: Our pipeline is required to adhere to the following guidelines:\n", 94 | "* The DAG should run *daily* from `2018-01-20` to `2018-03-30`\n", 95 | "* The DAG should not have any dependencies on past runs.\n", 96 | "* On failure, the task is retried for 3 times.\n", 97 | "* Retries happen every 5 minutes.\n", 98 | "* Do not email on retry.\n", 99 | "\n", 100 | "> Shown below is the data pipeline (street_easy DAG) execution starting on **2018-01-20** and ending on **2018-03-30**.\n", 101 | "![airflow_tree_view](streeteasy-images/airflow_tree_view.png)\n", 102 | "> Note: The data for *2018-01-29 and 2018-01-30* is not available, thus we are skipping over that.\n", 103 | "\n", 104 | "**Destination S3 datasets and Redshift Table**:\n", 105 | "After each successful run of the DAG, two files are stored in the destination bucket: \n", 106 | "* `s3://skuchkula-etl/unique_valid_searches_.csv`: Contains a list of unique valid searches for each day.\n", 107 | "* `s3://skuchkula-etl/valid_searches_.csv`: Contains a dataset with the following fields:\n", 108 | " * user_id: Unique id of the user\n", 109 | " * num_valid_searches: Number of valid searches\n", 110 | " * avg_listings: Avg number of listings for that user\n", 111 | " * type_of_search: Did the user search for:\n", 112 | " * Only Rental\n", 113 | " * Only Sale\n", 114 | " * Both Rental and Sale\n", 115 | " * Neither\n", 116 | " * list_of_valid_searches: A list of valid searches for that user\n", 117 | "\n", 118 | "\n", 119 | "**unique_valid_searches_{date}.csv** contains unique valid searches per day:\n", 120 | "```bash\n", 121 | "s3://skuchkula-etl/\n", 122 | "unique_valid_searches_20180120.csv\n", 123 | "unique_valid_searches_20180121.csv\n", 124 | "unique_valid_searches_20180122.csv\n", 125 | "unique_valid_searches_20180123.csv\n", 126 | "unique_valid_searches_20180214.csv\n", 127 | "...\n", 128 | "```\n", 129 | "\n", 130 | "**valid_searches_{date}.csv** contains the valid searches dataset per day:\n", 131 | "```bash\n", 132 | "s3://skuchkula-etl/\n", 133 | "valid_searches_20180120.csv\n", 134 | "valid_searches_20180121.csv\n", 135 | "valid_searches_20180122.csv\n", 136 | "valid_searches_20180123.csv\n", 137 | "valid_searches_20180214.csv\n", 138 | "...\n", 139 | "```\n", 140 | "**Amazon Redshift table:**\n", 141 | "\n", 142 | "The `ValidSearchesStatsOperator` then takes each of datasets `valid_searches_{date}.csv` and calcuates summary stats and loads the results to **search_stats** table, as shown:\n", 143 | "\n", 144 | "![redshift](streeteasy-images/redshift.png)\n" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "## Answering business questions using data" 152 | ] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "metadata": {}, 157 | "source": [ 158 | "### Business question: Produce a list of all unique \"valid searches\" given the above requirements.\n", 159 | "The list of all unique searches is stored in the destination S3 bucket: `s3://skuchkula-etl/unique_valid_searches_{date}.csv`. An example of the output is shown here\n", 160 | "\n", 161 | "```bash\n", 162 | "$ head -10 unique_valid_searches_20180330.csv\n", 163 | "searches\n", 164 | "38436711\n", 165 | "56011095\n", 166 | "3161333\n", 167 | "43841677\n", 168 | "42719934\n", 169 | "40166212\n", 170 | "44847718\n", 171 | "36981443\n", 172 | "13923552\n", 173 | "```\n", 174 | "The code used to calculate the unique valid searches can be found here: TODO" 175 | ] 176 | }, 177 | { 178 | "cell_type": "markdown", 179 | "metadata": {}, 180 | "source": [ 181 | "We will be making using of `pandas`, `psycopg2` and `matplotlib` to use the data we gathered to answer the next set of business questions." 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": 13, 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "import pandas as pd\n", 191 | "import pandas.io.sql as sqlio\n", 192 | "import configparser\n", 193 | "import psycopg2\n", 194 | "\n", 195 | "import matplotlib.pyplot as plt\n", 196 | "plt.style.use('fivethirtyeight')" 197 | ] 198 | }, 199 | { 200 | "cell_type": "markdown", 201 | "metadata": {}, 202 | "source": [ 203 | "### Business question: Produce, for each date, the total number of valid searches that existed on that date.\n", 204 | "To answer this we need to connect to the Redshift cluster and query the `search_stats` table. First, we obtain a connection to Redshift cluster. The secrets are stored in the `dwh-streeteasy.cfg` file. Next, we execute the SQL query and store the result as a pandas dataframe." 205 | ] 206 | }, 207 | { 208 | "cell_type": "code", 209 | "execution_count": 2, 210 | "metadata": {}, 211 | "outputs": [ 212 | { 213 | "name": "stdout", 214 | "output_type": "stream", 215 | "text": [ 216 | "(68, 6)\n" 217 | ] 218 | }, 219 | { 220 | "data": { 221 | "text/html": [ 222 | "
\n", 223 | "\n", 236 | "\n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " \n", 287 | " \n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | "
num_searchesnum_usersnum_rental_searchesnum_sales_searchesnum_rental_and_sales_searchesnum_none_type_searches
day
2018-01-202244871285449183915592284018273
2018-01-212249451287999180515541285218601
2018-01-222255771291679174915487283619095
2018-01-232263061295049177515531284219356
2018-01-242269621298389179515560284819635
\n", 305 | "
" 306 | ], 307 | "text/plain": [ 308 | " num_searches num_users num_rental_searches num_sales_searches \\\n", 309 | "day \n", 310 | "2018-01-20 224487 128544 91839 15592 \n", 311 | "2018-01-21 224945 128799 91805 15541 \n", 312 | "2018-01-22 225577 129167 91749 15487 \n", 313 | "2018-01-23 226306 129504 91775 15531 \n", 314 | "2018-01-24 226962 129838 91795 15560 \n", 315 | "\n", 316 | " num_rental_and_sales_searches num_none_type_searches \n", 317 | "day \n", 318 | "2018-01-20 2840 18273 \n", 319 | "2018-01-21 2852 18601 \n", 320 | "2018-01-22 2836 19095 \n", 321 | "2018-01-23 2842 19356 \n", 322 | "2018-01-24 2848 19635 " 323 | ] 324 | }, 325 | "execution_count": 2, 326 | "metadata": {}, 327 | "output_type": "execute_result" 328 | } 329 | ], 330 | "source": [ 331 | "config = configparser.ConfigParser()\n", 332 | "config.read('dwh-streeteasy.cfg')\n", 333 | "\n", 334 | "# connect to redshift cluster\n", 335 | "conn = psycopg2.connect(\"host={} dbname={} user={} password={} port={}\".format(*config['CLUSTER'].values()))\n", 336 | "cur = conn.cursor()\n", 337 | "\n", 338 | "sql_query = \"SELECT * FROM search_stats\"\n", 339 | "df = sqlio.read_sql_query(sql_query, conn)\n", 340 | "df['day'] = pd.to_datetime(df['day'])\n", 341 | "df = df.set_index('day')\n", 342 | "print(df.shape)\n", 343 | "df.head()" 344 | ] 345 | }, 346 | { 347 | "cell_type": "markdown", 348 | "metadata": {}, 349 | "source": [ 350 | "From this dataframe, for this question, we are interested in finding out the **total number of valid searches** on a given day. This is captured in the `num_searches` column. Shown below is a plot showing the num_searches per day for the entire time-period." 351 | ] 352 | }, 353 | { 354 | "cell_type": "code", 355 | "execution_count": 4, 356 | "metadata": {}, 357 | "outputs": [ 358 | { 359 | "data": { 360 | "image/png": "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\n", 361 | "text/plain": [ 362 | "
" 363 | ] 364 | }, 365 | "metadata": {}, 366 | "output_type": "display_data" 367 | } 368 | ], 369 | "source": [ 370 | "ax = df['num_searches'].plot(figsize=(12, 8), fontsize=12, linewidth=3, linestyle='--')\n", 371 | "ax.set_xlabel('Date', fontsize=16)\n", 372 | "ax.set_ylabel('Valid Searches', fontsize=16)\n", 373 | "ax.set_title('Total number of valid searches on each day')\n", 374 | "ax.axvspan('2018-03-21', '2018-03-24', color='red', alpha=0.3)\n", 375 | "plt.show()" 376 | ] 377 | }, 378 | { 379 | "cell_type": "markdown", 380 | "metadata": {}, 381 | "source": [ 382 | "**Observation**: The red band indicates a sharp drop in the number of valid searches on `2018-03-24`." 383 | ] 384 | }, 385 | { 386 | "cell_type": "markdown", 387 | "metadata": {}, 388 | "source": [ 389 | "### Business Question: Produce, for each date, the total number of valid searches that existed on that date.\n", 390 | "The **total number of users with valid searches per day** is captured in the `num_users` column of the dataframe. A similar trend can be observed for the num_users indicated by the red band." 391 | ] 392 | }, 393 | { 394 | "cell_type": "code", 395 | "execution_count": 5, 396 | "metadata": {}, 397 | "outputs": [ 398 | { 399 | "data": { 400 | "image/png": "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\n", 401 | "text/plain": [ 402 | "
" 403 | ] 404 | }, 405 | "metadata": {}, 406 | "output_type": "display_data" 407 | } 408 | ], 409 | "source": [ 410 | "ax = df['num_users'].plot(figsize=(12, 8), fontsize=12, linewidth=3, linestyle='--')\n", 411 | "ax.set_xlabel('Date', fontsize=16)\n", 412 | "ax.set_ylabel('Number of users', fontsize=16)\n", 413 | "ax.set_title('Total number of users on each day')\n", 414 | "ax.axvspan('2018-03-21', '2018-03-24', color='red', alpha=0.3)\n", 415 | "plt.show()" 416 | ] 417 | }, 418 | { 419 | "cell_type": "markdown", 420 | "metadata": {}, 421 | "source": [ 422 | "### Business question: Most engaging search\n", 423 | "From the data that is available, it appears that `Rental` searches are the most engaging ones. I am assuming that the number of valid searches is a good indicator to guage user engagement. It is evident from the below plot, that Rental Searches are consistently producing more valid searches than `Sale` type searches." 424 | ] 425 | }, 426 | { 427 | "cell_type": "code", 428 | "execution_count": 10, 429 | "metadata": {}, 430 | "outputs": [ 431 | { 432 | "data": { 433 | "image/png": "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jBs3jk2bNnHooYfy0Ucf8cc//pHnnnuOeDzO4sWL+eEPf0h9fT1/+ctfCIfDPPHEExQXFzd7rFmzZjFhwgTeffddjjrqKE499VQuvfRS1qxZA8D111/PxIkTuf7661mzZg0rVqxgzZo1XHjhhVxwwQX86le/Yvny5UyZMoXp06fzk5/8hNNOO42ysjJisRhXXHEFs2bN2uY5r1+/nrPPPpvKykri8Ti33norJSUlvPrqq1x//fXU1dUxePBg7rrrLvLy8rjxxht58cUXiUQijB8/nttvvx0z2+p8Tj75ZC699FJWrFgBwG233UafPn2Ix+NcfPHFja5vdnY2y5cv57LLLmPjxo3k5OTw29/+ln322Yenn36aG2+8kUAgQEFBAS+88ELaf55dTbojQfOB81so+z7wr7bpjoiIiIjsLpYtW8b3vvc93n33XQoLC/n73//eav3Fixdz//338+qrr3LdddeRk5PDG2+8wcEHH8yf/vSnVtuWl5fz/PPPc9FFF/HTn/6U2bNnM3fuXB5++GEuvvjiZL2lS5fy1FNP8eqrr3LjjTcSjUa56qqrGDx4MG+++SbXXnstWVlZPProo8ybN49nn32WX/ziFzi37Se+PPHEE8yYMYM333yTN998k9GjR7Np0yZuvvlmnn76aebNm8e4ceO46667ADj//POZO3cu77zzDrW1tbz44ovNns9PfvITJk+ezFtvvcW8efMYPnx4q9f3kksu4aabbuL111/n2muv5Uc/+hEAN910E08++SRvvfXWNq9nV5fuSNCvgX/4D0q9H/gC6A98DxgPHNE+3RMRERGRbUl3BOesfXOTo0JtYdCgQYwZMwaAsWPHsmrVqlbrT506lfz8fPLz8ykoKODII48EYOTIkSxcuLDVtscee2zy/WuvvcYnn3yS/FxZWUllZSUAhx9+OOFwmHA4TM+ePdmwYcNW+3LOce211/LWW28RCARYt24dGzZsoHfv3q324YADDuCHP/wh0WiUWbNmMWbMGF588UU+/fRTjjjC+zocjUY5+OCDAZg3bx6/+93vqK2tpbS0lBEjRnDUUUdtdT7z5s3jnnvuASAYDFJYWEhZWVmz17eqqor58+dz5plnJtvX19cDMGHCBGbPns2xxx7L0Ucf3eq5dHVpBUHOublmdjLwGyB1QuVq4CTn3Kvt0TkRERER6bzC4XDy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4MCBPP/88/bn7777rsb0qampdk0RQHFxMTk5ObhcLhYtWsTWrVvjOu5P3S4jI4O0tDS7OdqcOXNqTL9p0yZatWrFuHHjGDp0KKtXr6a4uJj09HSSkpJYt24dX3/99WHb9evXj3nz5tk1KgcPHmTLli3s378f0zQZPnw49913HytXrox5XNM02bZtG3379mXy5MkUFRVRWlrKoEGDmDFjhh2khLcvLi4mNzcXwzB48803qw1g+/Xrx9y5czlw4ICdr5oMGjSI1157jdLSUgB27NjB3r172blzJ4mJiYwcOZIJEyZUex4/RU01QVnAtvAHpZQD6AN8eMhocMuwaoKEEEIIIUQ999vf/pbRo0cza9Ys+vbte1SO8eijj3LnnXfSu3dvgsEgvXv35oknnqg2fVZWFj179qRXr16cd955/OEPf+DKK6+kf//+dOnShbZt28Z13CuuuOInb/f000/bAyPU1BwN4K233mL27Nk4nU5yc3O5++67SUpK4uWXX6Z3797k5eXRrVu3w7Zr3749f/rTn7j00ksxTROXy8Xjjz9OQkICt956q10zF6umCKyR3G6++WaKi4vRWjN+/HgyMjKYOHEi99xzD+eccw5aa1q0aMGsWbO44YYbuPbaa5k3bx59+vSJqv2J1KFDB+644w676dtpp53GM888U+35Dxw4kLVr1zJ48GDAqlWaMWMGGzZsYNKkSRiGgcvl4m9/+1uN1zEeqrpqOqXUFuAOrfWc0OduwHJgotZ6akS6AcA8rXXaL85NHVZUVPTT6jNPIOvXrz+smlOIQ8l9IuIl94qIl9wrP19RURHp6enHOxvHRGVlJQkJCcc7G+IEsHXrVpo3bx5zXXp6elS7vJqaw/0XGBeqAQIYDWjgP4ek64Q1SpwQQgghhBBC1Hk1NYd7GPgS2KSUOgB0warxyT8k3TVYTeKEEEIIIYT42e68806WLYsuVo4dOzZmR/mjafXq1dx8881R33k8Hj777LNa2f9nn312WNO0li1bMnPmzFrZf01iXeNx48ZxzTXXHPVj1yXVNocDUEr1BH4HZGI1hXtUa10esb4p8ALwhNb646Oc1+NKmsOJ+k7uExEvuVdEvORe+fmkOZwQh/spzeFqnCxVa72MGmp5tNbbseYMEkIIIYQQQogTQk19goQQQgghhBDipCNBkBBCCCGEEKJekSBICCGEEEIIUa9IECSEEEIIIUQMTz/9NOXl5UdOeIK55ZZbmDdv3vHOxnElQZAQQgghhBAxPPPMM1RUVBzvbPwswWDweGehTqtxdDghhBBCCFH3TfrHb6pdd3Gv0XRvNwCA/679nHe+/Ee1aR8a/Urcx9y8eTOXX345PXv2ZPny5TRu3JjXX3+dESNG8PDDD3PmmWeyf/9++vfvz6pVq5g5cybvv/8+wWCQ/Px8JkyYgM/nY9asWXg8HubMmUNmZmbMYw0bNoxu3brxxRdfUFRUxFNPPUXv3r2prKzk9ttv59tvv8XhcPDII4/Qt29fZs6cyQcffEBFRQUbN27kV7/6FZMnTwZg/vz5TJkyBa/XyymnnML06dNJSUk57JjPPvssu3bt4qKLLiIrK4srrriC/Px8pkyZAsArr7zC2rVrufnmmxkxYgRnnXUW3333HW3atOHZZ58lKSmJb7/9lnvvvZeysjKys7N5+umnadSoUcxzfPbZZ3n55ZdxOBy0b9+el156ibKyMu666y5++OEHAoEAf/zjHxk2bBibN29m3LhxlJWVAfDYY4/Ro0cPvvjiCx599FEaNWrEqlWr+Oqrr3jjjTd46qmnUErRqVMnZsyYAcCSJUuYPn06u3fvZvLkyQwfPhyAadOmMXfuXLxeL7/61a/s/I8ZM4bt27djmiYTJ07ksssui/teqYuqDYKUUjN+wn601vrmIycTQgghhBAni4KCAl544QWmTZvG6NGjeeedd2pMn5+fz6JFi/B6vXTt2pUHHniAL774gnvuuYc33niD8ePHV7ttIBBg/vz5fPzxxzz66KPMmzeP559/HoClS5eybt06LrvsMr7++msAVq1axaJFi/B4PHTr1o2bbrqJxMREHnvsMd5++22Sk5N58sknmT59Onffffdhxxs3bhzTp0/n3XffJTs7m7KyMs455xwmT56My+Vi5syZPPHEE4A159VTTz1Fz549ufXWW3nxxRcZN24cd911F6+//jo5OTm89dZbPPTQQ0yfPj3m+T355JOsXLkSj8dDYWEhAFOnTqVv375Mnz6dwsJCBg0aRP/+/WnQoAFz584lISGBgoICxo4dy4IFCwD45ptvWLp0Ka1atSI/P5+pU6fy0UcfkZ2dzcGDB+3j7d69mw8//JB169YxatQohg8fzvz58ykoKGD+/PlorRk1ahRLlixh3759NGrUiNmzZwPWPFUnuppqgi4EIicITQXSABM4iDWBqgEUh15CCCGEEOI4iLcGp3u7AXatUG1o2bIlp512GgBnnHEGW7ZsqTF9nz59SE1NJTU1lbS0NC644AIAOnbsyOrVq2vc9qKLLjrsOMuWLeOmm24CoG3btjRv3pwff/wRgH79+tkTyrZv356tW7dSVFTE2rVrGTJkCAB+v5/u3bvHda7Jycn07duXDz/8kHbt2uH3++nUqRObN2+mWbNm9OzZE4ArrriC5557jkGDBpGfn88ll1wCgGma5ObmVrv/Tp06ceONNzJs2DCGDRsGWLVWH3zwAU899RQAXq+Xbdu20ahRIyZOnMj333+PYRgUFBTY++natSutWrUCYNGiRQwfPpzs7GyAqJq2YcOGYRgG7du3Z+/evfbx5s+fT58+fQAoKyujoKCA3r17M2nSJO6//36GDBlC796947pmdVm1QZDWull4WSnVC5gFjAfmaK39SikXcAUwJfQuhBBCCCHqEY/HYy87HA4qKipwOp2YpglAZWVltemVUvZnwzCO2IclnNbhcBAIBADQWh8xfeQ2WmsGDBjAiy++GM/pHea6665j6tSptG3blquvvrradEoptNa0b9+eTz75JK59z549myVLlvDBBx/w2GOPsWzZMrTWvPrqq+Tl5UWlnTJlCg0bNmTx4sWHBVfJycn2stYapVTM40Ven/B11Fpz++23M2bMmMPSL1y4kI8//pjJkyczYMCAmLVnJ5J4B0Z4Avir1vp1rbUfQGvt11rPBB4D/n60MiiEEEIIIU4cLVq04NtvvwU46iOQ9e7dmzlz5gDw448/snXr1sMChkjdu3fnq6++YsOGDQCUl5fbNUexpKamUlJSYn/u1q0b27dv51//+hcjRoywv9+2bRvLly8H4N///jc9e/YkLy+Pffv22d/7/X7y8/NjHsc0TbZt20bfvn2ZPHkyRUVFlJaWMmjQIGbMmGEHKStXrgSguLiY3NxcDMPgzTffrDaA7NevH3PnzuXAgQMAUc3hYhk0aBCvvfYapaWlAOzYsYO9e/eyc+dOEhMTGTlyJBMmTLDzcSKLd2CE04FJ1axbC3SpnewIIYQQQogT2W9/+1tGjx7NrFmz6Nu371E91g033MBtt91G7969cTgcPP3001E1HIfKyclh+vTpjB07Fq/XC8Cf/vQn2rRp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\n", 434 | "text/plain": [ 435 | "
" 436 | ] 437 | }, 438 | "metadata": {}, 439 | "output_type": "display_data" 440 | } 441 | ], 442 | "source": [ 443 | "ax = df[['num_rental_searches', \n", 444 | " 'num_sales_searches', \n", 445 | " 'num_rental_and_sales_searches',\n", 446 | " 'num_none_type_searches']].plot(figsize=(12, 8), fontsize=12, linewidth=2, linestyle='--')\n", 447 | "ax.set_xlabel('Date', fontsize=16)\n", 448 | "ax.set_ylabel('Valid Searches', fontsize=16)\n", 449 | "ax.set_title('Types of searches every day')\n", 450 | "ax.legend(fontsize=10)\n", 451 | "plt.show()" 452 | ] 453 | }, 454 | { 455 | "cell_type": "markdown", 456 | "metadata": {}, 457 | "source": [ 458 | "### Business question: What would the email traffic look like if we changed the definition of a valid search from 3 clicks to 2?\n", 459 | "When the defintion of valid search is changed from `clicks >= 3` to `clicks >= 2` the number of searches and its corresponding stats increase in size. Shown below is a comparison for the first 3 days:\n", 460 | "\n", 461 | "![clicks](streeteasy-images/clicks.png)\n", 462 | "\n", 463 | "This means that the **email traffic would increase**." 464 | ] 465 | }, 466 | { 467 | "cell_type": "markdown", 468 | "metadata": {}, 469 | "source": [ 470 | "### Business question: Report any interesting trends over the timespan of the data available.\n", 471 | "Mainly there are two trends observed with this timeseries data:\n", 472 | "- One is that there is a steady increase in the number of searches made and also in the number of users. The stats corresponding to individual search type shows that Rental searches are growing faster than Sales searches. \n", 473 | "- Second interesting thing that I found was a sharp dip in the number of searches and users on 2018-03-23, which could be something interesting to investigate." 474 | ] 475 | }, 476 | { 477 | "cell_type": "markdown", 478 | "metadata": {}, 479 | "source": [ 480 | "## Recommendations\n", 481 | "\n", 482 | "### Recommendations in data storage: \n", 483 | "In terms of storing data, using CSV files comes with some problems down the line. Here are some difficulties with CSV files:\n", 484 | "- No defined schema: There are no data types included and column names beyond a header row.\n", 485 | "- Nested data requires special handling:\n", 486 | "-\n", 487 | "In addition to these issues with using CSV file format, **Spark** has some **specific problems** when working with CSV data:\n", 488 | "- CSV files are quite **slow to import** and parse.\n", 489 | "- The files cannot be shared between workers during the import process.\n", 490 | "- If no schema is defined, then all data must be read before a schema can be inferred.\n", 491 | "- Spark has a feature known as **predicate pushdown** - which is an idea of ordering tasks to do the least amount of work. Example, *filtering* data prior to processing is one of the primary optimizations of predicate pushdown, this drastically reduces the amount of information that must be processed in large data sets. Unfortunately, we cannot filter the CSV data via predicate pushdown.\n", 492 | "- Finally, Spark processes are often multi-step and may utilize an intermediate file representation. These representations allow data to be used later without regenerating the data from source.\n", 493 | "\n", 494 | "Instead of using CSV, when possible use **parquet file format**.\n", 495 | "\n", 496 | "**Parquet Format**: Parquet is a compressed columnar data format and is structured with data accessible in chunks that allows efficient read/write operations without processing the entire file. This structured format supports Spark's predicate pushdown functionality, thus providing significant performance improvement. Finally, parquet files automatically include schema information and handle data encoding. This is perfect for intermediary or on-disk representation of processed data. Note that parquet files are binary file format and can only be used with proper tools.\n", 497 | "\n", 498 | "### Recommendations for downstream processing:\n", 499 | "The search field coming through from the application appear to by `YAML` format. I found that writing regular expression to parse out the search field is prone to errors if the schema evolves. A better way to capture the search field is using JSON or AVRO, as this has some form of schema tied to it, so that downstream applications can know when the schema evolves." 500 | ] 501 | } 502 | ], 503 | "metadata": { 504 | "kernelspec": { 505 | "display_name": "Python 3", 506 | "language": "python", 507 | "name": "python3" 508 | }, 509 | "language_info": { 510 | "codemirror_mode": { 511 | "name": "ipython", 512 | "version": 3 513 | }, 514 | "file_extension": ".py", 515 | "mimetype": "text/x-python", 516 | "name": "python", 517 | "nbconvert_exporter": "python", 518 | "pygments_lexer": "ipython3", 519 | "version": "3.6.6" 520 | } 521 | }, 522 | "nbformat": 4, 523 | "nbformat_minor": 2 524 | } 525 | -------------------------------------------------------------------------------- /Report/Report_Shravan_Kuchkula.md: -------------------------------------------------------------------------------- 1 | 2 | ## ETL data pipeline to process StreetEasy data 3 | ### Author: Shravan Kuchkula (email: shravan.kuchkula@gmail.com) 4 | **Project Description**: 5 | 6 | An online real-estate company is interested in understanding `user enagagement` by analyzing user search patterns to send targeted emails to the users with valid searches. A valid search is termed as one where the search metadata contains `enabled:true` and number of clicks is atleast `3`. 7 | 8 | A daily snapshot of user search history and related data is saved to S3. Each file represents a single date, as noted by the filename: `inferred_users.20180330.csv.gz`. Each line in each file represents a *unique user*, as identified by `id` column. Information on each user's searches and engagement is stored in `searches` column. An example of this is shown below: 9 | 10 | ![rawdata](Report_Shravan_Kuchkula_files/rawdata.png) 11 | 12 | 13 | **Data Description**: The source data resides in S3 `s3://` for each day from **2018-01-20** till **2018-03-30**, as shown: 14 | ```bash 15 | s3:/// 16 | inferred_users.20180120.csv.gz 17 | inferred_users.20180121.csv.gz 18 | inferred_users.20180122.csv.gz 19 | inferred_users.20180123.csv.gz 20 | inferred_users.20180124.csv.gz 21 | .. 22 | inferred_users.20180325.csv.gz 23 | inferred_users.20180326.csv.gz 24 | inferred_users.20180327.csv.gz 25 | inferred_users.20180328.csv.gz 26 | inferred_users.20180329.csv.gz 27 | inferred_users.20180330.csv.gz 28 | ``` 29 | 30 | All this data needs to be processed using a data pipeline to answer the following **business questions:** 31 | 1. Produce a list of **unique "valid searches"**. 32 | 2. Produce, for each date, the **total number of valid searches** that existed on that date. 33 | 3. Produce, for each date, the **total number of users** who had valid searches on that date. 34 | 4. Given this data, determine which is the **most engaging search.** 35 | 5. What would the email traffic look like if the definition of a valid search is changed from **3 clicks to 2 clicks**? 36 | 6. Report any interesting **trends over the timespan** of the data available. 37 | 38 | 39 | **Data Pipeline design**: 40 | The design of the pipeline can be summarized as: 41 | - Extract data from source S3 location. 42 | - Process and Transform it using python and custom **Airflow operators**. 43 | - Load a clean dataset and intermediate artifacts to **destination S3 location**. 44 | - Calculate summary statistics and load the summary stats into **Amazon Redshift**. 45 | 46 | > Figure showns the structure of the data pipeline as represented by a Airflow DAG 47 | ![dag](Report_Shravan_Kuchkula_files/dag.png) 48 | 49 | Finally, I have made use of `Jupyter Notebook` to connect to the `Redshift` cluster and answer the questions of interest. 50 | 51 | **Design Goals**: 52 | As the data is stored in S3, we need a way to incrementally load each file, then process it and store that particular day's results back into S3. Doing so will allow us to perform further analysis later-on, on the cleaned dataset. Secondly, we need a way to aggregate the data and store it in a table to facilitate time-based analysis. Keeping these two goals in mind, the following tools were chosen: 53 | - Apache Airflow will incrementally extract the data from S3 and process it *in-memory* and store the results back into a destination S3 bucket. The reason we need to process this in-memory is because, we don't want to download the file from S3 to airflow worker's disk, as this might fill-up the worker's disk and crash the worker process. 54 | - Amazon Redshift is a simple cloud-managed data warehouse that can be integrated into pipelines without much effort. Airflow will then read the intermediate dataset created in the first step and aggregate the data per day and store it into a Redshift table. 55 | 56 | **Pipeline Implementation**: 57 | Apache Airflow is a Python framework for programmatically creating workflows in DAGs, e.g. ETL processes, generating reports, and retraining models on a daily basis. The Airflow UI automatically parses our DAG and creates a natural representation for the movement and transformation of data. A DAG simply is a collection of all the tasks you want to run, organized in a way that reflects their relationships and dependencies. A **DAG** describes *how* you want to carry out your workflow, and **Operators** determine *what* actually gets done. 58 | 59 | By default, airflow comes with some simple built-in operators like `PythonOperator`, `BashOperator`, `DummyOperator` etc., however, airflow lets you extend the features of a `BaseOperator` and create custom operators. For this project, I developed two custom operators: 60 | 61 | ![operators](Report_Shravan_Kuchkula_files/operators.png) 62 | 63 | - **StreetEasyOperator**: Extract data from **source S3 bucket**, processes the data in-memory by applying a series of transformations found inside `transforms.py`, then loads it to destination S3 bucket. Please see the code here: 64 | - **ValidSearchStatsOperator**: Takes data from **destination S3 bucket**, aggregates the data on a per-day basis, and uploads it to Reshift table `search_stats`. Please see the code here: 65 | 66 | Here's the directory organization: 67 | 68 | ```bash 69 | ├── README.md 70 | ├── docker-compose.yml 71 | └── street-easy 72 | ├── dags 73 | │   ├── create_postgres_table.py 74 | │   └── street_easy.py 75 | ├── plugins 76 | │   ├── __init__.py 77 | │   ├── helpers 78 | │   │   ├── __init__.py 79 | │   │   └── transforms.py 80 | │   └── operators 81 | │   ├── __init__.py 82 | │   ├── extract_and_transform_streeteasy.py 83 | │   └── valid_search_stats.py 84 | └── requirements.txt 85 | ``` 86 | 87 | 88 | **Pipeline Schedule**: Our pipeline is required to adhere to the following guidelines: 89 | * The DAG should run *daily* from `2018-01-20` to `2018-03-30` 90 | * The DAG should not have any dependencies on past runs. 91 | * On failure, the task is retried for 3 times. 92 | * Retries happen every 5 minutes. 93 | * Do not email on retry. 94 | 95 | > Shown below is the data pipeline (street_easy DAG) execution starting on **2018-01-20** and ending on **2018-03-30**. 96 | ![airflow_tree_view](Report_Shravan_Kuchkula_files/airflow_tree_view.png) 97 | > Note: The data for *2018-01-29 and 2018-01-30* is not available, thus we are skipping over that. 98 | 99 | **Destination S3 datasets and Redshift Table**: 100 | After each successful run of the DAG, two files are stored in the destination bucket: 101 | * `s3://skuchkula-etl/unique_valid_searches_.csv`: Contains a list of unique valid searches for each day. 102 | * `s3://skuchkula-etl/valid_searches_.csv`: Contains a dataset with the following fields: 103 | * user_id: Unique id of the user 104 | * num_valid_searches: Number of valid searches 105 | * avg_listings: Avg number of listings for that user 106 | * type_of_search: Did the user search for: 107 | * Only Rental 108 | * Only Sale 109 | * Both Rental and Sale 110 | * Neither 111 | * list_of_valid_searches: A list of valid searches for that user 112 | 113 | 114 | **unique_valid_searches_{date}.csv** contains unique valid searches per day: 115 | ```bash 116 | s3://skuchkula-etl/ 117 | unique_valid_searches_20180120.csv 118 | unique_valid_searches_20180121.csv 119 | unique_valid_searches_20180122.csv 120 | unique_valid_searches_20180123.csv 121 | unique_valid_searches_20180214.csv 122 | ... 123 | ``` 124 | 125 | **valid_searches_{date}.csv** contains the valid searches dataset per day: 126 | ```bash 127 | s3://skuchkula-etl/ 128 | valid_searches_20180120.csv 129 | valid_searches_20180121.csv 130 | valid_searches_20180122.csv 131 | valid_searches_20180123.csv 132 | valid_searches_20180214.csv 133 | ... 134 | ``` 135 | **Amazon Redshift table:** 136 | 137 | The `ValidSearchesStatsOperator` then takes each of datasets `valid_searches_{date}.csv` and calcuates summary stats and loads the results to **search_stats** table, as shown: 138 | 139 | ![redshift](Report_Shravan_Kuchkula_files/redshift.png) 140 | 141 | 142 | ## Answering business questions using data 143 | 144 | ### Business question: Produce a list of all unique "valid searches" given the above requirements. 145 | The list of all unique searches is stored in the destination S3 bucket: `s3://skuchkula-etl/unique_valid_searches_{date}.csv`. An example of the output is shown here 146 | 147 | ```bash 148 | $ head -10 unique_valid_searches_20180330.csv 149 | searches 150 | 38436711 151 | 56011095 152 | 3161333 153 | 43841677 154 | 42719934 155 | 40166212 156 | 44847718 157 | 36981443 158 | 13923552 159 | ``` 160 | The code used to calculate the unique valid searches can be found here: TODO 161 | 162 | We will be making using of `pandas`, `psycopg2` and `matplotlib` to use the data we gathered to answer the next set of business questions. 163 | 164 | 165 | ```python 166 | import pandas as pd 167 | import pandas.io.sql as sqlio 168 | import configparser 169 | import psycopg2 170 | 171 | import matplotlib.pyplot as plt 172 | plt.style.use('fivethirtyeight') 173 | ``` 174 | 175 | ### Business question: Produce, for each date, the total number of valid searches that existed on that date. 176 | To answer this we need to connect to the Redshift cluster and query the `search_stats` table. First, we obtain a connection to Redshift cluster. The secrets are stored in the `dwh-streeteasy.cfg` file. Next, we execute the SQL query and store the result as a pandas dataframe. 177 | 178 | 179 | ```python 180 | config = configparser.ConfigParser() 181 | config.read('dwh-streeteasy.cfg') 182 | 183 | # connect to redshift cluster 184 | conn = psycopg2.connect("host={} dbname={} user={} password={} port={}".format(*config['CLUSTER'].values())) 185 | cur = conn.cursor() 186 | 187 | sql_query = "SELECT * FROM search_stats" 188 | df = sqlio.read_sql_query(sql_query, conn) 189 | df['day'] = pd.to_datetime(df['day']) 190 | df = df.set_index('day') 191 | print(df.shape) 192 | df.head() 193 | ``` 194 | (68, 6) 195 | ``` 196 | ![dataframe](Report_Shravan_Kuchkula_files/dataframe.png) 197 | 198 | From this dataframe, for this question, we are interested in finding out the **total number of valid searches** on a given day. This is captured in the `num_searches` column. Shown below is a plot showing the num_searches per day for the entire time-period. 199 | 200 | 201 | ```python 202 | ax = df['num_searches'].plot(figsize=(12, 8), fontsize=12, linewidth=3, linestyle='--') 203 | ax.set_xlabel('Date', fontsize=16) 204 | ax.set_ylabel('Valid Searches', fontsize=16) 205 | ax.set_title('Total number of valid searches on each day') 206 | ax.axvspan('2018-03-21', '2018-03-24', color='red', alpha=0.3) 207 | plt.show() 208 | ``` 209 | 210 | 211 | ![png](Report_Shravan_Kuchkula_files/Report_Shravan_Kuchkula_8_0.png) 212 | 213 | 214 | **Observation**: The red band indicates a sharp drop in the number of valid searches on `2018-03-24`. 215 | 216 | ### Business Question: Produce, for each date, the total number of valid searches that existed on that date. 217 | The **total number of users with valid searches per day** is captured in the `num_users` column of the dataframe. A similar trend can be observed for the num_users indicated by the red band. 218 | 219 | 220 | ```python 221 | ax = df['num_users'].plot(figsize=(12, 8), fontsize=12, linewidth=3, linestyle='--') 222 | ax.set_xlabel('Date', fontsize=16) 223 | ax.set_ylabel('Number of users', fontsize=16) 224 | ax.set_title('Total number of users on each day') 225 | ax.axvspan('2018-03-21', '2018-03-24', color='red', alpha=0.3) 226 | plt.show() 227 | ``` 228 | 229 | 230 | ![png](Report_Shravan_Kuchkula_files/Report_Shravan_Kuchkula_11_0.png) 231 | 232 | 233 | ### Business question: Most engaging search 234 | From the data that is available, it appears that `Rental` searches are the most engaging ones. I am assuming that the number of valid searches is a good indicator to guage user engagement. It is evident from the below plot, that Rental Searches are consistently producing more valid searches than `Sale` type searches. 235 | 236 | 237 | ```python 238 | ax = df[['num_rental_searches', 239 | 'num_sales_searches', 240 | 'num_rental_and_sales_searches', 241 | 'num_none_type_searches']].plot(figsize=(12, 8), fontsize=12, linewidth=2, linestyle='--') 242 | ax.set_xlabel('Date', fontsize=16) 243 | ax.set_ylabel('Valid Searches', fontsize=16) 244 | ax.set_title('Types of searches every day') 245 | ax.legend(fontsize=10) 246 | plt.show() 247 | ``` 248 | 249 | 250 | ![png](Report_Shravan_Kuchkula_files/Report_Shravan_Kuchkula_13_0.png) 251 | 252 | 253 | ### Business question: What would the email traffic look like if we changed the definition of a valid search from 3 clicks to 2? 254 | When the defintion of valid search is changed from `clicks >= 3` to `clicks >= 2` the number of searches and its corresponding stats increase in size. Shown below is a comparison for the first 3 days: 255 | 256 | ![clicks](Report_Shravan_Kuchkula_files/clicks.png) 257 | 258 | This means that the **email traffic would increase**. 259 | 260 | ### Business question: Report any interesting trends over the timespan of the data available. 261 | Mainly there are two trends observed with this timeseries data: 262 | - One is that there is a steady increase in the number of searches made and also in the number of users. The stats corresponding to individual search type shows that Rental searches are growing faster than Sales searches. 263 | - Second interesting thing that I found was a sharp dip in the number of searches and users on 2018-03-23, which could be something interesting to investigate. 264 | 265 | ## Recommendations 266 | 267 | ### Recommendations in data storage: 268 | In terms of storing data, using CSV files comes with some problems down the line. Here are some difficulties with CSV files: 269 | - No defined schema: There are no data types included and column names beyond a header row. 270 | - Nested data requires special handling: 271 | - 272 | In addition to these issues with using CSV file format, **Spark** has some **specific problems** when working with CSV data: 273 | - CSV files are quite **slow to import** and parse. 274 | - The files cannot be shared between workers during the import process. 275 | - If no schema is defined, then all data must be read before a schema can be inferred. 276 | - Spark has a feature known as **predicate pushdown** - which is an idea of ordering tasks to do the least amount of work. Example, *filtering* data prior to processing is one of the primary optimizations of predicate pushdown, this drastically reduces the amount of information that must be processed in large data sets. Unfortunately, we cannot filter the CSV data via predicate pushdown. 277 | - Finally, Spark processes are often multi-step and may utilize an intermediate file representation. These representations allow data to be used later without regenerating the data from source. 278 | 279 | Instead of using CSV, when possible use **parquet file format**. 280 | 281 | **Parquet Format**: Parquet is a compressed columnar data format and is structured with data accessible in chunks that allows efficient read/write operations without processing the entire file. This structured format supports Spark's predicate pushdown functionality, thus providing significant performance improvement. Finally, parquet files automatically include schema information and handle data encoding. This is perfect for intermediary or on-disk representation of processed data. Note that parquet files are binary file format and can only be used with proper tools. 282 | 283 | ### Recommendations for downstream processing: 284 | The search field coming through from the application appear to by `YAML` format. I found that writing regular expression to parse out the search field is prone to errors if the schema evolves. A better way to capture the search field is using JSON or AVRO, as this has some form of schema tied to it, so that downstream applications can know when the schema evolves. 285 | -------------------------------------------------------------------------------- /Report/Report_Shravan_Kuchkula_files/Report_Shravan_Kuchkula_11_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/Report/Report_Shravan_Kuchkula_files/Report_Shravan_Kuchkula_11_0.png -------------------------------------------------------------------------------- /Report/Report_Shravan_Kuchkula_files/Report_Shravan_Kuchkula_13_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/Report/Report_Shravan_Kuchkula_files/Report_Shravan_Kuchkula_13_0.png -------------------------------------------------------------------------------- /Report/Report_Shravan_Kuchkula_files/Report_Shravan_Kuchkula_8_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/Report/Report_Shravan_Kuchkula_files/Report_Shravan_Kuchkula_8_0.png -------------------------------------------------------------------------------- /Report/Report_Shravan_Kuchkula_files/airflow_tree_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/Report/Report_Shravan_Kuchkula_files/airflow_tree_view.png -------------------------------------------------------------------------------- /Report/Report_Shravan_Kuchkula_files/clicks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/Report/Report_Shravan_Kuchkula_files/clicks.png -------------------------------------------------------------------------------- /Report/Report_Shravan_Kuchkula_files/dag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/Report/Report_Shravan_Kuchkula_files/dag.png -------------------------------------------------------------------------------- /Report/Report_Shravan_Kuchkula_files/dataframe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/Report/Report_Shravan_Kuchkula_files/dataframe.png -------------------------------------------------------------------------------- /Report/Report_Shravan_Kuchkula_files/operators.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/Report/Report_Shravan_Kuchkula_files/operators.png -------------------------------------------------------------------------------- /Report/Report_Shravan_Kuchkula_files/rawdata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/Report/Report_Shravan_Kuchkula_files/rawdata.png -------------------------------------------------------------------------------- /Report/Report_Shravan_Kuchkula_files/redshift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/Report/Report_Shravan_Kuchkula_files/redshift.png -------------------------------------------------------------------------------- /Report/Report_Shravan_Kuchkula_files/validsearches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/Report/Report_Shravan_Kuchkula_files/validsearches.png -------------------------------------------------------------------------------- /Report/dwh-streeteasy.cfg: -------------------------------------------------------------------------------- 1 | [CLUSTER] 2 | HOST=dwhcluster.cpczrz48gy51.us-east-1.redshift.amazonaws.com 3 | DB_NAME=dwh 4 | DB_USER=dwhuser 5 | DB_PASSWORD= 6 | DB_PORT=5439 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | postgres: 4 | image: postgres:9.6 5 | environment: 6 | - POSTGRES_USER=airflow 7 | - POSTGRES_PASSWORD=airflow 8 | - POSTGRES_DB=airflow 9 | ports: 10 | - "5432:5432" 11 | 12 | webserver: 13 | image: puckel/docker-airflow:1.10.4 14 | build: 15 | context: https://github.com/puckel/docker-airflow.git#1.10.4 16 | dockerfile: Dockerfile 17 | args: 18 | AIRFLOW_DEPS: gcp_api,s3 19 | restart: always 20 | depends_on: 21 | - postgres 22 | environment: 23 | - LOAD_EX=n 24 | - EXECUTOR=Local 25 | - FERNET_KEY=jsDPRErfv8Z_eVTnGfF8ywd19j4pyqE3NpdUBA_oRTo= 26 | volumes: 27 | - ./street-easy/dags:/usr/local/airflow/dags 28 | # Uncomment to include custom plugins 29 | - ./street-easy/plugins:/usr/local/airflow/plugins 30 | # Additional python packages used inside airflow operators 31 | - ./street-easy/requirements.txt:/requirements.txt 32 | ports: 33 | - "8080:8080" 34 | command: webserver 35 | healthcheck: 36 | test: ["CMD-SHELL", "[ -f /usr/local/airflow/airflow-webserver.pid ]"] 37 | interval: 30s 38 | timeout: 30s 39 | retries: 3 40 | -------------------------------------------------------------------------------- /images/Report_Shravan_Kuchkula_11_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/images/Report_Shravan_Kuchkula_11_0.png -------------------------------------------------------------------------------- /images/Report_Shravan_Kuchkula_13_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/images/Report_Shravan_Kuchkula_13_0.png -------------------------------------------------------------------------------- /images/Report_Shravan_Kuchkula_8_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/images/Report_Shravan_Kuchkula_8_0.png -------------------------------------------------------------------------------- /images/airflow_tree_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/images/airflow_tree_view.png -------------------------------------------------------------------------------- /images/clicks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/images/clicks.png -------------------------------------------------------------------------------- /images/connections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/images/connections.png -------------------------------------------------------------------------------- /images/dag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/images/dag.png -------------------------------------------------------------------------------- /images/dataframe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/images/dataframe.png -------------------------------------------------------------------------------- /images/operators.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/images/operators.png -------------------------------------------------------------------------------- /images/rawdata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/images/rawdata.png -------------------------------------------------------------------------------- /images/redshift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/images/redshift.png -------------------------------------------------------------------------------- /images/validsearches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/images/validsearches.png -------------------------------------------------------------------------------- /images/variables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/images/variables.png -------------------------------------------------------------------------------- /street-easy/dags/create_postgres_table.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | import os 3 | from airflow import DAG 4 | from airflow.operators.dummy_operator import DummyOperator 5 | from airflow.operators import PostgresOperator 6 | 7 | default_args = { 8 | 'owner': 'shravan', 9 | 'start_date': datetime.utcnow() - timedelta(hours=5), 10 | 'depends_on_past': False, 11 | 'email_on_retry': False, 12 | 'retries': 2, 13 | 'retry_delay': timedelta(minutes=1), 14 | 'catchup_by_default': False, 15 | } 16 | 17 | dag = DAG('create_search_tables_dag', 18 | default_args=default_args, 19 | description='Create tables in Redshift using Airflow', 20 | schedule_interval=None, 21 | max_active_runs=1 22 | ) 23 | 24 | start_operator = DummyOperator(task_id='Begin_execution', dag=dag) 25 | 26 | create_search_stats_table = PostgresOperator( 27 | task_id="create_search_stats_table", 28 | dag=dag, 29 | postgres_conn_id="redshift", 30 | sql=''' 31 | CREATE TABLE IF NOT EXISTS public.search_stats ( 32 | day date, 33 | num_searches int, 34 | num_users int, 35 | num_rental_searches int, 36 | num_sales_searches int, 37 | num_rental_and_sales_searches int, 38 | num_none_type_searches int 39 | ); 40 | ''' 41 | ) 42 | 43 | end_operator = DummyOperator(task_id='Stop_execution', dag=dag) 44 | 45 | # task dependencies 46 | start_operator >> create_search_stats_table 47 | create_search_stats_table >> end_operator 48 | -------------------------------------------------------------------------------- /street-easy/dags/street_easy.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from datetime import datetime, timedelta 4 | 5 | from airflow import DAG 6 | from airflow.models import Variable 7 | from airflow.operators.python_operator import PythonOperator 8 | from airflow.operators.dummy_operator import DummyOperator 9 | from airflow.hooks.S3_hook import S3Hook 10 | from airflow.contrib.hooks.aws_hook import AwsHook 11 | from airflow.operators import (StreetEasyOperator, ValidSearchStatsOperator) 12 | 13 | # Default arguments for DAG: 14 | # start_date : date from when we need to start processing. 15 | # end_data : date until when we need to process the data. 16 | # depends_on_past : this DAG is independent of previous runs. 17 | # email_on_retry : We don't want emails on retry. 18 | # retries : Number of times the task is retries upon failure. 19 | # retry_delay : How much time should the scheduler wait before re-attempt. 20 | # provide_context : When you provide_context=True to an operator, we pass 21 | # along the Airflow context variables to be used inside the operator. 22 | default_args = { 23 | 'owner': 'shravan', 24 | 'start_date': datetime(2018, 1, 20), 25 | 'end_date': datetime(2018, 3, 30), 26 | 'depends_on_past': False, 27 | 'email_on_retry': False, 28 | 'retries': 3, 29 | 'retry_delay': timedelta(minutes=5), 30 | 'provide_context': True, 31 | } 32 | 33 | # Dag will start automatically when turned on 34 | # This is because the start date is in the past 35 | dag = DAG( 36 | 'street_easy', 37 | default_args=default_args, 38 | description='Load and Transform street easy data', 39 | schedule_interval='@daily', 40 | max_active_runs=1 41 | ) 42 | 43 | def check_connectivity_to_s3(*args, **kwargs): 44 | hook = S3Hook(aws_conn_id='aws_credentials') 45 | bucket = Variable.get('s3_bucket') 46 | logging.info(f"Listing Keys from {bucket}") 47 | keys = hook.list_keys(bucket) 48 | for key in keys: 49 | logging.info(f"- s3://{bucket}/{key}") 50 | 51 | start_operator = DummyOperator(task_id='Begin_Execution', dag=dag) 52 | 53 | check_connectivity_to_s3 = PythonOperator( 54 | task_id="check_connectivity_to_s3", 55 | python_callable=check_connectivity_to_s3, 56 | dag=dag 57 | ) 58 | 59 | extract_and_transform_streeteasy_data = StreetEasyOperator( 60 | task_id = "extract_and_transform_streeteasy_data", 61 | dag=dag, 62 | aws_credentials_id = "aws_credentials", 63 | aws_credentials_dest_id = "aws_credentials_dest", 64 | s3_bucket = Variable.get('s3_bucket'), 65 | s3_dest_bucket = Variable.get('s3_dest_bucket'), 66 | s3_key = "inferred_users.{ds}.csv.gz", 67 | s3_dest_key = "unique_valid_searches_{ds}.csv", 68 | s3_dest_df_key = "valid_searches_{ds}.csv", 69 | ) 70 | 71 | calculate_valid_search_stats = ValidSearchStatsOperator( 72 | task_id = "calculate_valid_search_stats", 73 | dag=dag, 74 | aws_credentials_id = "aws_credentials_dest", 75 | redshift_conn_id = "redshift", 76 | table = "search_stats", 77 | columns = """ 78 | day, 79 | num_searches, 80 | num_users, 81 | num_rental_searches, 82 | num_sales_searches, 83 | num_rental_and_sales_searches, 84 | num_none_type_searches 85 | """, 86 | s3_bucket = Variable.get('s3_dest_bucket'), 87 | s3_key = "valid_searches_{ds}.csv", 88 | today = "{ds}", 89 | ) 90 | 91 | end_operator = DummyOperator(task_id='End_Execution', dag=dag) 92 | 93 | # DAG layout 94 | start_operator >> check_connectivity_to_s3 95 | check_connectivity_to_s3 >> extract_and_transform_streeteasy_data 96 | extract_and_transform_streeteasy_data >> calculate_valid_search_stats 97 | calculate_valid_search_stats >> end_operator 98 | -------------------------------------------------------------------------------- /street-easy/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import division, absolute_import, print_function 2 | 3 | from airflow.plugins_manager import AirflowPlugin 4 | 5 | import operators 6 | import helpers 7 | 8 | # Defining the plugin class 9 | class SEPlugin(AirflowPlugin): 10 | name = "se_plugin" 11 | operators = [ 12 | operators.StreetEasyOperator, 13 | operators.ValidSearchStatsOperator 14 | ] 15 | helpers = [ 16 | ] 17 | -------------------------------------------------------------------------------- /street-easy/plugins/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shravan-kuchkula/udacity-data-eng-proj2/a8bb5ae1298695b01581c38395bc9508cb1e3eba/street-easy/plugins/helpers/__init__.py -------------------------------------------------------------------------------- /street-easy/plugins/helpers/transforms.py: -------------------------------------------------------------------------------- 1 | import re 2 | import pandas as pd 3 | import numpy as np 4 | 5 | def valid_searches(searches): 6 | ''' Parses the search string and returns only valid searches. 7 | Additional details: intended to be applied to a pandas series. 8 | 9 | :param searches: raw unparsed search string 10 | :type str 11 | :return valid_searches: list of valid searches 12 | :type list 13 | ''' 14 | # each search is delimited by \\n- 15 | searches = searches.split('\\n-') 16 | 17 | # filter the list 18 | searches = [item for item in searches if not item.startswith('---')] 19 | 20 | # if no searches then return empty list otherwise keep parsing 21 | if len(searches) == 0: 22 | return [] 23 | else: 24 | # parse the searches and make a list of searches 25 | searches = [item for item in searches if not item.startswith('---')] 26 | searches = [re.sub(r'(\\.n|\\.n\s+:|\\)', ' ', item) for item in searches] 27 | searches = [re.sub(r'\s+:', ',', item) for item in searches] 28 | searches = [item.split(',') for item in searches] 29 | 30 | # Determine validity: 31 | # a valid search contains enabled==true and has clicks >= 3 32 | # store only valid searches in the list to return. 33 | valid_searches = [] 34 | for item in searches: 35 | search_dict = {} 36 | for key in item: 37 | if key.split(':')[0] in ('search_id', 'enabled', 'clicks', 38 | 'type', 'listings_sent', 'recommended'): 39 | d_key = key.split(':')[0] 40 | d_value = key.split(':')[1].strip() 41 | search_dict[d_key] = d_value 42 | if search_dict['enabled'] == 'true' and int(search_dict.get('clicks', 0)) >= 3: 43 | valid_searches.append(search_dict) 44 | 45 | return valid_searches 46 | 47 | def avg_listings_sent(valid_searches): 48 | num_listings = 0 49 | listings = 0 50 | for item in valid_searches: 51 | if item.get('listings_sent'): 52 | num_listings = num_listings + 1 53 | listings = listings + int(item.get('listings_sent')) 54 | 55 | if num_listings > 0: 56 | return np.round(np.sum(listings)/num_listings, 2) 57 | else: 58 | return 0 59 | 60 | def type_of_search(valid_searches): 61 | ''' 62 | Categorize the type of search given a list of searches. 63 | 64 | :params valid_searches: list of searches 65 | :return enum('rental_and_sale', 'sale', 'rental', 'none') 66 | ''' 67 | rental = 0 68 | sale = 0 69 | for item in valid_searches: 70 | if item.get('type') == 'Rental': 71 | rental = rental + 1 72 | elif item.get('type') == 'Sale': 73 | sale = sale + 1 74 | else: 75 | pass 76 | 77 | if rental > 0 and sale > 0: 78 | return "rental_and_sale" 79 | elif rental > 0: 80 | return "rental" 81 | elif sale > 0: 82 | return "sale" 83 | else: 84 | return "none" 85 | 86 | def list_of_valid_searches(valid_searches): 87 | ''' 88 | Convert a list of lists to a single list 89 | 90 | :param valid_searches: list of lists 91 | :return search_list: single list of searches. 92 | ''' 93 | search_list = [] 94 | for item in valid_searches: 95 | if item.get('search_id'): 96 | search_list.append(item.get('search_id')) 97 | return search_list 98 | -------------------------------------------------------------------------------- /street-easy/plugins/operators/__init__.py: -------------------------------------------------------------------------------- 1 | from operators.extract_and_transform_streeteasy import StreetEasyOperator 2 | from operators.valid_search_stats import ValidSearchStatsOperator 3 | 4 | __all__ = [ 5 | 'StreetEasyOperator', 6 | 'ValidSearchStatsOperator' 7 | ] 8 | -------------------------------------------------------------------------------- /street-easy/plugins/operators/extract_and_transform_streeteasy.py: -------------------------------------------------------------------------------- 1 | from airflow.contrib.hooks.aws_hook import AwsHook 2 | from airflow.models import BaseOperator 3 | from airflow.utils.decorators import apply_defaults 4 | from helpers.transforms import valid_searches 5 | from helpers.transforms import avg_listings_sent 6 | from helpers.transforms import type_of_search 7 | from helpers.transforms import list_of_valid_searches 8 | 9 | import pandas as pd 10 | import numpy as np 11 | import re 12 | from s3fs.core import S3FileSystem 13 | 14 | class StreetEasyOperator(BaseOperator): 15 | """ 16 | Extract data from source S3, process it in-memory, load it to dest S3. 17 | 18 | :param aws_credentials_id: reference to source aws hook containing iam details. 19 | :type aws_credentials_id: str 20 | :param aws_credentials_dest_id: reference to dest aws hook containing iam details. 21 | :type aws_credentials_id: str 22 | :param s3_bucket: source s3 bucket name 23 | :type s3_bucket: str 24 | :param s3_dest_bucket: destination s3 bucket name 25 | :type s3_dest_bucket: str 26 | :param s3_key: source s3 file (templated) 27 | :type s3_key: Can receive a str representing a prefix, 28 | the prefix can contain a path that is partitioned by some field. 29 | :param s3_dest_key: first destination s3 file (templated) 30 | :type s3_dest_key: Can receive a str representing a prefix, 31 | the prefix can contain a path that is partitioned by some field. 32 | :param s3_dest_df_key: second destination s3 file (templated) 33 | :type s3_dest_df_key: Can receive a str representing a prefix, 34 | the prefix can contain a path that is partitioned by some field. 35 | """ 36 | template_fields = ("s3_key", "s3_dest_key", "s3_dest_df_key",) 37 | 38 | @apply_defaults 39 | def __init__(self, 40 | aws_credentials_id="", 41 | aws_credentials_dest_id="", 42 | s3_bucket="", 43 | s3_dest_bucket="", 44 | s3_key="", 45 | s3_dest_key="", 46 | s3_dest_df_key="", 47 | *args, **kwargs): 48 | 49 | super(StreetEasyOperator, self).__init__(*args, **kwargs) 50 | self.aws_credentials_id = aws_credentials_id 51 | self.aws_credentials_dest_id = aws_credentials_dest_id 52 | self.s3_bucket = s3_bucket 53 | self.s3_dest_bucket = s3_dest_bucket 54 | self.s3_key = s3_key 55 | self.s3_dest_key = s3_dest_key 56 | self.s3_dest_df_key = s3_dest_df_key 57 | 58 | 59 | def execute(self, context): 60 | self.log.info("Executing StreetEasyOperator!!") 61 | 62 | # get the aws hooks 63 | aws_hook = AwsHook(self.aws_credentials_id) 64 | aws_dest_hook = AwsHook(self.aws_credentials_dest_id) 65 | 66 | # get the credentials for source and destination 67 | credentials = aws_hook.get_credentials() 68 | credentials_dest = aws_dest_hook.get_credentials() 69 | 70 | # build the s3 source path 71 | # as we are providing_context = True, we get them in kwargs form 72 | # use **context to upack the dictionary and format the s3_key 73 | rendered_key = self.s3_key.format(**context) 74 | rendered_key_no_dashes = re.sub(r'-', '', rendered_key) 75 | self.log.info("Rendered Key no dashes {}".format(rendered_key_no_dashes)) 76 | s3_path = "s3://{}/{}".format(self.s3_bucket, rendered_key_no_dashes) 77 | 78 | # get a S3 file handle 79 | s3 = S3FileSystem(anon=False, key=credentials.access_key, secret=credentials.secret_key) 80 | 81 | self.log.info("Extract data from {}".format(s3_path)) 82 | # stream data from s3 and transform it 83 | with s3.open(s3_path, mode='rb') as s3_file: 84 | # read in the data from s3 85 | data = pd.read_csv(s3_file, compression='gzip', names=['user_id', 'searches']) 86 | 87 | # create valid searches 88 | data['valid_searches'] = data['searches'].apply(valid_searches) 89 | 90 | # calculate num valid searches per user 91 | data['num_valid_searches'] = data['valid_searches'].apply(len) 92 | 93 | # keep only valid searches 94 | data = data[data.num_valid_searches > 0].reset_index(drop=True) 95 | 96 | # remove original searches 97 | data = data.drop(['searches'], axis=1) 98 | 99 | # calculate avg_listings_sent 100 | data['avg_listings'] = data['valid_searches'].apply(avg_listings_sent) 101 | 102 | # calculate type_of_search 103 | data['type_of_search'] = data['valid_searches'].apply(type_of_search) 104 | 105 | # prepare a list of valid search ids 106 | data['list_of_valid_searches'] = data['valid_searches'].apply(list_of_valid_searches) 107 | 108 | # drop valid searches as we don't need it anymore 109 | data = data.drop(['valid_searches'], axis=1) 110 | 111 | # get unique valid searches 112 | unique_valid_searches = set() 113 | for sublist in data['list_of_valid_searches']: 114 | for item in sublist: 115 | unique_valid_searches.add(re.sub(r'\'', '', item)) 116 | 117 | # construct a dataframe 118 | unique_valid_searches_df = pd.DataFrame({'searches': list(unique_valid_searches)}) 119 | 120 | self.log.info("Total valid searches today are: {}".format(np.sum(data['num_valid_searches']))) 121 | self.log.info("Total users today are: {}".format(np.sum(data['num_valid_searches'] > 0))) 122 | 123 | # build the s3 destination path 124 | rendered_dest_key = self.s3_dest_key.format(**context) 125 | rendered_dest_key_no_dashes = re.sub(r'-', '', rendered_dest_key) 126 | self.log.info("Rendered Key no dashes {}".format(rendered_dest_key_no_dashes)) 127 | s3_dest_path = "s3://{}/{}".format(self.s3_dest_bucket, rendered_dest_key_no_dashes) 128 | 129 | # get a S3 file handle for destination 130 | s3_dest = S3FileSystem(anon=False, key=credentials_dest.access_key, secret=credentials_dest.secret_key) 131 | 132 | # stream the transformed data into s3 133 | with s3_dest.open(s3_dest_path, mode='wb') as s3_dest_file: 134 | self.log.info("Started writing {}".format(unique_valid_searches_df.shape)) 135 | s3_dest_file.write(unique_valid_searches_df.to_csv(None, index=False).encode()) 136 | self.log.info("Completed writing {}".format(unique_valid_searches_df.shape)) 137 | 138 | rendered_dest_df_key = self.s3_dest_df_key.format(**context) 139 | rendered_dest_df_key_no_dashes = re.sub(r'-', '', rendered_dest_df_key) 140 | self.log.info("Rendered Key no dashes {}".format(rendered_dest_df_key_no_dashes)) 141 | s3_dest_df_path = "s3://{}/{}".format(self.s3_dest_bucket, rendered_dest_df_key_no_dashes) 142 | 143 | with s3_dest.open(s3_dest_df_path, mode='wb') as s3_dest_file: 144 | self.log.info("Started writing {}".format(data.shape)) 145 | s3_dest_file.write(data.to_csv(None, index=False).encode()) 146 | self.log.info("Completed writing {}".format(data.shape)) 147 | 148 | self.log.info("StreetEasyOperator completed") 149 | -------------------------------------------------------------------------------- /street-easy/plugins/operators/valid_search_stats.py: -------------------------------------------------------------------------------- 1 | from airflow.hooks.postgres_hook import PostgresHook 2 | from airflow.contrib.hooks.aws_hook import AwsHook 3 | from airflow.models import BaseOperator 4 | from airflow.utils.decorators import apply_defaults 5 | 6 | import pandas as pd 7 | import numpy as np 8 | import re 9 | from s3fs.core import S3FileSystem 10 | 11 | class ValidSearchStatsOperator(BaseOperator): 12 | """ 13 | Takes data from S3, calculates search stats, uploads it to Reshift table. 14 | 15 | :param aws_credentials_id: reference to source aws hook containing iam details. 16 | :type aws_credentials_id: str 17 | :param redshift_conn_id: reference to a specific redshift cluster hook 18 | :type redshift_conn_id: str 19 | :param table: destination table on redshift. 20 | :type table: str 21 | :param columns: columns of the destination table 22 | :type columns: str containing column names in csv format. 23 | :param s3_bucket: source s3 bucket name 24 | :type s3_bucket: str 25 | :param s3_key: source s3 file (templated) 26 | :type s3_key: Can receive a str representing a prefix, 27 | the prefix can contain a path that is partitioned by some field. 28 | :param today: date of execution (templated) 29 | :type today: Can receive a str representing the execution_date. 30 | """ 31 | ui_color = '#80BD9E' 32 | template_fields = ("s3_key", "today", ) 33 | load_search_stats_sql = """ 34 | INSERT INTO {} {} VALUES {} 35 | """ 36 | @apply_defaults 37 | def __init__(self, 38 | # Define your operators params (with defaults) here 39 | aws_credentials_id="", 40 | redshift_conn_id="", 41 | table="", 42 | columns="", 43 | s3_bucket="", 44 | s3_key="", 45 | today="", 46 | *args, **kwargs): 47 | 48 | super(ValidSearchStatsOperator, self).__init__(*args, **kwargs) 49 | # Map params here 50 | self.aws_credentials_id = aws_credentials_id 51 | self.redshift_conn_id = redshift_conn_id 52 | self.table = table 53 | self.columns = columns 54 | self.s3_bucket = s3_bucket 55 | self.s3_key = s3_key 56 | self.today = today 57 | 58 | def execute(self, context): 59 | self.log.info('ValidSearchStatsOperator has started') 60 | # get the hooks 61 | redshift_hook = PostgresHook(self.redshift_conn_id) 62 | aws_hook = AwsHook(self.aws_credentials_id) 63 | 64 | # get the credentials for s3 65 | credentials = aws_hook.get_credentials() 66 | 67 | # put the columns in the format INSERT table expect 68 | columns = "({})".format(self.columns) 69 | 70 | # build the s3 source path 71 | # as we are providing_context = True, we get them in kwargs form 72 | # use **context to upack the dictionary and format the s3_key 73 | rendered_key = self.s3_key.format(**context) 74 | rendered_key_no_dashes = re.sub(r'-', '', rendered_key) 75 | self.log.info("Rendered Key no dashes {}".format(rendered_key_no_dashes)) 76 | s3_path = "s3://{}/{}".format(self.s3_bucket, rendered_key_no_dashes) 77 | 78 | # get a S3 file handle and pass in the creds 79 | s3 = S3FileSystem(anon=False, key=credentials.access_key, secret=credentials.secret_key) 80 | 81 | # stream data from s3 82 | # we don't want to store a local copy of the file on airflow worker's disk 83 | # thus, we are processing this in-memory using with-open-file construct. 84 | with s3.open(s3_path, mode='rb') as s3_file: 85 | # read in the data from s3 86 | data = pd.read_csv(s3_file) 87 | self.log.info("Shape of the data is {}".format(data.shape)) 88 | 89 | # as we are providing_context = True, we get them in kwargs form 90 | # use **context to upack the dictionary and format the today ivar 91 | render_today = self.today.format(**context) 92 | self.log.info("Today is {}".format(render_today)) 93 | 94 | # calculate summary stats 95 | num_valid_searches = np.sum(data['num_valid_searches']) 96 | num_users_with_valid_searches = np.sum(data['num_valid_searches'] > 0) 97 | num_rental_searches = np.sum(data['type_of_search'] == 'rental') 98 | num_sales_searches = np.sum(data['type_of_search'] == 'sale') 99 | num_rental_and_sales_searches = np.sum(data['type_of_search'] == 'rental_and_sale') 100 | num_none_type_searches = np.sum(data['type_of_search'] == 'none') 101 | 102 | # prepare values to be sent to INSERT stmt 103 | values = (render_today, num_valid_searches, num_users_with_valid_searches, 104 | num_rental_searches, num_sales_searches, num_rental_and_sales_searches, 105 | num_none_type_searches) 106 | 107 | self.log.info("Loading stats into Redshift table") 108 | self.log.info("Total valid searches today are: {}".format(np.sum(data['num_valid_searches']))) 109 | self.log.info("Total users today are: {}".format(np.sum(data['num_valid_searches'] > 0))) 110 | self.log.info("Total rental searches today are: {}".format(np.sum(data['type_of_search'] == 'rental'))) 111 | self.log.info("Total sales searches today are: {}".format(np.sum(data['type_of_search'] == 'sale'))) 112 | self.log.info("Total rental and sales searches today are: {}".format(np.sum(data['type_of_search'] == 'rental_and_sale'))) 113 | self.log.info("Total none type searches today are: {}".format(np.sum(data['type_of_search'] == 'none'))) 114 | 115 | # build the insert statement 116 | load_sql = ValidSearchStatsOperator.load_search_stats_sql.format( 117 | self.table, 118 | columns, 119 | values 120 | ) 121 | 122 | # load data into redshift 123 | redshift_hook.run(load_sql) 124 | -------------------------------------------------------------------------------- /street-easy/requirements.txt: -------------------------------------------------------------------------------- 1 | s3fs==0.4.0 2 | --------------------------------------------------------------------------------