├── ch.1 ├── R │ └── SettingUpREnvironment.R └── python │ └── Setting Up Python Environment.ipynb ├── ch.10 ├── R │ └── CustomerSegmentation.R └── python │ └── CustomerSegmentation.ipynb ├── ch.11 ├── R │ └── CustomerRetention.R └── python │ └── CustomerRetention.ipynb ├── ch.12 ├── R │ └── ABTesting.R └── python │ └── ABTesting.ipynb ├── ch.2 ├── R │ └── ConversionRate.R └── python │ └── ConversionRate.ipynb ├── ch.3 ├── R │ └── RegressionAnalysis.R └── python │ └── RegressionAnalysis.ipynb ├── ch.4 ├── R │ └── FromEngagementToConversions.R └── python │ └── From Engagement to Conversions.ipynb ├── ch.5 ├── R │ └── ProductAnalytics.R └── python │ └── Product Analytics.ipynb ├── ch.6 ├── R │ └── ProductRecommendation.R └── python │ └── ProductRecommendation.ipynb ├── ch.7 ├── R │ └── CustomerBehaviors.R └── python │ └── CustomerBehaviors.ipynb ├── ch.8 ├── R │ └── PredictingEngagement.R └── python │ └── PredictingEngagement.ipynb └── ch.9 ├── R └── CustomerLifetimeValue.R └── python └── CustomerLifetimeValue.ipynb /ch.1/R/SettingUpREnvironment.R: -------------------------------------------------------------------------------- 1 | # Test Data 2 | data <- data.frame( 3 | "X"=c(0, 0.25, 0.5, 1), 4 | "Y"=c(0, 0.5, 0.5, 1), 5 | "output"=c(0, 0, 1, 1) 6 | ) 7 | 8 | # Train Logistic Regression 9 | logit.fit <- glm( 10 | output ~ X + Y, 11 | data = data, 12 | family = binomial 13 | ) 14 | 15 | # Show Fitted Results 16 | summary(logit.fit) 17 | 18 | # Predict Class Probabilities 19 | logit.probs <- predict( 20 | logit.fit, 21 | newdata=data, 22 | type="response" 23 | ) 24 | 25 | # Predict Classes 26 | logit.pred <- ifelse(logit.probs > 0.5, 1, 0) 27 | logit.pred 28 | 29 | 30 | # Plotting Library 31 | library(ggplot2) 32 | 33 | # Simple Scatterplot 34 | ggplot(data, aes(x=X, y=Y, color=output)) + 35 | geom_point(size=3, shape=19) + 36 | ggtitle('Actual') + 37 | theme(plot.title = element_text(hjust = 0.5)) 38 | 39 | ggplot(data, aes(x=X, y=Y, color=logit.pred)) + 40 | geom_point(size=3, shape=19) + 41 | ggtitle('Predicted') + 42 | theme(plot.title = element_text(hjust = 0.5)) 43 | -------------------------------------------------------------------------------- /ch.1/python/Setting Up Python Environment.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "from sklearn.linear_model import LogisticRegression" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 2, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "input_data = np.array([\n", 20 | " [0, 0],\n", 21 | " [0.25, 0.25],\n", 22 | " [0.5, 0.5],\n", 23 | " [1, 1],\n", 24 | "])" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 3, 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "output_data = [\n", 34 | " 0,\n", 35 | " 0,\n", 36 | " 1,\n", 37 | " 1\n", 38 | "]" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": 4, 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [ 47 | "logit_model = LogisticRegression()" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 5, 53 | "metadata": {}, 54 | "outputs": [ 55 | { 56 | "data": { 57 | "text/plain": [ 58 | "LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,\n", 59 | " intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,\n", 60 | " penalty='l2', random_state=None, solver='liblinear', tol=0.0001,\n", 61 | " verbose=0, warm_start=False)" 62 | ] 63 | }, 64 | "execution_count": 5, 65 | "metadata": {}, 66 | "output_type": "execute_result" 67 | } 68 | ], 69 | "source": [ 70 | "logit_model.fit(input_data, output_data)" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 6, 76 | "metadata": {}, 77 | "outputs": [ 78 | { 79 | "data": { 80 | "text/plain": [ 81 | "array([[0.43001235, 0.43001235]])" 82 | ] 83 | }, 84 | "execution_count": 6, 85 | "metadata": {}, 86 | "output_type": "execute_result" 87 | } 88 | ], 89 | "source": [ 90 | "logit_model.coef_" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": 7, 96 | "metadata": {}, 97 | "outputs": [ 98 | { 99 | "data": { 100 | "text/plain": [ 101 | "array([-0.18498028])" 102 | ] 103 | }, 104 | "execution_count": 7, 105 | "metadata": {}, 106 | "output_type": "execute_result" 107 | } 108 | ], 109 | "source": [ 110 | "logit_model.intercept_" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": 8, 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "predicted_output = logit_model.predict(input_data)" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": 9, 125 | "metadata": {}, 126 | "outputs": [ 127 | { 128 | "data": { 129 | "text/plain": [ 130 | "array([0, 1, 1, 1])" 131 | ] 132 | }, 133 | "execution_count": 9, 134 | "metadata": {}, 135 | "output_type": "execute_result" 136 | } 137 | ], 138 | "source": [ 139 | "predicted_output" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": 10, 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [ 148 | "%matplotlib inline" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": 11, 154 | "metadata": {}, 155 | "outputs": [], 156 | "source": [ 157 | "import matplotlib.pyplot as plt" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": 12, 163 | "metadata": {}, 164 | "outputs": [ 165 | { 166 | "data": { 167 | "image/png": "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\n", 168 | "text/plain": [ 169 | "
" 170 | ] 171 | }, 172 | "metadata": {}, 173 | "output_type": "display_data" 174 | } 175 | ], 176 | "source": [ 177 | "plt.scatter(\n", 178 | " x=input_data[:,0], \n", 179 | " y=input_data[:,1], \n", 180 | " color=[('red' if x == 1 else 'blue') for x in output_data]\n", 181 | ")\n", 182 | "plt.xlabel('X')\n", 183 | "plt.ylabel('Y')\n", 184 | "plt.title('Actual')\n", 185 | "plt.grid()\n", 186 | "plt.show()" 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": 13, 192 | "metadata": {}, 193 | "outputs": [ 194 | { 195 | "data": { 196 | "image/png": "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\n", 197 | "text/plain": [ 198 | "
" 199 | ] 200 | }, 201 | "metadata": {}, 202 | "output_type": "display_data" 203 | } 204 | ], 205 | "source": [ 206 | "plt.scatter(\n", 207 | " x=input_data[:,0], \n", 208 | " y=input_data[:,1], \n", 209 | " color=[('red' if x == 1 else 'blue') for x in predicted_output]\n", 210 | ")\n", 211 | "plt.xlabel('X')\n", 212 | "plt.ylabel('Y')\n", 213 | "plt.title('Predicted')\n", 214 | "plt.grid()\n", 215 | "plt.show()" 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": null, 221 | "metadata": {}, 222 | "outputs": [], 223 | "source": [] 224 | } 225 | ], 226 | "metadata": { 227 | "kernelspec": { 228 | "display_name": "Python 3", 229 | "language": "python", 230 | "name": "python3" 231 | }, 232 | "language_info": { 233 | "codemirror_mode": { 234 | "name": "ipython", 235 | "version": 3 236 | }, 237 | "file_extension": ".py", 238 | "mimetype": "text/x-python", 239 | "name": "python", 240 | "nbconvert_exporter": "python", 241 | "pygments_lexer": "ipython3", 242 | "version": "3.6.5" 243 | } 244 | }, 245 | "nbformat": 4, 246 | "nbformat_minor": 2 247 | } 248 | -------------------------------------------------------------------------------- /ch.10/R/CustomerSegmentation.R: -------------------------------------------------------------------------------- 1 | library(dplyr) 2 | library(readxl) 3 | library(ggplot2) 4 | #### 1. Load Data #### 5 | df <- read_excel( 6 | path="~/Documents/data-science-for-marketing/ch.10/data/Online Retail.xlsx", 7 | sheet="Online Retail" 8 | ) 9 | 10 | #### 2. Date Clean-Up #### 11 | 12 | # ignore negative quantity 13 | dim(df) 14 | df <- df[which(df$Quantity > 0),] 15 | dim(df) 16 | 17 | # remove records with NA 18 | df <- na.omit(df) 19 | dim(df) 20 | 21 | # excluding incomplete month 22 | sprintf("Date Range: %s ~ %s", min(df$InvoiceDate), max(df$InvoiceDate)) 23 | dim(df) 24 | df <- df[which(df$InvoiceDate < '2011-12-01'),] 25 | dim(df) 26 | 27 | # total sales 28 | df$Sales <- df$Quantity * df$UnitPrice 29 | 30 | # per customer data 31 | customerDF <- df %>% 32 | group_by(CustomerID) %>% 33 | summarize(TotalSales=sum(Sales), OrderCount=length(unique(InvoiceDate))) %>% 34 | mutate(AvgOrderValue=TotalSales/OrderCount) 35 | 36 | rankDF <- customerDF %>% 37 | mutate(TotalSales=rank(TotalSales), OrderCount=rank(OrderCount, ties.method="first"), AvgOrderValue=rank(AvgOrderValue)) 38 | 39 | normalizedDF <- rankDF %>% 40 | mutate(TotalSales=scale(TotalSales), OrderCount=scale(OrderCount), AvgOrderValue=scale(AvgOrderValue)) 41 | 42 | # check for normalization - mean of 0 & std of 1 43 | summary(normalizedDF) 44 | sapply(normalizedDF, sd) 45 | 46 | #### 3. Customer Segmentation via K-Means Clustering #### 47 | 48 | cluster <- kmeans(normalizedDF[c("TotalSales", "OrderCount", "AvgOrderValue")], 4) 49 | 50 | # cluster centers 51 | cluster$centers 52 | # cluster labels 53 | normalizedDF$Cluster <- cluster$cluster 54 | 55 | normalizedDF %>% group_by(Cluster) %>% summarise(Count=n()) 56 | 57 | ggplot(normalizedDF, aes(x=AvgOrderValue, y=OrderCount, color=Cluster)) + 58 | geom_point() 59 | 60 | ggplot(normalizedDF, aes(x=TotalSales, y=OrderCount, color=Cluster)) + 61 | geom_point() 62 | 63 | ggplot(normalizedDF, aes(x=TotalSales, y=AvgOrderValue, color=Cluster)) + 64 | geom_point() 65 | 66 | 67 | # Selecting the best number of cluster 68 | library(cluster) 69 | 70 | for(n_cluster in 4:8){ 71 | cluster <- kmeans(normalizedDF[c("TotalSales", "OrderCount", "AvgOrderValue")], n_cluster) 72 | 73 | silhouetteScore <- mean( 74 | silhouette( 75 | cluster$cluster, 76 | dist(normalizedDF[c("TotalSales", "OrderCount", "AvgOrderValue")], method = "euclidean") 77 | )[,3] 78 | ) 79 | print(sprintf('Silhouette Score for %i Clusters: %0.4f', n_cluster, silhouetteScore)) 80 | } 81 | 82 | # Interpreting customer segments 83 | cluster <- kmeans(normalizedDF[c("TotalSales", "OrderCount", "AvgOrderValue")], 4) 84 | normalizedDF$Cluster <- cluster$cluster 85 | # count per cluster 86 | normalizedDF %>% group_by(Cluster) %>% summarise(Count=n()) 87 | # cluster centers 88 | cluster$centers 89 | 90 | # High value cluster summary 91 | summary(customerDF[which(normalizedDF$Cluster == 4),]) 92 | 93 | highValueCustomers <- unlist( 94 | customerDF[which(normalizedDF$Cluster == 4),'CustomerID'][,1], use.names = FALSE 95 | ) 96 | 97 | df[which(df$CustomerID %in% highValueCustomers),] %>% 98 | group_by(Description) %>% 99 | summarise(Count=n()) %>% 100 | arrange(desc(Count)) 101 | 102 | 103 | -------------------------------------------------------------------------------- /ch.11/R/CustomerRetention.R: -------------------------------------------------------------------------------- 1 | library(dplyr) 2 | library(tidyr) 3 | library(readxl) 4 | 5 | #### 1. Load Data #### 6 | df <- read_excel( 7 | path="~/Documents/data-science-for-marketing/ch.11/data/WA_Fn-UseC_-Telco-Customer-Churn.xlsx" 8 | ) 9 | 10 | #### 2. Date Analysis & Preparation #### 11 | df <- df %>% drop_na() 12 | 13 | apply(df, 2, function(x) length(unique(x))) 14 | 15 | ggplot(df %>% group_by(gender) %>% summarise(Count=n()), aes(x=gender, y=Count)) + 16 | geom_bar(width=0.5, stat="identity") + 17 | ggtitle('') + 18 | xlab("Gender") + 19 | ylab("Count") + 20 | theme(plot.title = element_text(hjust = 0.5)) 21 | 22 | ggplot(df %>% group_by(InternetService) %>% summarise(Count=n()), aes(x=InternetService, y=Count)) + 23 | geom_bar(width=0.5, stat="identity") + 24 | ggtitle('') + 25 | xlab("Internet Service") + 26 | ylab("Count") + 27 | theme(plot.title = element_text(hjust = 0.5)) 28 | 29 | ggplot(df %>% group_by(PaymentMethod) %>% summarise(Count=n()), aes(x=PaymentMethod, y=Count)) + 30 | geom_bar(width=0.5, stat="identity") + 31 | ggtitle('') + 32 | xlab("Payment Method") + 33 | ylab("Count") + 34 | theme(plot.title = element_text(hjust = 0.5)) 35 | 36 | # Binary & Continuous Vars 37 | sampleDF <- df %>% 38 | select(tenure, MonthlyCharges, TotalCharges, gender, Partner, Dependents, PhoneService, PaperlessBilling, Churn) %>% 39 | mutate( 40 | # transforming continuous vars 41 | tenure=(tenure - mean(tenure))/sd(tenure), 42 | MonthlyCharges=(log(MonthlyCharges) - mean(log(MonthlyCharges)))/sd(log(MonthlyCharges)), 43 | TotalCharges=(log(TotalCharges) - mean(log(TotalCharges)))/sd(log(TotalCharges)), 44 | # encoding binary categorical vars 45 | gender=gender %>% as.factor() %>% as.numeric() - 1, 46 | Partner=Partner %>% as.factor() %>% as.numeric() - 1, 47 | Dependents=Dependents %>% as.factor() %>% as.numeric() - 1, 48 | PhoneService=PhoneService %>% as.factor() %>% as.numeric() - 1, 49 | PaperlessBilling=PaperlessBilling %>% as.factor() %>% as.numeric() - 1, 50 | Churn=Churn %>% as.factor() %>% as.numeric() - 1 51 | ) 52 | 53 | summary(df[,c("tenure", "MonthlyCharges", "TotalCharges")]) 54 | apply(df[,c("tenure", "MonthlyCharges", "TotalCharges")], 2, sd) 55 | 56 | summary(sampleDF[,c("tenure", "MonthlyCharges", "TotalCharges")]) 57 | apply(sampleDF[,c("tenure", "MonthlyCharges", "TotalCharges")], 2, sd) 58 | 59 | # Dummy vars 60 | # install.packages('dummies') 61 | library(dummies) 62 | 63 | sampleDF <- cbind(sampleDF, dummy(df$MultipleLines, sep=".")) 64 | names(sampleDF) = gsub("sampleDF", "MultipleLines", names(sampleDF)) 65 | 66 | sampleDF <- cbind(sampleDF, dummy(df$InternetService, sep=".")) 67 | names(sampleDF) = gsub("sampleDF", "InternetService", names(sampleDF)) 68 | 69 | sampleDF <- cbind(sampleDF, dummy(df$OnlineSecurity, sep=".")) 70 | names(sampleDF) = gsub("sampleDF", "OnlineSecurity", names(sampleDF)) 71 | 72 | sampleDF <- cbind(sampleDF, dummy(df$OnlineBackup, sep=".")) 73 | names(sampleDF) = gsub("sampleDF", "OnlineBackup", names(sampleDF)) 74 | 75 | sampleDF <- cbind(sampleDF, dummy(df$DeviceProtection, sep=".")) 76 | names(sampleDF) = gsub("sampleDF", "DeviceProtection", names(sampleDF)) 77 | 78 | sampleDF <- cbind(sampleDF, dummy(df$TechSupport, sep=".")) 79 | names(sampleDF) = gsub("sampleDF", "TechSupport", names(sampleDF)) 80 | 81 | sampleDF <- cbind(sampleDF, dummy(df$StreamingTV, sep=".")) 82 | names(sampleDF) = gsub("sampleDF", "StreamingTV", names(sampleDF)) 83 | 84 | sampleDF <- cbind(sampleDF, dummy(df$StreamingMovies, sep=".")) 85 | names(sampleDF) = gsub("sampleDF", "StreamingMovies", names(sampleDF)) 86 | 87 | sampleDF <- cbind(sampleDF, dummy(df$Contract, sep=".")) 88 | names(sampleDF) = gsub("sampleDF", "Contract", names(sampleDF)) 89 | 90 | sampleDF <- cbind(sampleDF, dummy(df$PaymentMethod, sep=".")) 91 | names(sampleDF) = gsub("sampleDF", "PaymentMethod", names(sampleDF)) 92 | 93 | 94 | #### 3. Train & Test Set Split #### 95 | library(caTools) 96 | 97 | sample <- sample.split(sampleDF$Churn, SplitRatio = .7) 98 | 99 | train <- as.data.frame(subset(sampleDF, sample == TRUE)) 100 | test <- as.data.frame(subset(sampleDF, sample == FALSE)) 101 | 102 | trainX <- as.matrix(train[,names(train) != "Churn"]) 103 | trainY <- train$Churn 104 | testX <- as.matrix(test[,names(test) != "Churn"]) 105 | testY <- test$Churn 106 | 107 | 108 | #### 4. Aritificial Neural Network (ANN) with Keras #### 109 | install.packages("devtools") 110 | devtools::install_github("rstudio/tensorflow") 111 | library(tensorflow) 112 | install_tensorflow() 113 | 114 | devtools::install_github("rstudio/keras") 115 | library(keras) 116 | install_keras() 117 | 118 | 119 | model <- keras_model_sequential() 120 | model %>% 121 | layer_dense(units = 16, kernel_initializer = "uniform", activation = 'relu', input_shape=ncol(sampleDF)-1) %>% 122 | layer_dense(units = 8, kernel_initializer = "uniform", activation = 'relu') %>% 123 | layer_dense(units = 1, kernel_initializer = "uniform", activation = 'sigmoid') %>% 124 | compile( 125 | optimizer = 'adam', 126 | loss = 'binary_crossentropy', 127 | metrics = c('accuracy') 128 | ) 129 | 130 | history <- model %>% fit( 131 | trainX, 132 | trainY, 133 | epochs = 50, batch_size = 100, 134 | validation_split = 0.2 135 | ) 136 | 137 | # Evaluating ANN model 138 | inSamplePreds <- as.double(model %>% predict_classes(trainX)) 139 | outSamplePreds <- as.double(model %>% predict_classes(testX)) 140 | 141 | # - Accuracy, Precision, and Recall 142 | inSampleAccuracy <- mean(trainY == inSamplePreds) 143 | outSampleAccuracy <- mean(testY == outSamplePreds) 144 | 145 | inSamplePrecision <- sum(inSamplePreds & trainY) / sum(inSamplePreds) 146 | outSamplePrecision <- sum(outSamplePreds & testY) / sum(outSamplePreds) 147 | 148 | inSampleRecall <- sum(inSamplePreds & trainY) / sum(trainY) 149 | outSampleRecall <- sum(outSamplePreds & testY) / sum(testY) 150 | 151 | 152 | print(sprintf('In-Sample Accuracy: %0.4f', inSampleAccuracy)) 153 | print(sprintf('Out-Sample Accuracy: %0.4f', outSampleAccuracy)) 154 | print(sprintf('In-Sample Precision: %0.4f', inSamplePrecision)) 155 | print(sprintf('Out-Sample Precision: %0.4f', outSamplePrecision)) 156 | print(sprintf('In-Sample Recall: %0.4f', inSampleRecall)) 157 | print(sprintf('Out-Sample Recall: %0.4f', outSampleRecall)) 158 | 159 | 160 | # - ROC & AUC 161 | library(ROCR) 162 | 163 | outSamplePredProbs <- as.double(predict(model, testX)) 164 | 165 | pred <- prediction(outSamplePredProbs, testY) 166 | perf <- performance(pred, measure = "tpr", x.measure = "fpr") 167 | auc <- performance(pred, measure='auc')@y.values[[1]] 168 | 169 | plot( 170 | perf, 171 | main=sprintf('Model ROC Curve (AUC: %0.2f)', auc), 172 | col='darkorange', 173 | lwd=2 174 | ) + grid() 175 | abline(a = 0, b = 1, col='darkgray', lty=3, lwd=2) 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /ch.11/python/CustomerRetention.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%matplotlib inline" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 2, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import matplotlib.pyplot as plt\n", 19 | "import pandas as pd\n", 20 | "import numpy as np" 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [ 27 | "# 1. Load Data" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 3, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "df = pd.read_excel('../data/WA_Fn-UseC_-Telco-Customer-Churn.xlsx')" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 4, 42 | "metadata": {}, 43 | "outputs": [ 44 | { 45 | "data": { 46 | "text/plain": [ 47 | "(7043, 21)" 48 | ] 49 | }, 50 | "execution_count": 4, 51 | "metadata": {}, 52 | "output_type": "execute_result" 53 | } 54 | ], 55 | "source": [ 56 | "df.shape" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": 5, 62 | "metadata": {}, 63 | "outputs": [ 64 | { 65 | "data": { 66 | "text/html": [ 67 | "
\n", 68 | "\n", 81 | "\n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \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 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | " \n", 339 | " \n", 340 | " \n", 341 | " \n", 342 | " \n", 343 | " \n", 344 | " \n", 345 | " \n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | " \n", 350 | "
customerIDgenderSeniorCitizenPartnerDependentstenurePhoneServiceMultipleLinesInternetServiceOnlineSecurity...DeviceProtectionTechSupportStreamingTVStreamingMoviesContractPaperlessBillingPaymentMethodMonthlyChargesTotalChargesChurn
07590-VHVEGFemale0YesNo1NoNo phone serviceDSLNo...NoNoNoNoMonth-to-monthYesElectronic check29.8529.85No
15575-GNVDEMale0NoNo34YesNoDSLYes...YesNoNoNoOne yearNoMailed check56.951889.5No
23668-QPYBKMale0NoNo2YesNoDSLYes...NoNoNoNoMonth-to-monthYesMailed check53.85108.15Yes
37795-CFOCWMale0NoNo45NoNo phone serviceDSLYes...YesYesNoNoOne yearNoBank transfer (automatic)42.301840.75No
49237-HQITUFemale0NoNo2YesNoFiber opticNo...NoNoNoNoMonth-to-monthYesElectronic check70.70151.65Yes
59305-CDSKCFemale0NoNo8YesYesFiber opticNo...YesNoYesYesMonth-to-monthYesElectronic check99.65820.5Yes
61452-KIOVKMale0NoYes22YesYesFiber opticNo...NoNoYesNoMonth-to-monthYesCredit card (automatic)89.101949.4No
76713-OKOMCFemale0NoNo10NoNo phone serviceDSLYes...NoNoNoNoMonth-to-monthNoMailed check29.75301.9No
87892-POOKPFemale0YesNo28YesYesFiber opticNo...YesYesYesYesMonth-to-monthYesElectronic check104.803046.05Yes
96388-TABGUMale0NoYes62YesNoDSLYes...NoNoNoNoOne yearNoBank transfer (automatic)56.153487.95No
\n", 351 | "

10 rows × 21 columns

\n", 352 | "
" 353 | ], 354 | "text/plain": [ 355 | " customerID gender SeniorCitizen Partner Dependents tenure PhoneService \\\n", 356 | "0 7590-VHVEG Female 0 Yes No 1 No \n", 357 | "1 5575-GNVDE Male 0 No No 34 Yes \n", 358 | "2 3668-QPYBK Male 0 No No 2 Yes \n", 359 | "3 7795-CFOCW Male 0 No No 45 No \n", 360 | "4 9237-HQITU Female 0 No No 2 Yes \n", 361 | "5 9305-CDSKC Female 0 No No 8 Yes \n", 362 | "6 1452-KIOVK Male 0 No Yes 22 Yes \n", 363 | "7 6713-OKOMC Female 0 No No 10 No \n", 364 | "8 7892-POOKP Female 0 Yes No 28 Yes \n", 365 | "9 6388-TABGU Male 0 No Yes 62 Yes \n", 366 | "\n", 367 | " MultipleLines InternetService OnlineSecurity ... DeviceProtection \\\n", 368 | "0 No phone service DSL No ... No \n", 369 | "1 No DSL Yes ... Yes \n", 370 | "2 No DSL Yes ... No \n", 371 | "3 No phone service DSL Yes ... Yes \n", 372 | "4 No Fiber optic No ... No \n", 373 | "5 Yes Fiber optic No ... Yes \n", 374 | "6 Yes Fiber optic No ... No \n", 375 | "7 No phone service DSL Yes ... No \n", 376 | "8 Yes Fiber optic No ... Yes \n", 377 | "9 No DSL Yes ... No \n", 378 | "\n", 379 | " TechSupport StreamingTV StreamingMovies Contract PaperlessBilling \\\n", 380 | "0 No No No Month-to-month Yes \n", 381 | "1 No No No One year No \n", 382 | "2 No No No Month-to-month Yes \n", 383 | "3 Yes No No One year No \n", 384 | "4 No No No Month-to-month Yes \n", 385 | "5 No Yes Yes Month-to-month Yes \n", 386 | "6 No Yes No Month-to-month Yes \n", 387 | "7 No No No Month-to-month No \n", 388 | "8 Yes Yes Yes Month-to-month Yes \n", 389 | "9 No No No One year No \n", 390 | "\n", 391 | " PaymentMethod MonthlyCharges TotalCharges Churn \n", 392 | "0 Electronic check 29.85 29.85 No \n", 393 | "1 Mailed check 56.95 1889.5 No \n", 394 | "2 Mailed check 53.85 108.15 Yes \n", 395 | "3 Bank transfer (automatic) 42.30 1840.75 No \n", 396 | "4 Electronic check 70.70 151.65 Yes \n", 397 | "5 Electronic check 99.65 820.5 Yes \n", 398 | "6 Credit card (automatic) 89.10 1949.4 No \n", 399 | "7 Mailed check 29.75 301.9 No \n", 400 | "8 Electronic check 104.80 3046.05 Yes \n", 401 | "9 Bank transfer (automatic) 56.15 3487.95 No \n", 402 | "\n", 403 | "[10 rows x 21 columns]" 404 | ] 405 | }, 406 | "execution_count": 5, 407 | "metadata": {}, 408 | "output_type": "execute_result" 409 | } 410 | ], 411 | "source": [ 412 | "df.head(10)" 413 | ] 414 | }, 415 | { 416 | "cell_type": "markdown", 417 | "metadata": {}, 418 | "source": [ 419 | "# 2. Data Analysis & Preparation" 420 | ] 421 | }, 422 | { 423 | "cell_type": "markdown", 424 | "metadata": {}, 425 | "source": [ 426 | "#### - Encoding target var: Churn" 427 | ] 428 | }, 429 | { 430 | "cell_type": "code", 431 | "execution_count": 6, 432 | "metadata": {}, 433 | "outputs": [], 434 | "source": [ 435 | "df['Churn'] = df['Churn'].apply(lambda x: 1 if x == 'Yes' else 0)" 436 | ] 437 | }, 438 | { 439 | "cell_type": "code", 440 | "execution_count": 7, 441 | "metadata": {}, 442 | "outputs": [ 443 | { 444 | "data": { 445 | "text/plain": [ 446 | "0.2653698707936959" 447 | ] 448 | }, 449 | "execution_count": 7, 450 | "metadata": {}, 451 | "output_type": "execute_result" 452 | } 453 | ], 454 | "source": [ 455 | "df['Churn'].mean()" 456 | ] 457 | }, 458 | { 459 | "cell_type": "markdown", 460 | "metadata": {}, 461 | "source": [ 462 | "#### - TotalCharges" 463 | ] 464 | }, 465 | { 466 | "cell_type": "code", 467 | "execution_count": 8, 468 | "metadata": {}, 469 | "outputs": [], 470 | "source": [ 471 | "df['TotalCharges'] = df['TotalCharges'].replace(' ', np.nan).astype(float)" 472 | ] 473 | }, 474 | { 475 | "cell_type": "code", 476 | "execution_count": 9, 477 | "metadata": {}, 478 | "outputs": [ 479 | { 480 | "data": { 481 | "text/plain": [ 482 | "(7043, 21)" 483 | ] 484 | }, 485 | "execution_count": 9, 486 | "metadata": {}, 487 | "output_type": "execute_result" 488 | } 489 | ], 490 | "source": [ 491 | "df.shape" 492 | ] 493 | }, 494 | { 495 | "cell_type": "code", 496 | "execution_count": 10, 497 | "metadata": {}, 498 | "outputs": [ 499 | { 500 | "data": { 501 | "text/plain": [ 502 | "(7032, 21)" 503 | ] 504 | }, 505 | "execution_count": 10, 506 | "metadata": {}, 507 | "output_type": "execute_result" 508 | } 509 | ], 510 | "source": [ 511 | "df.dropna().shape" 512 | ] 513 | }, 514 | { 515 | "cell_type": "code", 516 | "execution_count": 11, 517 | "metadata": {}, 518 | "outputs": [], 519 | "source": [ 520 | "df = df.dropna()" 521 | ] 522 | }, 523 | { 524 | "cell_type": "markdown", 525 | "metadata": {}, 526 | "source": [ 527 | "#### - Continuous Vars" 528 | ] 529 | }, 530 | { 531 | "cell_type": "code", 532 | "execution_count": 12, 533 | "metadata": {}, 534 | "outputs": [ 535 | { 536 | "data": { 537 | "text/html": [ 538 | "
\n", 539 | "\n", 552 | "\n", 553 | " \n", 554 | " \n", 555 | " \n", 556 | " \n", 557 | " \n", 558 | " \n", 559 | " \n", 560 | " \n", 561 | " \n", 562 | " \n", 563 | " \n", 564 | " \n", 565 | " \n", 566 | " \n", 567 | " \n", 568 | " \n", 569 | " \n", 570 | " \n", 571 | " \n", 572 | " \n", 573 | " \n", 574 | " \n", 575 | " \n", 576 | " \n", 577 | " \n", 578 | " \n", 579 | " \n", 580 | " \n", 581 | " \n", 582 | " \n", 583 | " \n", 584 | " \n", 585 | " \n", 586 | " \n", 587 | " \n", 588 | " \n", 589 | " \n", 590 | " \n", 591 | " \n", 592 | " \n", 593 | " \n", 594 | " \n", 595 | " \n", 596 | " \n", 597 | " \n", 598 | " \n", 599 | " \n", 600 | " \n", 601 | " \n", 602 | " \n", 603 | " \n", 604 | " \n", 605 | " \n", 606 | " \n", 607 | " \n", 608 | " \n", 609 | " \n", 610 | " \n", 611 | "
tenureMonthlyChargesTotalCharges
count7032.0000007032.0000007032.000000
mean32.42178664.7982082283.300441
std24.54526030.0859742266.771362
min1.00000018.25000018.800000
25%9.00000035.587500401.450000
50%29.00000070.3500001397.475000
75%55.00000089.8625003794.737500
max72.000000118.7500008684.800000
\n", 612 | "
" 613 | ], 614 | "text/plain": [ 615 | " tenure MonthlyCharges TotalCharges\n", 616 | "count 7032.000000 7032.000000 7032.000000\n", 617 | "mean 32.421786 64.798208 2283.300441\n", 618 | "std 24.545260 30.085974 2266.771362\n", 619 | "min 1.000000 18.250000 18.800000\n", 620 | "25% 9.000000 35.587500 401.450000\n", 621 | "50% 29.000000 70.350000 1397.475000\n", 622 | "75% 55.000000 89.862500 3794.737500\n", 623 | "max 72.000000 118.750000 8684.800000" 624 | ] 625 | }, 626 | "execution_count": 12, 627 | "metadata": {}, 628 | "output_type": "execute_result" 629 | } 630 | ], 631 | "source": [ 632 | "df[['tenure', 'MonthlyCharges', 'TotalCharges']].describe()" 633 | ] 634 | }, 635 | { 636 | "cell_type": "code", 637 | "execution_count": 13, 638 | "metadata": {}, 639 | "outputs": [], 640 | "source": [ 641 | "df['MonthlyCharges'] = np.log(df['MonthlyCharges'])\n", 642 | "df['MonthlyCharges'] = (df['MonthlyCharges'] - df['MonthlyCharges'].mean())/df['MonthlyCharges'].std()\n", 643 | "\n", 644 | "df['TotalCharges'] = np.log(df['TotalCharges'])\n", 645 | "df['TotalCharges'] = (df['TotalCharges'] - df['TotalCharges'].mean())/df['TotalCharges'].std()\n", 646 | "\n", 647 | "df['tenure'] = (df['tenure'] - df['tenure'].mean())/df['tenure'].std()" 648 | ] 649 | }, 650 | { 651 | "cell_type": "code", 652 | "execution_count": 14, 653 | "metadata": {}, 654 | "outputs": [ 655 | { 656 | "data": { 657 | "text/html": [ 658 | "
\n", 659 | "\n", 672 | "\n", 673 | " \n", 674 | " \n", 675 | " \n", 676 | " \n", 677 | " \n", 678 | " \n", 679 | " \n", 680 | " \n", 681 | " \n", 682 | " \n", 683 | " \n", 684 | " \n", 685 | " \n", 686 | " \n", 687 | " \n", 688 | " \n", 689 | " \n", 690 | " \n", 691 | " \n", 692 | " \n", 693 | " \n", 694 | " \n", 695 | " \n", 696 | " \n", 697 | " \n", 698 | " \n", 699 | " \n", 700 | " \n", 701 | " \n", 702 | " \n", 703 | " \n", 704 | " \n", 705 | " \n", 706 | " \n", 707 | " \n", 708 | " \n", 709 | " \n", 710 | " \n", 711 | " \n", 712 | " \n", 713 | " \n", 714 | " \n", 715 | " \n", 716 | " \n", 717 | " \n", 718 | " \n", 719 | " \n", 720 | " \n", 721 | " \n", 722 | " \n", 723 | " \n", 724 | " \n", 725 | " \n", 726 | " \n", 727 | " \n", 728 | " \n", 729 | " \n", 730 | " \n", 731 | "
tenureMonthlyChargesTotalCharges
count7.032000e+037.032000e+037.032000e+03
mean-1.028756e-164.688495e-147.150708e-15
std1.000000e+001.000000e+001.000000e+00
min-1.280157e+00-1.882268e+00-2.579056e+00
25%-9.542285e-01-7.583727e-01-6.080585e-01
50%-1.394072e-013.885103e-011.950521e-01
75%9.198605e-018.004829e-018.382338e-01
max1.612459e+001.269576e+001.371323e+00
\n", 732 | "
" 733 | ], 734 | "text/plain": [ 735 | " tenure MonthlyCharges TotalCharges\n", 736 | "count 7.032000e+03 7.032000e+03 7.032000e+03\n", 737 | "mean -1.028756e-16 4.688495e-14 7.150708e-15\n", 738 | "std 1.000000e+00 1.000000e+00 1.000000e+00\n", 739 | "min -1.280157e+00 -1.882268e+00 -2.579056e+00\n", 740 | "25% -9.542285e-01 -7.583727e-01 -6.080585e-01\n", 741 | "50% -1.394072e-01 3.885103e-01 1.950521e-01\n", 742 | "75% 9.198605e-01 8.004829e-01 8.382338e-01\n", 743 | "max 1.612459e+00 1.269576e+00 1.371323e+00" 744 | ] 745 | }, 746 | "execution_count": 14, 747 | "metadata": {}, 748 | "output_type": "execute_result" 749 | } 750 | ], 751 | "source": [ 752 | "df[['tenure', 'MonthlyCharges', 'TotalCharges']].describe()" 753 | ] 754 | }, 755 | { 756 | "cell_type": "code", 757 | "execution_count": 15, 758 | "metadata": {}, 759 | "outputs": [ 760 | { 761 | "data": { 762 | "text/plain": [ 763 | "['SeniorCitizen', 'tenure', 'MonthlyCharges', 'TotalCharges', 'Churn']" 764 | ] 765 | }, 766 | "execution_count": 15, 767 | "metadata": {}, 768 | "output_type": "execute_result" 769 | } 770 | ], 771 | "source": [ 772 | "continuous_vars = list(df.describe().columns)\n", 773 | "continuous_vars" 774 | ] 775 | }, 776 | { 777 | "cell_type": "markdown", 778 | "metadata": {}, 779 | "source": [ 780 | "#### - One-Hot Encoding" 781 | ] 782 | }, 783 | { 784 | "cell_type": "code", 785 | "execution_count": 16, 786 | "metadata": {}, 787 | "outputs": [ 788 | { 789 | "name": "stdout", 790 | "output_type": "stream", 791 | "text": [ 792 | "customerID 7032\n", 793 | "gender 2\n", 794 | "SeniorCitizen 2\n", 795 | "Partner 2\n", 796 | "Dependents 2\n", 797 | "tenure 72\n", 798 | "PhoneService 2\n", 799 | "MultipleLines 3\n", 800 | "InternetService 3\n", 801 | "OnlineSecurity 3\n", 802 | "OnlineBackup 3\n", 803 | "DeviceProtection 3\n", 804 | "TechSupport 3\n", 805 | "StreamingTV 3\n", 806 | "StreamingMovies 3\n", 807 | "Contract 3\n", 808 | "PaperlessBilling 2\n", 809 | "PaymentMethod 4\n", 810 | "MonthlyCharges 1584\n", 811 | "TotalCharges 6530\n", 812 | "Churn 2\n" 813 | ] 814 | } 815 | ], 816 | "source": [ 817 | "for col in list(df.columns):\n", 818 | " print(col, df[col].nunique())" 819 | ] 820 | }, 821 | { 822 | "cell_type": "code", 823 | "execution_count": 17, 824 | "metadata": { 825 | "scrolled": true 826 | }, 827 | "outputs": [ 828 | { 829 | "data": { 830 | "image/png": "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\n", 831 | "text/plain": [ 832 | "
" 833 | ] 834 | }, 835 | "metadata": {}, 836 | "output_type": "display_data" 837 | }, 838 | { 839 | "data": { 840 | "image/png": "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\n", 841 | "text/plain": [ 842 | "
" 843 | ] 844 | }, 845 | "metadata": {}, 846 | "output_type": "display_data" 847 | }, 848 | { 849 | "data": { 850 | "image/png": "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\n", 851 | "text/plain": [ 852 | "
" 853 | ] 854 | }, 855 | "metadata": {}, 856 | "output_type": "display_data" 857 | } 858 | ], 859 | "source": [ 860 | "df.groupby('gender').count()['customerID'].plot(\n", 861 | " kind='bar', color='skyblue', grid=True, figsize=(8,6), title='Gender'\n", 862 | ")\n", 863 | "plt.show()\n", 864 | "\n", 865 | "df.groupby('InternetService').count()['customerID'].plot(\n", 866 | " kind='bar', color='skyblue', grid=True, figsize=(8,6), title='Internet Service'\n", 867 | ")\n", 868 | "plt.show()\n", 869 | "\n", 870 | "df.groupby('PaymentMethod').count()['customerID'].plot(\n", 871 | " kind='bar', color='skyblue', grid=True, figsize=(8,6), title='Payment Method'\n", 872 | ")\n", 873 | "plt.show()" 874 | ] 875 | }, 876 | { 877 | "cell_type": "code", 878 | "execution_count": 18, 879 | "metadata": {}, 880 | "outputs": [], 881 | "source": [ 882 | "dummy_cols = []\n", 883 | "\n", 884 | "sample_set = df[['tenure', 'MonthlyCharges', 'TotalCharges', 'Churn']].copy(deep=True)\n", 885 | "\n", 886 | "for col in list(df.columns):\n", 887 | " if col not in ['tenure', 'MonthlyCharges', 'TotalCharges', 'Churn'] and df[col].nunique() < 5:\n", 888 | " dummy_vars = pd.get_dummies(df[col])\n", 889 | " dummy_vars.columns = [col+str(x) for x in dummy_vars.columns] \n", 890 | " sample_set = pd.concat([sample_set, dummy_vars], axis=1)" 891 | ] 892 | }, 893 | { 894 | "cell_type": "code", 895 | "execution_count": 19, 896 | "metadata": {}, 897 | "outputs": [ 898 | { 899 | "data": { 900 | "text/html": [ 901 | "
\n", 902 | "\n", 915 | "\n", 916 | " \n", 917 | " \n", 918 | " \n", 919 | " \n", 920 | " \n", 921 | " \n", 922 | " \n", 923 | " \n", 924 | " \n", 925 | " \n", 926 | " \n", 927 | " \n", 928 | " \n", 929 | " \n", 930 | " \n", 931 | " \n", 932 | " \n", 933 | " \n", 934 | " \n", 935 | " \n", 936 | " \n", 937 | " \n", 938 | " \n", 939 | " \n", 940 | " \n", 941 | " \n", 942 | " \n", 943 | " \n", 944 | " \n", 945 | " \n", 946 | " \n", 947 | " \n", 948 | " \n", 949 | " \n", 950 | " \n", 951 | " \n", 952 | " \n", 953 | " \n", 954 | " \n", 955 | " \n", 956 | " \n", 957 | " \n", 958 | " \n", 959 | " \n", 960 | " \n", 961 | " \n", 962 | " \n", 963 | " \n", 964 | " \n", 965 | " \n", 966 | " \n", 967 | " \n", 968 | " \n", 969 | " \n", 970 | " \n", 971 | " \n", 972 | " \n", 973 | " \n", 974 | " \n", 975 | " \n", 976 | " \n", 977 | " \n", 978 | " \n", 979 | " \n", 980 | " \n", 981 | " \n", 982 | " \n", 983 | " \n", 984 | " \n", 985 | " \n", 986 | " \n", 987 | " \n", 988 | " \n", 989 | " \n", 990 | " \n", 991 | " \n", 992 | " \n", 993 | " \n", 994 | " \n", 995 | " \n", 996 | " \n", 997 | " \n", 998 | " \n", 999 | " \n", 1000 | " \n", 1001 | " \n", 1002 | " \n", 1003 | " \n", 1004 | " \n", 1005 | " \n", 1006 | " \n", 1007 | " \n", 1008 | " \n", 1009 | " \n", 1010 | " \n", 1011 | " \n", 1012 | " \n", 1013 | " \n", 1014 | " \n", 1015 | " \n", 1016 | " \n", 1017 | " \n", 1018 | " \n", 1019 | " \n", 1020 | " \n", 1021 | " \n", 1022 | " \n", 1023 | " \n", 1024 | " \n", 1025 | " \n", 1026 | " \n", 1027 | " \n", 1028 | " \n", 1029 | " \n", 1030 | " \n", 1031 | " \n", 1032 | " \n", 1033 | " \n", 1034 | " \n", 1035 | " \n", 1036 | " \n", 1037 | " \n", 1038 | " \n", 1039 | " \n", 1040 | " \n", 1041 | " \n", 1042 | " \n", 1043 | " \n", 1044 | " \n", 1045 | " \n", 1046 | " \n", 1047 | " \n", 1048 | " \n", 1049 | " \n", 1050 | " \n", 1051 | " \n", 1052 | " \n", 1053 | " \n", 1054 | " \n", 1055 | " \n", 1056 | " \n", 1057 | " \n", 1058 | " \n", 1059 | " \n", 1060 | " \n", 1061 | " \n", 1062 | " \n", 1063 | " \n", 1064 | " \n", 1065 | " \n", 1066 | " \n", 1067 | " \n", 1068 | " \n", 1069 | " \n", 1070 | " \n", 1071 | " \n", 1072 | " \n", 1073 | " \n", 1074 | " \n", 1075 | " \n", 1076 | " \n", 1077 | " \n", 1078 | " \n", 1079 | " \n", 1080 | " \n", 1081 | " \n", 1082 | " \n", 1083 | " \n", 1084 | " \n", 1085 | " \n", 1086 | " \n", 1087 | " \n", 1088 | " \n", 1089 | " \n", 1090 | " \n", 1091 | " \n", 1092 | " \n", 1093 | " \n", 1094 | " \n", 1095 | " \n", 1096 | " \n", 1097 | " \n", 1098 | " \n", 1099 | " \n", 1100 | " \n", 1101 | " \n", 1102 | " \n", 1103 | " \n", 1104 | " \n", 1105 | " \n", 1106 | " \n", 1107 | " \n", 1108 | " \n", 1109 | " \n", 1110 | " \n", 1111 | " \n", 1112 | " \n", 1113 | " \n", 1114 | " \n", 1115 | " \n", 1116 | " \n", 1117 | " \n", 1118 | " \n", 1119 | " \n", 1120 | " \n", 1121 | " \n", 1122 | " \n", 1123 | " \n", 1124 | " \n", 1125 | " \n", 1126 | " \n", 1127 | " \n", 1128 | " \n", 1129 | " \n", 1130 | " \n", 1131 | " \n", 1132 | " \n", 1133 | " \n", 1134 | " \n", 1135 | " \n", 1136 | " \n", 1137 | " \n", 1138 | " \n", 1139 | " \n", 1140 | " \n", 1141 | " \n", 1142 | " \n", 1143 | " \n", 1144 | " \n", 1145 | " \n", 1146 | " \n", 1147 | " \n", 1148 | " \n", 1149 | " \n", 1150 | " \n", 1151 | " \n", 1152 | " \n", 1153 | " \n", 1154 | " \n", 1155 | " \n", 1156 | " \n", 1157 | " \n", 1158 | " \n", 1159 | " \n", 1160 | " \n", 1161 | " \n", 1162 | " \n", 1163 | " \n", 1164 | " \n", 1165 | " \n", 1166 | " \n", 1167 | " \n", 1168 | " \n", 1169 | " \n", 1170 | " \n", 1171 | " \n", 1172 | " \n", 1173 | " \n", 1174 | " \n", 1175 | " \n", 1176 | " \n", 1177 | " \n", 1178 | " \n", 1179 | " \n", 1180 | " \n", 1181 | " \n", 1182 | " \n", 1183 | " \n", 1184 | "
tenureMonthlyChargesTotalChargesChurngenderFemalegenderMaleSeniorCitizen0SeniorCitizen1PartnerNoPartnerYes...StreamingMoviesYesContractMonth-to-monthContractOne yearContractTwo yearPaperlessBillingNoPaperlessBillingYesPaymentMethodBank transfer (automatic)PaymentMethodCredit card (automatic)PaymentMethodElectronic checkPaymentMethodMailed check
0-1.280157-1.054244-2.2813820101001...0100010010
10.0642980.0328960.3892690011010...0010100001
2-1.239416-0.061298-1.4525201011010...0100010001
30.512450-0.4675780.3724390011010...0010101000
4-1.2394160.396862-1.2348601101010...0100010010
5-0.9949700.974468-0.1478081101010...1100010010
6-0.4245950.7861420.4093630011010...0100010100
7-0.913487-1.059891-0.7915500101010...0100100001
8-0.1801481.0592690.6967331101001...1100010010
91.2050480.0090880.7839560011010...0010101000
\n", 1185 | "

10 rows × 47 columns

\n", 1186 | "
" 1187 | ], 1188 | "text/plain": [ 1189 | " tenure MonthlyCharges TotalCharges Churn genderFemale genderMale \\\n", 1190 | "0 -1.280157 -1.054244 -2.281382 0 1 0 \n", 1191 | "1 0.064298 0.032896 0.389269 0 0 1 \n", 1192 | "2 -1.239416 -0.061298 -1.452520 1 0 1 \n", 1193 | "3 0.512450 -0.467578 0.372439 0 0 1 \n", 1194 | "4 -1.239416 0.396862 -1.234860 1 1 0 \n", 1195 | "5 -0.994970 0.974468 -0.147808 1 1 0 \n", 1196 | "6 -0.424595 0.786142 0.409363 0 0 1 \n", 1197 | "7 -0.913487 -1.059891 -0.791550 0 1 0 \n", 1198 | "8 -0.180148 1.059269 0.696733 1 1 0 \n", 1199 | "9 1.205048 0.009088 0.783956 0 0 1 \n", 1200 | "\n", 1201 | " SeniorCitizen0 SeniorCitizen1 PartnerNo PartnerYes \\\n", 1202 | "0 1 0 0 1 \n", 1203 | "1 1 0 1 0 \n", 1204 | "2 1 0 1 0 \n", 1205 | "3 1 0 1 0 \n", 1206 | "4 1 0 1 0 \n", 1207 | "5 1 0 1 0 \n", 1208 | "6 1 0 1 0 \n", 1209 | "7 1 0 1 0 \n", 1210 | "8 1 0 0 1 \n", 1211 | "9 1 0 1 0 \n", 1212 | "\n", 1213 | " ... StreamingMoviesYes ContractMonth-to-month \\\n", 1214 | "0 ... 0 1 \n", 1215 | "1 ... 0 0 \n", 1216 | "2 ... 0 1 \n", 1217 | "3 ... 0 0 \n", 1218 | "4 ... 0 1 \n", 1219 | "5 ... 1 1 \n", 1220 | "6 ... 0 1 \n", 1221 | "7 ... 0 1 \n", 1222 | "8 ... 1 1 \n", 1223 | "9 ... 0 0 \n", 1224 | "\n", 1225 | " ContractOne year ContractTwo year PaperlessBillingNo \\\n", 1226 | "0 0 0 0 \n", 1227 | "1 1 0 1 \n", 1228 | "2 0 0 0 \n", 1229 | "3 1 0 1 \n", 1230 | "4 0 0 0 \n", 1231 | "5 0 0 0 \n", 1232 | "6 0 0 0 \n", 1233 | "7 0 0 1 \n", 1234 | "8 0 0 0 \n", 1235 | "9 1 0 1 \n", 1236 | "\n", 1237 | " PaperlessBillingYes PaymentMethodBank transfer (automatic) \\\n", 1238 | "0 1 0 \n", 1239 | "1 0 0 \n", 1240 | "2 1 0 \n", 1241 | "3 0 1 \n", 1242 | "4 1 0 \n", 1243 | "5 1 0 \n", 1244 | "6 1 0 \n", 1245 | "7 0 0 \n", 1246 | "8 1 0 \n", 1247 | "9 0 1 \n", 1248 | "\n", 1249 | " PaymentMethodCredit card (automatic) PaymentMethodElectronic check \\\n", 1250 | "0 0 1 \n", 1251 | "1 0 0 \n", 1252 | "2 0 0 \n", 1253 | "3 0 0 \n", 1254 | "4 0 1 \n", 1255 | "5 0 1 \n", 1256 | "6 1 0 \n", 1257 | "7 0 0 \n", 1258 | "8 0 1 \n", 1259 | "9 0 0 \n", 1260 | "\n", 1261 | " PaymentMethodMailed check \n", 1262 | "0 0 \n", 1263 | "1 1 \n", 1264 | "2 1 \n", 1265 | "3 0 \n", 1266 | "4 0 \n", 1267 | "5 0 \n", 1268 | "6 0 \n", 1269 | "7 1 \n", 1270 | "8 0 \n", 1271 | "9 0 \n", 1272 | "\n", 1273 | "[10 rows x 47 columns]" 1274 | ] 1275 | }, 1276 | "execution_count": 19, 1277 | "metadata": {}, 1278 | "output_type": "execute_result" 1279 | } 1280 | ], 1281 | "source": [ 1282 | "sample_set.head(10)" 1283 | ] 1284 | }, 1285 | { 1286 | "cell_type": "code", 1287 | "execution_count": 20, 1288 | "metadata": {}, 1289 | "outputs": [ 1290 | { 1291 | "data": { 1292 | "text/plain": [ 1293 | "(7032, 47)" 1294 | ] 1295 | }, 1296 | "execution_count": 20, 1297 | "metadata": {}, 1298 | "output_type": "execute_result" 1299 | } 1300 | ], 1301 | "source": [ 1302 | "sample_set.shape" 1303 | ] 1304 | }, 1305 | { 1306 | "cell_type": "code", 1307 | "execution_count": 21, 1308 | "metadata": { 1309 | "scrolled": true 1310 | }, 1311 | "outputs": [ 1312 | { 1313 | "data": { 1314 | "text/plain": [ 1315 | "['tenure',\n", 1316 | " 'MonthlyCharges',\n", 1317 | " 'TotalCharges',\n", 1318 | " 'Churn',\n", 1319 | " 'genderFemale',\n", 1320 | " 'genderMale',\n", 1321 | " 'SeniorCitizen0',\n", 1322 | " 'SeniorCitizen1',\n", 1323 | " 'PartnerNo',\n", 1324 | " 'PartnerYes',\n", 1325 | " 'DependentsNo',\n", 1326 | " 'DependentsYes',\n", 1327 | " 'PhoneServiceNo',\n", 1328 | " 'PhoneServiceYes',\n", 1329 | " 'MultipleLinesNo',\n", 1330 | " 'MultipleLinesNo phone service',\n", 1331 | " 'MultipleLinesYes',\n", 1332 | " 'InternetServiceDSL',\n", 1333 | " 'InternetServiceFiber optic',\n", 1334 | " 'InternetServiceNo',\n", 1335 | " 'OnlineSecurityNo',\n", 1336 | " 'OnlineSecurityNo internet service',\n", 1337 | " 'OnlineSecurityYes',\n", 1338 | " 'OnlineBackupNo',\n", 1339 | " 'OnlineBackupNo internet service',\n", 1340 | " 'OnlineBackupYes',\n", 1341 | " 'DeviceProtectionNo',\n", 1342 | " 'DeviceProtectionNo internet service',\n", 1343 | " 'DeviceProtectionYes',\n", 1344 | " 'TechSupportNo',\n", 1345 | " 'TechSupportNo internet service',\n", 1346 | " 'TechSupportYes',\n", 1347 | " 'StreamingTVNo',\n", 1348 | " 'StreamingTVNo internet service',\n", 1349 | " 'StreamingTVYes',\n", 1350 | " 'StreamingMoviesNo',\n", 1351 | " 'StreamingMoviesNo internet service',\n", 1352 | " 'StreamingMoviesYes',\n", 1353 | " 'ContractMonth-to-month',\n", 1354 | " 'ContractOne year',\n", 1355 | " 'ContractTwo year',\n", 1356 | " 'PaperlessBillingNo',\n", 1357 | " 'PaperlessBillingYes',\n", 1358 | " 'PaymentMethodBank transfer (automatic)',\n", 1359 | " 'PaymentMethodCredit card (automatic)',\n", 1360 | " 'PaymentMethodElectronic check',\n", 1361 | " 'PaymentMethodMailed check']" 1362 | ] 1363 | }, 1364 | "execution_count": 21, 1365 | "metadata": {}, 1366 | "output_type": "execute_result" 1367 | } 1368 | ], 1369 | "source": [ 1370 | "list(sample_set.columns)" 1371 | ] 1372 | }, 1373 | { 1374 | "cell_type": "markdown", 1375 | "metadata": {}, 1376 | "source": [ 1377 | "# 3. Train & Test Sets" 1378 | ] 1379 | }, 1380 | { 1381 | "cell_type": "code", 1382 | "execution_count": 22, 1383 | "metadata": {}, 1384 | "outputs": [], 1385 | "source": [ 1386 | "target_var = 'Churn'\n", 1387 | "features = [x for x in list(sample_set.columns) if x != target_var]" 1388 | ] 1389 | }, 1390 | { 1391 | "cell_type": "markdown", 1392 | "metadata": {}, 1393 | "source": [ 1394 | "# 4. Aritificial Neural Network (ANN) with Keras" 1395 | ] 1396 | }, 1397 | { 1398 | "cell_type": "code", 1399 | "execution_count": 23, 1400 | "metadata": {}, 1401 | "outputs": [ 1402 | { 1403 | "name": "stderr", 1404 | "output_type": "stream", 1405 | "text": [ 1406 | "/anaconda3/lib/python3.6/site-packages/h5py/__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.\n", 1407 | " from ._conv import register_converters as _register_converters\n", 1408 | "Using TensorFlow backend.\n" 1409 | ] 1410 | } 1411 | ], 1412 | "source": [ 1413 | "from keras.models import Sequential\n", 1414 | "from keras.layers import Dense" 1415 | ] 1416 | }, 1417 | { 1418 | "cell_type": "markdown", 1419 | "metadata": {}, 1420 | "source": [ 1421 | "#### - Training a Neural Network Model" 1422 | ] 1423 | }, 1424 | { 1425 | "cell_type": "code", 1426 | "execution_count": 24, 1427 | "metadata": {}, 1428 | "outputs": [], 1429 | "source": [ 1430 | "model = Sequential()\n", 1431 | "model.add(Dense(16, input_dim=len(features), activation='relu'))\n", 1432 | "model.add(Dense(8, activation='relu'))\n", 1433 | "model.add(Dense(1, activation='sigmoid'))" 1434 | ] 1435 | }, 1436 | { 1437 | "cell_type": "code", 1438 | "execution_count": 25, 1439 | "metadata": {}, 1440 | "outputs": [], 1441 | "source": [ 1442 | "model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])" 1443 | ] 1444 | }, 1445 | { 1446 | "cell_type": "code", 1447 | "execution_count": 26, 1448 | "metadata": {}, 1449 | "outputs": [], 1450 | "source": [ 1451 | "from sklearn.model_selection import train_test_split" 1452 | ] 1453 | }, 1454 | { 1455 | "cell_type": "code", 1456 | "execution_count": 27, 1457 | "metadata": {}, 1458 | "outputs": [], 1459 | "source": [ 1460 | "X_train, X_test, y_train, y_test = train_test_split(\n", 1461 | " sample_set[features], \n", 1462 | " sample_set[target_var], \n", 1463 | " test_size=0.3\n", 1464 | ")" 1465 | ] 1466 | }, 1467 | { 1468 | "cell_type": "code", 1469 | "execution_count": 28, 1470 | "metadata": { 1471 | "scrolled": true 1472 | }, 1473 | "outputs": [ 1474 | { 1475 | "name": "stdout", 1476 | "output_type": "stream", 1477 | "text": [ 1478 | "Epoch 1/50\n", 1479 | "4922/4922 [==============================] - 0s 61us/step - loss: 0.5847 - acc: 0.7152\n", 1480 | "Epoch 2/50\n", 1481 | "4922/4922 [==============================] - 0s 14us/step - loss: 0.4703 - acc: 0.7678\n", 1482 | "Epoch 3/50\n", 1483 | "4922/4922 [==============================] - 0s 13us/step - loss: 0.4310 - acc: 0.7944\n", 1484 | "Epoch 4/50\n", 1485 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4210 - acc: 0.7985\n", 1486 | "Epoch 5/50\n", 1487 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4172 - acc: 0.8023\n", 1488 | "Epoch 6/50\n", 1489 | "4922/4922 [==============================] - 0s 13us/step - loss: 0.4146 - acc: 0.8027\n", 1490 | "Epoch 7/50\n", 1491 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4126 - acc: 0.8052\n", 1492 | "Epoch 8/50\n", 1493 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4114 - acc: 0.8054\n", 1494 | "Epoch 9/50\n", 1495 | "4922/4922 [==============================] - 0s 16us/step - loss: 0.4104 - acc: 0.8064\n", 1496 | "Epoch 10/50\n", 1497 | "4922/4922 [==============================] - 0s 15us/step - loss: 0.4092 - acc: 0.8048\n", 1498 | "Epoch 11/50\n", 1499 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4089 - acc: 0.8070\n", 1500 | "Epoch 12/50\n", 1501 | "4922/4922 [==============================] - 0s 15us/step - loss: 0.4093 - acc: 0.8078\n", 1502 | "Epoch 13/50\n", 1503 | "4922/4922 [==============================] - 0s 13us/step - loss: 0.4076 - acc: 0.8082\n", 1504 | "Epoch 14/50\n", 1505 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4068 - acc: 0.8090\n", 1506 | "Epoch 15/50\n", 1507 | "4922/4922 [==============================] - 0s 13us/step - loss: 0.4074 - acc: 0.8054\n", 1508 | "Epoch 16/50\n", 1509 | "4922/4922 [==============================] - 0s 13us/step - loss: 0.4073 - acc: 0.8084\n", 1510 | "Epoch 17/50\n", 1511 | "4922/4922 [==============================] - 0s 13us/step - loss: 0.4057 - acc: 0.8082\n", 1512 | "Epoch 18/50\n", 1513 | "4922/4922 [==============================] - 0s 13us/step - loss: 0.4058 - acc: 0.8104\n", 1514 | "Epoch 19/50\n", 1515 | "4922/4922 [==============================] - 0s 13us/step - loss: 0.4046 - acc: 0.8098\n", 1516 | "Epoch 20/50\n", 1517 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4054 - acc: 0.8102\n", 1518 | "Epoch 21/50\n", 1519 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4040 - acc: 0.8121\n", 1520 | "Epoch 22/50\n", 1521 | "4922/4922 [==============================] - 0s 11us/step - loss: 0.4038 - acc: 0.8102\n", 1522 | "Epoch 23/50\n", 1523 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4033 - acc: 0.8094\n", 1524 | "Epoch 24/50\n", 1525 | "4922/4922 [==============================] - 0s 11us/step - loss: 0.4031 - acc: 0.8094\n", 1526 | "Epoch 25/50\n", 1527 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4026 - acc: 0.8117\n", 1528 | "Epoch 26/50\n", 1529 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4018 - acc: 0.8125\n", 1530 | "Epoch 27/50\n", 1531 | "4922/4922 [==============================] - 0s 14us/step - loss: 0.4030 - acc: 0.8106\n", 1532 | "Epoch 28/50\n", 1533 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4023 - acc: 0.8098\n", 1534 | "Epoch 29/50\n", 1535 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4013 - acc: 0.8135\n", 1536 | "Epoch 30/50\n", 1537 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4004 - acc: 0.8129\n", 1538 | "Epoch 31/50\n", 1539 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4014 - acc: 0.8125\n", 1540 | "Epoch 32/50\n", 1541 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.4007 - acc: 0.8145\n", 1542 | "Epoch 33/50\n", 1543 | "4922/4922 [==============================] - 0s 10us/step - loss: 0.4000 - acc: 0.8108\n", 1544 | "Epoch 34/50\n", 1545 | "4922/4922 [==============================] - 0s 10us/step - loss: 0.4007 - acc: 0.8123\n", 1546 | "Epoch 35/50\n", 1547 | "4922/4922 [==============================] - 0s 11us/step - loss: 0.3991 - acc: 0.8121\n", 1548 | "Epoch 36/50\n", 1549 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.3988 - acc: 0.8104\n", 1550 | "Epoch 37/50\n", 1551 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.3982 - acc: 0.8119\n", 1552 | "Epoch 38/50\n", 1553 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.3980 - acc: 0.8121\n", 1554 | "Epoch 39/50\n", 1555 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.3976 - acc: 0.8143\n", 1556 | "Epoch 40/50\n", 1557 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.3985 - acc: 0.8111\n", 1558 | "Epoch 41/50\n", 1559 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.3968 - acc: 0.8125\n", 1560 | "Epoch 42/50\n", 1561 | "4922/4922 [==============================] - 0s 13us/step - loss: 0.3972 - acc: 0.8145\n", 1562 | "Epoch 43/50\n", 1563 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.3962 - acc: 0.8106\n", 1564 | "Epoch 44/50\n", 1565 | "4922/4922 [==============================] - 0s 13us/step - loss: 0.3963 - acc: 0.8119\n", 1566 | "Epoch 45/50\n", 1567 | "4922/4922 [==============================] - 0s 13us/step - loss: 0.3959 - acc: 0.8137\n", 1568 | "Epoch 46/50\n", 1569 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.3950 - acc: 0.8121\n", 1570 | "Epoch 47/50\n", 1571 | "4922/4922 [==============================] - 0s 18us/step - loss: 0.3943 - acc: 0.8133\n", 1572 | "Epoch 48/50\n", 1573 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.3944 - acc: 0.8125\n", 1574 | "Epoch 49/50\n", 1575 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.3948 - acc: 0.8135\n", 1576 | "Epoch 50/50\n", 1577 | "4922/4922 [==============================] - 0s 12us/step - loss: 0.3937 - acc: 0.8139\n" 1578 | ] 1579 | }, 1580 | { 1581 | "data": { 1582 | "text/plain": [ 1583 | "" 1584 | ] 1585 | }, 1586 | "execution_count": 28, 1587 | "metadata": {}, 1588 | "output_type": "execute_result" 1589 | } 1590 | ], 1591 | "source": [ 1592 | "model.fit(X_train, y_train, epochs=50, batch_size=100)" 1593 | ] 1594 | }, 1595 | { 1596 | "cell_type": "markdown", 1597 | "metadata": {}, 1598 | "source": [ 1599 | "#### - Accuracy, Precision, Recall" 1600 | ] 1601 | }, 1602 | { 1603 | "cell_type": "code", 1604 | "execution_count": 29, 1605 | "metadata": {}, 1606 | "outputs": [], 1607 | "source": [ 1608 | "from sklearn.metrics import accuracy_score, precision_score, recall_score" 1609 | ] 1610 | }, 1611 | { 1612 | "cell_type": "code", 1613 | "execution_count": 30, 1614 | "metadata": {}, 1615 | "outputs": [], 1616 | "source": [ 1617 | "in_sample_preds = [round(x[0]) for x in model.predict(X_train)]\n", 1618 | "out_sample_preds = [round(x[0]) for x in model.predict(X_test)]" 1619 | ] 1620 | }, 1621 | { 1622 | "cell_type": "code", 1623 | "execution_count": 31, 1624 | "metadata": {}, 1625 | "outputs": [ 1626 | { 1627 | "name": "stdout", 1628 | "output_type": "stream", 1629 | "text": [ 1630 | "In-Sample Accuracy: 0.8143\n", 1631 | "Out-of-Sample Accuracy: 0.8028\n", 1632 | "\n", 1633 | "\n", 1634 | "In-Sample Precision: 0.6837\n", 1635 | "Out-of-Sample Precision: 0.6604\n", 1636 | "\n", 1637 | "\n", 1638 | "In-Sample Recall: 0.5684\n", 1639 | "Out-of-Sample Recall: 0.5099\n" 1640 | ] 1641 | } 1642 | ], 1643 | "source": [ 1644 | "print('In-Sample Accuracy: %0.4f' % accuracy_score(y_train, in_sample_preds))\n", 1645 | "print('Out-of-Sample Accuracy: %0.4f' % accuracy_score(y_test, out_sample_preds))\n", 1646 | "\n", 1647 | "print('\\n')\n", 1648 | "\n", 1649 | "print('In-Sample Precision: %0.4f' % precision_score(y_train, in_sample_preds))\n", 1650 | "print('Out-of-Sample Precision: %0.4f' % precision_score(y_test, out_sample_preds))\n", 1651 | "\n", 1652 | "print('\\n')\n", 1653 | "\n", 1654 | "print('In-Sample Recall: %0.4f' % recall_score(y_train, in_sample_preds))\n", 1655 | "print('Out-of-Sample Recall: %0.4f' % recall_score(y_test, out_sample_preds))" 1656 | ] 1657 | }, 1658 | { 1659 | "cell_type": "markdown", 1660 | "metadata": {}, 1661 | "source": [ 1662 | "#### - ROC & AUC" 1663 | ] 1664 | }, 1665 | { 1666 | "cell_type": "code", 1667 | "execution_count": 32, 1668 | "metadata": {}, 1669 | "outputs": [], 1670 | "source": [ 1671 | "from sklearn.metrics import roc_curve, auc" 1672 | ] 1673 | }, 1674 | { 1675 | "cell_type": "code", 1676 | "execution_count": 33, 1677 | "metadata": {}, 1678 | "outputs": [], 1679 | "source": [ 1680 | "in_sample_preds = [x[0] for x in model.predict(X_train)]\n", 1681 | "out_sample_preds = [x[0] for x in model.predict(X_test)]" 1682 | ] 1683 | }, 1684 | { 1685 | "cell_type": "code", 1686 | "execution_count": 34, 1687 | "metadata": {}, 1688 | "outputs": [], 1689 | "source": [ 1690 | "in_sample_fpr, in_sample_tpr, in_sample_thresholds = roc_curve(y_train, in_sample_preds)\n", 1691 | "out_sample_fpr, out_sample_tpr, out_sample_thresholds = roc_curve(y_test, out_sample_preds)" 1692 | ] 1693 | }, 1694 | { 1695 | "cell_type": "code", 1696 | "execution_count": 35, 1697 | "metadata": {}, 1698 | "outputs": [ 1699 | { 1700 | "name": "stdout", 1701 | "output_type": "stream", 1702 | "text": [ 1703 | "In-Sample AUC: 0.8659\n", 1704 | "Out-Sample AUC: 0.8466\n" 1705 | ] 1706 | } 1707 | ], 1708 | "source": [ 1709 | "in_sample_roc_auc = auc(in_sample_fpr, in_sample_tpr)\n", 1710 | "out_sample_roc_auc = auc(out_sample_fpr, out_sample_tpr)\n", 1711 | "\n", 1712 | "print('In-Sample AUC: %0.4f' % in_sample_roc_auc)\n", 1713 | "print('Out-Sample AUC: %0.4f' % out_sample_roc_auc)" 1714 | ] 1715 | }, 1716 | { 1717 | "cell_type": "code", 1718 | "execution_count": 36, 1719 | "metadata": {}, 1720 | "outputs": [ 1721 | { 1722 | "data": { 1723 | "image/png": "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kL527ZrWWutLly6ZXlPcv828vDzt6empT5w4obXW+vXXX9eff/651tr4PQgKCtLp6elFXlfcdxTYp+8w55GWtCpw4sQJPv30U06cOIFSipYtW1o6JCGErdv6V/guvNw/rqsGlV1u619LveSWLVtwcXFh4sSJgLEF4qOPPmLRokXcvHmTxYsXM23aNFP5IUOGEBkZyfTp08nIyCAoKIhx48YVOe/FixcLTdYdEGCcqDwtLY0+ffoQEhJCx44dWbFiBWBsCWvfvj2TJ0/G39+fcePGsWnTJnr06IGPjw979+4FYMaMGTz++OP07t0bHx8fFixYUOTaeXl5vPzyy4SFhREQEMC8efNK/QwAunXrxvnz5wH45ptv6NGjB/369QOgVq1azJ49m3fffReA9957j7///e+0b98eACcnJ6ZOnVrknGlpaUycOJGOHTsSEBDA//73PwDq1PnjJrlly5YxYcIEACZMmMALL7xAr169ePnll2nVqlWh1r02bdpw6dIlrly5wiOPPEJYWBhhYWHs2rWryLVTU1M5fPgwgYGBAOzdu5fu3bsTHBxM9+7dOXHiBACLFy/m0UcfZejQoab3pfxavgAAIABJREFU+/7775s+u4KtSyNGjKBTp074+fkxf/78Mj/TsqxYsYIxY8ZQs2ZNWrduTZs2bUz1XJBSipSUFMA452izZs0AWLBgAc8++6yp96px48alXu/q1avUrFmTtm3bAtC3b19TnSilCA8PZ/Xq1Xf9vsoiNw6Y2Y4dO4iJieHhhx+WBdGFEDbt2LFjRdYOrlu3Li1btizUrXS7d999l9mzZ3Pw4MFijz/77LOMHj2a2bNn8+CDDzJx4kSaNWuGi4sLy5cvp27duiQlJdG1a1dTd9SpU6f44YcfmD9/PmFhYXzzzTfs3LmTlStX8s477/DTTz8BcPjwYXbv3k16ejrBwcEMHjy40LUXLlxIvXr1iI6OJisry5RwlTSNQl5eHps3b2bSpEklfibe3t6kpaWRkpLC0aNHefHFF0v5VI3efvtt6tWrx5EjRwC4fv16ma+Ji4tj06ZNODo6YjAYWL58ORMnTmTPnj20atWKJk2aMHbsWJ5//nnuu+8+zp49S//+/YvMubZv375C3b/t27dn+/btODk5sWnTJl599VVTghIVFcXhw4dp2LAhGzZs4OTJk+zduxetNcOGDWP7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\n", 1724 | "text/plain": [ 1725 | "
" 1726 | ] 1727 | }, 1728 | "metadata": {}, 1729 | "output_type": "display_data" 1730 | } 1731 | ], 1732 | "source": [ 1733 | "plt.figure(figsize=(10,7))\n", 1734 | "\n", 1735 | "plt.plot(\n", 1736 | " out_sample_fpr, out_sample_tpr, color='darkorange', label='Out-Sample ROC curve (area = %0.4f)' % in_sample_roc_auc\n", 1737 | ")\n", 1738 | "plt.plot(\n", 1739 | " in_sample_fpr, in_sample_tpr, color='navy', label='In-Sample ROC curve (area = %0.4f)' % out_sample_roc_auc\n", 1740 | ")\n", 1741 | "plt.plot([0, 1], [0, 1], color='gray', lw=1, linestyle='--')\n", 1742 | "plt.grid()\n", 1743 | "plt.xlim([0.0, 1.0])\n", 1744 | "plt.ylim([0.0, 1.05])\n", 1745 | "plt.xlabel('False Positive Rate')\n", 1746 | "plt.ylabel('True Positive Rate')\n", 1747 | "plt.title('ROC Curve')\n", 1748 | "plt.legend(loc=\"lower right\")\n", 1749 | "\n", 1750 | "plt.show()" 1751 | ] 1752 | }, 1753 | { 1754 | "cell_type": "code", 1755 | "execution_count": null, 1756 | "metadata": {}, 1757 | "outputs": [], 1758 | "source": [] 1759 | } 1760 | ], 1761 | "metadata": { 1762 | "kernelspec": { 1763 | "display_name": "Python 3", 1764 | "language": "python", 1765 | "name": "python3" 1766 | }, 1767 | "language_info": { 1768 | "codemirror_mode": { 1769 | "name": "ipython", 1770 | "version": 3 1771 | }, 1772 | "file_extension": ".py", 1773 | "mimetype": "text/x-python", 1774 | "name": "python", 1775 | "nbconvert_exporter": "python", 1776 | "pygments_lexer": "ipython3", 1777 | "version": "3.6.5" 1778 | } 1779 | }, 1780 | "nbformat": 4, 1781 | "nbformat_minor": 2 1782 | } 1783 | -------------------------------------------------------------------------------- /ch.12/R/ABTesting.R: -------------------------------------------------------------------------------- 1 | library(dplyr) 2 | library(readxl) 3 | library(ggplot2) 4 | 5 | #### 1. Load Data #### 6 | df <- read_excel( 7 | path="~/Documents/data-science-for-marketing/ch.12/data/WA_Fn-UseC_-Marketing-Campaign-Eff-UseC_-FastF.xlsx" 8 | ) 9 | 10 | #### 2. Date Analysis #### 11 | 12 | # - total sales 13 | summary(df$SalesInThousands) 14 | 15 | salesPerPromo <- df %>% 16 | group_by(Promotion) %>% 17 | summarise(Sales=sum(SalesInThousands)) 18 | 19 | salesPerPromo 20 | 21 | ggplot(salesPerPromo, aes(x="", y=Sales, fill=Promotion)) + 22 | geom_bar(width=1, stat = "identity", position=position_fill()) + 23 | geom_text(aes(x=1.25, label=Sales), position=position_fill(vjust = 0.5), color='white') + 24 | coord_polar("y") + 25 | ggtitle('sales distribution across different promotions') 26 | 27 | # - market size 28 | df %>% 29 | group_by(MarketSize) %>% 30 | summarise(Count=n()) 31 | 32 | marketSizePerPromo <- df %>% 33 | group_by(Promotion, MarketSize) %>% 34 | summarise(Count=n()) 35 | 36 | marketSizePerPromo 37 | 38 | ggplot(marketSizePerPromo, aes(x=Promotion, y=Count, fill=MarketSize)) + 39 | geom_bar(width=0.5, stat="identity", position="dodge") + 40 | ylab("Count") + 41 | xlab("Promotion") + 42 | ggtitle("breakdowns of market sizes across different promotions") + 43 | theme(plot.title=element_text(hjust=0.5)) 44 | 45 | ggplot(marketSizePerPromo, aes(x=Promotion, y=Count, fill=MarketSize)) + 46 | geom_bar(width=0.5, stat="identity", position="stack") + 47 | ylab("Count") + 48 | xlab("Promotion") + 49 | ggtitle("breakdowns of market sizes across different promotions") + 50 | theme(plot.title=element_text(hjust=0.5)) 51 | 52 | 53 | # - store age 54 | summary(df$AgeOfStore) 55 | 56 | overallAge <- df %>% 57 | group_by(AgeOfStore) %>% 58 | summarise(Count=n()) 59 | 60 | overallAge 61 | 62 | ggplot(overallAge, aes(x=AgeOfStore, y=Count)) + 63 | geom_bar(width=0.5, stat="identity") + 64 | ylab("Count") + 65 | xlab("Store Age") + 66 | ggtitle("overall distributions of age of store") + 67 | theme(plot.title=element_text(hjust=0.5)) 68 | 69 | AgePerPromo <- df %>% 70 | group_by(Promotion, AgeOfStore) %>% 71 | summarise(Count=n()) 72 | 73 | AgePerPromo 74 | 75 | ggplot(AgePerPromo, aes(x=AgeOfStore, y=Count, fill=Promotion)) + 76 | geom_bar(width=0.5, stat="identity", position="dodge2") + 77 | ylab("Count") + 78 | xlab("Store Age") + 79 | ggtitle("distributions of age of store") + 80 | theme(plot.title=element_text(hjust=0.5)) 81 | 82 | tapply(df$AgeOfStore, df$Promotion, summary) 83 | 84 | 85 | # - week number 86 | df %>% 87 | group_by(Week) %>% 88 | summarise(Count=n()) 89 | 90 | weekPerPromo <- df %>% 91 | group_by(Week, Promotion) %>% 92 | summarise(Count=n()) 93 | 94 | weekPerPromo 95 | 96 | ggplot(weekPerPromo, aes(x="", y=Count, fill=Promotion)) + 97 | geom_bar(width=1, stat = "identity", position=position_fill()) + 98 | geom_text(aes(x=1.25, label=Count), position=position_fill(vjust = 0.5), color='white') + 99 | coord_polar("y") + 100 | facet_wrap(~Week) + 101 | ggtitle('distribution across different weeks') 102 | 103 | 104 | #### 3. Statistical Significance #### 105 | 106 | # Promotion 1 vs. 2 107 | promo_1 <- df[which(df$Promotion == 1),]$SalesInThousands 108 | promo_2 <- df[which(df$Promotion == 2),]$SalesInThousands 109 | 110 | mean_1 <- mean(promo_1) 111 | mean_2 <- mean(promo_2) 112 | std_1 <- sd(promo_1) 113 | std_2 <- sd(promo_2) 114 | n_1 <- length(promo_1) 115 | n_2 <- length(promo_2) 116 | 117 | df_1_2 <- n_1 + n_2 - 2 118 | 119 | t_val <- ( 120 | mean_1 - mean_2 121 | ) / sqrt( 122 | (std_1**2/n_1 + std_2**2/n_2) 123 | ) 124 | 125 | p_val <- 2 * pt(t_val, df_1_2, lower=FALSE) 126 | 127 | # - using t.test 128 | t.test( 129 | promo_1, 130 | promo_2 131 | ) 132 | 133 | # Promotion 1 vs. 3 134 | promo_1 <- df[which(df$Promotion == 1),]$SalesInThousands 135 | promo_3 <- df[which(df$Promotion == 3),]$SalesInThousands 136 | 137 | mean_1 <- mean(promo_1) 138 | mean_3 <- mean(promo_3) 139 | std_1 <- sd(promo_1) 140 | std_3 <- sd(promo_3) 141 | n_1 <- length(promo_1) 142 | n_3 <- length(promo_3) 143 | df_1_3 <- n_1 + n_3 - 2 144 | 145 | t_val <- ( 146 | mean_1 - mean_3 147 | ) / sqrt( 148 | (std_1**2/n_1 + std_3**2/n_3) 149 | ) 150 | 151 | p_val <- 2 * pt(t_val, df_1_3, lower=FALSE) 152 | 153 | # - using t.test 154 | t.test( 155 | promo_1, 156 | promo_3 157 | ) 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /ch.2/R/ConversionRate.R: -------------------------------------------------------------------------------- 1 | library(dplyr) # Data Wrangling and Manipulation 2 | library(ggplot2) 3 | 4 | conversionsDF <- read.csv( 5 | file="~/Documents/data-science-for-marketing/ch.2/data/bank-additional-full.csv", 6 | header=TRUE, 7 | sep=";" 8 | ) 9 | 10 | # Shape of conversionsDF 11 | dim(conversionsDF) 12 | # Quick look at conversionsDF 13 | head(conversionsDF) 14 | 15 | # Encode conversions as 0s and 1s 16 | conversionsDF$conversion <- as.integer(conversionsDF$y) - 1 17 | tail(conversionsDF) 18 | 19 | #### 1. Aggregate Conversion Rate #### 20 | sprintf("total conversions: %i out of %i", sum(conversionsDF$conversion), nrow(conversionsDF)) 21 | sprintf("conversion rate: %0.2f%%", sum(conversionsDF$conversion)/nrow(conversionsDF)*100.0) 22 | 23 | #### 2. Conversion Rates by Number of Contacts #### 24 | conversionsByNumContact <- conversionsDF %>% 25 | group_by(NumContact=campaign) %>% 26 | summarise(TotalCount=n(), NumConversions=sum(conversion)) %>% 27 | mutate(ConversionRate=NumConversions/TotalCount*100.0) 28 | 29 | head(conversionsByNumContact, 10) 30 | 31 | # line chart 32 | ggplot(data=head(conversionsByNumContact, 10), aes(x=NumContact, y=ConversionRate)) + 33 | geom_line() + 34 | ggtitle('Conversion Rates by Number of Contacts') + 35 | xlab("Number of Contacts") + 36 | ylab("Conversion Rate (%)") + 37 | ylim(c(0, 15)) + 38 | theme(plot.title = element_text(hjust = 0.5)) 39 | 40 | 41 | #### 3. Conversion Rates by Age #### 42 | 43 | # a. by age 44 | conversionsByAge <- conversionsDF %>% 45 | group_by(Age=age) %>% 46 | summarise(TotalCount=n(), NumConversions=sum(conversion)) %>% 47 | mutate(ConversionRate=NumConversions/TotalCount*100.0) 48 | 49 | head(conversionsByAge) 50 | 51 | # line chart 52 | ggplot(data=conversionsByAge, aes(x=Age, y=ConversionRate)) + 53 | geom_line() + 54 | ggtitle('Conversion Rates by Age') + 55 | xlab("Age") + 56 | ylab("Conversion Rate (%)") + 57 | theme(plot.title = element_text(hjust = 0.5)) 58 | 59 | # b. by age groups 60 | conversionsByAgeGroup <- conversionsDF %>% 61 | group_by(AgeGroup=cut(age, breaks= seq(20, 70, by = 10)) ) %>% 62 | summarise(TotalCount=n(), NumConversions=sum(conversion)) %>% 63 | mutate(ConversionRate=NumConversions/TotalCount*100.0) 64 | 65 | conversionsByAgeGroup$AgeGroup <- as.character(conversionsByAgeGroup$AgeGroup) 66 | conversionsByAgeGroup$AgeGroup[6] <- "70+" 67 | 68 | # bar chart 69 | ggplot(conversionsByAgeGroup, aes(x=AgeGroup, y=ConversionRate)) + 70 | geom_bar(width=0.5, stat="identity") + 71 | ggtitle('Conversion Rates by Age Groups') + 72 | xlab("Age") + 73 | ylab("Conversion Rate (%)") + 74 | theme(plot.title = element_text(hjust = 0.5)) 75 | 76 | #### 4. Conversions vs. Non-Conversions #### 77 | 78 | # 4.1. Marital Status 79 | conversionsByMaritalStatus <- conversionsDF %>% 80 | group_by(Marital=marital, Conversion=conversion) %>% 81 | summarise(Count=n()) 82 | 83 | conversionsByMaritalStatus 84 | 85 | # pie chart 86 | ggplot(conversionsByMaritalStatus, aes(x="", y=Count, fill=Marital)) + 87 | geom_bar(width=1, stat = "identity", position=position_fill()) + 88 | geom_text(aes(x=1.25, label=Count), position=position_fill(vjust = 0.5)) + 89 | coord_polar("y") + 90 | facet_wrap(~Conversion) + 91 | ggtitle('Marital Status (0: Non Conversions, 1: Conversions)') + 92 | theme( 93 | axis.title.x=element_blank(), 94 | axis.title.y=element_blank(), 95 | plot.title=element_text(hjust=0.5), 96 | legend.position='bottom' 97 | ) 98 | 99 | # 4.2. Education 100 | conversionsByEducation <- conversionsDF %>% 101 | group_by(Education=education, Conversion=conversion) %>% 102 | summarise(Count=n()) 103 | 104 | conversionsByEducation 105 | 106 | # pie chart 107 | ggplot(conversionsByEducation, aes(x="", y=Count, fill=Education)) + 108 | geom_bar(width=1, stat = "identity", position=position_fill()) + 109 | geom_text(aes(x=1.25, label=Count), position=position_fill(vjust = 0.5)) + 110 | coord_polar("y") + 111 | facet_wrap(~Conversion) + 112 | ggtitle('Education (0: Non Conversions, 1: Conversions)') + 113 | theme( 114 | axis.title.x=element_blank(), 115 | axis.title.y=element_blank(), 116 | plot.title=element_text(hjust=0.5), 117 | legend.position='bottom' 118 | ) 119 | 120 | # 4.3. Last Contact Duration 121 | conversionsDF$duration <- conversionsDF$duration / (60*60) 122 | 123 | ggplot(conversionsDF, aes(x="", y=duration)) + 124 | geom_boxplot() + 125 | facet_wrap(~conversion) + 126 | ylab("duration (in hours)") + 127 | xlab("0: Non-Conversion, 1: Conversion") + 128 | ggtitle("Conversion vs. Non-Conversions: Last Contact Duration") + 129 | theme(plot.title=element_text(hjust=0.5)) 130 | 131 | #### 5. Conversions by Age Groups & Marital Status #### 132 | conversionsByAgeMarital <- conversionsDF %>% 133 | group_by(AgeGroup=cut(age, breaks= seq(20, 70, by = 10)), Marital=marital) %>% 134 | summarise(Count=n(), NumConversions=sum(conversion)) %>% 135 | mutate(TotalCount=sum(Count)) %>% 136 | mutate(ConversionRate=NumConversions/TotalCount) 137 | 138 | conversionsByAgeMarital$AgeGroup <- as.character(conversionsByAgeMarital$AgeGroup) 139 | conversionsByAgeMarital$AgeGroup[is.na(conversionsByAgeMarital$AgeGroup)] <- "70+" 140 | 141 | # bar chart 142 | ggplot(conversionsByAgeMarital, aes(x=AgeGroup, y=ConversionRate, fill=Marital)) + 143 | geom_bar(width=0.5, stat="identity", position="dodge") + 144 | ylab("Conversion Rate (%)") + 145 | xlab("Age") + 146 | ggtitle("Conversion Rates by Age and Marital Status") + 147 | theme(plot.title=element_text(hjust=0.5)) 148 | 149 | # stacked bar chart 150 | ggplot(conversionsByAgeMarital, aes(x=AgeGroup, y=ConversionRate, fill=Marital)) + 151 | geom_bar(width=0.5, stat="identity", position="stack") + 152 | ylab("Conversion Rate (%)") + 153 | xlab("Age") + 154 | ggtitle("Conversion Rates by Age and Marital Status") + 155 | theme(plot.title=element_text(hjust=0.5)) 156 | 157 | -------------------------------------------------------------------------------- /ch.3/R/RegressionAnalysis.R: -------------------------------------------------------------------------------- 1 | library(dplyr) 2 | library(ggplot2) 3 | 4 | # Load data 5 | df <- read.csv( 6 | file="~/Documents/data-science-for-marketing/ch.3/data/WA_Fn-UseC_-Marketing-Customer-Value-Analysis.csv", 7 | header=TRUE, 8 | sep="," 9 | ) 10 | 11 | #### 1. Engagement Rate #### 12 | 13 | # Encode Response as 0s and 1s 14 | df$Engaged <- as.integer(df$Response) - 1 15 | 16 | engagementRate <- df %>% 17 | group_by(Engaged) %>% 18 | summarise(Count=n()) %>% 19 | mutate(Percentage=Count/nrow(df)*100.0) 20 | 21 | engagementRate 22 | 23 | # Transpose 24 | transposed <- t(engagementRate) 25 | 26 | colnames(transposed) <- engagementRate$Engaged 27 | transposed <- transposed[-1,] 28 | transposed 29 | 30 | #### 2. Renewal Offer Type #### 31 | renewalOfferType <- df %>% 32 | group_by(Engaged, Type=Renew.Offer.Type) %>% 33 | summarise(Count=n()) 34 | 35 | renewalOfferType 36 | 37 | # pie chart 38 | ggplot(renewalOfferType, aes(x="", y=Count, fill=Type)) + 39 | geom_bar(width=1, stat = "identity", position=position_fill()) + 40 | geom_text(aes(x=1.25, label=Count), position=position_fill(vjust = 0.5)) + 41 | coord_polar("y") + 42 | facet_wrap(~Engaged) + 43 | ggtitle('Renwal Offer Type (0: Not Engaged, 1: Engaged)') + 44 | theme( 45 | axis.title.x=element_blank(), 46 | axis.title.y=element_blank(), 47 | plot.title=element_text(hjust=0.5), 48 | legend.position='bottom' 49 | ) 50 | 51 | #### 3. Sales Channel #### 52 | salesChannel <- df %>% 53 | group_by(Engaged, Channel=Sales.Channel) %>% 54 | summarise(Count=n()) 55 | 56 | salesChannel 57 | 58 | # pie chart 59 | ggplot(salesChannel, aes(x="", y=Count, fill=Channel)) + 60 | geom_bar(width=1, stat = "identity", position=position_fill()) + 61 | geom_text(aes(x=1.25, label=Count), position=position_fill(vjust = 0.5)) + 62 | coord_polar("y") + 63 | facet_wrap(~Engaged) + 64 | ggtitle('Sales Channel (0: Not Engaged, 1: Engaged)') + 65 | theme( 66 | axis.title.x=element_blank(), 67 | axis.title.y=element_blank(), 68 | plot.title=element_text(hjust=0.5), 69 | legend.position='bottom' 70 | ) 71 | 72 | #### 4. Total Claim Amount #### 73 | ggplot(df, aes(x="", y=Total.Claim.Amount)) + 74 | geom_boxplot() + 75 | facet_wrap(~Engaged) + 76 | ylab("Total Claim Amount") + 77 | xlab("0: Not Engaged, 1: Engaged") + 78 | ggtitle("Engaed vs. Not Engaged: Total Claim Amount") + 79 | theme(plot.title=element_text(hjust=0.5)) 80 | 81 | # without outliers 82 | ggplot(df, aes(x="", y=Total.Claim.Amount)) + 83 | geom_boxplot(outlier.shape = NA) + 84 | scale_y_continuous(limits = quantile(df$Total.Claim.Amount, c(0.1, 0.9))) + 85 | facet_wrap(~Engaged) + 86 | ylab("Total Claim Amount") + 87 | xlab("0: Not Engaged, 1: Engaged") + 88 | ggtitle("Engaed vs. Not Engaged: Total Claim Amount") + 89 | theme(plot.title=element_text(hjust=0.5)) 90 | 91 | #### 5. Income #### 92 | 93 | # boxplot 94 | ggplot(df, aes(x="", y=Income)) + 95 | geom_boxplot() + 96 | facet_wrap(~Engaged) + 97 | ylab("Income") + 98 | xlab("0: Not Engaged, 1: Engaged") + 99 | ggtitle("Engaed vs. Not Engaged: Income") + 100 | theme(plot.title=element_text(hjust=0.5)) 101 | 102 | # summary statistics 103 | incomeDescription <- df %>% 104 | group_by(Engaged) %>% 105 | summarise( 106 | Min=min(Income), Q1=quantile(Income, 0.25), 107 | Median=median(Income), Q3=quantile(Income, 0.75), 108 | Max=max(Income) 109 | ) 110 | 111 | incomeDescription 112 | 113 | 114 | #### 6. Regression Analysis #### 115 | # summary statistics per column 116 | summary(df) 117 | # get data types of each column 118 | sapply(df, class) 119 | 120 | ## 6.1. Continuous Variables ## 121 | 122 | # get numeric columns 123 | continuousDF <- select_if(df, is.numeric) 124 | colnames(continuousDF) 125 | 126 | # Fit regression model with continuous variables 127 | logit.fit <- glm(Engaged ~ ., data = continuousDF, family = binomial) 128 | summary(logit.fit) 129 | 130 | 131 | ## 6.2. Categorical Variables ## 132 | 133 | # a. Education 134 | # Fit regression model with Education factor variables 135 | logit.fit <- glm(Engaged ~ factor(Education), data = df, family = binomial) 136 | summary(logit.fit) 137 | 138 | # b. Education + Gender 139 | # Fit regression model with Education & Gender variables 140 | logit.fit <- glm(Engaged ~ factor(Education) + factor(Gender), data = df, family = binomial) 141 | summary(logit.fit) 142 | 143 | 144 | ## 6.3. Continuous & Categorical Variables ## 145 | 146 | continuousDF$Gender <- factor(df$Gender) 147 | continuousDF$Education <- factor(df$Education) 148 | 149 | # Fit regression model with Education & Gender variables 150 | logit.fit <- glm(Engaged ~ ., data = continuousDF, family = binomial) 151 | summary(logit.fit) 152 | 153 | -------------------------------------------------------------------------------- /ch.4/R/FromEngagementToConversions.R: -------------------------------------------------------------------------------- 1 | library(dplyr) 2 | library(ggplot2) 3 | 4 | # install.packages('rattle') 5 | library(rattle) 6 | 7 | # install.packages('rpart') 8 | library(rpart) # used to build decision tree 9 | 10 | # install.packages('rpart.plot') 11 | library(rpart.plot) 12 | 13 | #### 1. Load Data #### 14 | df <- read.csv( 15 | file="~/Documents/data-science-for-marketing/ch.4/data/bank-full.csv", 16 | header=TRUE, 17 | sep=";" 18 | ) 19 | 20 | # Encode conversions as 0s and 1s 21 | df$conversion <- as.integer(df$y) - 1 22 | 23 | head(df) 24 | 25 | #### 2. Data Analysis #### 26 | 27 | # column names 28 | colnames(df) 29 | 30 | #### 2.1. Conversion Rate #### 31 | sprintf("total conversions: %i out of %i", sum(df$conversion), nrow(df)) 32 | sprintf("conversion rate: %0.2f%%", sum(df$conversion)/nrow(df)*100.0) 33 | 34 | #### 2.2. Conversion Rates by Marital Status #### 35 | conversionsByMarital <- df %>% 36 | group_by(Marital=marital) %>% 37 | summarise(Count=n(), NumConversions=sum(conversion)) %>% 38 | mutate(ConversionRate=NumConversions/Count*100.0) 39 | 40 | conversionsByMarital 41 | 42 | ggplot(conversionsByMarital, aes(x=Marital, y=ConversionRate)) + 43 | geom_bar(width=0.5, stat="identity") + 44 | ggtitle('Conversion Rates by Marital Status') + 45 | xlab("Marital Status") + 46 | ylab("Conversion Rate (%)") + 47 | theme(plot.title = element_text(hjust = 0.5)) 48 | 49 | #### 2.2. Conversion Rates by Job #### 50 | conversionsByJob <- df %>% 51 | group_by(Job=job) %>% 52 | summarise(Count=n(), NumConversions=sum(conversion)) %>% 53 | mutate(ConversionRate=NumConversions/Count*100.0) 54 | 55 | conversionsByJob 56 | 57 | ggplot(conversionsByJob, aes(x=Job, y=ConversionRate)) + 58 | geom_bar(width=0.5, stat="identity") + 59 | coord_flip() + 60 | ggtitle('Conversion Rates by Job') + 61 | xlab("Job") + 62 | ylab("Conversion Rate (%)") + 63 | theme(plot.title = element_text(hjust = 0.5)) 64 | 65 | #### 2.3. Default Rates by Conversions #### 66 | defaultByConversion <- df %>% 67 | group_by(Default=default, Conversion=conversion) %>% 68 | summarise(Count=n()) 69 | 70 | defaultByConversion 71 | 72 | ggplot(defaultByConversion, aes(x="", y=Count, fill=Default)) + 73 | geom_bar(width=1, stat = "identity", position=position_fill()) + 74 | geom_text(aes(x=1.25, label=Count), position=position_fill(vjust = 0.5)) + 75 | coord_polar("y") + 76 | facet_wrap(~Conversion) + 77 | ggtitle('Default (0: Non Conversions, 1: Conversions)') + 78 | theme( 79 | axis.title.x=element_blank(), 80 | axis.title.y=element_blank(), 81 | plot.title=element_text(hjust=0.5), 82 | legend.position='bottom' 83 | ) 84 | 85 | 86 | #### 2.4. Bank Balance by Conversions #### 87 | ggplot(df, aes(x="", y=balance)) + 88 | geom_boxplot() + 89 | facet_wrap(~conversion) + 90 | ylab("balance") + 91 | xlab("0: Non-Conversion, 1: Conversion") + 92 | ggtitle("Conversion vs. Non-Conversions: Balance") + 93 | theme(plot.title=element_text(hjust=0.5)) 94 | 95 | ggplot(df, aes(x="", y=balance)) + 96 | geom_boxplot(outlier.shape = NA) + 97 | scale_y_continuous(limits = c(-2000, 5000)) + 98 | facet_wrap(~conversion) + 99 | ylab("balance") + 100 | xlab("0: Non-Conversion, 1: Conversion") + 101 | ggtitle("Conversion vs. Non-Conversions: Balance") + 102 | theme(plot.title=element_text(hjust=0.5)) 103 | 104 | #### 2.5. Conversions by Number of Contacts #### 105 | conversionsByNumContacts <- df %>% 106 | group_by(Campaign=campaign) %>% 107 | summarise(Count=n(), NumConversions=sum(conversion)) %>% 108 | mutate(ConversionRate=NumConversions/Count*100.0) 109 | 110 | conversionsByNumContacts 111 | 112 | ggplot(conversionsByNumContacts, aes(x=Campaign, y=ConversionRate)) + 113 | geom_bar(width=0.5, stat="identity") + 114 | ggtitle('Conversion Rates by Number of Contacts') + 115 | xlab("Number of Contacts") + 116 | ylab("Conversion Rate (%)") + 117 | theme(plot.title = element_text(hjust = 0.5)) 118 | 119 | #### 3. Encoding Categorical Variables #### 120 | rapply(df, function(x) length(unique(x))) 121 | 122 | #### 3.1. encoding 'month' #### 123 | # unique values 124 | unique(df$month) 125 | # convert to numbers 126 | months = lapply(month.abb, function(x) tolower(x)) 127 | months 128 | # test 129 | match(unique(df$month), months) 130 | 131 | # encode 132 | df$month <- match(df$month, months) 133 | # check 134 | df %>% 135 | group_by(month) %>% 136 | summarise(Count=n()) 137 | 138 | #### 3.2. encoding job, housing, marital #### 139 | df$job <- factor(df$job) 140 | df$housing <- factor(df$housing) 141 | df$marital <- factor(df$marital) 142 | 143 | 144 | #### 4. Fitting Decision Trees #### 145 | 146 | # grow tree 147 | fit <- rpart( 148 | conversion ~ age + balance + campaign + previous + housing + job + marital, 149 | method="class", 150 | data=df, 151 | control=rpart.control(maxdepth=4, cp=0.0001) 152 | ) 153 | 154 | # plot tree 155 | fancyRpartPlot(fit) 156 | -------------------------------------------------------------------------------- /ch.5/R/ProductAnalytics.R: -------------------------------------------------------------------------------- 1 | library(dplyr) 2 | library(ggplot2) 3 | 4 | # install.packages("readxl") 5 | library(readxl) 6 | # install.packages("lubridate") 7 | library(lubridate) 8 | 9 | 10 | #### 1. Load Data #### 11 | df <- read_excel( 12 | path="~/Documents/research/data-science-marketing/ch.5/data/Online Retail.xlsx", 13 | sheet="Online Retail" 14 | ) 15 | 16 | #### 2. Product Analytics #### 17 | 18 | #### - Quantity Distribution #### 19 | summary(df$Quantity) 20 | 21 | ggplot(df, aes(x="", y=Quantity)) + 22 | geom_boxplot(outlier.shape = NA) + 23 | ylim(c(-15, 25))+ 24 | ylab("order quantity") + 25 | xlab("") + 26 | ggtitle("Quantity Distribution") + 27 | theme(plot.title=element_text(hjust=0.5)) 28 | 29 | # filter out orders with negative quantity (cancel orders) 30 | dim(df[which(df$Quantity > 0),]) 31 | dim(df) 32 | 33 | df <- df[which(df$Quantity > 0),] 34 | dim(df) 35 | 36 | #### 2.1. Time-series Number of Orders #### 37 | timeSeriesNumInvoices <- df %>% 38 | group_by(InvoiceDate=floor_date(InvoiceDate, "month")) %>% 39 | summarise(NumOrders=n_distinct(InvoiceNo)) 40 | 41 | ggplot(timeSeriesNumInvoices, aes(x=InvoiceDate, y=NumOrders)) + 42 | geom_line() + 43 | ylim(c(0, max(timeSeriesNumInvoices$NumOrders) + 1000)) + 44 | ylab("number of orders") + 45 | xlab("date") + 46 | ggtitle("Number of Orders over Time") + 47 | theme(plot.title=element_text(hjust=0.5)) 48 | 49 | summary(df[which(df$InvoiceDate >= as.Date("2011-12-01")),"InvoiceDate"]) 50 | 51 | dim(df[which(df$InvoiceDate < as.Date("2011-12-01")),]) 52 | dim(df) 53 | 54 | df <- df[which(df$InvoiceDate < as.Date("2011-12-01")),] 55 | dim(df) 56 | 57 | timeSeriesNumInvoices <- df %>% 58 | group_by(InvoiceDate=floor_date(InvoiceDate, "month")) %>% 59 | summarise(NumOrders=n_distinct(InvoiceNo)) 60 | 61 | ggplot(timeSeriesNumInvoices, aes(x=InvoiceDate, y=NumOrders)) + 62 | geom_line()+ 63 | ylim(c(0, max(timeSeriesNumInvoices$NumOrders) + 100)) + 64 | ylab("number of orders") + 65 | xlab("date") + 66 | ggtitle("Number of Orders over Time") + 67 | theme(plot.title=element_text(hjust=0.5)) 68 | 69 | 70 | #### 2.2. Time-series Revenue #### 71 | df$Sales <- df$Quantity * df$UnitPrice 72 | 73 | timeSeriesRevenue <- df %>% 74 | group_by(InvoiceDate=floor_date(InvoiceDate, "month")) %>% 75 | summarise(Sales=sum(Sales)) 76 | 77 | ggplot(timeSeriesRevenue, aes(x=InvoiceDate, y=Sales)) + 78 | geom_line() + 79 | ylim(c(0, max(timeSeriesRevenue$Sales) + 10000)) + 80 | ylab("sales") + 81 | xlab("date") + 82 | ggtitle("Revenue over Time") + 83 | theme(plot.title=element_text(hjust=0.5)) 84 | 85 | #### 2.3. Revenue from Repeat Customers #### 86 | 87 | # Repeat Customers 88 | invoiceCustomerDF <- df %>% 89 | group_by(InvoiceNo, InvoiceDate) %>% 90 | summarise(CustomerID=max(CustomerID), Sales=sum(Sales)) 91 | 92 | timeSeriesCustomerDF <- invoiceCustomerDF %>% 93 | group_by(InvoiceDate=floor_date(InvoiceDate, "month"), CustomerID) %>% 94 | summarise(Count=n_distinct(InvoiceNo), Sales=sum(Sales)) 95 | 96 | repeatCustomers <- na.omit(timeSeriesCustomerDF[which(timeSeriesCustomerDF$Count > 1),]) 97 | 98 | timeSeriesRepeatCustomers <- repeatCustomers %>% 99 | group_by(InvoiceDate) %>% 100 | summarise(Count=n_distinct(CustomerID), Sales=sum(Sales)) 101 | 102 | # Unique Customers 103 | timeSeriesUniqCustomers <- df %>% 104 | group_by(InvoiceDate=floor_date(InvoiceDate, "month")) %>% 105 | summarise(Count=n_distinct(CustomerID)) 106 | 107 | timeSeriesRepeatCustomers$Perc <- timeSeriesRepeatCustomers$Sales / timeSeriesRevenue$Sales*100.0 108 | timeSeriesRepeatCustomers$Total <- timeSeriesUniqCustomers$Count 109 | 110 | ggplot(timeSeriesRepeatCustomers) + 111 | geom_line(aes(x=InvoiceDate, y=Total), stat="identity", color="navy") + 112 | geom_line(aes(x=InvoiceDate, y=Count), stat="identity", color="orange") + 113 | geom_bar(aes(x=InvoiceDate, y=Perc*20), stat="identity", fill='gray', alpha=0.5) + 114 | scale_y_continuous(sec.axis = sec_axis(~./20, name="Percentage (%)")) + 115 | ggtitle("Number of Unique vs. Repeat & Revenue from Repeat Customers") + 116 | theme(plot.title=element_text(hjust=0.5)) 117 | 118 | #### 2.4. Popular Items Over Time #### 119 | popularItems <- df %>% 120 | group_by(InvoiceDate=floor_date(InvoiceDate, "month"), StockCode) %>% 121 | summarise(Quantity=sum(Quantity)) 122 | 123 | top5Items <- popularItems[ 124 | which(popularItems$InvoiceDate == as.Date("2011-11-01")), 125 | ] %>% 126 | arrange(desc(Quantity)) %>% 127 | head(5) 128 | 129 | timeSeriesTop5 <- popularItems[ 130 | which(popularItems$StockCode %in% top5Items$StockCode), 131 | ] 132 | 133 | ggplot(timeSeriesTop5, aes(x=InvoiceDate, y=Quantity, color=StockCode)) + 134 | geom_line() + 135 | ylab("number of purchases") + 136 | xlab("date") + 137 | ggtitle("Top 5 Popular Items over Time") + 138 | theme(plot.title=element_text(hjust=0.5)) 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /ch.6/R/ProductRecommendation.R: -------------------------------------------------------------------------------- 1 | library(dplyr) 2 | library(readxl) 3 | 4 | #### 1. Load Data #### 5 | df <- read_excel( 6 | path="~/Documents/research/data-science-marketing/ch.6/data/Online Retail.xlsx", 7 | sheet="Online Retail" 8 | ) 9 | 10 | # ignore cancel orders 11 | df <- df[which(df$Quantity > 0),] 12 | 13 | #### 2. Data Preparation #### 14 | 15 | ## 2.1. Handle NaNs in CustomerID field 16 | 17 | # there are 133,361 records with no CustomerID 18 | sum(is.na(df$CustomerID)) 19 | # sneak peek at records with no CustomerID 20 | head(df[which(is.na(df$CustomerID)),]) 21 | 22 | # current DataFrame shape 23 | dim(df) 24 | 25 | # remove records with NA 26 | df <- na.omit(df) 27 | dim(df) 28 | 29 | ## 2.2. Customer-Item Matrix 30 | # install.packages("reshape2") 31 | library(reshape2) 32 | 33 | customerItemMatrix <- dcast( 34 | df, CustomerID ~ StockCode, value.var="Quantity" 35 | ) 36 | # 0-1 encode 37 | encode_fn <- function(x) {as.integer(x > 0)} 38 | customerItemMatrix <- customerItemMatrix %>% 39 | mutate_at(vars(-CustomerID), funs(encode_fn)) 40 | 41 | #### 3. Collaborative Filtering ### 42 | # install.packages("coop") 43 | library(coop) 44 | 45 | ## 3.1. User-based Collaborative Filtering 46 | 47 | # User-to-User Similarity Matrix 48 | userToUserSimMatrix <- cosine( 49 | as.matrix( 50 | # excluding CustomerID column 51 | t(customerItemMatrix[, 2:dim(customerItemMatrix)[2]]) 52 | ) 53 | ) 54 | colnames(userToUserSimMatrix) <- customerItemMatrix$CustomerID 55 | 56 | # Making Recommendations 57 | top10SimilarCustomersTo12350 <- customerItemMatrix$CustomerID[ 58 | order(userToUserSimMatrix[,"12350"], decreasing = TRUE)[1:11] 59 | ] 60 | 61 | itemsBoughtByA <- customerItemMatrix[ 62 | which(customerItemMatrix$CustomerID == "12350"), 63 | ] 64 | itemsBoughtByA <- colnames(customerItemMatrix)[which(itemsBoughtByA != 0)] 65 | 66 | itemsBoughtByB <- customerItemMatrix[ 67 | which(customerItemMatrix$CustomerID == "17935"), 68 | ] 69 | itemsBoughtByB <- colnames(customerItemMatrix)[which(itemsBoughtByB != 0)] 70 | 71 | itemsToRecommendToB <- setdiff(itemsBoughtByA, itemsBoughtByB) 72 | itemsToRecommendToB 73 | 74 | itemsToRecommendToBDescriptions <- unique( 75 | df[ 76 | which(df$StockCode %in% itemsToRecommendToB), 77 | c("StockCode", "Description") 78 | ] 79 | ) 80 | itemsToRecommendToBDescriptions <- itemsToRecommendToBDescriptions[ 81 | match(itemsToRecommendToB, itemsToRecommendToBDescriptions$StockCode), 82 | ] 83 | 84 | 85 | ## 3.2. Item-based Collaborative Filtering 86 | 87 | # Item-to-Item Similarity Matrix 88 | itemToItemSimMatrix <- cosine( 89 | as.matrix( 90 | # excluding CustomerID column 91 | customerItemMatrix[, 2:dim(customerItemMatrix)[2]] 92 | ) 93 | ) 94 | 95 | # Making Recommendations 96 | top10SimilarItemsTo23166 <- colnames(itemToItemSimMatrix)[ 97 | order(itemToItemSimMatrix[,"23166"], decreasing = TRUE)[1:11] 98 | ] 99 | top10SimilarItemsTo23166 100 | 101 | top10SimilarItemDescriptions <- unique( 102 | df[ 103 | which(df$StockCode %in% top10SimilarItemsTo23166), 104 | c("StockCode", "Description") 105 | ] 106 | ) 107 | top10SimilarItemDescriptions <- top10SimilarItemDescriptions[ 108 | match(top10SimilarItemsTo23166, top10SimilarItemDescriptions$StockCode), 109 | ] 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /ch.7/R/CustomerBehaviors.R: -------------------------------------------------------------------------------- 1 | library(dplyr) 2 | library(ggplot2) 3 | 4 | #### 1. Load Data #### 5 | df <- read.csv( 6 | file="~/Documents/data-science-for-marketing/ch.7/data/WA_Fn-UseC_-Marketing-Customer-Value-Analysis.csv", 7 | header=TRUE 8 | ) 9 | 10 | # Encode engaged customers as 0s and 1s 11 | df$Engaged <- as.integer(df$Response) - 1 12 | 13 | #### 2. Analytics on Engaged Customers #### 14 | 15 | ## - Overall Engagement Rates ## 16 | engagementRate <- df %>% group_by(Response) %>% 17 | summarise(Count=n()) %>% 18 | mutate(EngagementRate=Count/nrow(df)*100.0) 19 | 20 | ggplot(engagementRate, aes(x=Response, y=EngagementRate)) + 21 | geom_bar(width=0.5, stat="identity") + 22 | ggtitle('Engagement Rate') + 23 | xlab("Engaged") + 24 | ylab("Percentage (%)") + 25 | theme(plot.title = element_text(hjust = 0.5)) 26 | 27 | 28 | ## - Engagement Rates by Offer Type ## 29 | engagementRateByOfferType <- df %>% 30 | group_by(Renew.Offer.Type) %>% 31 | summarise(Count=n(), NumEngaged=sum(Engaged)) %>% 32 | mutate(EngagementRate=NumEngaged/Count*100.0) 33 | 34 | ggplot(engagementRateByOfferType, aes(x=Renew.Offer.Type, y=EngagementRate)) + 35 | geom_bar(width=0.5, stat="identity") + 36 | ggtitle('Engagement Rates by Offer Type') + 37 | xlab("Offer Type") + 38 | ylab("Engagement Rate (%)") + 39 | theme(plot.title = element_text(hjust = 0.5)) 40 | 41 | 42 | ## - Offer Type & Vehicle Class ## 43 | engagementRateByOfferTypeVehicleClass <- df %>% 44 | group_by(Renew.Offer.Type, Vehicle.Class) %>% 45 | summarise(NumEngaged=sum(Engaged)) %>% 46 | left_join(engagementRateByOfferType[,c("Renew.Offer.Type", "Count")], by="Renew.Offer.Type") %>% 47 | mutate(EngagementRate=NumEngaged/Count*100.0) 48 | 49 | ggplot(engagementRateByOfferTypeVehicleClass, aes(x=Renew.Offer.Type, y=EngagementRate, fill=Vehicle.Class)) + 50 | geom_bar(width=0.5, stat="identity", position = "dodge") + 51 | ggtitle('Engagement Rates by Offer Type & Vehicle Class') + 52 | xlab("Offer Type") + 53 | ylab("Engagement Rate (%)") + 54 | theme(plot.title = element_text(hjust = 0.5)) 55 | 56 | 57 | ## - Engagement Rates by Sales Channel ## 58 | engagementRateBySalesChannel <- df %>% 59 | group_by(Sales.Channel) %>% 60 | summarise(Count=n(), NumEngaged=sum(Engaged)) %>% 61 | mutate(EngagementRate=NumEngaged/Count*100.0) 62 | 63 | ggplot(engagementRateBySalesChannel, aes(x=Sales.Channel, y=EngagementRate)) + 64 | geom_bar(width=0.5, stat="identity") + 65 | ggtitle('Engagement Rates by Sales Channel') + 66 | xlab("Sales Channel") + 67 | ylab("Engagement Rate (%)") + 68 | theme(plot.title = element_text(hjust = 0.5)) 69 | 70 | 71 | ## - Sales Channel & Vehicle Size ## 72 | engagementRateBySalesChannelVehicleSize <- df %>% 73 | group_by(Sales.Channel, Vehicle.Size) %>% 74 | summarise(NumEngaged=sum(Engaged)) %>% 75 | left_join(engagementRateBySalesChannel[,c("Sales.Channel", "Count")], by="Sales.Channel") %>% 76 | mutate(EngagementRate=NumEngaged/Count*100.0) 77 | 78 | ggplot(engagementRateBySalesChannelVehicleSize, aes(x=Sales.Channel, y=EngagementRate, fill=Vehicle.Size)) + 79 | geom_bar(width=0.5, stat="identity", position = "dodge") + 80 | ggtitle('Engagement Rates by Sales Channel & Vehicle Size') + 81 | xlab("Sales Channel") + 82 | ylab("Engagement Rate (%)") + 83 | theme(plot.title = element_text(hjust = 0.5)) 84 | 85 | 86 | ## - Engagement Rates by Months Since Policy Inception ## 87 | engagementRateByPolicyAge <- df %>% 88 | group_by(Months.Since.Policy.Inception) %>% 89 | summarise(Count=n(), NumEngaged=sum(Engaged)) %>% 90 | mutate(EngagementRate=NumEngaged/Count*100.0) 91 | 92 | ggplot(engagementRateByPolicyAge, aes(x=Months.Since.Policy.Inception, y=EngagementRate)) + 93 | geom_line() + 94 | ylab("Engagement Rate (%)") + 95 | xlab("Months Since Policy Inception") + 96 | ggtitle("Engagement Rates by Months Since Policy Inception") + 97 | theme(plot.title=element_text(hjust=0.5)) 98 | 99 | 100 | #### 3. Customer Segmentation by CLV & Months Since Inception #### 101 | summary(df$Customer.Lifetime.Value) 102 | summary(df$Months.Since.Policy.Inception) 103 | 104 | clv_encode_fn <- function(x) {if(x > median(df$Customer.Lifetime.Value)) "High" else "Low"} 105 | df$CLV.Segment <- sapply(df$Customer.Lifetime.Value, clv_encode_fn) 106 | 107 | policy_age_encode_fn <- function(x) {if(x > median(df$Months.Since.Policy.Inception)) "High" else "Low"} 108 | df$Policy.Age.Segment <- sapply(df$Months.Since.Policy.Inception, policy_age_encode_fn) 109 | 110 | ggplot( 111 | df[which(df$CLV.Segment=="High" & df$Policy.Age.Segment=="High"),], 112 | aes(x=Months.Since.Policy.Inception, y=log(Customer.Lifetime.Value)) 113 | ) + 114 | geom_point(color='red') + 115 | geom_point( 116 | data=df[which(df$CLV.Segment=="High" & df$Policy.Age.Segment=="Low"),], 117 | color='orange' 118 | ) + 119 | geom_point( 120 | data=df[which(df$CLV.Segment=="Low" & df$Policy.Age.Segment=="Low"),], 121 | color='green' 122 | ) + 123 | geom_point( 124 | data=df[which(df$CLV.Segment=="Low" & df$Policy.Age.Segment=="High"),], 125 | color='blue' 126 | ) + 127 | ggtitle('Segments by CLV and Policy Age') + 128 | xlab("Months Since Policy Inception") + 129 | ylab("CLV (in log scale)") + 130 | theme(plot.title = element_text(hjust = 0.5)) 131 | 132 | engagementRateBySegment <- df %>% 133 | group_by(CLV.Segment, Policy.Age.Segment) %>% 134 | summarise(Count=n(), NumEngaged=sum(Engaged)) %>% 135 | mutate(EngagementRate=NumEngaged/Count*100.0) 136 | 137 | ggplot(engagementRateBySegment, aes(x=CLV.Segment, y=EngagementRate, fill=Policy.Age.Segment)) + 138 | geom_bar(width=0.5, stat="identity", position = "dodge") + 139 | ggtitle('Engagement Rates by Customer Segments') + 140 | ylab("Engagement Rate (%)") + 141 | theme(plot.title = element_text(hjust = 0.5)) 142 | 143 | -------------------------------------------------------------------------------- /ch.8/R/PredictingEngagement.R: -------------------------------------------------------------------------------- 1 | library(dplyr) 2 | library(ggplot2) 3 | 4 | #### 1. Load Data #### 5 | df <- read.csv( 6 | file="~/Documents/data-science-for-marketing/ch.8/data/WA_Fn-UseC_-Marketing-Customer-Value-Analysis.csv", 7 | header=TRUE 8 | ) 9 | 10 | #### 2. Variable Encoding #### 11 | 12 | ## 2.1. Response Variable: Response 13 | df$Engaged <- as.integer(df$Response) - 1 14 | mean(df$Engaged) 15 | 16 | ## 2.2. Categorical Features 17 | categoricalVars = c( 18 | 'Sales.Channel', 'Vehicle.Size', 'Vehicle.Class', 'Policy', 'Policy.Type', 19 | 'EmploymentStatus', 'Marital.Status', 'Education', 'Coverage', 'Gender' 20 | ) 21 | 22 | encodedDF <- model.matrix(~.-1, df[categoricalVars]) 23 | 24 | ## 2.3. Continuous Features 25 | continuousFeatures <- c( 26 | 'Customer.Lifetime.Value', 'Income', 'Monthly.Premium.Auto', 27 | 'Months.Since.Last.Claim', 'Months.Since.Policy.Inception', 28 | 'Number.of.Open.Complaints', 'Number.of.Policies', 'Total.Claim.Amount' 29 | ) 30 | 31 | encodedDF <- cbind(encodedDF, df[continuousFeatures]) 32 | 33 | 34 | #### 3. Training & Testing #### 35 | 36 | # install.packages('caTools') 37 | library(caTools) 38 | 39 | sample <- sample.split(df$Customer, SplitRatio = .7) 40 | 41 | trainX <- as.matrix(subset(encodedDF, sample == TRUE)) 42 | trainY <- as.double(as.matrix(subset(df$Engaged, sample == TRUE))) 43 | 44 | testX <- as.matrix(subset(encodedDF, sample == FALSE)) 45 | testY <- as.double(as.matrix(subset(df$Engaged, sample == FALSE))) 46 | 47 | ## 3.1. Building Random Forest Model 48 | 49 | # - Training 50 | # install.packages('randomForest') 51 | library(randomForest) 52 | 53 | rfModel <- randomForest(x=trainX, y=factor(trainY), ntree=200, maxnodes=24) 54 | 55 | # - Individual Tree Predictions 56 | getTree(rfModel, 1) 57 | predict(rfModel, trainX, predict.all=TRUE)$individual 58 | 59 | # - Feature Importances 60 | importance(rfModel) 61 | 62 | ## 3.2. Evaluating Models 63 | 64 | inSamplePreds <- as.double(predict(rfModel, trainX)) - 1 65 | outSamplePreds <- as.double(predict(rfModel, testX)) - 1 66 | 67 | # - Accuracy, Precision, and Recall 68 | inSampleAccuracy <- mean(trainY == inSamplePreds) 69 | outSampleAccuracy <- mean(testY == outSamplePreds) 70 | print(sprintf('In-Sample Accuracy: %0.4f', inSampleAccuracy)) 71 | print(sprintf('Out-Sample Accuracy: %0.4f', outSampleAccuracy)) 72 | 73 | inSamplePrecision <- sum(inSamplePreds & trainY) / sum(inSamplePreds) 74 | outSamplePrecision <- sum(outSamplePreds & testY) / sum(outSamplePreds) 75 | print(sprintf('In-Sample Precision: %0.4f', inSamplePrecision)) 76 | print(sprintf('Out-Sample Precision: %0.4f', outSamplePrecision)) 77 | 78 | inSampleRecall <- sum(inSamplePreds & trainY) / sum(trainY) 79 | outSampleRecall <- sum(outSamplePreds & testY) / sum(testY) 80 | print(sprintf('In-Sample Recall: %0.4f', inSampleRecall)) 81 | print(sprintf('Out-Sample Recall: %0.4f', outSampleRecall)) 82 | 83 | # - ROC & AUC 84 | # install.packages('ROCR') 85 | library(ROCR) 86 | 87 | inSamplePredProbs <- as.double(predict(rfModel, trainX, type='prob')[,2]) 88 | outSamplePredProbs <- as.double(predict(rfModel, testX, type='prob')[,2]) 89 | 90 | pred <- prediction(outSamplePredProbs, testY) 91 | perf <- performance(pred, measure = "tpr", x.measure = "fpr") 92 | auc <- performance(pred, measure='auc')@y.values[[1]] 93 | 94 | plot( 95 | perf, 96 | main=sprintf('Random Forest Model ROC Curve (AUC: %0.2f)', auc), 97 | col='darkorange', 98 | lwd=2 99 | ) + grid() 100 | abline(a = 0, b = 1, col='darkgray', lty=3, lwd=2) 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /ch.9/R/CustomerLifetimeValue.R: -------------------------------------------------------------------------------- 1 | library(dplyr) 2 | library(readxl) 3 | library(ggplot2) 4 | 5 | #### 1. Load Data #### 6 | df <- read_excel( 7 | path="~/Documents/data-science-for-marketing/ch.9/data/Online Retail.xlsx", 8 | sheet="Online Retail" 9 | ) 10 | 11 | #### 2. Date Clean-Up #### 12 | 13 | # ignore negative quantity 14 | dim(df) 15 | df <- df[which(df$Quantity > 0),] 16 | dim(df) 17 | 18 | # remove records with NA 19 | df <- na.omit(df) 20 | dim(df) 21 | 22 | # excluding incomplete month 23 | sprintf("Date Range: %s ~ %s", min(df$InvoiceDate), max(df$InvoiceDate)) 24 | dim(df) 25 | df <- df[which(df$InvoiceDate < '2011-12-01'),] 26 | dim(df) 27 | 28 | # total sales 29 | df$Sales <- df$Quantity * df$UnitPrice 30 | 31 | # per order data 32 | ordersDF <- df %>% 33 | group_by(CustomerID, InvoiceNo) %>% 34 | summarize(Sales=sum(Sales), InvoiceDate=max(InvoiceDate)) 35 | 36 | #### 3. Date Analysis #### 37 | 38 | # order amount & frequency summary 39 | summaryDF <- ordersDF %>% 40 | group_by(CustomerID) %>% 41 | summarize( 42 | SalesMin=min(Sales), SalesMax=max(Sales), SalesSum=sum(Sales), SalesAvg=mean(Sales), SalesCount=n(), 43 | InvoiceDateMin=min(InvoiceDate), InvoiceDateMax=max(InvoiceDate), 44 | PurchaseDuration=as.double(floor(max(InvoiceDate)-min(InvoiceDate))), 45 | PurchaseFrequency=as.double(floor(max(InvoiceDate)-min(InvoiceDate)))/n() 46 | ) 47 | 48 | # customers with repeat purchases 49 | dim(summaryDF) 50 | summaryDF <- summaryDF[which(summaryDF$PurchaseDuration > 0),] 51 | dim(summaryDF) 52 | 53 | salesCount <- summaryDF %>% 54 | group_by(SalesCount) %>% 55 | summarize(Count=n()) 56 | 57 | ggplot(salesCount[1:19,], aes(x=SalesCount, y=Count)) + 58 | geom_bar(width=0.5, stat="identity") + 59 | ggtitle('') + 60 | xlab("Sales Count") + 61 | ylab("Count") + 62 | theme(plot.title = element_text(hjust = 0.5)) 63 | 64 | summary(summaryDF$SalesCount) 65 | summary(summaryDF$SalesAvg) 66 | 67 | hist( 68 | summaryDF$PurchaseFrequency, 69 | breaks=20, 70 | xlab='avg. number of days between purchases', 71 | ylab='count', 72 | main='' 73 | ) 74 | 75 | summary(summaryDF$PurchaseDuration) 76 | summary(summaryDF$PurchaseFrequency) 77 | 78 | 79 | #### 4. Predicting 3-Month CLV #### 80 | 81 | ## 4.1. Data Prep ## 82 | 83 | # group data into every 3 months 84 | library(lubridate) 85 | 86 | ordersDF$Quarter = as.character(round_date(ordersDF$InvoiceDate, '3 months')) 87 | 88 | dataDF <- ordersDF %>% 89 | group_by(CustomerID, Quarter) %>% 90 | summarize(SalesSum=sum(Sales), SalesAvg=mean(Sales), SalesCount=n()) 91 | 92 | dataDF$Quarter[dataDF$Quarter == "2012-01-01"] <- "Q1" 93 | dataDF$Quarter[dataDF$Quarter == "2011-10-01"] <- "Q2" 94 | dataDF$Quarter[dataDF$Quarter == "2011-07-01"] <- "Q3" 95 | dataDF$Quarter[dataDF$Quarter == "2011-04-01"] <- "Q4" 96 | dataDF$Quarter[dataDF$Quarter == "2011-01-01"] <- "Q5" 97 | 98 | # building sample set 99 | # install.packages('reshape2') 100 | library(reshape2) 101 | 102 | salesSumFeaturesDF <- dcast( 103 | dataDF[which(dataDF$Quarter != "Q1"),], 104 | CustomerID ~ Quarter, 105 | value.var="SalesSum" 106 | ) 107 | colnames(salesSumFeaturesDF) <- c("CustomerID", "SalesSum.Q2", "SalesSum.Q3", "SalesSum.Q4", "SalesSum.Q5") 108 | 109 | salesAvgFeaturesDF <- dcast( 110 | dataDF[which(dataDF$Quarter != "Q1"),], 111 | CustomerID ~ Quarter, 112 | value.var="SalesAvg" 113 | ) 114 | colnames(salesAvgFeaturesDF) <- c("CustomerID", "SalesAvg.Q2", "SalesAvg.Q3", "SalesAvg.Q4", "SalesAvg.Q5") 115 | 116 | salesCountFeaturesDF <- dcast( 117 | dataDF[which(dataDF$Quarter != "Q1"),], 118 | CustomerID ~ Quarter, 119 | value.var="SalesCount" 120 | ) 121 | colnames(salesCountFeaturesDF) <- c("CustomerID", "SalesCount.Q2", "SalesCount.Q3", "SalesCount.Q4", "SalesCount.Q5") 122 | 123 | featuresDF <- merge( 124 | merge(salesSumFeaturesDF, salesAvgFeaturesDF, by="CustomerID"), 125 | salesCountFeaturesDF, by="CustomerID" 126 | ) 127 | featuresDF[is.na(featuresDF)] <- 0 128 | 129 | responseDF <- dataDF[which(dataDF$Quarter == "Q1"),] %>% 130 | select(CustomerID, SalesSum) 131 | colnames(responseDF) <- c("CustomerID", "CLV_3_Month") 132 | 133 | sampleDF <- merge(featuresDF, responseDF, by="CustomerID", all.x=TRUE) 134 | sampleDF[is.na(sampleDF)] <- 0 135 | 136 | summary(sampleDF$CLV_3_Month) 137 | 138 | ## 4.2. Regression Models ## 139 | 140 | # train/test set split 141 | library(caTools) 142 | 143 | sample <- sample.split(sampleDF$CustomerID, SplitRatio = .8) 144 | 145 | train <- as.data.frame(subset(sampleDF, sample == TRUE))[,-1] 146 | test <- as.data.frame(subset(sampleDF, sample == FALSE))[,-1] 147 | 148 | # Linear Regression model 149 | regFit <- lm(CLV_3_Month ~ ., data=train) 150 | 151 | summary(regFit) 152 | 153 | ## 4.3. Evaluation ## 154 | train_preds <- predict(regFit, train) 155 | test_preds <- predict(regFit, test) 156 | 157 | # R-squared 158 | # install.packages('miscTools') 159 | library(miscTools) 160 | 161 | inSampleR2 <- rSquared(train$CLV_3_Month, resid=train$CLV_3_Month - train_preds) 162 | outOfSampleR2 <- rSquared(test$CLV_3_Month, resid=test$CLV_3_Month - test_preds) 163 | 164 | sprintf('In-Sample R-Squared: %0.4f', inSampleR2) 165 | sprintf('Out-of-Sample R-Squared: %0.4f', outOfSampleR2) 166 | 167 | # Median Absolute Error 168 | inSampleMAE <- median(abs(train$CLV_3_Month - train_preds)) 169 | outOfSampleMAE <- median(abs(test$CLV_3_Month - test_preds)) 170 | 171 | sprintf('In-Sample MAE: %0.4f', inSampleMAE) 172 | sprintf('Out-of-Sample MAE: %0.4f', outOfSampleMAE) 173 | 174 | # Actual vs. Predicted Scatter Plot 175 | plot( 176 | train$CLV_3_Month, 177 | train_preds, 178 | xlab='actual', 179 | ylab='predicted', 180 | main='In-Sample Actual vs. Predicted' 181 | ) 182 | abline(a=0, b=1) 183 | 184 | plot( 185 | test$CLV_3_Month, 186 | test_preds, 187 | xlab='actual', 188 | ylab='predicted', 189 | main='Out-of-Sample Actual vs. Predicted' 190 | ) 191 | abline(a=0, b=1) 192 | 193 | 194 | 195 | 196 | --------------------------------------------------------------------------------