import pandas as pd
from statsmodels.stats.proportion import proportions_ztest
from scipy.stats import ttest_ind_from_stats
import numpy as np
import warnings
# Suprimir apenas RuntimeWarnings
warnings.filterwarnings("ignore", category=RuntimeWarning)5 👥 Analisando diversos perfis
ou diversas campanhas em um resumo.
Agora que você chegou até aqui, acho que posso te dar uma boa notícia! Os indicadores analiticos campanha, público, quantidade de teste e controle e as quantidades de conversão, costumam vir calculados, pois é um processo que pode ser automatizado via banco de dados.
Com isso, cada linha da sua tabela pode representar uma campanha diferente, ou uma segmentação de público diferente dentro da mesma!
Nesse exemplo aplicado, irei seguir com quebras de público, onde quero testar diferenças entre conversões de taxas de emprestimos diferentes. Um teste A/B raiz, afinal, todos foram abordados, mas receberam ofertas diferentes, se aplicamos em marketing, seriam ofertas e/ou banners diferentes para cada público
5.1 ➕Um pouco mais de contexto
*dados ficticios
Esses resultados foram simulados a partir de uma atividade de princing, onde foram usados dois modelos para caracterizar os grupos de maneiras comparáveis.
MODELO 1: Risco de realizar essa operação
MODELO 2: Probabilidade do cliente aceitar a oferta
5.1.1 📥Import das bibliotecas
# Carregar a planilha com colunas a serem completadas
df = pd.read_excel("./diversos_grupos_calculo.xlsx")
df.head()| MODELO 1 | MODELO2 | Taxa Teste | Taxa Controle | Qtde Teste | Qtde Controle | Qtde Conversao Teste | Qtde Conversao Controle | Faturamento Teste | Faturamento Controle | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | BAIXO | BAIXO | 0.0299 | 0.0399 | 21996 | 197964 | 1979 | 15837 | 8232897.27 | 3.895902e+06 |
| 1 | BAIXO | ALTO | 0.0399 | 0.0499 | 31259 | 281331 | 1562 | 22506 | 1098086.00 | 9.585305e+07 |
| 2 | MÉDIO | BAIXO | 0.0499 | 0.0599 | 36027 | 324243 | 1801 | 19454 | 6933850.00 | 7.322486e+07 |
| 3 | MÉDIO | ALTO | 0.0599 | 0.0699 | 81498 | 733482 | 814 | 66013 | 4004066.00 | 3.671643e+07 |
| 4 | ALTO | BAIXO | 0.0699 | 0.0799 | 57985 | 521865 | 4058 | 52186 | 7925274.00 | 2.569811e+08 |
5.1.2 🧠 Pró-Memória
Agora vamos passar por todos os cálculos feitos no capítulo anterior:
Conversão e Taxas
Gasto Médio
Incrementos
Testes de hipoteses para avaliar a significancia dos resultados
Avaliação Final
# Função para enriquecer o dataframe
def enrich_with_proportions(row):
# Conversões e taxas
conv_teste = row['Qtde Conversao Teste'] / row['Qtde Teste'] if row['Qtde Teste'] > 0 else 0
conv_controle = row['Qtde Conversao Controle'] / row['Qtde Controle'] if row['Qtde Controle'] > 0 else 0
delta_conv = conv_teste - conv_controle
lift_conversao = (conv_teste - conv_controle) / conv_controle if conv_controle > 0 else np.nan
lift_conversao_percentual = lift_conversao * 100
# Gasto médio como proporção = Faturamento / Qtde Conversões
faturamento_teste = row['Faturamento Teste']
faturamento_controle = row['Faturamento Controle']
gasto_medio_teste = faturamento_teste / row['Qtde Conversao Teste'] if row['Qtde Conversao Teste'] > 0 else 0
gasto_medio_controle = faturamento_controle / row['Qtde Conversao Controle'] if row['Qtde Conversao Controle'] > 0 else 0
delta_gasto = gasto_medio_teste - gasto_medio_controle
# Incrementos
incremento_contas = delta_conv * row['Qtde Teste']
incremento_gasto = delta_gasto * row['Qtde Conversao Teste']
incremento_faturamento = incremento_contas * gasto_medio_controle + incremento_gasto
# Teste de hipótese para conversão (proporções)
if row['Qtde Teste'] > 0 and row['Qtde Controle'] > 0:
count_conv = [row['Qtde Conversao Controle'], row['Qtde Conversao Teste']]
nobs_conv = [row['Qtde Controle'], row['Qtde Teste']]
_, p_conv = proportions_ztest(count_conv, nobs_conv)
else:
p_conv = np.nan
resultado_conv = "Rejeita H₀" if p_conv < 0.05 and delta_conv > 0 else "Aceita H₀"
# Teste de hipótese para gasto (proporções também, pois gasto médio é faturamento/conversão)
if row['Qtde Conversao Controle'] > 0 and row['Qtde Conversao Teste'] > 0:
count_gasto = [faturamento_controle, faturamento_teste]
nobs_gasto = [row['Qtde Conversao Controle'], row['Qtde Conversao Teste']]
_, p_gasto = proportions_ztest(count_gasto, nobs_gasto)
else:
p_gasto = np.nan
resultado_gasto = "Rejeita H₀" if p_gasto < 0.05 and delta_gasto > 0 else "Aceita H₀"
# Avaliação final (tabela de decisão)
amostra_suficiente = row['Qtde Teste'] >= 30 and row['Qtde Controle'] >= 30
conv_significativa = p_conv < 0.05 if not np.isnan(p_conv) else False
gasto_significativo = p_gasto < 0.05 if not np.isnan(p_gasto) else False
if amostra_suficiente:
if conv_significativa and gasto_significativo:
interpretacao = "✅ Resultados estatisticamente confiáveis. Pode fazer rollout com segurança."
elif conv_significativa and not gasto_significativo:
interpretacao = "⚠️ Conversão positiva e confiável, mas impacto financeiro incerto. Teste complementar recomendado."
elif not conv_significativa and gasto_significativo:
interpretacao = "⚠️ Conversão estável, mas há aumento de gasto. Reavaliar se isso é desejável."
else:
interpretacao = "❌ Sem evidências de ganho. Não prosseguir com rollout."
else:
if conv_significativa and gasto_significativo:
interpretacao = "⚠️ Indicadores promissores, mas resultados frágeis estatisticamente. Aumentar amostra."
elif conv_significativa:
interpretacao = "⚠️ Conversão aparente, mas não confiável. Repetir experimento com mais dados."
elif gasto_significativo:
interpretacao = "⚠️ Gasto parece maior, mas não confiável. Não tomar decisão ainda."
else:
interpretacao = "❌ Nada é significativo e a amostra é fraca. Não prosseguir."
return pd.Series([
conv_teste, conv_controle, delta_conv, lift_conversao_percentual,
gasto_medio_teste, gasto_medio_controle, delta_gasto,
incremento_contas, incremento_gasto, incremento_faturamento,
resultado_conv, resultado_gasto, interpretacao
], index=[
'Conversão Teste', 'Conversão Controle', 'Delta Conversão', 'Lift Conversão (%)',
'Gasto Médio Teste', 'Gasto Médio Controle', 'Delta Gasto Médio',
'Incremento Contas', 'Incremento Gasto', 'Incremento Faturamento',
'Resultado Conversão', 'Resultado Gasto', 'Interpretação Final'
])
# Aplicar a função
df_enriched = df.join(df.apply(enrich_with_proportions, axis=1))
df_enriched| MODELO 1 | MODELO2 | Taxa Teste | Taxa Controle | Qtde Teste | Qtde Controle | Qtde Conversao Teste | Qtde Conversao Controle | Faturamento Teste | Faturamento Controle | ... | Lift Conversão (%) | Gasto Médio Teste | Gasto Médio Controle | Delta Gasto Médio | Incremento Contas | Incremento Gasto | Incremento Faturamento | Resultado Conversão | Resultado Gasto | Interpretação Final | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | BAIXO | BAIXO | 0.0299 | 0.0399 | 21996 | 197964 | 1979 | 15837 | 8232897.27 | 3.895902e+06 | ... | 12.464482 | 4160.13 | 246.00 | 3914.13 | 219.333333 | 7746063.27 | 7.800019e+06 | Rejeita H₀ | Aceita H₀ | ⚠️ Conversão positiva e confiável, mas impacto... |
| 1 | BAIXO | ALTO | 0.0399 | 0.0499 | 31259 | 281331 | 1562 | 22506 | 1098086.00 | 9.585305e+07 | ... | -37.536657 | 703.00 | 4259.00 | -3556.00 | -938.666667 | -5554472.00 | -9.552253e+06 | Aceita H₀ | Aceita H₀ | ⚠️ Conversão positiva e confiável, mas impacto... |
| 2 | MÉDIO | BAIXO | 0.0499 | 0.0599 | 36027 | 324243 | 1801 | 19454 | 6933850.00 | 7.322486e+07 | ... | -16.680374 | 3850.00 | 3764.00 | 86.00 | -360.555556 | 154886.00 | -1.202245e+06 | Aceita H₀ | Aceita H₀ | ⚠️ Conversão positiva e confiável, mas impacto... |
| 3 | MÉDIO | ALTO | 0.0599 | 0.0699 | 81498 | 733482 | 814 | 66013 | 4004066.00 | 3.671643e+07 | ... | -88.902186 | 4919.00 | 556.20 | 4362.80 | -6520.777778 | 3551319.20 | -7.553740e+04 | Aceita H₀ | Aceita H₀ | ⚠️ Conversão positiva e confiável, mas impacto... |
| 4 | ALTO | BAIXO | 0.0699 | 0.0799 | 57985 | 521865 | 4058 | 52186 | 7925274.00 | 2.569811e+08 | ... | -30.015713 | 1953.00 | 4924.33 | -2971.33 | -1740.444444 | -12057657.14 | -2.062818e+07 | Aceita H₀ | Aceita H₀ | ⚠️ Conversão positiva e confiável, mas impacto... |
| 5 | ALTO | ALTO | 0.0799 | 0.0899 | 68921 | 620289 | 6892 | 31014 | 26058652.00 | 1.059748e+08 | ... | 100.000000 | 3781.00 | 3417.00 | 364.00 | 3446.000000 | 2508688.00 | 1.428367e+07 | Rejeita H₀ | Aceita H₀ | ⚠️ Conversão positiva e confiável, mas impacto... |
| 6 | ALTISSIMO | BAIXO | 0.0899 | 0.0999 | 85970 | 773730 | 3438 | 30949 | 4201236.00 | 7.471089e+07 | ... | -0.022618 | 1222.00 | 2414.00 | -1192.00 | -0.777778 | -4098096.00 | -4.099974e+06 | Aceita H₀ | Aceita H₀ | ❌ Sem evidências de ganho. Não prosseguir com ... |
| 7 | ALTISSIMO | ALTO | 0.0999 | 0.1099 | 98759 | 888831 | 6913 | 71106 | 31571671.00 | 2.320189e+08 | ... | -12.501055 | 4567.00 | 3263.00 | 1304.00 | -987.666667 | 9014552.00 | 5.791796e+06 | Aceita H₀ | Aceita H₀ | ⚠️ Conversão positiva e confiável, mas impacto... |
8 rows × 23 columns
Agora que você possui o impacto de cada taxa é só tomar a decisão se fará um roll-out ou não.
#Exporta o resultado para o excel
df_enriched.to_excel("TesteABDiversosGrupos.xlsx", index=False)