-
Notifications
You must be signed in to change notification settings - Fork 0
04. Data Preparation
The data cleaning process is crucial for ensuring our dataset's accuracy and reliability. Below are the key steps we followed in this project:
- Actions: Analyzed the structure and types of data, looking for missing values, duplicates, and inconsistencies.
-
Actions:
- Identified columns with missing values.
- Customer Metrics: Chose to impute missing values with 0 to ensure consistency, especially for features where a lack of data might indicate no applicable information (e.g., new customers).
- Customer Profile: The
Returnscolumn contained 47,382 missing records, representing approximately 19% of the dataset. The rows with missing values in theReturnscolumn were dropped from the dataset to maintain quality. This is becasueReturnsis likely a key feature in understanding customer purchase behavior and patterns, and imputing a significant portion of customer data might have introduced arbitrary assumptions about customer behavior. - Customer Reviews: Remove observations with missing values in
rating. This number is very small – 2, which is merely 0.0014% of all observations. Removal will not impact data analysis significantly. - Products: Removed all rows where 'Price' is missing. There was not enough data to compute the missing prices, and the number of entries with missing prices was insignificant relative to total number of entries.
-
Actions: Remove duplicate entries to avoid skewed analysis.
- Customer Profile: The
Customer AgeandAgecolumns were found to be duplicates. TheCustomer Agecolumn was dropped, retaining theAgecolumn.
- Customer Profile: The
-
Actions:
- Customer Metrics: Change the data type of
CouponUsedfromfloat64toint64to ensure it only accepts whole numbers. - Converted date columns to
datetimeformat. - Customer Reviews: Convert
actual_priceandratingto numeric data types, the float data type. Encodecategoryas a categorical variable, which groups the observations into categories.Order_IDandreview_IDare converted to string data types. - Messages: Converted
is_opened,is_clicked,is_unsubscribed,is_complained,is_blocked,is_purchasedcolumns tobooleanvalues
- Customer Metrics: Change the data type of
-
Actions:
- Customer Profile & Customer Metrics: Remove columns with information already found in other datasets to prevent data inconsistencies (e.g.
OrderCountandProduct Price) - Customer Metrics: Dropped columns lacking sufficient information (
CityTieretc.) or having extremely low correlation with churn to streamline the dataset and focus on the most impactful features for our analysis. - Customer Profile:
Customer Namewas dropped because theCustomer IDalready serves as a unique identifier. - Customer Reviews: We focus on analyzing customer ratings and reviews. We removed many redundant columns that are not relevant to our data analysis, keeping only
Category,actual_price,rating,review_id,review_title, andreview_contentcolumns. - Campaigns:
- Dropped columns with more than 50% of missing values (e.g.,
ab_test,position,is_test,hour_limit) - Dropped columns not useful for analysis (e.g.,
started_at,finished_at,warmup_mode)
- Dropped columns with more than 50% of missing values (e.g.,
- Messages:
- Dropped columns with more than 50% of missing values (e.g.,
purchased_at,blocked_at,complained_at,soft_bounced_at,unsubscribed_at,clicked_first_time_at,clicked_last_time_at,hard_bounced_at,platform,opened_first_time_at,opened_last_time_at) - Dropped columns not useful analysis (e.g.,
id,created_at,updated_at,category,email_provider,is_hard_bounced,is_soft_bounced)
- Dropped columns with more than 50% of missing values (e.g.,
- Customer Profile & Customer Metrics: Remove columns with information already found in other datasets to prevent data inconsistencies (e.g.
- Objective: Confirm the accuracy of the cleaned dataset.
-
Actions:
- Validated the dataset to ensure that no new errors were introduced during the cleaning process.
- Conducted initial analyses to confirm that the data behaved as expected (no duplicate rows etc).
- Assigning Random Product IDs and Prices: Product IDs and Prices are randomly sampled from our Product dataset and aligned to the campaigns.
- Diversity: Random assignment ensures diverse product-campaign pairings, minimizing biases that can occur if the same few products were repeatedly assigned to campaigns. This diversity helps prevent overfitting during model training by ensuring broad product representation.
-
Reproducibility: Using a fixed
random_stateenables reproducibility of the sampling process, ensuring consistent results during testing and evaluation - Industry Alignment: Though this process primarily introduces variation, it aligns with market practices where product offerings vary across campaigns.
- Generating Synthetic Data for Product Costs: To calculate ROI, we generated data on product costs.
-
Process: We generated the
product_costcolumn by applying a random markup reduction within predefined bounds (30%-50%) to the product_price. - Realism: Applying a random markup assumes natural variability in markup percentages, reflecting real-world pricing strategies.
- Research-Backed Markup Range: The 30%-50% markup range is based on typical industry practices for pricing products (Carney, 2023). Aligning these percentages with market standards ensures the synthetic data remains plausible.
-
Generating Synthetic Data for Percentage of Recipients Purchasing a Product: To calculate ROI, we generated the
percentage_purchasedcolumn based on each channel. This feature directly measures campaign success probabilities across channels, providing strategic insights for marketing optimizations.
-
Process: We transformed channel data into a quantitative
percentage_purchasedfeature based on researched conversion rates for different channels. For multichannel campaigns where all three channels (mobile_push, email, sms) are used, we assumed the highest individual channel conversion rate (35% from email) is augmented by a 10% booster effect, resulting in an enhanced conversion rate of 38.5%. This booster effect is grounded in the theoretical synergy of using multiple channels simultaneously. -
Variability: The process involves random selection within researched conversion rate ranges for each channel (except fixed values for
mobile_pushandmultichannel), introducing realistic variability in consumer behavior. This accounts for unpredictable response differences across campaigns, enhancing the dataset's robustness. - Empirical Grounding: Conversion rate ranges such as 25%-35% for email, 11%-20% for sms, or fixed values for channels like mobile_push (28%) and multichannel (38.5%) are derived from credible industry reports and studies. This ensures that the synthetic data respects historical and empirical consumer interaction patterns, making the modeling process not only robust but also reflective of real-world marketing dynamics.
- Calculating Channel Costs: Understanding the financial impact of each marketing channel is crucial in addition to analyzing consumer behavior insights. We engineered features related to the costs associated with different marketing channels, drawing on comprehensive research and pricing models from OneSignal (Onesignal, n.d.) and Twilio (specifically for SMS per recipient cost) (Twilio, n.d.).
-
Channel-Specific Pricing:
- Mobile Push: Calculated per recipient, with an additional a flat fee.
- Email: Pricing is either a flat fee or tiered based on total recipient count.
- SMS: Involves a per recipient cost combined with a base fee.
- Multichannel: Utilizes a combined cost structure that reflects the simultaneous use of email, SMS, and mobile_push.
- Generating Synthetic Prices for Competitor's Pricing:
-
Process: We generated the
Competitor Pricecolumn by applying a random price differential of 30% to the product price. - Reasoning: The difference between the two lowest prices averages 23 per cent when two firms list prices (Baye et al., 2004). To allow for some flexibility and noise, we chose the price difference to differ in a range of 30%.
- Generating Synthetic Data for Stock Levels:
- Process: We generated stock levels by selecting a random integer from 0 to 100 to represent the stock levels.
- Reasoning: This method simulates realistic variability in stock availability, accounting for situations where certain products may be in surplus, low supply, or even temporarily out of stock. Generating these values randomly allows for diversity in data, which can be useful in testing inventory models and algorithms, as well as in understanding demand-supply impacts without requiring real stock data.
- Generating Synthetic Data for Restock Frequency:
- Process: We generated restock frequency be selecting a random frequency from 10, 20, or 30 days.
- Reasoning: Choosing a random restock frequency from a set of 10, 20, or 30 days provides a realistic and flexible simulation for synthetic data. These intervals represent common inventory restocking schedules, capturing the diversity in how often products might be restocked. For example, items with high sales may require more frequent restocks (10 days), while low sales items might be replenished less often (20 or 30 days).
- Generating Synthetic Data for Historical Price:
- Process: We generated old prices (for analysis of Price Elasticity) by applying a random price differential of 20% to the product price.
- Reasoning: This creates variability in historical pricing, enabling a realistic assessment of price elasticity. This range reflects typical adjustments due to seasonal sales, promotions, or market fluctuations, which companies often implement to optimise revenue.
- Generating Synthetic Data for Historical Sales:
- Process: We calculated historical total sales by using a formula that scales the average sales according to the ratio of the average price to each product's historical price. A small random integer adjustment between 0 and 4 was added to introduce minor variability and noise.
- Reasoning: This method creates historical total sales values that reflect realistic variations in response to past price changes. By adjusting sales based on historical prices, we can simulate how price changes may have impacted sales volume.
- Distributing Supplier IDs Based on Product Grouping:
- Process: We assigned Supplier IDs by first creating an initial grouping based on the first word of each product’s name combined with its category. Unique (first word, category) pairs were assigned supplier IDs sequentially. IDs 1-49 were fixed, and the remaining products were assigned new supplier IDs to reach a total of 150, distributed evenly across categories. A minor adjustment was made by incrementally increasing the count for some categories to ensure precisely 150 suppliers.
- Reasoning: This approach ensures that Supplier IDs are logically distributed based on product groupings and categories, allowing for a structured assignment that maintains consistency for the initial 49 suppliers while evenly balancing the remaining suppliers across categories. This method accommodates varying product types and enables flexibility for future additions or adjustments in supplier distribution.
- Assigning random IDs for CustomerID, ProductID:
- Process: CustomerID and ProductID are randomly sampled from the Customer and Products dataset respectively.
- Reasoning: Each order entry reflects a plausible combination of customers and products, simulating a natural purchasing pattern. This technique also retains the diversity of the customer and product base.
- Generating Synthetic Data for Returns:
- Process: We generated Returns data by randomly selecting 0 or 1 for each order, with a probability distribution of 90% and 10% respectively. 1 indicates the items was returned, and 0 indicates otherwise.
- Reasoning: This reflects typical return rates found in e-commerce. Most orders are successfully fulfilled without returns, with only a small fraction involving returns, often around 10% depending on industry and product type (Hjort, 2010). This distribution reflects real-world customer behaviour.
- Generating Synthetic Data for Promotional Periods:
- Process: We generated Promotional data by randomly selecting 0 or 1 for each order. 1 indicates the order was placed during a promotional period, and 0 indicates otherwise.
- Reasoning: This method reflects how promotions often only apply to a subset of orders, helping to capture the influence of promotions on customer buying behaviour.
To ensure consistency across our entire database, which comprises datasets sourced from various origins, we decided to generate synthetic data to standardize key attributes, like the number of customers. This standardization process aims to align the datasets and facilitate comprehensive analysis. Additionally, we hope to prevent the issue of small dataset sizes and ensure that our analyses are robust and reliable.
Customer Dataset is created by merging the synthetic datasets modelled from Customer Profile and Customer Metric table, resulting in a comprehensive dataset where:
-
Age,Gender, andPayment Methodare augmented from Customer Profile Dataset -
Churn,Tenure,NumberOfDeviceRegistered,HourSpendOnApp,SatisfactionScore,CouponUsed, andCashbackAmountare augmented from Customer Metric Dataset
Category Dropped
- The
Bookscategory was removed from the dataset to align it with other datasets that do not contain this category. This ensures greater consistency across different datasets being used in the analysis.
Stratified Sampling
- The dataset is grouped by
GenderandPayment Method, which are categorical variables. - A specified sample size (
n_samples = 100001) is selected, proportionally sampling from each group to maintain the original distribution of these categorical variables.
Age Resampling with KDE
- The
resample_agefunction uses Kernel Density Estimation (KDE) to fit the distribution of ages within each group. - KDE with a Gaussian kernel is applied to the "Age" column, with a bandwidth of 1.0.
- After fitting KDE to the age data in each group, the function generates new age values for the sampled data, maintaining the original age distribution.
Columns dropped:
- Remove columns with information that can be found in separate datasets (e.g.
DaySinceLastOrder) as retaining it could lead to data inconsistencies and complicate future analysis. - Dropped columns we did not have sufficient information on columns like
CityTierandWarehouseToHome, which could compromise our analysis.
Synthetic Generation:
- CTGAN from SDV package was chosen as the technique for generating synthetic data from Customer Metric Dataset since it is believed that relationships between its features is complicated
Category Dropped:
- Only Electronics and electronic-related products, such as Computers & Accessories are retained in the dataset. Remove observations under other categories that are not central to our project.
Order_ID:
- This dataset has a primary key called
Order_IDto establish its relationship with Orders Table, because product IDs and any other identifiers follow very different formats and are redundant to be considered. Order IDs are sourced from Orders Table and randomly assigned without replacement
Miscellaneous:
- Renaming of columns and adjusting
actual_priceto reflect the price of products in USD.
Since real A/B test data is not available, we generate synthetic data for both a control group (customers who did not receive the email) and a treatment group (customers who received the email).
To simulate a realistic A/B test on customers of the ecommerce platform, we:
- Used actual churn, emails opened and purchase rates from our current dataset to ensure the synthetic data reflects actual customer behavior.
- Introduce random perturbations to the treatment group's churn and purchase rates to represent natural variability in how customers interact with the email campaign.
Price
- Conversion of Prices from Rupees to USD for a more universal interpretation
- Created a new column 'log price' which is the logarithmic transformation of price. This was done due to the right-skewed distribution of prices.
Category Dropped
- Only products under Home Appliances, Electronics and Clothing are kept to be consistent with the product categories across our datasets.
Category Dropped
- Only kept relevant columns needed for analysis (Order and Shipping Dates, Actual and Scheduled Days for Shipping, Late Delivery Risk, Discount)
There was no SQL queries use for data transformation. SQL query was only used to create the Messages.db and reading the whole dataset from Messages.db. The code to create the Messages.db, which only contain the messages dataset table, is here.
The dataset consists of multiple tables, each designed to represent different aspects of customer behavior and demographic information. The tables are linked by a unique identifier, CustomerID, to allow for efficient data merging and analysis.
-
Customer Dataset
- Description: Contains information about customers.
- Row Count: 100,000 rows
| Column Name | Data Type | Description |
|---|---|---|
CustomerID |
int64 | Primary Key. |
Age |
int64 | Age of the customer. |
Gender |
object | Gender of the customer. |
Payment Method |
object | The method of payment used by the customer (e.g., credit card, PayPal). |
Churn |
int64 | Churn Flag (binary). |
Tenure |
float64 | Tenure of customer in organization. |
Complain |
int64 | Any complaints raised in the last month |
NumberOfDeviceRegistered |
int64 | Total devices registered by the customer. |
HourSpendOnApp |
float64 | Average hours spent on the app weekly. |
SatisfactionScore |
int64 | Satisfaction score of customer on service. |
CouponUsed |
float64 | Total number of coupons used in the last month. |
CashbackAmount |
float64 | Average cashback in the last month. |
-
Campaigns Dataset
- Description: Contains detailed information about various marketing campaigns, their attributes, and associated product information post-cleaning.
- Row Count: 89 rows
-
Key Column:
campaign_type+id
| Column Name | Data Type | Description |
|---|---|---|
id |
int64 | Campaign ID, unique within each campaign_type. |
campaign_type |
object | Specifies the type of campaign (e.g., 'bulk', 'trigger', 'transactional'). |
channel |
object | The medium used for the campaign, such as 'email', 'mobile_push', 'web_push', or 'sms'. |
topic |
object | Represents the meaning of the campaign. |
total_count |
float64 | Total recipients in the campaign. |
subject_length |
float64 | The length of the email subject line. |
subject_with_personalization |
object | Indicates if the subject contains the recipient's name. |
subject_with_deadline |
object | Denotes if the subject includes a deadline. |
subject_with_emoji |
object | Indicates if the subject includes emoji symbols. |
subject_with_bonuses |
object | Specifies if the subject mentions bonuses for actions. |
subject_with_discount |
object | Indicates if the subject mentions a discount. |
subject_with_saleout |
object | Denotes if the subject mentions a sale out. |
product_id |
int64 | Identifier for the product associated with the campaign. |
product_price |
float64 | The selling price of the product. |
product_cost |
float64 | The cost incurred for the product. |
percentage_purchased |
float64 | The estimated percentage of recipients who made a purchase. |
-
Messages Dataset
- Description: Contains information about messages sent in marketing campaigns, including user interactions and campaign attributes.
- Row Count: 20,583,769 entries
-
Key Column:
message_id
| Column Name | Data Type | Description |
|---|---|---|
message_id |
object | Unique identifier for each message. |
campaign_id |
int64 | Identifier for each campaign. |
message_type |
object | Campaign type. |
client_id |
int64 | Identifier for the client receiving the message. |
channel |
object | Message channel (email, web_push, mobile_push, sms). |
stream |
object | Additional identifier of data source. |
date |
object | Date of the message event. |
sent_at |
object | Specific timestamp when the message was sent. |
is_opened |
int64 | Binary flag (0 or 1) indicating if the message was opened. |
is_clicked |
int64 | Binary flag (0 or 1) indicating if a link in the message was clicked. |
is_unsubscribed |
int64 | Binary flag (0 or 1) indicating if the recipient unsubscribed through the message. |
is_complained |
int64 | Binary flag (0 or 1) indicating if the recipient complained. |
is_blocked |
int64 | Binary flag (0 or 1) indicating if the message was blocked from being delivered. |
is_purchased |
int64 | Binary flag (0 or 1) indicating if a purchase was made following the message. |
-
Products Table
- Description: Contains information about each product.
- Row Count: 20,642 rows
| Column Name | Data Type | Description |
|---|---|---|
ProductID |
int64 | Unique identifier for each product. Primary Key. |
Supplier_ID |
int64 | Unique identifier for the supplier of each product. |
Product Name |
object | Full Name of the Product |
Category |
object | Product Category (Electronics, Home Appliances or Clothing) |
Price |
float64 | Sale price of the product. |
-
Orders Table
- Description: Contains information about every order made.
- Row Count: 168,946 rows
| Column Name | Data Type | Description |
|---|---|---|
OrderID |
int64 | Unique identifier for each order. Primary Key. |
ProductID |
int64 | Identifier for the product purchased during this order. Foreign Key linking to the products table. |
CustomerID |
int64 | Identifier for the customer who placed the order. Foreign Key linking to the customers table. |
Order Date |
object | Date when the order was placed. |
Shipping Date |
object | Date when the product was shipped. |
Actual days for shipping |
int64 | Actual number of days taken to ship the product to the customer. |
Scheduled days for shipping |
int64 | Number of days scheduled for shipping as initially planned. |
Late Delivery Risk |
int64 | Indicator (0 or 1) showing if the product was at risk of late delivery |
Discount Percentage |
float64 | Discount applied on the product during the order, represented as a percentage. |
Returns |
int64 | Indicator (0 or 1) showing if the product was returned. |
Promotional Period |
int64 | Indicator (0 or 1) showing if the product was purchased during a promotional period. |
- Customer Reviews Table
- Description: Contains customers' reviews and ratings of their product bought.
- Row count: 979 rows
| Column Name | Data Type | Description |
|---|---|---|
category |
category | Category the product belongs to |
actual_price (USD) |
float | Price of the product in USD |
rating |
float | Rating of the product on Amazon’s Website, on a scale from 0 to 5 |
review_id |
string | Review ID of consumer who purchased the product gave |
review_title |
object | Summary of the review |
review_content |
object | Content of the review given by the consumer |
Order_ID |
string | Order ID of the product sent to customer |
- A/B Test Data
- Tables:
campaign_treatmentcampaign_control
- Description: These tables contain A/B test results, with data on customer interactions with marketing campaigns. Each table includes information on whether the customer opened the campaign, made a purchase, and subsequently churned.
- Row count: Each table has 5,000 rows.
| Column Name | Type | Description |
|---|---|---|
customer_id |
int64 | Unique identifier for each customer |
group |
object | Campaign group designation (treatment or control) |
is_opened |
int64 | Indicates if the customer opened the campaign (1 for opened, 0 for not opened) |
is_purchased |
int64 | Indicates if the customer made a purchase after opening the campaign (1 for purchased, 0 for not purchased) |
churned |
int64 | Specifies if the customer churned after the campaign (1 for churned, 0 for retained) |