Akila-5/customer-segmentation-dashboard
0
1/* =========================================================2 CUSTOMER SEGMENTATION ANALYSIS3 Complete JavaScript Project4 Platform: Hugging Face Spaces5 Technology: HTML + CSS + JavaScript + Chart.js6 7 Dataset:8 - 500 synthetic customers9 - Demographic information10 - Income11 - Purchases12 - Purchase frequency13 - Total spending14 - Preferred category15 - Membership16 - Location17 18 Machine Learning:19 - K-Means Clustering20 - Features used:21 1. Income22 2. Purchases23 3. Total Spending24 ========================================================= */25 26 27/* =========================================================28 1. CREATE CUSTOMER DATASET29 ========================================================= */30 31const customers = [];32 33 34/*35 A deterministic random number generator is used so that36 the dashboard produces consistent results every time.37*/38 39let seed = 12345;40 41function random() {42 43 seed = (seed * 9301 + 49297) % 233280;44 45 return seed / 233280;46}47 48 49/* Generate random integer */50 51function randomInt(min, max) {52 53 return Math.floor(random() * (max - min + 1)) + min;54 55}56 57 58/* Select random item from an array */59 60function randomItem(array) {61 62 return array[Math.floor(random() * array.length)];63 64}65 66 67/* Dataset options */68 69const genders = [70 "Male",71 "Female"72];73 74 75const locations = [76 "Chennai",77 "Coimbatore",78 "Madurai",79 "Trichy",80 "Salem",81 "Thanjavur",82 "Bangalore",83 "Hyderabad"84];85 86 87const categories = [88 "Electronics",89 "Clothing",90 "Groceries",91 "Beauty",92 "Home",93 "Sports"94];95 96 97const memberships = [98 "Basic",99 "Silver",100 "Gold",101 "Platinum"102];103 104 105const frequencies = [106 "Occasional",107 "Monthly",108 "Bi-weekly",109 "Weekly"110];111 112 113/*114 Create 500 customers.115 116 The values are generated to represent realistic117 customer behavior.118*/119 120for (let i = 1; i <= 500; i++) {121 122 const age = randomInt(18, 70);123 124 const gender = randomItem(genders);125 126 /*127 Income between approximately ₹20,000 and ₹180,000128 */129 130 const income = randomInt(20000, 180000);131 132 133 /*134 Purchase frequency135 */136 137 const purchaseFrequency = randomItem(frequencies);138 139 140 let purchases;141 142 143 if (purchaseFrequency === "Occasional") {144 145 purchases = randomInt(5, 18);146 147 }148 149 else if (purchaseFrequency === "Monthly") {150 151 purchases = randomInt(19, 36);152 153 }154 155 else if (purchaseFrequency === "Bi-weekly") {156 157 purchases = randomInt(30, 55);158 159 }160 161 else {162 163 purchases = randomInt(45, 65);164 165 }166 167 168 /*169 Spending is influenced by income and purchases.170 */171 172 let spending =173 (income * (0.15 + random() * 0.45))174 + (purchases * randomInt(300, 900));175 176 177 /*178 Add some variation.179 */180 181 spending = Math.round(spending);182 183 184 /*185 Keep spending within a realistic range.186 */187 188 spending = Math.max(5000, Math.min(330000, spending));189 190 191 /*192 Membership is influenced by spending.193 */194 195 let membership;196 197 if (spending >= 180000) {198 199 membership = randomItem(["Gold", "Platinum"]);200 201 }202 203 else if (spending >= 90000) {204 205 membership = randomItem(["Silver", "Gold"]);206 207 }208 209 else {210 211 membership = randomItem(["Basic", "Silver"]);212 213 }214 215 216 customers.push({217 218 Customer_ID:219 "C" + String(i).padStart(4, "0"),220 221 Age: age,222 223 Gender: gender,224 225 Income: income,226 227 Location: randomItem(locations),228 229 Membership: membership,230 231 Preferred_Category: randomItem(categories),232 233 Purchases: purchases,234 235 Purchase_Frequency: purchaseFrequency,236 237 Total_Spending: spending238 239 });240 241}242 243 244/* =========================================================245 2. BASIC CALCULATIONS246 ========================================================= */247 248 249/* Total customers */250 251const totalCustomers = customers.length;252 253 254/* Average income */255 256const averageIncome =257 customers.reduce(258 (sum, customer) => sum + customer.Income,259 0260 ) / totalCustomers;261 262 263/* Total spending */264 265const totalSpending =266 customers.reduce(267 (sum, customer) => sum + customer.Total_Spending,268 0269 );270 271 272/* Average purchases */273 274const averagePurchases =275 customers.reduce(276 (sum, customer) => sum + customer.Purchases,277 0278 ) / totalCustomers;279 280 281/* =========================================================282 3. DISPLAY KPI VALUES283 ========================================================= */284 285document.getElementById("totalCustomers").textContent =286 totalCustomers;287 288 289document.getElementById("avgIncome").textContent =290 formatCurrency(averageIncome);291 292 293document.getElementById("totalSpending").textContent =294 formatCurrency(totalSpending);295 296 297document.getElementById("avgPurchases").textContent =298 averagePurchases.toFixed(1);299 300 301/* =========================================================302 4. CURRENCY FORMATTER303 ========================================================= */304 305function formatCurrency(value) {306 307 return "₹" + Math.round(value).toLocaleString("en-IN");308 309}310 311 312/* =========================================================313 5. STANDARDIZATION314 ========================================================= */315 316/*317 K-Means works better when features are standardized.318 319 We use:320 - Income321 - Purchases322 - Total Spending323*/324 325function standardize(values) {326 327 const means = [];328 329 const standardDeviations = [];330 331 332 for (let column = 0; column < values[0].length; column++) {333 334 const columnValues =335 values.map(row => row[column]);336 337 338 const mean =339 columnValues.reduce(340 (a, b) => a + b,341 0342 ) / columnValues.length;343 344 345 const variance =346 columnValues.reduce(347 (sum, value) =>348 sum + Math.pow(value - mean, 2),349 0350 ) / columnValues.length;351 352 353 const standardDeviation =354 Math.sqrt(variance) || 1;355 356 357 means.push(mean);358 359 standardDeviations.push(standardDeviation);360 361 }362 363 364 return values.map(row =>365 366 row.map(367 (value, index) =>368 (value - means[index]) /369 standardDeviations[index]370 )371 372 );373 374}375 376 377/* =========================================================378 6. EUCLIDEAN DISTANCE379 ========================================================= */380 381function distance(point1, point2) {382 383 let total = 0;384 385 for (let i = 0; i < point1.length; i++) {386 387 total +=388 Math.pow(389 point1[i] - point2[i],390 2391 );392 393 }394 395 return Math.sqrt(total);396 397}398 399 400/* =========================================================401 7. K-MEANS CLUSTERING402 ========================================================= */403 404function kMeans(data, k = 3, iterations = 100) {405 406 /*407 Select initial centroids from the dataset.408 */409 410 let centroids = [411 412 [...data[0]],413 414 [...data[Math.floor(data.length / 2)]],415 416 [...data[data.length - 1]]417 418 ];419 420 421 let assignments =422 new Array(data.length).fill(0);423 424 425 for (let iteration = 0; iteration < iterations; iteration++) {426 427 let changed = false;428 429 430 /* ---------------------------------------------431 Assign each customer to nearest centroid432 --------------------------------------------- */433 434 for (let i = 0; i < data.length; i++) {435 436 let nearestCluster = 0;437 438 let nearestDistance =439 distance(440 data[i],441 centroids[0]442 );443 444 445 for (let cluster = 1; cluster < k; cluster++) {446 447 const currentDistance =448 distance(449 data[i],450 centroids[cluster]451 );452 453 454 if (currentDistance < nearestDistance) {455 456 nearestDistance = currentDistance;457 458 nearestCluster = cluster;459 460 }461 462 }463 464 465 if (assignments[i] !== nearestCluster) {466 467 assignments[i] = nearestCluster;468 469 changed = true;470 471 }472 473 }474 475 476 /* ---------------------------------------------477 Calculate new centroids478 --------------------------------------------- */479 480 const sums =481 Array.from(482 { length: k },483 () => [0, 0, 0]484 );485 486 487 const counts =488 new Array(k).fill(0);489 490 491 for (let i = 0; i < data.length; i++) {492 493 const cluster =494 assignments[i];495 496 497 counts[cluster]++;498 499 500 for (let j = 0; j < data[i].length; j++) {501 502 sums[cluster][j] +=503 data[i][j];504 505 }506 507 }508 509 510 for (let cluster = 0; cluster < k; cluster++) {511 512 if (counts[cluster] === 0) {513 514 centroids[cluster] =515 [...data[randomInt(0, data.length - 1)]];516 517 }518 519 else {520 521 centroids[cluster] =522 sums[cluster].map(523 value =>524 value / counts[cluster]525 );526 527 }528 529 }530 531 532 if (!changed) {533 534 break;535 536 }537 538 }539 540 541 return {542 543 assignments,544 centroids545 546 };547 548}549 550 551/* =========================================================552 8. PREPARE FEATURES553 ========================================================= */554 555const featureData = customers.map(customer => [556 557 customer.Income,558 559 customer.Purchases,560 561 customer.Total_Spending562 563]);564 565 566/* Standardize */567 568const standardizedData =569 standardize(featureData);570 571 572/* Run K-Means */573 574const clustering =575 kMeans(576 standardizedData,577 3,578 100579 );580 581 582/* Store cluster number in each customer */583 584customers.forEach((customer, index) => {585 586 customer.Cluster =587 clustering.assignments[index];588 589});590 591 592/* =========================================================593 9. CALCULATE CLUSTER STATISTICS594 ========================================================= */595 596const clusterStatistics = [];597 598 599for (let cluster = 0; cluster < 3; cluster++) {600 601 const members =602 customers.filter(603 customer =>604 customer.Cluster === cluster605 );606 607 608 const count =609 members.length;610 611 612 const avgIncome =613 members.reduce(614 (sum, customer) =>615 sum + customer.Income,616 0617 ) / count;618 619 620 const avgSpending =621 members.reduce(622 (sum, customer) =>623 sum + customer.Total_Spending,624 0625 ) / count;626 627 628 const avgPurchases =629 members.reduce(630 (sum, customer) =>631 sum + customer.Purchases,632 0633 ) / count;634 635 636 clusterStatistics.push({637 638 cluster,639 640 count,641 642 avgIncome,643 644 avgSpending,645 646 avgPurchases647 648 });649 650}651 652 653/* =========================================================654 10. SORT CLUSTERS BY SPENDING655 ========================================================= */656 657const sortedClusters =658 [...clusterStatistics].sort(659 (a, b) =>660 a.avgSpending - b.avgSpending661 );662 663 664/*665 Lowest spending = Budget666 Middle spending = Regular667 Highest spending = High Value668*/669 670const clusterNames = {};671 672 673clusterNames[674 sortedClusters[0].cluster675] = "Budget Customers";676 677 678clusterNames[679 sortedClusters[1].cluster680] = "Regular Customers";681 682 683clusterNames[684 sortedClusters[2].cluster685] = "High Value Customers";686 687 688/* =========================================================689 11. ADD SEGMENT NAME TO CUSTOMERS690 ========================================================= */691 692customers.forEach(customer => {693 694 customer.Segment =695 clusterNames[customer.Cluster];696 697});698 699 700/* =========================================================701 12. SEGMENT COUNTS702 ========================================================= */703 704const segmentNames = [705 706 "Budget Customers",707 708 "Regular Customers",709 710 "High Value Customers"711 712];713 714 715const segmentCounts =716 segmentNames.map(717 name =>718 customers.filter(719 customer =>720 customer.Segment === name721 ).length722 );723 724 725/* =========================================================726 13. CUSTOMER SEGMENT DOUGHNUT CHART727 ========================================================= */728 729new Chart(730 731 document.getElementById("segmentChart"),732 733 {734 735 type: "doughnut",736 737 data: {738 739 labels: segmentNames,740 741 datasets: [742 743 {744 745 label: "Customers",746 747 data: segmentCounts748 749 }750 751 ]752 753 },754 755 options: {756 757 responsive: true,758 759 maintainAspectRatio: false,760 761 plugins: {762 763 legend: {764 765 position: "bottom"766 767 }768 769 }770 771 }772 773 }774 775);776 777 778/* =========================================================779 14. INCOME VS SPENDING SCATTER CHART780 ========================================================= */781 782const scatterData =783 customers.map(customer => ({784 785 x: customer.Income,786 787 y: customer.Total_Spending788 789 }));790 791 792new Chart(793 794 document.getElementById("incomeSpendingChart"),795 796 {797 798 type: "scatter",799 800 data: {801 802 datasets: [803 804 {805 806 label: "Customers",807 808 data: scatterData809 810 }811 812 ]813 814 },815 816 options: {817 818 responsive: true,819 820 maintainAspectRatio: false,821 822 scales: {823 824 x: {825 826 title: {827 828 display: true,829 830 text: "Annual Income (₹)"831 832 }833 834 },835 836 y: {837 838 title: {839 840 display: true,841 842 text: "Total Spending (₹)"843 844 }845 846 }847 848 }849 850 }851 852 }853 854);855 856 857/* =========================================================858 15. AGE DISTRIBUTION859 ========================================================= */860 861const ageGroups = [862 863 "18-25",864 865 "26-35",866 867 "36-45",868 869 "46-55",870 871 "56-65",872 873 "66-70"874 875];876 877 878const ageCounts =879 ageGroups.map(group => {880 881 const [min, max] =882 group.split("-").map(Number);883 884 885 return customers.filter(886 customer =>887 customer.Age >= min &&888 customer.Age <= max889 ).length;890 891 });892 893 894new Chart(895 896 document.getElementById("ageChart"),897 898 {899 900 type: "bar",901 902 data: {903 904 labels: ageGroups,905 906 datasets: [907 908 {909 910 label: "Customers",911 912 data: ageCounts913 914 }915 916 ]917 918 },919 920 options: {921 922 responsive: true,923 924 maintainAspectRatio: false,925 926 scales: {927 928 y: {929 930 beginAtZero: true,931 932 title: {933 934 display: true,935 936 text: "Number of Customers"937 938 }939 940 }941 942 }943 944 }945 946 }947 948);949 950 951/* =========================================================952 16. PURCHASE FREQUENCY953 ========================================================= */954 955const frequencyCounts =956 frequencies.map(957 frequency =>958 customers.filter(959 customer =>960 customer.Purchase_Frequency === frequency961 ).length962 );963 964 965new Chart(966 967 document.getElementById("purchaseChart"),968 969 {970 971 type: "bar",972 973 data: {974 975 labels: frequencies,976 977 datasets: [978 979 {980 981 label: "Customers",982 983 data: frequencyCounts984 985 }986 987 ]988 989 },990 991 options: {992 993 responsive: true,994 995 maintainAspectRatio: false,996 997 scales: {998 999 y: {1000 1001 beginAtZero: true,1002 1003 title: {1004 1005 display: true,1006 1007 text: "Number of Customers"1008 1009 }1010 1011 }1012 1013 }1014 1015 }1016 1017 }1018 1019);1020 1021 1022/* =========================================================1023 17. CUSTOMER PREFERENCES1024 ========================================================= */1025 1026const preferenceCounts =1027 categories.map(1028 category =>1029 customers.filter(1030 customer =>1031 customer.Preferred_Category === category1032 ).length1033 );1034 1035 1036new Chart(1037 1038 document.getElementById("preferenceChart"),1039 1040 {1041 1042 type: "bar",1043 1044 data: {1045 1046 labels: categories,1047 1048 datasets: [1049 1050 {1051 1052 label: "Customers",1053 1054 data: preferenceCounts1055 1056 }1057 1058 ]1059 1060 },1061 1062 options: {1063 1064 responsive: true,1065 1066 maintainAspectRatio: false,1067 1068 scales: {1069 1070 y: {1071 1072 beginAtZero: true,1073 1074 title: {1075 1076 display: true,1077 1078 text: "Number of Customers"1079 1080 }1081 1082 }1083 1084 }1085 1086 }1087 1088 }1089 1090);1091 1092 1093/* =========================================================1094 18. MEMBERSHIP DISTRIBUTION1095 ========================================================= */1096 1097const membershipCounts =1098 memberships.map(1099 membership =>1100 customers.filter(1101 customer =>1102 customer.Membership === membership1103 ).length1104 );1105 1106 1107new Chart(1108 1109 document.getElementById("membershipChart"),1110 1111 {1112 1113 type: "doughnut",1114 1115 data: {1116 1117 labels: memberships,1118 1119 datasets: [1120 1121 {1122 1123 label: "Customers",1124 1125 data: membershipCounts1126 1127 }1128 1129 ]1130 1131 },1132 1133 options: {1134 1135 responsive: true,1136 1137 maintainAspectRatio: false,1138 1139 plugins: {1140 1141 legend: {1142 1143 position: "bottom"1144 1145 }1146 1147 }1148 1149 }1150 1151 }1152 1153);1154 1155 1156/* =========================================================1157 19. CUSTOMER SEGMENT SUMMARY TABLE1158 ========================================================= */1159 1160const table =1161 document.getElementById("segmentTable");1162 1163 1164table.innerHTML = "";1165 1166 1167segmentNames.forEach(segment => {1168 1169 const members =1170 customers.filter(1171 customer =>1172 customer.Segment === segment1173 );1174 1175 1176 const avgIncome =1177 members.reduce(1178 (sum, customer) =>1179 sum + customer.Income,1180 01181 ) / members.length;1182 1183 1184 const avgSpending =1185 members.reduce(1186 (sum, customer) =>1187 sum + customer.Total_Spending,1188 01189 ) / members.length;1190 1191 1192 const avgPurchases =1193 members.reduce(1194 (sum, customer) =>1195 sum + customer.Purchases,1196 01197 ) / members.length;1198 1199 1200 const row =