Lab

15.4.2. Lab#

  • Use this assignment to practice the chapter’s concepts and coding skills in runnable Python code.

  • Sign in from the account menu before you start working on the assignment.

  • Write and run your code directly in the coding cells.

  • Multiple submissions are allowed. The highest score is kept.

### Question 1: The Assignment Step
# Six customers (in standard units) and two current cluster centers are below.
#   1. Assign each customer to the nearer center (0 or 1) using distance.
#   2. Print the assignments as a list and the number of customers in each cluster.
# Print exactly:
# Assignments: [0, 0, 1, 1, 0, 1]
# Cluster sizes: [3, 3]
import numpy as np
import pandas as pd

points = pd.DataFrame({
    "spend_su": [-1.0, -0.8, 1.1, 0.9, -1.2, 1.3],
    "orders_su": [-0.5, -0.9, 0.8, 1.2, -0.7, 0.6],
})
centers = np.array([[-1.0, -1.0], [1.0, 1.0]])
### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
import numpy as np
import pandas as pd

points = pd.DataFrame({
    "spend_su": [-1.0, -0.8, 1.1, 0.9, -1.2, 1.3],
    "orders_su": [-0.5, -0.9, 0.8, 1.2, -0.7, 0.6],
})
centers = np.array([[-1.0, -1.0], [1.0, 1.0]])

assignments = []
for spend, orders in zip(points['spend_su'], points['orders_su']):
    point = np.array([spend, orders])
    d0 = np.sqrt(np.sum((point - centers[0]) ** 2))
    d1 = np.sqrt(np.sum((point - centers[1]) ** 2))
    if d0 <= d1:
        assignments.append(0)
    else:
        assignments.append(1)

print("Assignments:", assignments)
print("Cluster sizes:", [assignments.count(0), assignments.count(1)])
Assignments: [0, 0, 1, 1, 0, 1]
Cluster sizes: [3, 3]
### Question 2: The Update Step
# Using the assignments from Question 1 (given below), move each center to the
# mean of the customers assigned to it.
#   1. Use groupby on the cluster column to compute the new centers.
#   2. Print each new center rounded to 2 decimals, cluster 0 first.
# Print exactly:
# Center 0: [-1.0, -0.7]
# Center 1: [1.1, 0.87]
import numpy as np
import pandas as pd

points = pd.DataFrame({
    "spend_su": [-1.0, -0.8, 1.1, 0.9, -1.2, 1.3],
    "orders_su": [-0.5, -0.9, 0.8, 1.2, -0.7, 0.6],
    "cluster": [0, 0, 1, 1, 0, 1],
})
### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
import numpy as np
import pandas as pd

points = pd.DataFrame({
    "spend_su": [-1.0, -0.8, 1.1, 0.9, -1.2, 1.3],
    "orders_su": [-0.5, -0.9, 0.8, 1.2, -0.7, 0.6],
    "cluster": [0, 0, 1, 1, 0, 1],
})

new_centers = points.groupby('cluster').mean().round(2)
print("Center 0:", new_centers.loc[0].tolist())
print("Center 1:", new_centers.loc[1].tolist())
Center 0: [-1.0, -0.7]
Center 1: [1.1, 0.87]
### Question 3: Raw vs. Scaled Features
# Spend is in dollars and orders is a count, so spend dominates the distances.
#   1. Fit KMeans(n_clusters=4, n_init=10, random_state=0) to the raw customers
#      and, separately, to the features in standard units (scaled).
#   2. Print the cluster sizes for each, sorted from smallest to largest.
# Print exactly:
# Raw sizes: [3, 3, 5, 5]
# Scaled sizes: [4, 4, 4, 4]
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans

customers = pd.DataFrame({
    "spend": [300, 650, 450, 800, 500, 900, 700, 1100, 2000, 2500, 2900, 2300, 2100, 2700, 3000, 2400],
    "orders": [3, 2, 4, 3, 24, 22, 26, 25, 5, 4, 6, 5, 28, 26, 30, 27],
})
scaled = (customers - customers.mean()) / customers.std(ddof=0)
### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans

customers = pd.DataFrame({
    "spend": [300, 650, 450, 800, 500, 900, 700, 1100, 2000, 2500, 2900, 2300, 2100, 2700, 3000, 2400],
    "orders": [3, 2, 4, 3, 24, 22, 26, 25, 5, 4, 6, 5, 28, 26, 30, 27],
})
scaled = (customers - customers.mean()) / customers.std(ddof=0)

raw_labels = KMeans(n_clusters=4, n_init=10, random_state=0).fit_predict(customers)
scaled_labels = KMeans(n_clusters=4, n_init=10, random_state=0).fit_predict(scaled)

print("Raw sizes:", sorted(pd.Series(raw_labels).value_counts().tolist()))
print("Scaled sizes:", sorted(pd.Series(scaled_labels).value_counts().tolist()))
# Without scaling, k-means splits customers mostly by spend and mixes frequent
# and occasional buyers. With scaling, it finds the four customer types.
Raw sizes: [3, 3, 5, 5]
Scaled sizes: [4, 4, 4, 4]
### Question 4: Choosing k With the Silhouette Score
#   1. For k = 2, 3, 4, and 5, fit KMeans(n_clusters=k, n_init=10, random_state=0)
#      to scaled and compute silhouette_score(scaled, labels), rounded to 3 decimals.
#   2. Print each score, then the k with the highest score.
# Print exactly:
# k=2: 0.457
# k=3: 0.573
# k=4: 0.746
# k=5: 0.652
# Best k: 4
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

customers = pd.DataFrame({
    "spend": [300, 650, 450, 800, 500, 900, 700, 1100, 2000, 2500, 2900, 2300, 2100, 2700, 3000, 2400],
    "orders": [3, 2, 4, 3, 24, 22, 26, 25, 5, 4, 6, 5, 28, 26, 30, 27],
})
scaled = (customers - customers.mean()) / customers.std(ddof=0)
### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

customers = pd.DataFrame({
    "spend": [300, 650, 450, 800, 500, 900, 700, 1100, 2000, 2500, 2900, 2300, 2100, 2700, 3000, 2400],
    "orders": [3, 2, 4, 3, 24, 22, 26, 25, 5, 4, 6, 5, 28, 26, 30, 27],
})
scaled = (customers - customers.mean()) / customers.std(ddof=0)

best_k = None
best_score = -1
for k in [2, 3, 4, 5]:
    labels = KMeans(n_clusters=k, n_init=10, random_state=0).fit_predict(scaled)
    score = round(silhouette_score(scaled, labels), 3)
    print("k=" + str(k) + ":", score)
    if score > best_score:
        best_k = k
        best_score = score
print("Best k:", best_k)
k=2: 0.457
k=3: 0.573
k=4: 0.746
k=5: 0.652
Best k: 4
### Question 5: Describing the Segments
#   1. Fit KMeans(n_clusters=4, n_init=10, random_state=0) to scaled and add the
#      labels to customers as a column named cluster.
#   2. Use groupby to compute each cluster's mean spend and mean orders in the
#      ORIGINAL units, and sort the clusters by mean spend.
#   3. Print one line per cluster, lowest spend first, with both means rounded
#      to 2 decimals.
# Print exactly:
# Spend 550.0, orders 3.0
# Spend 800.0, orders 24.25
# Spend 2425.0, orders 5.0
# Spend 2550.0, orders 27.75
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans

customers = pd.DataFrame({
    "spend": [300, 650, 450, 800, 500, 900, 700, 1100, 2000, 2500, 2900, 2300, 2100, 2700, 3000, 2400],
    "orders": [3, 2, 4, 3, 24, 22, 26, 25, 5, 4, 6, 5, 28, 26, 30, 27],
})
scaled = (customers - customers.mean()) / customers.std(ddof=0)
### Your code starts here.



### Your code ends here.

Hide code cell source

### Solution
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans

customers = pd.DataFrame({
    "spend": [300, 650, 450, 800, 500, 900, 700, 1100, 2000, 2500, 2900, 2300, 2100, 2700, 3000, 2400],
    "orders": [3, 2, 4, 3, 24, 22, 26, 25, 5, 4, 6, 5, 28, 26, 30, 27],
})
scaled = (customers - customers.mean()) / customers.std(ddof=0)

customers['cluster'] = KMeans(n_clusters=4, n_init=10, random_state=0).fit_predict(scaled)
profile = customers.groupby('cluster')[['spend', 'orders']].mean().sort_values('spend')

for spend, orders in zip(profile['spend'], profile['orders']):
    print("Spend " + str(round(spend, 2)) + ", orders " + str(round(orders, 2)))
# Occasional buyers, frequent small baskets, big-ticket buyers, and loyal
# high-value customers. The cluster numbers themselves carry no meaning.
Spend 550.0, orders 3.0
Spend 800.0, orders 24.25
Spend 2425.0, orders 5.0
Spend 2550.0, orders 27.75