What is Cohort Analysis?
Cohort analysis tracks groups of users over time to understand behavior patterns, retention, and engagement. Common use cases include:- User retention rates
- Customer lifetime value
- Feature adoption
- Subscription retention
Building Cohort Views
Using Table Component
# views.py
from django.views.generic import TemplateView
from unfold.views import UnfoldModelAdminViewMixin
from django.db.models import Count, Q
from datetime import timedelta
import calendar
class CohortAnalysisView(UnfoldModelAdminViewMixin, TemplateView):
title = "Cohort Analysis"
template_name = "admin/cohort_analysis.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Generate cohort data (simplified example)
cohorts = self.generate_cohort_data()
# Prepare table data
headers = ['Cohort'] + [f'Month {i}' for i in range(6)]
rows = []
for cohort_date, retention in cohorts.items():
row = [
cohort_date.strftime('%B %Y'),
*[self.format_retention_cell(r) for r in retention]
]
rows.append(row)
context['cohort_table'] = {
'headers': headers,
'rows': rows
}
return context
def generate_cohort_data(self):
from django.utils import timezone
from dateutil.relativedelta import relativedelta
cohorts = {}
current_date = timezone.now().date()
# Get last 6 months of cohorts
for i in range(6):
cohort_date = current_date - relativedelta(months=i)
cohort_start = cohort_date.replace(day=1)
# Get users who joined in this cohort
cohort_users = User.objects.filter(
date_joined__year=cohort_start.year,
date_joined__month=cohort_start.month
).values_list('id', flat=True)
total_users = len(cohort_users)
retention = []
# Calculate retention for each subsequent month
for month_offset in range(6):
period_start = cohort_start + relativedelta(months=month_offset)
period_end = period_start + relativedelta(months=1)
active_users = Activity.objects.filter(
user_id__in=cohort_users,
created_at__gte=period_start,
created_at__lt=period_end
).values('user_id').distinct().count()
retention_rate = (active_users / total_users * 100) if total_users > 0 else 0
retention.append(retention_rate)
cohorts[cohort_start] = retention
return cohorts
def format_retention_cell(self, rate):
from django.utils.html import format_html
# Color code based on retention rate
if rate >= 80:
bg_class = 'bg-green-100 dark:bg-green-900'
text_class = 'text-green-800 dark:text-green-200'
elif rate >= 50:
bg_class = 'bg-yellow-100 dark:bg-yellow-900'
text_class = 'text-yellow-800 dark:text-yellow-200'
else:
bg_class = 'bg-red-100 dark:bg-red-900'
text_class = 'text-red-800 dark:text-red-200'
return format_html(
'<div class="{} {} px-3 py-1 rounded text-center font-medium">{:.1f}%</div>',
bg_class, text_class, rate
)
{# templates/admin/cohort_analysis.html #}
{% extends "unfold/layouts/base.html" %}
{% load unfold %}
{% block content %}
<div class="container mx-auto">
{% component "unfold/components/card.html" with title="User Retention by Cohort" %}
{% component "unfold/components/table.html" with table=cohort_table card_included=1 %}
{% endcomponent %}
{% slot footer %}
<p class="text-sm text-gray-600">Percentage of users active in each subsequent month</p>
{% endslot %}
{% endcomponent %}
</div>
{% endblock %}
Cohort Heatmap
Using Tracker for Visual Cohorts
class CohortHeatmapView(UnfoldModelAdminViewMixin, TemplateView):
title = "Cohort Heatmap"
template_name = "admin/cohort_heatmap.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Generate cohort rows
cohort_rows = []
for cohort_date, retention in self.generate_cohort_data().items():
tracker_data = []
for rate in retention:
# Map retention rate to color intensity
if rate >= 80:
color = 'bg-green-700'
elif rate >= 60:
color = 'bg-green-500'
elif rate >= 40:
color = 'bg-green-300'
elif rate >= 20:
color = 'bg-yellow-400'
else:
color = 'bg-red-400'
tracker_data.append({
'color': color,
'tooltip': f"{rate:.1f}% retention"
})
cohort_rows.append({
'label': cohort_date.strftime('%B %Y'),
'data': tracker_data
})
context['cohort_rows'] = cohort_rows
return context
{# templates/admin/cohort_heatmap.html #}
{% extends "unfold/layouts/base.html" %}
{% load unfold %}
{% block content %}
<div class="container mx-auto">
{% component "unfold/components/card.html" with title="Cohort Retention Heatmap" %}
<div class="space-y-2">
{% for cohort in cohort_rows %}
<div class="flex items-center gap-4">
<div class="w-32 text-sm font-medium">
{{ cohort.label }}
</div>
{% component "unfold/components/tracker.html" with data=cohort.data class="flex-1" %}
{% endcomponent %}
</div>
{% endfor %}
</div>
{# Legend #}
<div class="mt-6 flex items-center gap-4 text-sm">
<span class="text-gray-600">Retention:</span>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-red-400 rounded"></div>
<span>0-20%</span>
</div>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-yellow-400 rounded"></div>
<span>20-40%</span>
</div>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-green-300 rounded"></div>
<span>40-60%</span>
</div>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-green-500 rounded"></div>
<span>60-80%</span>
</div>
<div class="flex items-center gap-2">
<div class="w-4 h-4 bg-green-700 rounded"></div>
<span>80-100%</span>
</div>
</div>
{% endcomponent %}
</div>
{% endblock %}
Advanced Example: Multi-Metric Cohorts
class MultiMetricCohortView(UnfoldModelAdminViewMixin, TemplateView):
title = "Advanced Cohort Analysis"
template_name = "admin/multi_metric_cohort.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
cohorts = self.generate_advanced_cohorts()
# Retention table
context['retention_table'] = self.format_cohort_table(
cohorts, 'retention', 'User Retention'
)
# Revenue per user table
context['revenue_table'] = self.format_cohort_table(
cohorts, 'revenue', 'Revenue per User'
)
return context
def generate_advanced_cohorts(self):
# Complex cohort calculation with multiple metrics
# Returns dict with retention, revenue, engagement, etc.
pass
def format_cohort_table(self, cohorts, metric, title):
headers = ['Cohort'] + [f'Month {i}' for i in range(6)]
rows = []
for cohort_date, data in cohorts.items():
row = [cohort_date.strftime('%b %Y')]
for value in data[metric]:
if metric == 'retention':
cell = self.format_retention_cell(value)
elif metric == 'revenue':
cell = self.format_revenue_cell(value)
else:
cell = str(value)
row.append(cell)
rows.append(row)
return {
'title': title,
'headers': headers,
'rows': rows
}
def format_revenue_cell(self, amount):
from django.utils.html import format_html
return format_html(
'<span class="font-mono">${:,.2f}</span>',
amount
)
Integration with Admin
# admin.py
from django.contrib import admin
from unfold.admin import ModelAdmin
@admin.register(User)
class UserAdmin(ModelAdmin):
def get_urls(self):
from django.urls import path
urls = super().get_urls()
custom_urls = [
path(
'cohort-analysis/',
self.admin_site.admin_view(
CohortAnalysisView.as_view(model_admin=self)
),
name='user_cohort_analysis',
),
path(
'cohort-heatmap/',
self.admin_site.admin_view(
CohortHeatmapView.as_view(model_admin=self)
),
name='user_cohort_heatmap',
),
]
return custom_urls + urls
Best Practices
- Date Granularity: Choose appropriate cohort periods (daily, weekly, monthly)
- Time Windows: Limit cohort analysis to relevant time periods
- Color Coding: Use consistent color schemes for metrics
- Performance: Cache cohort calculations for large datasets
- Context: Provide clear legends and explanations
Optimization Tips
from django.core.cache import cache
from django.views.decorators.cache import cache_page
class CohortAnalysisView(UnfoldModelAdminViewMixin, TemplateView):
# Cache cohort data for 1 hour
def get_context_data(self, **kwargs):
cache_key = 'cohort_analysis_data'
cohort_data = cache.get(cache_key)
if cohort_data is None:
cohort_data = self.generate_cohort_data()
cache.set(cache_key, cohort_data, 3600) # 1 hour
# Use cached data
# ...
Related Components
- Table - Display cohort data in tables
- Tracker - Visualize retention as heatmaps
- Chart - Line charts for retention curves
- Card - Organize cohort views