Projetos
Pandas · Retenção · Desenvolvimento

Retenção por Coorte — Como foi feito (explicado fácil)

Pensa no Spotify. Você se cadastrou em janeiro. Eles querem saber: "a turma de janeiro continua usando em fevereiro? em março? em junho?". Quando agrupam todo mundo que entrou no mesmo mês, isso é uma coorte. Aqui fiz isso pro Olist.

Nível Intermediário Stack Python · Pandas Tipo Projeto analítico
Visão geral

O que é isso, em uma frase?

É uma matriz que mostra, pra cada grupo de clientes que comprou pela 1ª vez no mesmo mês, quantos % voltaram a comprar nos meses seguintes. Trabalhei com 4.339 clientes recorrentes do e-commerce brasileiro Olist (Kaggle).

Por que importa? Porque retenção é o KPI mais traiçoeiro do mundo. É fácil olhar pro total de clientes e ficar feliz com o crescimento, sem perceber que a turma antiga tá sumindo. A coorte separa cohorts (grupos) e mostra o ritmo de "morte" de cada um — quem caiu rápido, quem aguentou.

Resposta prática que sai daqui: "quando, em média, um cliente novo deixa de comprar?" Daí saem decisões: campanha no dia 30, e-mail no dia 60, oferta de re-engajamento no dia 90.

Passo a passo

Da tabela à matriz coorte

1 · Pegar pedido + cliente. Carrego as duas tabelas do Olist (orders + customers) e junto. Mantenho só o que importa: quando ele comprou e quem é ele (usando customer_unique_id, porque customer_id muda a cada pedido — pegadinha do dataset).

orders = pd.read_csv("olist_orders_dataset.csv",
                     usecols=["order_id", "customer_id", "order_purchase_timestamp"],
                     parse_dates=["order_purchase_timestamp"])
customers = pd.read_csv("olist_customers_dataset.csv",
                        usecols=["customer_id", "customer_unique_id"])
df = orders.merge(customers, on="customer_id", how="left")
df = df.rename(columns={"order_purchase_timestamp": "data",
                        "customer_unique_id": "cliente"})

2 · Filtrar recorrentes. Só faz sentido falar de "retenção" pra quem voltou pelo menos 1 vez. Pego só clientes com 2+ pedidos — descendo de ~96 mil pra ~4.339.

n = df.groupby("cliente")["data"].count()
recorrentes = n[n >= 2].index
df = df[df["cliente"].isin(recorrentes)]

3 · O pulo do gato: "mês de origem" e "período". Para cada cliente, descubro qual foi o mês da 1ª compra (mês de origem — define a coorte dele). Depois calculo, pra cada pedido, qual o período em meses desde a 1ª compra (0 = mesmo mês, 1 = mês seguinte, etc.).

df["mes_pedido"] = df["data"].dt.to_period("M")
df["mes_origem"] = df.groupby("cliente")["mes_pedido"].transform("min")
df["periodo"]    = (df["mes_pedido"] - df["mes_origem"]).apply(lambda r: r.n)
# cliente X: comprou em jan, mar, mai
# mes_pedido = jan/26, mar/26, mai/26
# mes_origem = jan/26 (sempre o menor)
# periodo    = 0, 2, 4

Sacada: o transform("min") espalha o mínimo do grupo pra todas as linhas do mesmo cliente. É como dizer "marca em todas as linhas dele a data da 1ª compra".

4 · Contar clientes únicos por (coorte, período). Quantos clientes da coorte X estavam ativos no período Y? Pivoteia.

coortes = (
    df.groupby(["mes_origem", "periodo"])["cliente"]
      .nunique().reset_index(name="ativos")
)
tamanho = coortes[coortes["periodo"] == 0][["mes_origem", "ativos"]]
tamanho = tamanho.rename(columns={"ativos": "tamanho"})
coortes = coortes.merge(tamanho, on="mes_origem")
coortes["retencao_pct"] = coortes["ativos"] / coortes["tamanho"] * 100
matriz = coortes.pivot(index="mes_origem", columns="periodo", values="retencao_pct")

5 · Ler a matriz. Cada linha = uma coorte. Coluna 0 = 100% (mesmo mês). Coluna 1 = % que voltou no mês seguinte. Quanto mais devagar a linha despenca, melhor a retenção daquele grupo. Visualizado como heatmap, vira aquela "escada descendo" clássica.

coorte (exemplo)mês 0mês 1mês 6
2017-01100,0%~10%~3%
2017-06100,0%~12%~4%

(valores ilustrativos — números reais saem do notebook).

Resultado

A "escada da retenção"

O que dá pra levar daqui

Em uma linha: o que esse projeto ensina

Que "crescimento total" engana. Só vendo por coortes dá pra saber se o produto está retendo melhor com o tempo ou se está só recrutando mais pra repor quem sai. Coorte é a forma honesta de medir saúde de base de clientes.

Coisas técnicas que apareceram aqui: Period arithmetic (to_period, subtração de períodos), groupby.transform("min") (espalhar mínimo no grupo), nunique, pivot, construção da matriz coorte sem pacote externo.