The purpose of this analysis is to identify groups of customers with similar clothing and shopping preferences. Businesses can use these groups to better understand their customers and develop more relevant marketing strategies. I used hierarchical cluster analysis to divide the survey respondents into three groups and compared their average survey ratings to understand how the groups differ.
# Accept either filename
possible_files <- c(
"Segmentation.csv",
"customer_segmentation.csv"
)
available_files <- possible_files[file.exists(possible_files)]
if (length(available_files) == 0) {
stop("Upload Segmentation.csv to the folder containing this R Markdown file.")
}
mydata <- read.csv(available_files[1])
if (!"ID" %in% names(mydata)) {
stop("The dataset must contain an ID column.")
}
# Exclude ID from the survey variables
survey_data <- mydata[, names(mydata) != "ID", drop = FALSE]
if (!all(vapply(survey_data, is.numeric, logical(1)))) {
stop("All survey variables must be numeric.")
}
# Remove respondents with missing survey responses
complete_rows <- complete.cases(survey_data)
analysis_data <- mydata[complete_rows, , drop = FALSE]
survey_data <- survey_data[complete_rows, , drop = FALSE]
if (nrow(survey_data) < 4) {
stop("At least four complete respondents are needed.")
}
if (any(!is.finite(as.matrix(survey_data)))) {
stop("Check the dataset for infinite or invalid values.")
}
# Exclude variables that have no variation
variable_sd <- vapply(survey_data, sd, numeric(1))
survey_data <- survey_data[, variable_sd > 0, drop = FALSE]
if (ncol(survey_data) == 0) {
stop("No survey variables with variation remain.")
}
# Display the data
head(analysis_data)
## ID Fashn Price Convnience ShpTime Fitness Perceptn ChNoise RetailEx
## 1 1 1 6 7 6 6 4 4 4
## 2 2 2 7 6 6 6 6 2 6
## 3 3 1 5 6 7 3 3 7 3
## 4 4 1 7 4 4 6 3 7 4
## 5 5 3 5 3 4 6 5 5 6
## 6 6 2 6 2 3 6 5 2 5
## KnowdgStaf Brand4Slf Brand4Els Populr StrDisp SaleStaf Fabric Cut Seam
## 1 4 1 1 1 1 1 3 4 1
## 2 4 3 5 4 1 5 5 6 6
## 3 3 1 1 3 1 4 1 4 4
## 4 4 1 1 1 7 6 5 4 4
## 5 6 2 2 2 4 4 6 6 6
## 6 2 2 2 2 1 1 6 6 6
## ShpOHngr ShpOBody Colrs Match
## 1 1 1 1 1
## 2 6 7 4 4
## 3 4 4 4 1
## 4 5 5 5 1
## 5 5 6 6 6
## 6 2 6 6 2
summary(survey_data)
## Fashn Price Convnience ShpTime Fitness
## Min. :1.000 Min. :1.00 Min. :1.000 Min. :1.000 Min. :1.000
## 1st Qu.:2.000 1st Qu.:4.00 1st Qu.:2.000 1st Qu.:3.000 1st Qu.:5.000
## Median :3.000 Median :5.00 Median :4.000 Median :4.000 Median :6.000
## Mean :3.204 Mean :4.67 Mean :3.765 Mean :4.081 Mean :5.724
## 3rd Qu.:4.000 3rd Qu.:6.00 3rd Qu.:5.000 3rd Qu.:5.000 3rd Qu.:6.000
## Max. :7.000 Max. :7.00 Max. :7.000 Max. :7.000 Max. :7.000
## Perceptn ChNoise RetailEx KnowdgStaf
## Min. :1.000 Min. :1.000 Min. :1.000 Min. :1.000
## 1st Qu.:3.000 1st Qu.:3.000 1st Qu.:4.000 1st Qu.:4.000
## Median :5.000 Median :4.000 Median :6.000 Median :5.000
## Mean :4.271 Mean :4.167 Mean :5.271 Mean :4.665
## 3rd Qu.:5.000 3rd Qu.:6.000 3rd Qu.:6.000 3rd Qu.:6.000
## Max. :7.000 Max. :7.000 Max. :7.000 Max. :7.000
## Brand4Slf Brand4Els Populr StrDisp
## Min. :1.000 Min. :1.000 Min. :1.000 Min. :1.000
## 1st Qu.:2.000 1st Qu.:3.000 1st Qu.:2.000 1st Qu.:2.000
## Median :4.000 Median :4.000 Median :4.000 Median :4.000
## Mean :3.756 Mean :3.959 Mean :3.656 Mean :3.484
## 3rd Qu.:5.000 3rd Qu.:5.000 3rd Qu.:5.000 3rd Qu.:4.000
## Max. :7.000 Max. :7.000 Max. :7.000 Max. :7.000
## SaleStaf Fabric Cut Seam ShpOHngr
## Min. :1.000 Min. :1.000 Min. :1.000 Min. :1.000 Min. :1.00
## 1st Qu.:4.000 1st Qu.:5.000 1st Qu.:5.000 1st Qu.:4.000 1st Qu.:3.00
## Median :5.000 Median :6.000 Median :6.000 Median :5.000 Median :4.00
## Mean :4.412 Mean :5.394 Mean :5.864 Mean :5.072 Mean :4.14
## 3rd Qu.:6.000 3rd Qu.:6.000 3rd Qu.:7.000 3rd Qu.:6.000 3rd Qu.:5.00
## Max. :7.000 Max. :7.000 Max. :7.000 Max. :7.000 Max. :7.00
## ShpOBody Colrs Match
## Min. :1.000 Min. :1.000 Min. :1.000
## 1st Qu.:6.000 1st Qu.:4.000 1st Qu.:2.000
## Median :7.000 Median :6.000 Median :4.000
## Mean :6.299 Mean :5.222 Mean :3.986
## 3rd Qu.:7.000 3rd Qu.:6.000 3rd Qu.:6.000
## Max. :7.000 Max. :7.000 Max. :7.000
The original dataset contains 221 respondents. After checking for missing responses, 221 respondents and 21 survey variables were used in the analysis.
The ID column was excluded because identification numbers do not measure customer preferences.
survey_scaled <- scale(survey_data)
head(survey_scaled)
## Fashn Price Convnience ShpTime Fitness Perceptn ChNoise
## 1 -1.3668217 0.8376530 1.8298065 1.1350645 0.2472547 -0.1834752 -0.09249362
## 2 -0.7465597 1.4673174 1.2642299 1.1350645 0.2472547 1.1681253 -1.19741737
## 3 -1.3668217 0.2079887 1.2642299 1.7266902 -2.4401202 -0.8592754 1.56489202
## 4 -1.3668217 1.4673174 0.1330768 -0.0481867 0.2472547 -0.8592754 1.56489202
## 5 -0.1262977 0.2079887 -0.4324997 -0.0481867 0.2472547 0.4923250 0.45996826
## 6 -0.7465597 0.8376530 -0.9980763 -0.6398123 0.2472547 0.4923250 -1.19741737
## RetailEx KnowdgStaf Brand4Slf Brand4Els Populr StrDisp SaleStaf
## 1 -0.9755685 -0.3752561 -1.4896869 -1.7234438 -1.4273102 -1.6491360 -2.1279568
## 2 0.5589556 -0.3752561 -0.4085020 0.6061041 0.1847966 -1.6491360 0.3668891
## 3 -1.7428306 -0.9394165 -1.4896869 -1.7234438 -0.3525724 -1.6491360 -0.2568224
## 4 -0.9755685 -0.3752561 -1.4896869 -1.7234438 -1.4273102 2.3340231 0.9906006
## 5 0.5589556 0.7530649 -0.9490944 -1.1410568 -0.8899413 0.3424436 -0.2568224
## 6 -0.2083065 -1.5035770 -0.9490944 -1.1410568 -0.8899413 -1.6491360 -2.1279568
## Fabric Cut Seam ShpOHngr ShpOBody Colrs
## 1 -1.7824430 -1.5363576 -2.4439778 -1.89734098 -4.9479613 -2.8226580
## 2 -0.2931428 0.1118707 0.5566838 1.12364141 0.6549394 -0.8168464
## 3 -3.2717432 -1.5363576 -0.6435808 -0.08475154 -2.1465110 -0.8168464
## 4 -0.2931428 -1.5363576 -0.6435808 0.51944494 -1.2126942 -0.1482425
## 5 0.4515073 0.1118707 0.5566838 0.51944494 -0.2788774 0.5203614
## 6 0.4515073 0.1118707 0.5566838 -1.29314450 -0.2788774 0.5203614
## Match
## 1 -1.549754115
## 2 0.007044337
## 3 -1.549754115
## 4 -1.549754115
## 5 1.044909971
## 6 -1.030821298
Standardization puts the variables on a comparable scale, with a mean of zero and a standard deviation of one. This helps prevent variables with greater numerical variation from dominating the clustering.
# Calculate Euclidean distances between respondents
customer_distances <- dist(
survey_scaled,
method = "euclidean"
)
# Perform complete-linkage hierarchical clustering
seg.hclust <- hclust(
customer_distances,
method = "complete"
)
# Plot the dendrogram
plot(
seg.hclust,
labels = FALSE,
hang = -1,
main = "Customer Segmentation Dendrogram",
xlab = "Customers",
ylab = "Merge Height",
sub = ""
)
# Highlight three clusters
rect.hclust(
seg.hclust,
k = 3,
border = c("steelblue", "darkorange", "darkgreen")
)
# Assign each respondent to a cluster
groups.3 <- cutree(seg.hclust, k = 3)
Hierarchical clustering groups respondents according to the similarity of their standardized survey ratings. The dendrogram shows how respondents and groups are merged. Groups that merge at greater heights are more different.
I selected three clusters to follow the lab exercise. Further analysis would be needed to determine whether three is the most appropriate number of segments.
cluster_counts <- table(factor(groups.3, levels = 1:3))
cluster_sizes <- data.frame(
Cluster = 1:3,
Customers = as.integer(cluster_counts),
Percentage = round(
100 * as.integer(cluster_counts) / length(groups.3),
1
)
)
knitr::kable(
cluster_sizes,
caption = "Customer Counts by Cluster"
)
| Cluster | Customers | Percentage |
|---|---|---|
| 1 | 6 | 2.7 |
| 2 | 90 | 40.7 |
| 3 | 125 | 56.6 |
Cluster 1 contains 6 customers (2.7%), Cluster 2 contains 90 customers (40.7%), and Cluster 3 contains 125 customers (56.6%).
These percentages describe the analyzed sample. They do not necessarily represent the entire customer market.
cluster_means <- aggregate(
survey_data,
by = list(Cluster = groups.3),
FUN = mean
)
cluster_medians <- aggregate(
survey_data,
by = list(Cluster = groups.3),
FUN = median
)
knitr::kable(
cluster_means,
digits = 2,
caption = "Average Survey Ratings by Cluster"
)
| Cluster | Fashn | Price | Convnience | ShpTime | Fitness | Perceptn | ChNoise | RetailEx | KnowdgStaf | Brand4Slf | Brand4Els | Populr | StrDisp | SaleStaf | Fabric | Cut | Seam | ShpOHngr | ShpOBody | Colrs | Match |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 2.50 | 5.17 | 5.00 | 5.17 | 4.83 | 3.33 | 3.67 | 5.67 | 5.00 | 3.00 | 2.67 | 2.67 | 2.50 | 1.67 | 2.17 | 2.33 | 1.50 | 1.33 | 2.17 | 2.17 | 1.33 |
| 2 | 3.44 | 4.91 | 3.71 | 4.06 | 5.96 | 4.66 | 3.77 | 4.66 | 3.66 | 4.08 | 4.03 | 3.69 | 3.56 | 4.07 | 5.52 | 5.87 | 5.32 | 4.28 | 6.38 | 5.09 | 3.73 |
| 3 | 3.06 | 4.47 | 3.74 | 4.05 | 5.60 | 4.04 | 4.48 | 5.70 | 5.38 | 3.56 | 3.97 | 3.68 | 3.48 | 4.79 | 5.46 | 6.03 | 5.06 | 4.18 | 6.44 | 5.46 | 4.30 |
knitr::kable(
cluster_medians,
digits = 2,
caption = "Median Survey Ratings by Cluster"
)
| Cluster | Fashn | Price | Convnience | ShpTime | Fitness | Perceptn | ChNoise | RetailEx | KnowdgStaf | Brand4Slf | Brand4Els | Populr | StrDisp | SaleStaf | Fabric | Cut | Seam | ShpOHngr | ShpOBody | Colrs | Match |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 1.5 | 5.5 | 5 | 5 | 5 | 3.5 | 3.5 | 6 | 5 | 3 | 2.5 | 2.5 | 2 | 1.5 | 2 | 2 | 1 | 1 | 2 | 2 | 1 |
| 2 | 3.0 | 5.0 | 3 | 4 | 6 | 5.0 | 4.0 | 5 | 4 | 4 | 4.0 | 4.0 | 4 | 4.0 | 5 | 6 | 5 | 4 | 7 | 5 | 3 |
| 3 | 3.0 | 5.0 | 4 | 4 | 6 | 4.0 | 5.0 | 6 | 6 | 4 | 4.0 | 4.0 | 4 | 5.0 | 6 | 6 | 5 | 4 | 7 | 6 | 5 |
The means and medians help explain what makes each group different. Comparing the same survey variable across clusters shows differences in response patterns. The questionnaire should be checked before interpreting a higher rating as greater importance or stronger agreement.
The following interpretations compare each cluster’s average ratings with the averages for all other respondents. Positive differences indicate higher ratings, while negative differences indicate lower ratings.
for (cluster_number in 1:3) {
in_cluster <- groups.3 == cluster_number
group_means <- colMeans(
survey_data[in_cluster, , drop = FALSE]
)
other_means <- colMeans(
survey_data[!in_cluster, , drop = FALSE]
)
differences <- group_means - other_means
# Find the three largest differences in either direction
strongest <- names(
sort(abs(differences), decreasing = TRUE)
)[1:min(3, length(differences))]
cat("### Cluster ", cluster_number, "\n\n", sep = "")
cat(
"This group contains ",
sum(in_cluster),
" respondents, representing ",
round(100 * mean(in_cluster), 1),
"% of the analyzed sample. ",
"Its largest differences from the remaining respondents are shown below.\n\n",
sep = ""
)
for (variable in strongest) {
difference <- differences[variable]
direction <- if (difference > 0) {
"higher than"
} else if (difference < 0) {
"lower than"
} else {
"equal to"
}
cat(
"- **", variable, "**: The cluster average is ",
sprintf("%.2f", group_means[variable]),
", compared with ",
sprintf("%.2f", other_means[variable]),
" for the remaining respondents. This is ",
sprintf("%.2f", abs(difference)),
" points ", direction,
" the comparison average.\n",
sep = ""
)
}
cat(
"\nThese variables provide a starting point for describing this segment. ",
"Their meaning and rating direction should be confirmed using the questionnaire ",
"before assigning a marketing label or recommending a specific message.\n\n"
)
}
This group contains 6 respondents, representing 2.7% of the analyzed sample. Its largest differences from the remaining respondents are shown below.
These variables provide a starting point for describing this segment. Their meaning and rating direction should be confirmed using the questionnaire before assigning a marketing label or recommending a specific message.
This group contains 90 respondents, representing 40.7% of the analyzed sample. Its largest differences from the remaining respondents are shown below.
These variables provide a starting point for describing this segment. Their meaning and rating direction should be confirmed using the questionnaire before assigning a marketing label or recommending a specific message.
This group contains 125 respondents, representing 56.6% of the analyzed sample. Its largest differences from the remaining respondents are shown below.
These variables provide a starting point for describing this segment. Their meaning and rating direction should be confirmed using the questionnaire before assigning a marketing label or recommending a specific message.
Cluster membership identifies which respondents have similar survey responses, but the group numbers do not explain their preferences. Means and medians help describe those preferences and show how the groups differ. This information can help businesses develop different messages or offers for different customer segments.
For example, differences in ratings for Price could suggest that the groups respond differently to that item. However, the survey wording is needed to determine whether higher scores indicate greater concern about price or something else.
I would examine both measures. Medians are useful for ordered survey ratings because they identify the middle response without assuming equal distances between rating categories. Means provide a more detailed comparison of average responses, although they treat the numerical steps as equally spaced.
Comparing both measures helps determine whether the interpretation changes depending on how the responses are summarized.
Useful summaries include each cluster’s customer count, percentage of the sample, mean ratings, and median ratings. These measures describe the size and response patterns of each segment.
The results do not show which group is most profitable. Additional information, such as purchase frequency, spending, and customer retention, would be needed to evaluate profitability.
Hierarchical clustering creates a tree of nested groups that can be displayed in a dendrogram. The analyst can examine the tree and divide it into different numbers of clusters.
K-means requires the number of clusters to be selected before fitting the model. It repeatedly assigns observations to cluster centers and updates those centers. Its results can depend on the starting centers, so multiple starts are useful.
For this assignment, I prefer hierarchical clustering because the dendrogram helps me understand how the customer groups form. Comparing the results with K-means would be a useful next step.
A local clothing retailer could use these customer profiles to develop and test more relevant marketing messages. After confirming the questionnaire’s meaning, the retailer could examine whether particular groups respond more strongly to price, convenience, fashion, or product features.
The business could then test different messages with each group and compare actual campaign results. This would help determine whether the differences found in the survey translate into differences in customer behavior.
This analysis uses three clusters because the lab asks for a three-group solution. It does not establish that three is the best number of clusters. Results may also change with different variables, distance measures, or linkage methods.
Because these are survey ratings, some differences may reflect how respondents use the rating scale rather than distinct shopping priorities. The findings should therefore be treated as exploratory and checked against the questionnaire and additional customer information.
cluster_membership <- data.frame(
ID = analysis_data$ID,
Cluster = groups.3
)
write.csv(
cluster_membership,
"clusterID.csv",
row.names = FALSE
)
write.csv(
cluster_sizes,
"cluster_sizes.csv",
row.names = FALSE
)
write.csv(
cluster_means,
"cluster_means.csv",
row.names = FALSE
)
write.csv(
cluster_medians,
"cluster_medians.csv",
row.names = FALSE
)
The exported files contain each customer’s cluster assignment, the cluster sizes, and the mean and median survey ratings for each group.