Introducción
Un taller comunitario tiene dos versiones de su página de inscripción, A y B. Antes de compararlas, su analista debe decidir qué cuenta como una observación. Una persona puede visitar la página tres veces o pulsar dos veces el botón de inscripción, mientras otra no genera ningún evento. Contar esas filas de eventos como personas distintas distorsionaría la comparación.
Primero leerás un ejemplo de seis personas y después construirás un resultado binario de siete días para cada visitante asignado en un experimento fijo más amplio. Los registros son datos didácticos sintéticos originales que representan una asignación aleatoria; no son seguimiento de visitantes en tiempo real ni evidencia sobre un negocio real. El entorno WebIDE independiente ya dispone de Python, pandas, NumPy, SciPy y Matplotlib. Necesitas navegación básica por la terminal, funciones de Python y selección introductoria de tablas con pandas; aquí se explican las uniones y operaciones temporales necesarias. No se requiere inferencia estadística previa. Los 55 minutos son una estimación del autor que incluye lectura y correcciones habituales.
Compara personas en lugar de clics
En este paso leerás seis fichas de visitantes y calcularás las tasas de inscripción usando como denominadores a las personas asignadas.
Empieza en WebIDE y abre Terminal → New Terminal. La terminal y el explorador comparten los mismos archivos. cd cambia el directorio de trabajo de esta terminal; repítelo al abrir otra terminal.
cd /home/labex/project/signup-experiment
cat muestra un archivo de texto sin modificarlo. Lee el ejemplo pequeño:
cat warmup-visitors.csv
Cada fila representa un visitante asignado. Los tres primeros pertenecen a A y los tres últimos a B. En este ejemplo, qualifying_signup_events ya se ha clasificado dentro de la ventana de siete días de cada persona. signed_up es el resultado binario correspondiente: 1 si hubo al menos una inscripción válida y 0 en caso contrario.
P-8 tiene tres visitas y dos eventos de inscripción, pero aporta una persona y un resultado positivo. Q-3 no tiene visitas ni eventos de inscripción, pero sigue siendo una persona asignada con resultado 0. Lee las seis fichas antes de calcular: A tiene una inscripción entre tres personas asignadas; B tiene dos entre tres.
La tasa de A es 1 / 3, aproximadamente 33.33%. La de B es 2 / 3, aproximadamente 66.67%. La diferencia es B menos A, unos +33.33 puntos porcentuales. Los puntos porcentuales restan directamente dos porcentajes; no son un aumento porcentual relativo. Más visitas no amplían ninguno de los denominadores.
Escribe una función de comparación reutilizable. El ejecutor le pasa un DataFrame de pandas llamado people, con las columnas visitor_id, group y signed_up. Una máscara booleana como people["group"] == "A" selecciona las filas de A. len(a) cuenta personas asignadas; sumar una columna binaria cuenta personas inscritas. int y float producen números normales de Python para los archivos JSON.
La redirección del shell > sustituye el archivo indicado. Un here-document entre comillas, <<'PY', envía todo al archivo hasta una línea que contiene únicamente PY, sin expansión del shell. Pega el bloque entero, incluido el PY final. También puedes editar el mismo archivo en WebIDE y guardarlo con 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 ejecuta Python. El run.py preparado llama a tu función y escribe su valor de retorno real; el argumento warmup selecciona esta entrada. No completa respuestas ausentes.
python3 run.py warmup
El comando guarda output/warmup.json. JSON almacena objetos con nombre y números. El lector preparado solo muestra archivos existentes; nunca vuelve a ejecutar tu programa ni repara un resultado ausente.
python3 show_results.py warmup
Confirma que los recuentos asignados son 3 y 3, los positivos 1 y 2 y la diferencia está cerca de +33.33 puntos porcentuales. Conserva los números sin redondear en los resultados guardados; el redondeo de esta explicación solo facilita la lectura. Una tasa observada mayor no demuestra por sí sola que B haya mejorado la página.
En este laboratorio solo necesitan cambios compare.py y, a continuación, outcomes.py. Puedes añadir archivos auxiliares normales de Python junto a ellos. Conserva assignments.csv, events.csv, warmup-visitors.csv, config.json, data-dictionary.md, run.py y show_results.py. Las otras unidades tienen copias independientes y no dependen de archivos de esta VM.
Si el nombre de un archivo o el directorio es incorrecto, usa pwd para ver el directorio actual y ls para listar sus archivos; después vuelve con el comando cd anterior. Vuelve a copiar el bloque completo de la función para corregir un error de edición habitual; no hace falta ejecutar setup de nuevo ni reiniciar el entorno.
Construye resultados completos de siete días
En este paso convertirás los registros originales de asignaciones y eventos en la tabla completa por persona que necesita la comparación.
Continúa en la misma terminal de WebIDE, en /home/labex/project/signup-experiment. Lee las definiciones de los campos y después las tablas originales:
cat data-dictionary.md
cat assignments.csv
cat events.csv
La tabla completa contiene 12 personas asignadas, seis en cada grupo. visitor_id es una identidad textual estable, no una posición de fila. La tabla de eventos repite personas e incluye al visitante ajeno OUTSIDE, así que las dos tablas no se pueden emparejar por número de fila. El registro está completo para este ejemplo didáctico: no tener un evento válido significa no haberse inscrito, no un resultado desconocido.
Cada persona tiene un instante de asignación con zona horaria, assigned_at. La ventana es [asignación, asignación + siete días): el inicio cuenta y el límite superior exacto no. UTC es una referencia temporal común; 12:00:00+02:00 y 10:00:00Z describen el mismo instante. pd.to_datetime(..., utc=True, format="ISO8601") convierte estas cadenas con formato explícito en marcas temporales UTC comparables. Timedelta(days=7) representa siete días de tiempo transcurrido.
Todo seguimiento debe haber terminado antes del instante de análisis proporcionado o en ese mismo instante:
cat config.json
En esta unidad, todas las ventanas terminan antes de analysis_at. Las demás opciones se usarán en laboratorios posteriores. No cambies el instante de análisis para que una persona con seguimiento incompleto parezca haberlo terminado. Todas las tablas del curso contienen 4–14 personas, con al menos dos por grupo. Los ID de asignación duplicados, incluidos los grupos en conflicto, son inválidos; escoger la fila que produzca un resultado más atractivo no es una reparación.
Una unión izquierda (left merge) parte de cada asignación y adjunta los eventos coincidentes. how="left" conserva a las personas sin eventos. validate="one_to_many" exige que cada ID aparezca como máximo una vez en la tabla de asignaciones, aunque permite varias filas de eventos. Un ID de evento ajeno no puede añadir una persona a la población asignada.
Las comparaciones crean máscaras booleanas. En pandas, & combina condiciones y | significa cualquiera de ellas; encierra cada comparación entre paréntesis. Después, groupby(...).any() pregunta si existe algún evento válido para cada persona. Volver a asociar esas marcas por ID conserva una fila por asignación, incluidos los ceros legítimos. ValueError rechaza explícitamente las asignaciones inválidas o el seguimiento incompleto, en lugar de fingir que esos datos se pueden comparar.
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
Un evento no coincidente en la unión izquierda tiene campos de evento ausentes. Sus comparaciones de elegibilidad son falsas, por lo que esa persona obtiene 0. Esto no rellena con cero una asignación desconocida ni un seguimiento incompleto.
El siguiente comando escribe mal a propósito el nombre de una etapa. Debe mostrar un mensaje invalid choice y terminar con estado 2. Es un error de argumento del comando; no ha invalidado ni cambiado tus datos.
python3 run.py pepole
Corrige el nombre de la etapa y guarda la tabla por persona:
python3 run.py people
python3 show_results.py people
Debe haber 12 ID distintos. Comprueba P-8 (dos eventos válidos pero resultado 1), Q-3 (sin eventos pero resultado 0), M-90 (inscripción exactamente en el límite superior excluido, resultado 0) y K-4 (un segundo antes de ese límite, resultado 1). X-02 se inscribió antes de la asignación y permanece en 0. OUTSIDE no debe aparecer.
Ahora reutiliza tu función de comparación con esta tabla realmente guardada:
python3 run.py summary
python3 show_results.py summary
En el experimento completo se inscribió 1 de 6 personas de A y 5 de 6 de B, una diferencia cercana a +66.67 puntos porcentuales. Ya tienes un denominador y una ventana defendibles, pero todavía no una afirmación de que la nueva página causó una mejora. Los próximos laboratorios examinan cómo la asignación aleatoria puede variar la diferencia observada.
Las comprobaciones leen tu trabajo guardado y llaman a tus funciones sin cambios únicamente sobre copias aisladas del mismo contrato público. Cubren órdenes independientes de las filas de entrada, otros ID, entradas sin eventos y asignaciones o seguimientos inválidos. Se aceptan implementaciones equivalentes de pandas o Python si build_people devuelve las mismas columnas con nombre del DataFrame y summarize devuelve los objetos documentados. Repetir las comprobaciones no reconstruye los resultados existentes ni cambia tu progreso.
Resumen
Comparaste seis fichas de visitantes, escribiste una comparación de tasas reutilizable y construiste resultados completos de siete días a partir de eventos originales. Los ID estables, una ventana UTC explícita y los denominadores de personas asignadas evitaron que los clics repetidos y la ausencia de eventos distorsionaran el resultado. Tu tabla por persona y tu resumen guardados ya permiten examinar honestamente la variación aleatoria.



