Introdução
Uma oficina comunitária tem duas versões da sua página de inscrição, A e B. Antes de compará-las, a analista precisa definir o que conta como uma observação. Uma pessoa pode visitar três vezes ou clicar duas vezes para se inscrever, enquanto outra não gera evento algum. Contar essas linhas de eventos como pessoas diferentes distorceria a comparação.
Você começará com um exemplo de seis pessoas e depois construirá um resultado binário de sete dias para cada visitante designado em um experimento fixo maior. Estes dados didáticos sintéticos originais representam uma designação aleatória; não são rastreamento de visitantes reais nem evidências sobre um negócio real. Python, pandas, NumPy, SciPy e Matplotlib estão preparados em um ambiente WebIDE independente. É necessário conhecer navegação básica no terminal, funções Python e seleção de tabelas com pandas; as junções e operações de tempo necessárias são explicadas aqui. Não é preciso conhecer inferência estatística. Os 55 minutos são uma estimativa do autor que inclui leitura e correções comuns.
Compare pessoas em vez de cliques
Nesta etapa, você lerá seis cartões de visitantes e calculará taxas de inscrição usando as pessoas designadas como denominadores.
Comece no WebIDE e abra Terminal → New Terminal. O terminal e o explorador de arquivos compartilham os mesmos arquivos. cd muda o diretório de trabalho deste terminal; repita o comando ao abrir outro terminal.
cd /home/labex/project/signup-experiment
cat mostra um arquivo de texto sem modificá-lo. Leia o pequeno exemplo resolvido:
cat warmup-visitors.csv
Cada linha é um visitante designado. Os três primeiros pertencem a A e os três últimos a B. Neste exemplo, qualifying_signup_events já foi classificado dentro da janela de sete dias de cada pessoa. signed_up é o resultado binário correspondente: 1 se houve pelo menos uma inscrição válida, ou 0 caso contrário.
P-8 tem três visitas e dois eventos de inscrição, mas contribui com uma pessoa e um resultado positivo. Q-3 não tem visita nem inscrição, mas continua sendo uma pessoa designada com resultado 0. Leia os seis cartões antes de calcular: A tem uma inscrição entre três pessoas designadas; B tem duas entre três.
A taxa de inscrição de A é 1 / 3, aproximadamente 33.33%. A de B é 2 / 3, aproximadamente 66.67%. A diferença é B menos A, cerca de +33.33 pontos percentuais. Pontos percentuais subtraem percentuais diretamente; isso não é um aumento percentual relativo. Mais visitas não ampliam nenhum denominador.
Escreva uma função de comparação reutilizável. O executor passa um DataFrame pandas chamado people, com as colunas visitor_id, group e signed_up. Uma máscara booleana como people["group"] == "A" seleciona as linhas de A. len(a) conta pessoas designadas; somar uma coluna binária conta as pessoas inscritas. int e float produzem números Python comuns para arquivos JSON.
O redirecionamento de shell > substitui o arquivo indicado. Um here-document entre aspas, <<'PY', passa tudo até uma linha contendo apenas PY para esse arquivo sem expansão do shell. Cole o bloco inteiro, incluindo o PY final. Como alternativa, edite o mesmo arquivo no WebIDE e use File → Save.
cat > compare.py <<'PY'
def summarize(people):
result = {}
for group in ["A", "B"]:
selected = people[people["group"] == group]
assigned = len(selected)
signed_up = int(selected["signed_up"].sum())
result[group] = {
"assigned": assigned,
"signed_up": signed_up,
"rate_pct": 100 * signed_up / assigned,
}
result["difference_pp"] = result["B"]["rate_pct"] - result["A"]["rate_pct"]
return result
PY
python3 executa Python. O run.py preparado chama sua função e grava seu valor de retorno real; o argumento warmup seleciona esta entrada. Ele não completa respostas ausentes.
python3 run.py warmup
O comando salva output/warmup.json. JSON armazena objetos nomeados e números. O leitor preparado só exibe arquivos existentes; nunca reexecuta seu programa nem corrige um resultado ausente.
python3 show_results.py warmup
Confirme os totais designados 3 e 3, os positivos 1 e 2 e uma diferença próxima de +33.33 pontos percentuais. Mantenha números não arredondados nos resultados salvos; os arredondamentos desta explicação servem apenas à leitura. Uma taxa observada maior, por si só, não estabelece que B melhorou a página.
Somente compare.py e, depois, outcomes.py precisam mudar neste laboratório. Você pode acrescentar arquivos auxiliares Python comuns ao lado deles. Preserve assignments.csv, events.csv, warmup-visitors.csv, config.json, data-dictionary.md, run.py e show_results.py. As outras unidades têm cópias independentes e não dependem de arquivos desta VM.
Se um nome de arquivo ou diretório estiver errado, use pwd para mostrar o diretório atual e ls para listar seus arquivos, depois retorne com o cd acima. Copie novamente o bloco completo da função para corrigir um erro comum de edição; não é necessário repetir o setup nem redefinir o ambiente.
Construa resultados completos de sete dias
Nesta etapa, você transformará registros brutos de designações e eventos na tabela completa por pessoa necessária à comparação.
Continue no mesmo terminal WebIDE, em /home/labex/project/signup-experiment. Leia as definições dos campos e depois as tabelas originais:
cat data-dictionary.md
cat assignments.csv
cat events.csv
A tabela completa tem 12 pessoas designadas, seis por grupo. visitor_id é uma identidade textual estável, não uma posição de linha. A tabela de eventos repete pessoas e inclui o visitante alheio OUTSIDE, portanto as tabelas não podem ser emparelhadas pelo número da linha. O registro é completo neste exemplo didático: nenhum evento válido significa nenhuma inscrição, e não um resultado desconhecido.
Cada pessoa tem um instante de designação com fuso horário, assigned_at. A janela é [designação, designação + sete dias): o início conta, mas o limite superior exato não. UTC é uma referência temporal comum; 12:00:00+02:00 e 10:00:00Z descrevem o mesmo instante. pd.to_datetime(..., utc=True, format="ISO8601") converte essas strings de formato explícito em marcas temporais UTC comparáveis. Um Timedelta(days=7) representa sete dias de tempo decorrido.
Todo acompanhamento deve ter terminado até o instante de análise fornecido:
cat config.json
Nesta unidade, cada janela termina antes de analysis_at. As outras configurações serão usadas nos próximos laboratórios. Não mude o instante de análise para fazer uma pessoa com acompanhamento inacabado parecer completa. Todas as tabelas fornecidas têm 4–14 pessoas, com pelo menos duas por grupo. IDs de designação duplicados, incluindo grupos conflitantes, são inválidos; escolher a linha com o resultado mais bonito não é uma correção.
Uma junção à esquerda (left merge) começa por cada designação e associa os eventos correspondentes. how="left" mantém uma pessoa sem eventos. validate="one_to_many" exige que cada ID apareça no máximo uma vez na tabela de designações, mas permite várias linhas de eventos. Um ID de evento alheio não pode acrescentar alguém à população designada.
As comparações criam máscaras booleanas. No pandas, & combina condições e | significa uma ou outra; coloque cada comparação entre parênteses. Depois, groupby(...).any() pergunta se existe algum evento válido para cada pessoa. Mapear esses indicadores de volta por ID mantém uma linha por designação, incluindo zeros legítimos. ValueError recusa explicitamente designações inválidas ou acompanhamentos inacabados, em vez de fingir que os dados são comparáveis.
cat > outcomes.py <<'PY'
import pandas as pd
def build_people(assignments, events, analysis_at):
a = assignments.copy()
if (a["visitor_id"].isna().any()
or (a["visitor_id"] == "").any()
or a["visitor_id"].duplicated().any()):
raise ValueError("Each visitor needs one unique, nonempty assignment ID.")
if not a["group"].isin(["A", "B"]).all():
raise ValueError("Only groups A and B are valid.")
a["assigned_at"] = pd.to_datetime(a["assigned_at"], utc=True, format="ISO8601")
a["window_end"] = a["assigned_at"] + pd.Timedelta(days=7)
if not (a["window_end"] <= pd.Timestamp(analysis_at)).all():
raise ValueError("Every seven-day follow-up must be complete.")
e = events.copy()
e["occurred_at"] = pd.to_datetime(e["occurred_at"], utc=True, format="ISO8601")
joined = a.merge(e, on="visitor_id", how="left", validate="one_to_many")
eligible = ((joined["kind"] == "signup")
& (joined["occurred_at"] >= joined["assigned_at"])
& (joined["occurred_at"] < joined["window_end"]))
flags = eligible.groupby(joined["visitor_id"]).any()
a["signed_up"] = a["visitor_id"].map(flags).astype(int)
return a[["visitor_id", "group", "signed_up"]]
PY
Um evento sem correspondência na junção à esquerda tem campos de evento ausentes. Suas comparações de elegibilidade são falsas; assim, a pessoa recebe resultado 0. Isso não preenche com zero uma designação desconhecida ou um acompanhamento incompleto.
O próximo comando erra deliberadamente o nome de uma etapa. Ele deve mostrar invalid choice e terminar com status 2. É um erro de argumento do comando; seus dados não foram invalidados nem alterados.
python3 run.py pepole
Corrija o nome da etapa e salve a tabela por pessoa:
python3 run.py people
python3 show_results.py people
Devem existir 12 IDs distintos. Confira P-8 (dois eventos válidos, mas resultado 1), Q-3 (nenhum evento, mas resultado 0), M-90 (inscrição exatamente no limite superior excluído, resultado 0) e K-4 (um segundo antes desse limite, resultado 1). X-02 se inscreveu antes da designação e permanece 0. OUTSIDE não deve aparecer.
Agora reutilize a função de comparação nesta tabela realmente salva:
python3 run.py summary
python3 show_results.py summary
No experimento completo, 1 de 6 pessoas se inscreveu em A e 5 de 6 em B, uma diferença próxima de +66.67 pontos percentuais. Agora você tem um denominador e uma janela justificáveis, ainda não uma afirmação de que uma nova página causou melhoria. Os próximos laboratórios examinam como a designação aleatória pode variar a diferença observada.
As verificações leem seu trabalho salvo e chamam suas funções inalteradas apenas em cópias isoladas dentro do mesmo contrato público. Elas cobrem ordens independentes das linhas de entrada, outros IDs, entradas sem eventos e designações/acompanhamentos inválidos. Implementações pandas ou Python equivalentes são bem-vindas se build_people devolver as mesmas colunas nomeadas do DataFrame e summarize devolver os objetos documentados. Repetir verificações não reconstrói os resultados existentes nem altera seu progresso.
Resumo
Você comparou seis cartões, escreveu uma função de taxas reutilizável e construiu resultados completos de sete dias a partir de eventos brutos. IDs estáveis, uma janela UTC explícita e denominadores de pessoas designadas impediram que cliques repetidos e ausência de eventos distorcessem o resultado. A tabela por pessoa e o resumo salvos agora sustentam uma investigação honesta da variação aleatória.



