Skip to main content
The Cohort component is designed for displaying cohort analysis and retention data. While Django Unfold doesn’t include a dedicated cohort template, you can build cohort visualizations using the Table and Tracker components.

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

  1. Date Granularity: Choose appropriate cohort periods (daily, weekly, monthly)
  2. Time Windows: Limit cohort analysis to relevant time periods
  3. Color Coding: Use consistent color schemes for metrics
  4. Performance: Cache cohort calculations for large datasets
  5. 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
        # ...
  • Table - Display cohort data in tables
  • Tracker - Visualize retention as heatmaps
  • Chart - Line charts for retention curves
  • Card - Organize cohort views

External Resources

Build docs developers (and LLMs) love