Companies often spend significant resources on marketing campaigns without fully understanding which customers are most likely to respond. Sending the same promotions to all customers can result in low response rates and inefficient use of marketing budgets.
This project seeks to identify customer segments based on demographics and purchasing behavior and determine which characteristics are associated with successful campaign responses.
The goal of this project is to use customer analytics to better understand customer behavior, specifically their spending patterns to improve market decision-making.
What demographic characteristics are associated with higher customer spending?
What item(s) do they typically buy?
Can customers be segmented into meaningful groups based on purchasing behavior?
Which customer characteristics are associated with positive campaign responses?
How can marketers improve campaign targeting using customer segmentation?
Which customers should receive future marketing promotions?
Tailoring Marketing Campaigns
Customer Groups
Customer relationship management and targeted marketing have become increasingly important in today’s competitive business environment. Organizations that understand customer behavior can better allocate marketing resources and improve campaign effectiveness. The Customer Personality Analysis dataset provides demographic, purchasing, and campaign response information that can be used to better understand customer preferences and marketing outcomes.
The purpose of this project is to analyze customer characteristics and purchasing behavior to identify meaningful customer segments and evaluate factors associated with marketing campaign success. Using customer segmentation and predictive analytics techniques, this study seeks to provide actionable recommendations that improve marketing targeting and increase customer engagement.
The goal of this exploratory data analysis is to understand customer demographics, income, spending behavior, purchase channels, and marketing campaign responses.
This EDA will generate summary statistics, visualizations, trends, patterns, anomalies, and business insights for the Customer Personality Analysis dataset.
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.1 ✔ readr 2.2.0
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.3 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
##
## Attaching package: 'janitor'
##
##
## The following objects are masked from 'package:stats':
##
## chisq.test, fisher.test
##
##
##
## Attaching package: 'naniar'
##
##
## The following object is masked from 'package:skimr':
##
## n_complete
##
##
## corrplot 0.95 loaded
##
##
## Attaching package: 'kableExtra'
##
##
## The following object is masked from 'package:dplyr':
##
## group_rows
##
##
##
## Attaching package: 'scales'
##
##
## The following object is masked from 'package:purrr':
##
## discard
##
##
## The following object is masked from 'package:readr':
##
## col_factor
##
##
## Loading required package: lattice
##
##
## Attaching package: 'caret'
##
##
## The following object is masked from 'package:purrr':
##
## lift
##
##
## Type 'citation("pROC")' for a citation.
##
##
## Attaching package: 'pROC'
##
##
## The following objects are masked from 'package:stats':
##
## cov, smooth, var
| Item | Value |
|---|---|
| Rows | 2240 |
| Columns | 29 |
| Variable | Type | Missing_Values | |
|---|---|---|---|
| id | id | integer | 0 |
| year_birth | year_birth | integer | 0 |
| education | education | character | 0 |
| marital_status | marital_status | character | 0 |
| income | income | integer | 24 |
| kidhome | kidhome | integer | 0 |
| teenhome | teenhome | integer | 0 |
| dt_customer | dt_customer | character | 0 |
| recency | recency | integer | 0 |
| mnt_wines | mnt_wines | integer | 0 |
| mnt_fruits | mnt_fruits | integer | 0 |
| mnt_meat_products | mnt_meat_products | integer | 0 |
| mnt_fish_products | mnt_fish_products | integer | 0 |
| mnt_sweet_products | mnt_sweet_products | integer | 0 |
| mnt_gold_prods | mnt_gold_prods | integer | 0 |
| num_deals_purchases | num_deals_purchases | integer | 0 |
| num_web_purchases | num_web_purchases | integer | 0 |
| num_catalog_purchases | num_catalog_purchases | integer | 0 |
| num_store_purchases | num_store_purchases | integer | 0 |
| num_web_visits_month | num_web_visits_month | integer | 0 |
| accepted_cmp3 | accepted_cmp3 | integer | 0 |
| accepted_cmp4 | accepted_cmp4 | integer | 0 |
| accepted_cmp5 | accepted_cmp5 | integer | 0 |
| accepted_cmp1 | accepted_cmp1 | integer | 0 |
| accepted_cmp2 | accepted_cmp2 | integer | 0 |
| complain | complain | integer | 0 |
| z_cost_contact | z_cost_contact | integer | 0 |
| z_revenue | z_revenue | integer | 0 |
| response | response | integer | 0 |
| id | year_birth | education | marital_status | income | recency |
|---|---|---|---|---|---|
| 5524 | 1957 | Graduation | Single | 58138 | 58 |
| 2174 | 1954 | Graduation | Single | 46344 | 38 |
| 4141 | 1965 | Graduation | Together | 71613 | 26 |
| 6182 | 1984 | Graduation | Together | 26646 | 26 |
| 5324 | 1981 | PhD | Married | 58293 | 94 |
| 7446 | 1967 | Master | Together | 62513 | 16 |
| 965 | 1971 | Graduation | Divorced | 55635 | 34 |
| 6177 | 1985 | PhD | Married | 33454 | 32 |
| 4855 | 1974 | PhD | Together | 30351 | 19 |
| 5899 | 1950 | PhD | Together | 5648 | 68 |
| Variable | Missing_Values |
|---|---|
| income | 24 |
One of the main data quality issues we found was that the Income variable contained 24 missing values, while all other variables were complete. Because income is an important factor in understanding customer purchasing behavior, we couldn’t simply ignore these missing values. Instead of deleting those records, we replaced the missing income values with the median income. We chose the median because it is less affected by extreme values than the mean, making it a better representation of a typical customer’s income
| Cleaning_Check | Count |
|---|---|
| Duplicate Rows | 0 |
| id | income | education | marital_status |
|---|---|---|---|
| 8475 | 157243 | PhD | Married |
| 1503 | 162397 | PhD | Together |
| 5555 | 153924 | Graduation | Divorced |
| 1501 | 160803 | PhD | Married |
| 5336 | 157733 | Master | Together |
| 4931 | 157146 | Graduation | Together |
| 11181 | 156924 | PhD | Married |
| 9432 | 666666 | Graduation | Together |
Next, we checked for duplicate records and found zero duplicate rows, so no records needed to be removed for duplication.
We also converted the customer enrollment date into the proper date format so it could be used correctly in later analyses. After that, we examined the income variable for unusual values using the Interquartile Range, or IQR, method. This identified several extremely high-income outliers, including one value of 666,666, which was much higher than the rest of the dataset. These extreme outliers were removed because they could distort summary statistics and influence our models.
Finally, we prepared the data for exploratory analysis by creating several new variables through feature engineering, including customer age, total spending, total purchases, number of children, and total campaigns accepted. These new variables allowed us to better analyze customer behavior and served as important inputs for the clustering and logistic regression models later in the project.
Overall, these preprocessing steps improved the quality of the dataset and ensured that our analyses and business recommendations were based on clean, consistent, and reliable data.
| Statistic | Value |
|---|---|
| age_Mean | 57.21 |
| age_Median | 56.00 |
| age_SD | 11.99 |
| age_Min | 30.00 |
| age_Max | 133.00 |
| income_Mean | 51630.93 |
| income_Median | 51381.50 |
| income_SD | 20601.68 |
| income_Min | 1730.00 |
| income_Max | 113734.00 |
| recency_Mean | 49.11 |
| recency_Median | 49.00 |
| recency_SD | 28.95 |
| recency_Min | 0.00 |
| recency_Max | 99.00 |
| children_Mean | 0.95 |
| children_Median | 1.00 |
| children_SD | 0.75 |
| children_Min | 0.00 |
| children_Max | 3.00 |
| total_spent_Mean | 605.60 |
| total_spent_Median | 396.50 |
| total_spent_SD | 601.44 |
| total_spent_Min | 5.00 |
| total_spent_Max | 2525.00 |
| total_purchases_Mean | 12.54 |
| total_purchases_Median | 12.00 |
| total_purchases_SD | 7.18 |
| total_purchases_Min | 0.00 |
| total_purchases_Max | 32.00 |
| total_campaigns_accepted_Mean | 0.30 |
| total_campaigns_accepted_Median | 0.00 |
| total_campaigns_accepted_SD | 0.68 |
| total_campaigns_accepted_Min | 0.00 |
| total_campaigns_accepted_Max | 4.00 |
The age distribution helps identify the main age groups in the customer base.
The income distribution and boxplot help identify variation and possible high-income outliers.
Total spending helps identify high-value customers.
| Product | Spending |
|---|---|
| Wine | 680604 |
| Fruits | 58881 |
| Meat | 368993 |
| Fish | 84023 |
| Sweets | 60611 |
| Gold | 98579 |
This chart shows which product categories generate the most spending.
| Channel | Purchases |
|---|---|
| Web | 9146 |
| Catalog | 5884 |
| Store | 12964 |
This chart compares customer purchasing activity across web, catalog, and store channels.
| education | n | Percentage |
|---|---|---|
| 2n Cycle | 203 | 9.09 |
| Basic | 54 | 2.42 |
| Graduation | 1124 | 50.36 |
| Master | 369 | 16.53 |
| PhD | 482 | 21.59 |
| marital_status | n | Percentage |
|---|---|---|
| Absurd | 2 | 0.09 |
| Alone | 3 | 0.13 |
| Divorced | 231 | 10.35 |
| Married | 861 | 38.58 |
| Single | 480 | 21.51 |
| Together | 576 | 25.81 |
| Widow | 77 | 3.45 |
| YOLO | 2 | 0.09 |
| Campaign | Accepted |
|---|---|
| Campaign 1 | 144 |
| Campaign 2 | 30 |
| Campaign 3 | 163 |
| Campaign 4 | 167 |
| Campaign 5 | 163 |
| response | n | Percentage |
|---|---|---|
| 0 | 1898 | 85.04 |
| 1 | 334 | 14.96 |
## `geom_smooth()` using formula = 'y ~ x'
| id | year_birth | age | income | total_spent |
|---|---|---|---|---|
| 11004 | 1893 | 133 | 60182 | 22 |
| 1150 | 1899 | 127 | 83532 | 1853 |
| 7829 | 1900 | 126 | 36640 | 65 |
| 6663 | 1940 | 86 | 51141 | 157 |
| 6932 | 1941 | 85 | 93027 | 2119 |
| 2968 | 1943 | 83 | 48948 | 902 |
| 6142 | 1943 | 83 | 65073 | 900 |
| 7106 | 1943 | 83 | 75865 | 1242 |
| 8800 | 1943 | 83 | 48948 | 902 |
| 1453 | 1943 | 83 | 57513 | 1060 |
| id | income | age | total_spent |
|---|---|---|---|
| 4619 | 113734 | 81 | 277 |
| 4611 | 105471 | 56 | 1724 |
| 10089 | 102692 | 52 | 1112 |
| 2798 | 102160 | 49 | 1240 |
| 7215 | 101970 | 43 | 1135 |
| 4248 | 98777 | 66 | 2008 |
| 7451 | 98777 | 66 | 2008 |
| 500 | 96876 | 49 | 1941 |
| 2109 | 96843 | 36 | 1544 |
| 6815 | 96547 | 46 | 809 |
| id | income | age | total_spent |
|---|---|---|---|
| 5735 | 90638 | 35 | 2525 |
| 5350 | 90638 | 35 | 2525 |
| 1763 | 87679 | 38 | 2524 |
| 4580 | 75759 | 57 | 2486 |
| 4475 | 69098 | 77 | 2440 |
| 5453 | 90226 | 70 | 2352 |
| 10133 | 93790 | 56 | 2349 |
| 9010 | 83151 | 54 | 2346 |
| 5386 | 94384 | 73 | 2302 |
| 6024 | 94384 | 73 | 2302 |
The exploratory analysis shows that customer income and total spending vary substantially across the dataset. This suggests that the customer base contains different customer segments, including lower-spending customers and high-value customers.
Product spending is not evenly distributed across categories. Some product categories generate more total spending than others, which may help identify important product preferences.
Purchase channel analysis shows how customers interact with the company through web, catalog, and store purchases.
Campaign acceptance appears to be relatively low, meaning most customers do not accept marketing campaigns.
Based on the EDA, the modeling team should pay close attention to income, total spending, purchase channels, recency, and previous campaign acceptance.
High-spending customers and recently active customers may represent valuable segments for future marketing campaigns.
Can customers be segmented into meaningful groups based on purchasing behavior?
We used the k-means model to identify customer segments based on age, income, number of children, recency, total spending and purchase activity.
The Elbow plot helps justify how many customer segments to use. In this analysis, the elbow begins at about 2 clusters and forms the elbow at between 3 and 4. We elect to view 4 clusters for the analysis.
What demographic characteristics are associated with higher customer spending? Which customer characteristics are associated with positive campaign responses?
| cluster | Average Age | Average Income | Average Children | Average Total Spent | Average Total Purchases | Average Campaigns Accepted |
|---|---|---|---|---|---|---|
| 1 | 55.47 | 81175.71 | 0.26 | 1612.07 | 19.94 | 2.53 |
| 2 | 59.03 | 74210.67 | 0.21 | 1323.66 | 19.56 | 0.29 |
| 3 | 54.68 | 34417.29 | 1.25 | 98.86 | 5.85 | 0.08 |
| 4 | 60.48 | 58306.43 | 1.12 | 736.39 | 17.22 | 0.22 |
The data is split into training and testing sets. The model is trained on the training data and evaluated on the testing data.
## [1] 1563
## [1] 669
The dependent variable is: response: whether the
customer responded to the campaign
The independent variables include customer demographics, spending behavior, purchasing channels, campaign history, and recency.
##
## Call:
## glm(formula = response ~ income + age + children + recency +
## total_spent + total_purchases + total_campaigns_accepted +
## num_web_purchases + num_catalog_purchases + num_store_purchases +
## num_web_visits_month + num_deals_purchases + complain + education +
## marital_status, family = binomial(link = "logit"), data = train_data)
##
## Coefficients: (1 not defined because of singularities)
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -1.114e+00 7.731e-01 -1.441 0.149664
## income 5.020e-06 9.494e-06 0.529 0.596975
## age -1.400e-02 7.728e-03 -1.811 0.070112 .
## children -4.598e-01 1.778e-01 -2.586 0.009714 **
## recency -3.011e-02 3.466e-03 -8.688 < 2e-16 ***
## total_spent 6.445e-04 3.074e-04 2.097 0.036019 *
## total_purchases -2.117e-01 4.066e-02 -5.207 1.92e-07 ***
## total_campaigns_accepted 1.375e+00 1.306e-01 10.531 < 2e-16 ***
## num_web_purchases 2.639e-01 5.763e-02 4.579 4.68e-06 ***
## num_catalog_purchases 3.124e-01 5.960e-02 5.242 1.59e-07 ***
## num_store_purchases NA NA NA NA
## num_web_visits_month 1.714e-01 5.242e-02 3.270 0.001074 **
## num_deals_purchases 1.270e-01 5.541e-02 2.292 0.021910 *
## complain 1.417e+00 1.038e+00 1.364 0.172418
## educationBasic -1.196e+00 1.088e+00 -1.099 0.271719
## educationGraduation 2.247e-01 3.538e-01 0.635 0.525314
## educationMaster 6.476e-01 3.881e-01 1.668 0.095240 .
## educationPhD 9.431e-01 3.749e-01 2.516 0.011877 *
## marital_statusMarried -1.070e+00 2.865e-01 -3.733 0.000189 ***
## marital_statusSingle 3.688e-02 2.902e-01 0.127 0.898876
## marital_statusTogether -1.064e+00 3.063e-01 -3.475 0.000511 ***
## marital_statusWidow 2.485e-01 4.705e-01 0.528 0.597446
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 1319.83 on 1562 degrees of freedom
## Residual deviance: 873.04 on 1542 degrees of freedom
## AIC: 915.04
##
## Number of Fisher Scoring iterations: 6
An odds ratio greater than 1 means the variable is associated with higher odds of campaign response. An odds ratio below 1 means the variable is associated with lower odds of campaign response.
| term | estimate | conf.low | conf.high | p.value |
|---|---|---|---|---|
| total_campaigns_accepted | 3.9550 | 3.0800 | 5.1407 | 0.0000 |
| recency | 0.9703 | 0.9636 | 0.9768 | 0.0000 |
| num_catalog_purchases | 1.3667 | 1.2191 | 1.5404 | 0.0000 |
| total_purchases | 0.8092 | 0.7458 | 0.8749 | 0.0000 |
| num_web_purchases | 1.3020 | 1.1632 | 1.4595 | 0.0000 |
| marital_statusMarried | 0.3432 | 0.1962 | 0.6050 | 0.0002 |
| marital_statusTogether | 0.3449 | 0.1890 | 0.6301 | 0.0005 |
| num_web_visits_month | 1.1870 | 1.0727 | 1.3180 | 0.0011 |
| children | 0.6314 | 0.4415 | 0.8873 | 0.0097 |
| educationPhD | 2.5680 | 1.2583 | 5.4950 | 0.0119 |
| num_deals_purchases | 1.1354 | 1.0198 | 1.2675 | 0.0219 |
| total_spent | 1.0006 | 1.0000 | 1.0012 | 0.0360 |
| age | 0.9861 | 0.9711 | 1.0010 | 0.0701 |
| educationMaster | 1.9109 | 0.9095 | 4.1867 | 0.0952 |
| (Intercept) | 0.3283 | 0.0705 | 1.4663 | 0.1497 |
| complain | 4.1242 | 0.4925 | 28.4763 | 0.1724 |
| educationBasic | 0.3025 | 0.0158 | 1.7584 | 0.2717 |
| educationGraduation | 1.2520 | 0.6408 | 2.5768 | 0.5253 |
| income | 1.0000 | 1.0000 | 1.0000 | 0.5970 |
| marital_statusWidow | 1.2820 | 0.4957 | 3.1672 | 0.5974 |
| marital_statusSingle | 1.0376 | 0.5909 | 1.8474 | 0.8989 |
| num_store_purchases | NA | NA | NA | NA |
The confusion matrix evaluates how well the model classifies campaign responders and non-responders at a 50% probability threshold.
## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 553 66
## Yes 16 34
##
## Accuracy : 0.8774
## 95% CI : (0.8502, 0.9013)
## No Information Rate : 0.8505
## P-Value [Acc > NIR] : 0.02656
##
## Kappa : 0.3928
##
## Mcnemar's Test P-Value : 6.262e-08
##
## Sensitivity : 0.34000
## Specificity : 0.97188
## Pos Pred Value : 0.68000
## Neg Pred Value : 0.89338
## Prevalence : 0.14948
## Detection Rate : 0.05082
## Detection Prevalence : 0.07474
## Balanced Accuracy : 0.65594
##
## 'Positive' Class : Yes
##
The ROC curve and AUC score evaluate how well the model separates likely responders from unlikely responders across all possible thresholds.
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
## Area under the curve: 0.8658
The model can help determine how to spend advertising dollars by ranking customers according to predicted response probability. Customers with the highest predicted probability should receive the most advertising attention.
| advertising_tier | customers | avg_predicted_response | actual_response_rate | avg_total_spent | avg_income |
|---|---|---|---|---|---|
| High Priority | 134 | 0.4730 | 0.4925 | 1032.3657 | 61779.81 |
| Medium Priority | 201 | 0.1033 | 0.1194 | 580.6866 | 50780.73 |
| Low Priority | 334 | 0.0229 | 0.0299 | 436.6497 | 48542.03 |
How can marketers improve campaign targeting using customer segmentation?
Customer segmentation helps marketers improve campaign targeting by grouping customers with similar behaviors, preferences, and spending patterns. Instead of sending the same campaign to everyone, marketers can tailor promotions to the segments most likely to respond. For example, high-spending customers may receive premium product offers, while lower-spending or less-engaged customers may receive discounts, reactivation campaigns, or introductory offers. This makes campaigns more relevant, improves response rates, and reduces wasted marketing spend.
High-spending segments (1 & 2) should be prioritized for retention through loyalty offers, premium promotions, and personalized recommendations.
Product spend patterns also show that customers prefer different categories, such as wine, meat, fruits, fish, sweets, or gold products. These differences allow marketers to create more targeted campaigns instead of using the same message for every customer.
Overall, segmentation can improve campaign effectiveness by helping marketers send the right offer to the right customer group. This can increase response rates, improve customer lifetime value, and reduce wasted marketing spend.