4  📈 Mensurando os resultados

Muitas vezes não temos a oportunidade de participar da decisão do volume das amostras, pois muitas campanhas podem ser automatizadas e não atigirem o número que seria adequado, esse pode ser um cenário para campanhas estremamente segmentadas. Mesmo assim, essas precisam ser MENSURADAS.

4.1 🏞️ Cenário

Foi realizada uma campanha que visava fazer o cliente realizar uma compra para ativar o seu cartão. Ou seja, queremos saber se o estimulo de comunicar uma oferta fez com que esse cliente voltasse a transacionar, e como um efeito colateral também podemos avaliar o aumento de gasto.

4.2 🗂️ Base de dados

Para realizar essa mensuração, você precisa receber uma base que possua as seguintes informações:

Variável Descrição
CLIENTNUM Identificador único do cliente
GRUPO_AB Grupo experimental A/B ao qual pertence (Teste e Controle)
FL_ATIVADO Flag indicando se o cliente ativou o cartão durante o período da ação
VALOR_GASTO Valor total gasto pelo cliente

4.3 📈 Mensuração dos resultados

*dados ficticios

Garanta que a separação do grupo_ab tenha sido feita adequadamente antes de iniciar os envios das comunicações, pois caso isso não ocorra, sua mensuração está fadada ao fracasso. No anexo desse conteúdo, há uma sugestão de como criar um grupo controle confiável para sua empresa.

4.3.1 📥 Import das bibliotecas

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import ttest_ind
import numpy as np
from scipy.stats import chi2_contingency
from statsmodels.stats.power import zt_ind_solve_power
import sys
#sys.stdout.reconfigure(encoding='utf-8')
# Importa o arquivo gerado com os resultados de forma analitica
df = pd.read_csv('./base_ativados_gasto.csv')
df.head()
CLIENTNUM GRUPO_AB FL_ATIVADO VALOR_GASTO
0 1 Controle 0 0.000000
1 2 Teste 0 0.000000
2 3 Teste 0 0.000000
3 4 Teste 0 0.000000
4 5 Teste 1 2185.430535

4.3.2 🔎Avalia o tamanho da amostra

# Supondo um efeito mínimo detectável (MDE) de 1 ponto percentual (0.01)
p1 = 0.05           # Conversão esperada no grupo Controle
delta = 0.01        # MDE (efeito mínimo que desejamos detectar)
p2 = p1 + delta     # Conversão esperada no grupo Teste

# Calcular effect size para teste de duas proporções
pooled_var = ((p1 * (1 - p1)) + (p2 * (1 - p2))) / 2
effect_size = delta / pooled_var**0.5

# Calcular a amostra mínima por grupo
n_min = zt_ind_solve_power(effect_size=effect_size, alpha=0.05, power=0.80)
n_min = int(round(n_min))

# Avaliar se os tamanhos dos grupos são suficientes
n_controle = df['GRUPO_AB'].value_counts()['Controle']
n_teste = df['GRUPO_AB'].value_counts()['Teste']

teste_suficiente = n_teste >= n_min
controle_suficiente = n_controle >= n_min

{
    "MDE (delta)": f"{delta:.2%}",
    "Amostra mínima por grupo": n_min,
    "Tamanho Controle": n_controle,
    "Tamanho Teste": n_teste,
    "Controle Suficiente": controle_suficiente,
    "Teste Suficiente": teste_suficiente
}
{'MDE (delta)': '1.00%',
 'Amostra mínima por grupo': 8155,
 'Tamanho Controle': 12308,
 'Tamanho Teste': 109216,
 'Controle Suficiente': True,
 'Teste Suficiente': True}

4.3.3 🔎Amostra mínima

Qual seria a amostra mínima desse teste?

# Total de público disponível
total_disponivel = n_controle + n_teste

# Função para encontrar a melhor alocação que satisfaz a amostra mínima por grupo
def melhor_distribuicao(total, n_min):
    for controle in range(n_min, total - n_min + 1):
        teste = total - controle
        if teste >= n_min:
            return controle, teste
    return None, None

melhor_controle, melhor_teste = melhor_distribuicao(total_disponivel, n_min)

{
    "Total disponível": total_disponivel,
    "Amostra mínima por grupo (para delta de 1%)": n_min,
    "Distribuição ideal - Controle": melhor_controle,
    "Distribuição ideal - Teste": melhor_teste
}
{'Total disponível': 121524,
 'Amostra mínima por grupo (para delta de 1%)': 8155,
 'Distribuição ideal - Controle': 8155,
 'Distribuição ideal - Teste': 113369}

Independente dos resultados obtidos nessa fase, você deve continuar a mensurar os seus resultados, pois ao menos podemos interpretar algumas tendências de resultados, o que pode ser base para um novo teste corrigindo o tamanho da amostra.

4.3.4 📉 Analise do resultado do teste

  • O período de leitura terminou?
  • Sua variável de analise é binária ou contínua?
    • Comprou ou não comprou (ativou ou não, contratou ou não)
    • Valor gasto (valor contratado, valor faturamento)
# Criar tabela de contingência entre grupo e compra
contingencia = pd.crosstab(df['GRUPO_AB'], df['FL_ATIVADO'])
contingencia
FL_ATIVADO 0 1
GRUPO_AB
Controle 6158 6150
Teste 49157 60059
# Aplicar o teste do qui-quadrado -- Verifique se esse é o teste mais adequado para os tipos de variáveis que você possui.
chi2, p, dof, expected = chi2_contingency(contingencia)

Por que o valor de p é tão amado e odiado por muita gente. Me convenceram que é muito bom, mas não faz milagre.

# Exibir resultados
contingencia, p
(FL_ATIVADO      0      1
 GRUPO_AB                
 Controle     6158   6150
 Teste       49157  60059,
 2.979975996279689e-26)
# considere um alpha para sua área de rejeição
alpha = 0.05

if p < alpha:
    print('✅ Resultado Estatisticamente Significativo: Existe diferença real entre os grupos. ')
else:
    print('❌ Resultado NÃO Estatisticamente Significativo: Não existe evidência suficiente para afirmar diferença entre grupos.')
✅ Resultado Estatisticamente Significativo: Existe diferença real entre os grupos. 

4.3.5 📉 Delta Conversão

É a diferença entre os percentuais de conversão do grupo de teste e controle

taxas = df.groupby('GRUPO_AB')['FL_ATIVADO'].mean()
taxas
GRUPO_AB
Controle    0.499675
Teste       0.549910
Name: FL_ATIVADO, dtype: float64
# Calcular e formatar o delta de taxa de conversão entre Teste e Controle
delta_taxa_conversao = taxas['Teste'] - taxas['Controle']

# Reformatar delta como pontos percentuais (p.p.)
delta_taxa_formatado_pp = f"{delta_taxa_conversao * 100:.2f} p.p."

# Exibir com print
print(f"Diferença absoluta na taxa de conversão (Delta): {delta_taxa_formatado_pp}")
Diferença absoluta na taxa de conversão (Delta): 5.02 p.p.

4.3.6 📉 Lift

É o ganho percentual que foi obtido com essa ação.

lift_conversao = (taxas['Teste'] - taxas['Controle']) / taxas['Controle']
lift_conversao

# Formatar lift de conversão como percentual com 2 casas decimais
lift_conversao_percentual = f"{lift_conversao * 100:.2f}%"
lift_conversao_percentual
'10.05%'

4.3.7 📉 Incremento de contas

Pelo fato de ter feito o teste, quanto de contas vieram de

# Calcular o tamanho do público de teste
publico_teste = df[df['GRUPO_AB'] == 'Teste'].shape[0]

# Calcular o incremento de contas
incremento_contas = publico_teste * (taxas['Teste'] - taxas['Controle'])

# Arredondar o incremento
incremento_final = int(round(incremento_contas))

# Verificar se o teste foi positivo
resultado_incremento = (
    f"{incremento_final} contas a mais por causa do teste" if incremento_final > 0 
    else "O teste não gerou incremento de contas"
)

resultado_incremento
'5486 contas a mais por causa do teste'

4.3.8 📉 Incremento de gasto

Quanto a mais o grupo de teste gastou devido esse estimulo.

# Calcular gasto médio entre grupos
gasto_medio = df.groupby('GRUPO_AB')['VALOR_GASTO'].mean()

# Calcular delta gasto: diferença entre gasto médio do Teste e do Controle
delta_gasto = gasto_medio['Teste'] - gasto_medio['Controle']

# Número de clientes convertidos no grupo Controle
conversoes_controle = df[df['GRUPO_AB'] == 'Controle']['FL_ATIVADO'].sum()

# Incremento estimado de gasto
incremento_gasto = delta_gasto * conversoes_controle

#delta_gasto, incremento_gasto
# Usar print para exibir cada linha separadamente
print("Resultados do impacto financeiro no experimento:\n")
print(f"Delta de Gasto Médio (Teste - Controle): R$ {delta_gasto:,.2f}".replace(",", "X").replace(".", ",").replace("X", "."))
print(f"Incremento de Gasto Total (base Controle): R$ {incremento_gasto:,.2f}".replace(",", "X").replace(".", ",").replace("X", "."))
Resultados do impacto financeiro no experimento:

Delta de Gasto Médio (Teste - Controle): R$ 143,31
Incremento de Gasto Total (base Controle): R$ 881.357,95

4.3.9 📉 Incremento de faturamento

Os clientes que vieram a mais por causa do experimento também contribuem para o faturamento, logo precisamos incluí-los:

Incremento de faturamento = Incremento de Contas * Gasto Médio Controle + Incremento de Gasto

# Reutilizando variáveis já calculadas
gasto_medio_controle = gasto_medio['Controle']  # gasto médio no grupo Controle
gasto_medio_teste = gasto_medio['Teste']  # gasto médio no grupo Controle

# Incremento total de faturamento = contas a mais * gasto médio + incremento de gasto
incremento_faturamento = incremento_final * gasto_medio_controle + incremento_gasto

# Formatar resultado para apresentação
print(f"Incremento Total de Faturamento: R$ {incremento_faturamento:,.2f}".replace(",", "X").replace(".", ",").replace("X", "."))
Incremento Total de Faturamento: R$ 8.388.591,06

4.3.10 📈 Analise da variável contínua

metrica = 'VALOR_GASTO'

# Separar os dados por grupo
grupo_controle = df[df['GRUPO_AB'] == 'Controle'][metrica]
grupo_teste = df[df['GRUPO_AB'] == 'Teste'][metrica]

# Realizar o teste-t (comparação das médias)
t_stat, p_valor = ttest_ind(grupo_teste, grupo_controle)

# Avaliação dos resultados
alpha = 0.05
print(f'Média do grupo Controle: {grupo_controle.mean():.2f}')
print(f'Média do grupo Teste: {grupo_teste.mean():.2f}')
print(f'p-valor do teste: {p_valor:.4f}')

if p_valor < alpha:
    print('✅ Resultado Estatisticamente Significativo: Existe diferença real entre os grupos.')
else:
    print('❌ Resultado NÃO Estatisticamente Significativo: Não existe evidência suficiente para afirmar diferença entre grupos. ')
Média do grupo Controle: 1368.43
Média do grupo Teste: 1511.74
p-valor do teste: 0.0000
✅ Resultado Estatisticamente Significativo: Existe diferença real entre os grupos.

4.4 📊 Avalição completa dos resultados

qtde_teste = df[df['GRUPO_AB'] == 'Teste'].shape[0]
qtde_controle = df[df['GRUPO_AB'] == 'Controle'].shape[0]
conv_teste = taxas['Teste']
conv_controle = taxas['Controle']

hipotese_conv = "Rejeita H₀ - Existe Diferença" if p < 0.05 else "Aceita H₀ - Não Existe Diferença"
hipotese_gasto = "Rejeita H₀ - Existe Diferença" if p_valor < 0.05 else "Aceita H₀ - Não Existe Diferença"



# Avaliação simples da qualidade do resultado
def classifica_resultado(delta_conv, delta_gasto, pval_conv, pval_gasto, incremento_faturamento):
    aval = []

    if p < 0.05 and delta_conv > 0:
        aval.append("✅ Conversão melhor e significativa")
    elif delta_conv > 0:
        aval.append("⚠️ Conversão melhor, mas não significativa")
    else:
        aval.append("❌ Conversão pior ou igual")

    if p_valor < 0.05 and delta_gasto > 0:
        aval.append("✅ Gasto médio maior e significativo")
    elif delta_gasto > 0:
        aval.append("⚠️ Gasto médio maior, mas não significativo")
    else:
        aval.append("❌ Gasto médio menor ou igual")

    if incremento_faturamento > 0:
        aval.append("✅ Incremento financeiro positivo")
    else:
        aval.append("❌ Sem impacto financeiro")

    return " | ".join(aval)

avaliacao_geral = classifica_resultado(delta_taxa_conversao, delta_gasto, p, p_valor, incremento_faturamento)

# Tabela resumo com avaliação geral
tabela_resumo_completa = pd.DataFrame({
    'Métrica': [
        'Qtde Teste', 'Qtde Controle',
        'Conversão Teste', 'Conversão Controle', 'Delta Conversão', 'Lift',
        'Resultado Teste Hipótese Conversão', 'Incremento de Contas',
        'Gasto Médio Teste', 'Gasto Médio Controle', 'Delta Gasto',
        'Resultado Teste Hipótese Gasto', 'Incremento de Gasto',
        'Incremento de Faturamento', 'Avaliação Geral'
    ],
    'Resultado': [
        qtde_teste, qtde_controle,
        f"{conv_teste:.2%}", f"{conv_controle:.2%}", f"{delta_taxa_conversao * 100:.2f} p.p.",lift_conversao_percentual,
        hipotese_conv, incremento_final,
        f"R$ {gasto_medio_teste:,.2f}".replace(",", "X").replace(".", ",").replace("X", "."),
        f"R$ {gasto_medio_controle:,.2f}".replace(",", "X").replace(".", ",").replace("X", "."),
        f"R$ {delta_gasto:,.2f}".replace(",", "X").replace(".", ",").replace("X", "."),
        hipotese_gasto,
        f"R$ {incremento_gasto:,.2f}".replace(",", "X").replace(".", ",").replace("X", "."),
        f"R$ {incremento_faturamento:,.2f}".replace(",", "X").replace(".", ",").replace("X", "."),
        avaliacao_geral
    ]
})

# Como Excel
tabela_resumo_completa.to_excel("Mensuracao_TesteAB.xlsx", index=False)

tabela_resumo_completa
Métrica Resultado
0 Qtde Teste 109216
1 Qtde Controle 12308
2 Conversão Teste 54.99%
3 Conversão Controle 49.97%
4 Delta Conversão 5.02 p.p.
5 Lift 10.05%
6 Resultado Teste Hipótese Conversão Rejeita H₀ - Existe Diferença
7 Incremento de Contas 5486
8 Gasto Médio Teste R$ 1.511,74
9 Gasto Médio Controle R$ 1.368,43
10 Delta Gasto R$ 143,31
11 Resultado Teste Hipótese Gasto Rejeita H₀ - Existe Diferença
12 Incremento de Gasto R$ 881.357,95
13 Incremento de Faturamento R$ 8.388.591,06
14 Avaliação Geral ✅ Conversão melhor e significativa | ✅ Gasto m...

4.4.1 📚 Qual decisão tomar?

# Reescrevendo a função classifica_resultado com inserção da verificação da suficiência da amostra

def classifica_resultado_com_amostra(delta_conv, delta_gasto, p, p_valor, incremento_faturamento, teste_suf, controle_suf):
    aval = []

    # Verificação de conversão
    if not (teste_suf and controle_suf):
        aval.append("❌ Amostras insuficientes para avaliar conversão")
    elif p < 0.05 and delta_conv > 0:
        aval.append("✅ Conversão melhor e significativa")
    elif delta_conv > 0:
        aval.append("⚠️ Conversão melhor, mas não significativa")
    else:
        aval.append("❌ Conversão pior ou igual")

    # Verificação de gasto
    if p_valor < 0.05 and delta_gasto > 0:
        aval.append("✅ Gasto médio maior e significativo")
    elif delta_gasto > 0:
        aval.append("⚠️ Gasto médio maior, mas não significativo")
    else:
        aval.append("❌ Gasto médio menor ou igual")

    # Verificação de faturamento
    if incremento_faturamento > 0:
        aval.append("✅ Incremento financeiro positivo")
    else:
        aval.append("❌ Sem impacto financeiro")

    return " | ".join(aval)

# Calcular avaliação com variáveis existentes
avaliacao_completa = classifica_resultado_com_amostra(
    delta_taxa_conversao, delta_gasto, p, p_valor,
    incremento_faturamento, controle_suficiente, teste_suficiente
)

# Reavaliar a classificação anterior com base na decisão da tabela interpretativa
# Vamos mapear o resultado da função classifica_resultado_com_amostra para a decisão final

def decisao_final(avaliacao_texto):
    amostra_ok = "insuficientes" not in avaliacao_texto
    conversao_ok = "Conversão melhor e significativa" in avaliacao_texto
    gasto_ok = "Gasto médio maior e significativo" in avaliacao_texto

    # Definir a linha da tabela com base na combinação de flags
    if amostra_ok and conversao_ok and gasto_ok:
        return "✅ Resultados estatisticamente confiáveis. Pode fazer rollout com segurança."
    elif amostra_ok and conversao_ok and not gasto_ok:
        return "⚠️ Conversão positiva e confiável, mas impacto financeiro incerto. Teste complementar recomendado."
    elif amostra_ok and not conversao_ok and gasto_ok:
        return "⚠️ Conversão estável, mas há aumento de gasto. Reavaliar se isso é desejável."
    elif amostra_ok and not conversao_ok and not gasto_ok:
        return "❌ Sem evidências de ganho. Não prosseguir com rollout."
    elif not amostra_ok and conversao_ok and gasto_ok:
        return "⚠️ Indicadores promissores, mas resultados frágeis estatisticamente. Aumentar amostra."
    elif not amostra_ok and conversao_ok and not gasto_ok:
        return "⚠️ Conversão aparente, mas não confiável. Repetir experimento com mais dados."
    elif not amostra_ok and not conversao_ok and gasto_ok:
        return "⚠️ Gasto parece maior, mas não confiável. Não tomar decisão ainda."
    elif not amostra_ok and not conversao_ok and not gasto_ok:
        return "❌ Nada é significativo e a amostra é fraca. Não prosseguir."
    else:
        return "⚠️ Cenário não previsto. Revisar avaliação."

# Aplicar a função de decisão
decisao_experimento = decisao_final(avaliacao_completa)

print(avaliacao_completa)
print(decisao_experimento)
✅ Conversão melhor e significativa | ✅ Gasto médio maior e significativo | ✅ Incremento financeiro positivo
✅ Resultados estatisticamente confiáveis. Pode fazer rollout com segurança.

4.4.2 📋Tabela de avaliação dos resultados

Guia para os possíveis resultados que você pode obter!

Amostra Suficiente Conversão Significativa Gasto Significativo Interpretação Final
✅ Sim ✅ Sim ✅ Sim Resultados estatisticamente confiáveis. Pode fazer rollout com segurança.
✅ Sim ✅ Sim ❌ Não ⚠️ Conversão positiva e confiável, mas impacto financeiro incerto. Teste complementar recomendado.
✅ Sim ❌ Não ✅ Sim ⚠️ Conversão estável, mas há aumento de gasto. Reavaliar se isso é desejável.
✅ Sim ❌ Não ❌ Não ❌ Sem evidências de ganho. Não prosseguir com rollout.
❌ Não ✅ Sim ✅ Sim ⚠️ Indicadores promissores, mas resultados frágeis estatisticamente. Aumentar amostra.
❌ Não ✅ Sim ❌ Não ⚠️ Conversão aparente, mas não confiável. Repetir experimento com mais dados.
❌ Não ❌ Não ✅ Sim ⚠️ Gasto parece maior, mas não confiável. Não tomar decisão ainda.
❌ Não ❌ Não ❌ Não ❌ Nada é significativo e a amostra é fraca. Não prosseguir.