Machine learning
Learner Personas with Clustering
- Company
Duolingo- Job positions
- Data ScientistProduct AnalystData Analyst
- Topics
- ClusteringK-meansFeature scalingUnsupervised learningProduct analytics
The scenario
Duolingo: Use clustering to discover the kinds of learners using the app, and turn each group into a persona the product team can act on.
Duolingo's growth team sends the same reminders and offers to every learner. But some people practice for five minutes every morning, others binge on Sundays, and some cram before a trip and then disappear. The team wants to understand the kinds of learners it actually has so each group gets the right nudge. You're a data scientist on the learning insights team.
Your task
Group learners by how they use the app with clustering, describe each group as a persona the product team can act on, and assign every learner to a group.
Instructions
- 1Explore the behavior columns: their distributions, skew, missing values and correlations. Look for accounts whose activity no person could produce.
- 2Prepare the features: handle missing values and the unusual accounts, transform skewed columns, and scale everything so no single column dominates. Explain each choice.
- 3Run k-means for a range of cluster counts, and choose one using the elbow method, silhouette scores and whether the groups make sense.
- 4Try a second method, such as hierarchical clustering or a Gaussian mixture model, and compare it with k-means.
- 5Profile each cluster: its size, typical values for the key behaviors and what sets it apart. Give each one a short persona name.
- 6Recommend one product action for each persona, such as a different reminder time or offer, and a metric to test it with.
- 7Save clusters.csv with the columns learner_id and cluster for every learner in learners.csv.
Datasets
All files come in one download.
learners.csv
6,000 learners with their app activity over the last 90 days.
6,000 rows · 18 columns · 505 KB
| Column | Type | Description |
|---|---|---|
| learner_id | text | Unique ID of the learner. |
| signup_date | date | Date the learner created an account. |
| country | text | Learner's country. |
| platform | text | Device used most: android, ios or web. |
| courses_active | integer | Number of language courses in progress. |
| subscription | text | free or super. |
| days_active_last_30 | integer | Days with at least one lesson in the last 30 days. |
| sessions_per_week | decimal | Average practice sessions per week. |
| avg_session_minutes | decimal | Average length of a session, in minutes. |
| longest_streak_days | integer | Longest run of consecutive days practiced. |
| lessons_completed_90d | integer | Lessons completed in the last 90 days. |
| xp_earned_90d | integer | Experience points earned in the last 90 days. |
| share_sessions_weekend | decimal | Share of sessions on Saturday or Sunday. |
| share_sessions_morning | decimal | Share of sessions before noon, local time. |
| leaderboard_weeks_joined | integer | Weeks the learner took part in the weekly leaderboard, out of the last 13. |
| share_lessons_new_content | decimal | Share of lessons that taught new material rather than reviewing old material. |
| days_since_last_active | integer | Days since the learner last practiced. |
| notifications_enabled | text | Whether reminders are turned on: yes or no. |
Preview the first 5 rowsHide preview
| learner_id | signup_date | country | platform | courses_active | subscription | days_active_last_30 | sessions_per_week | avg_session_minutes | longest_streak_days | lessons_completed_90d | xp_earned_90d | share_sessions_weekend | share_sessions_morning | leaderboard_weeks_joined | share_lessons_new_content | days_since_last_active | notifications_enabled |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| L100001 | 2025-06-13 | United States | android | 1 | super | 16 | 3.1 | empty | 30 | 862 | 13039 | 0.39 | 0.66 | 7 | 0.16 | 0 | yes |
| L100002 | 2024-07-13 | India | android | 2 | free | 11 | 1.3 | 7.9 | 29 | 236 | 4151 | 0.42 | 0.19 | 0 | 0.72 | 16 | yes |
| L100003 | 2025-04-14 | Mexico | android | 2 | free | 14 | 4.4 | 111.7 | 7 | 152 | 2647 | 0.66 | 0 | 0 | 0.66 | 8 | no |
| L100004 | 2024-01-22 | Brazil | ios | 1 | super | 30 | 8.7 | 23.4 | 133 | 548 | 11093 | 0.63 | 0.43 | 13 | 0.48 | 0 | yes |
| L100005 | 2024-01-22 | India | ios | 1 | free | 6 | 6.6 | 87 | 3 | 86 | 1390 | 0.87 | 0.14 | 0 | 0.74 | 0 | yes |
Hints
K-means works on distances, so a column measured in thousands, like xp_earned_90d, outweighs a share between 0 and 1 unless you scale. Taking the log first tames long tails.
import numpy as np from sklearn.preprocessing import StandardScaler X = StandardScaler().fit_transform(np.log1p(features))
A handful of extreme accounts can pull a k-means center toward them. Decide how to treat them before you cluster, and give them their own label in clusters.csv if you set them aside.
Deliverable
A public GitHub repo with your notebook or scripts, clusters.csv assigning every learner to a cluster, and a README with persona profiles, supporting charts and your recommendations.
When you're done, post your repo in the Solutions tab to share it with other learners.
What grading checks
Use this checklist to review your own work before you post and share it.
- Submitted GitHub repo is public and reachable.
- Repo contains at least one notebook or script file.
- clusters.csv assigns a cluster to all 6,000 learners.
- Clusters agree with the behavior patterns the learners were drawn from, with an adjusted Rand index of at least 0.70.
- Scaling, skewed columns and unusual accounts are handled, with each choice explained.
- Each cluster is profiled with its size, key behaviors and a persona name.
- Each persona has a product recommendation and a metric to test it.