1. El problema
La Google Merchandise Store (https://shop.merch.google/) vende merchandising de Google, Android y YouTube.
Las preguntas que llevan a mirar estos datos son tres: - ¿Cuál es la conversión del ecommerce? - ¿Los productos que más facturan en volumen son los que mejor convierten? - ¿Qué categoría convierte mejor? ¿Es la que más factura?
Para poder responder estas preguntas se hace dos cosas. Primero, se audita la tabla de eventos para saber qué se puede medir y qué no. Después, con lo que sobrevive a esa auditoría, se intenta responder las preguntas.
Sin embargo, para poder responder a las preguntas de negocio, no podemos ir con la premisa que los datos vienen limpios, tenemos que responder antes una pregunta importante para poder analizar los datos:
¿Puede esta tienda saber qué productos convierten mejor, con la medición que tiene hoy?
2. Los datos
| Origen | bigquery-public-data.ga4_obfuscated_sample_ecommerce |
|---|---|
| Qué es | Datos ficticios de GA4 de la Google Merchandise Store publicados por Google |
| Grano | Un evento por fila, con los parámetros anidados en un ARRAY |
| Periodo | 1 nov 2020 → 31 ene 2021 |
| Volumen | 4,295,584 eventos , 17 tipos de evento , 270,154 usuarios |
| Estructura | 92 tablas diarias (events_20201101 … events_20210131) |
| Herramientas | GA4, BigQuery, SQL |
Es un dataset público de demostración, no un cliente. Todo lo que hay en esta página se puede reproducir sin acceso a ningún dato privado.
3. Diagnóstico
Antes de armar un reporte debemos tener un contexto de los datos y del negocio. Esto debido a que no siempre los datos vienen limpios y además es necesario saber cómo están estructurados sus datos.
La documentación del esquema de la tabla que vamos a analizar lo encontramos en el siguiente artículo: https://support.google.com/analytics/answer/ . Sin embargo, esto solo nos dice como está estructurado es esquema. Lo que debemos revisar son los datos específicos del negocio.
Los eventos
3.1 ¿Qué está midiendo la tienda?
Para saber si la tienda puede responder la pregunta de negocio principal, primero hay que ver qué mide. Y conviene no llegar buscando eventos concretos: en GA4 los nombres de evento los decide quien implementó la medición, así que no hay garantía de que existan los estándar, ni de que los que existan hagan lo que su nombre sugiere.
Lo que se pide es el mapa completo, sin filtrar.
SELECT
event_name,
COUNT(*) AS eventos
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
GROUP BY event_name
ORDER BY eventos DESC
| event_name | eventos |
|---|---|
| page_view | 1,350,428 |
| user_engagement | 1,058,721 |
| scroll | 493,072 |
| view_item | 386,068 |
| session_start | 354,970 |
| first_visit | 257,462 |
| view_promotion | 190,104 |
| add_to_cart | 58,543 |
| begin_checkout | 38,757 |
| select_item | 31,007 |
| view_search_results | 26,172 |
| add_shipping_info | 19,722 |
| add_payment_info | 13,899 |
| select_promotion | 9,450 |
| purchase | 5,692 |
| click | 1,446 |
| view_item_list | 71 |
Diecisiete tipos de evento, con los nombres estándar de ecommerce de GA4. Y entre ellos está el embudo entero: view_item → add_to_cart → begin_checkout → purchase.
Eso es más de lo que suele haber. Sobre el papel, la tienda tiene lo que hace falta para responder la pregunta.
3.2 ¿Qué eventos traen productos?
La pregunta es sobre productos, así que hay que construir el universo de productos antes de medir nada. Y para eso hay que saber de dónde salen: en GA4 los productos no son columnas, viajan anidados en un ARRAY llamado items que no todos los eventos llevan.
Hasta el momento podemos utilizaritem_id como la clave del producto.
SELECT
event_name,
COUNT(*) AS filas,
COUNT(DISTINCT i.item_id) AS productos_unicos
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND i.item_id IS NOT NULL AND i.item_id NOT IN ('(not set)', '')
GROUP BY event_name
ORDER BY filas DESC
| event_name | filas | productos_unicos |
|---|---|---|
| view_item | 2,748,237 | 426 |
| add_to_cart | 667,282 | 805 |
| select_item | 326,644 | 640 |
| begin_checkout | 76,047 | 796 |
| view_promotion | 16,843 | 1 |
| purchase | 15,555 | 809 |
| select_promotion | 975 | 1 |
| view_item_list | 508 | 63 |
Ocho eventos traen productos, no los cuatro del embudo. select_item aporta 640 y hasta view_promotion trae uno. Si el universo se sacara solo del embudo se dejarían fuera eventos que sí identifican productos.
Hay 426 productos en las vistas y **809 en las compras**. Se compraron el doble de productos de los que se vieron, y eso no puede ser: nadie compra lo que no está en el catálogo. Uno de los dos números no está contando productos.
Los productos
3.3 ¿Cuántos productos hay en total?
Aun con la contradicción anterior sin resolver, el conteo global es el que fija la base sobre la que se trabaja.
SELECT COUNT(DISTINCT i.item_id) AS productos_en_total
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND i.item_id IS NOT NULL AND i.item_id NOT IN ('(not set)', '')
| productos en total |
|---|
| 1,393 |
Mil trescientos noventa y tres productos con actividad en tres meses. Es una cifra alta para una tienda de merchandising, pero no imposible — y de momento no hay con qué contrastarla.
Conviene dejar dicho qué es y qué no es este número. No es el catálogo de la tienda: solo aparece lo que tuvo algún evento en la ventana analizada. El stock es dinámico, así que un producto que existía y nadie miró no está aquí, y no hay forma de saberlo desde estos datos. Como no hay tabla de dimensión de productos, esto es lo más cerca que se puede estar del catálogo.
3.4 ¿Están completos esos productos?
Un producto necesita como mínimo un identificador y un nombre. Toca comprobar que los 1,393 tengan las dos cosas.
No basta con preguntar si tienen nombre alguna vez: un identificador puede traerlo en una fila y no traerlo en las otras diez mil, y eso ya contaría como "tiene nombre". Hay que separar los tres casos.
WITH por_id AS (
SELECT
i.item_id AS id,
COUNTIF(i.item_name IS NULL OR i.item_name IN ('(not set)','')) AS sin_nombre,
COUNTIF(i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')) AS con_nombre
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND i.item_id IS NOT NULL AND i.item_id NOT IN ('(not set)','')
GROUP BY id
)
SELECT
COUNT(*) AS ids_totales,
COUNTIF(sin_nombre = 0) AS siempre_con_nombre,
COUNTIF(sin_nombre > 0 AND con_nombre > 0) AS a_veces_sin_nombre,
COUNTIF(con_nombre = 0) AS nunca_con_nombre
FROM por_id
| ids totales | siempre con nombre | a veces sin nombre | nunca con nombre |
|---|---|---|---|
| 1,393 | 1,392 | 1 | 0 |
La comprobación inversa —agrupar por item_name y contar los que no traen identificador— no encuentra ninguno: todos los nombres vienen siempre con su id.
1,392 identificadores traen nombre siempre. Uno lo trae en una sola de sus 17,818 filas — y ese uno no es un producto: es `fall_campaign`, una campaña de marketing que entra por los eventos de promoción.
Ninguno se queda sin nombre del todo, así que el filtro no descarta ningún producto. Lo que descarta la comprobación es otra cosa: una entidad que nunca debió estar en la base.
Entró porque en la pregunta 2 se admitió cualquier evento con items, y los de promoción no traen productos sino campañas. Es la decisión de alcance que faltaba declarar: una promoción no es un producto. Fuera view_promotion y select_promotion, la base queda en 1,392.
3.5 ¿item_id e item_name cuentan lo mismo?
Con los eventos de promoción fuera, la base baja a 1,392 identificadores. Sobre esa base ya limpia toca la comprobación siguiente: hay un segundo campo que también parece identificar productos, y si los dos identifican lo mismo tienen que dar el mismo número.
SELECT
COUNT(DISTINCT i.item_id) AS identificadores,
COUNT(DISTINCT i.item_name) AS nombres
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name NOT IN ('view_promotion', 'select_promotion')
AND i.item_id IS NOT NULL AND i.item_id NOT IN ('(not set)', '')
AND i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)', '')
| identificadores | nombres |
|---|---|
| 1,392 | 429 |
No cuentan lo mismo. Como cada fila es una pareja de identificador y nombre, comparar los dos totales ya dice de qué lado se rompe la relación:
| Comparación | Qué se deduce |
|---|---|
| Más ids que nombres | Algún nombre tiene varios ids |
| Más nombres que ids | Algún id tiene varios nombres |
| Iguales | Nada |
Así que item_id no identifica productos: identifica alguna otra cosa, más fina, que se repite dentro del mismo producto.
La base de 1,392 nunca fueron productos. `item_name` sí se comporta como clave —no se multiplica— y da **429**. Esa es la nueva base de análisis.
Se ve mejor con un producto cualquiera de los 416:
SELECT DISTINCT i.item_name, i.item_id
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND i.item_name = 'Google Land & Sea French Terry Sweatshirt'
ORDER BY i.item_id
| item_name | item_id |
|---|---|
| Google Land & Sea French Terry Sweatshirt | 9199117 |
| Google Land & Sea French Terry Sweatshirt | 9199201 |
| Google Land & Sea French Terry Sweatshirt | 9199204 |
| Google Land & Sea French Terry Sweatshirt | 9199205 |
| Google Land & Sea French Terry Sweatshirt | GGCOGXXX1609 |
| Google Land & Sea French Terry Sweatshirt | GGOEGXXX1615 |
| … | hasta 14 |
Una sudadera, catorce identificadores. Agrupando por item_id serían catorce productos en el análisis; agrupando por nombre, uno.
3.6 ¿Están los 429 productos en los eventos que vamos a analizar?
La pregunta de negocio necesita, para cada producto, cuánta gente lo vio y cuánta lo compró. De poco sirve tener 429 productos si no aparecen en las dos puntas del embudo.
WITH por_producto AS (
SELECT
i.item_name AS producto,
MAX(IF(event_name = 'view_item', 1, 0)) AS vista,
MAX(IF(event_name = 'add_to_cart', 1, 0)) AS carrito,
MAX(IF(event_name = 'begin_checkout', 1, 0)) AS checkout,
MAX(IF(event_name = 'purchase', 1, 0)) AS compra
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name IN ('view_item','add_to_cart','begin_checkout','purchase')
AND i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
GROUP BY producto
)
SELECT vista, carrito, checkout, compra, COUNT(*) AS productos
FROM por_producto
GROUP BY vista, carrito, checkout, compra
ORDER BY productos DESC
| vista | carrito | checkout | compra | productos |
|---|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ | 379 |
| ✓ | ✓ | — | — | 24 |
| — | — | ✓ | ✓ | 8 |
| ✓ | — | — | — | 6 |
| ✓ | ✓ | — | ✓ | 6 |
| ✓ | ✓ | ✓ | — | 4 |
| ✓ | — | — | ✓ | 2 |
379 de 429 recorren el embudo entero. Otros 24 se quedan a mitad —se ven, se añaden al carrito y nunca se compran—, y eso es normal en cualquier tienda.
Pero hay una fila que no puede existir: 8 productos con compra y sin vista. Nadie compra algo que no vio.
**8 productos aparecen comprados sin haberse visto nunca.** Eso no puede ocurrir: nadie compra lo que no vio. O falta la vista, o el producto se llama distinto en cada mitad del embudo.
3.7 ¿Con qué productos nos quedamos?
Los 8 imposibles no se pueden analizar: sin vistas no hay denominador, así que no tienen conversión. Antes de excluirlos conviene verlos, porque un producto que se vende sin registrar visitas es un problema de medición que alguien debería arreglar.
WITH por_producto AS (
SELECT
i.item_name AS producto,
COUNTIF(event_name = 'view_item') AS vistas,
COUNTIF(event_name = 'purchase') AS compras
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name IN ('view_item','add_to_cart','begin_checkout','purchase')
AND i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
GROUP BY producto
)
SELECT producto, vistas, compras
FROM por_producto
WHERE vistas = 0 AND compras > 0
ORDER BY compras DESC
| producto | vistas | compras |
|---|---|---|
| Google F/C Longsleeve Charcoal | 0 | 164 |
| Google Unisex Eco Tee Black | 0 | 156 |
| Google F/C Longsleeve Ash | 0 | 143 |
| Womens Google Striped LS | 0 | 89 |
| Unisex Google Pocket Tee Grey | 0 | 52 |
| Google Summer19 Crew Grey | 0 | 24 |
| Youth Jumbo Print Tee White | 0 | 15 |
| Unisex Google Jumbo Print Tee White | 0 | 2 |
164 compras sin una sola visita registrada, y las ocho son ropa.
La hipótesis más razonable es que en la mitad alta del embudo estos productos se llamen de otra forma —Womens Google Striped LS frente a algún Google Women's Striped L/S— y que el nombre, igual que antes el identificador, tampoco los una. Con estos datos no se puede confirmar: haría falta el catálogo para saber si dos nombres son el mismo artículo.
**8 productos aparecen comprados sin haberse visto nunca.** Queda como limitación declarada, no como problema resuelto: la causa probable es que cambien de nombre entre etapas, pero no hay forma de comprobarlo desde el dato.
Aun así el nombre sigue siendo la clave. Con item_id el problema no sería de ocho productos sino de 416, y además inventaría productos que no existen. Cambiar de clave no arregla esto: lo empeora.
Para el análisis se quitan, y la base queda así:
| productos | analizables | excluidos |
|---|---|---|
| 429 | 421 | 8 |
Esas 645 compras —un 4% del total— quedan fuera de cualquier cálculo por producto. No es un error del análisis, es una limitación de los datos, y se declara.
Las categorías
3.8 ¿Cada producto tiene una sola categoría?
Las preguntas de negocio no son solo por producto, también por categoría. Lo ideal es que cada producto tenga una y solo una.
Lo natural sería empezar descartando los productos sin categoría, pero todavía no se puede: eso daría por hecho que existe una categoría de catálogo y que sabemos dónde está. En este punto item_category es un campo del que no sabemos nada.
Primero hay que comprobar si es fiable; el descarte viene después y depende de lo que salga.
WITH base AS (
SELECT DISTINCT i.item_name AS producto
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name = 'view_item'
AND i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
),
cat AS (
SELECT i.item_name AS producto, COUNT(DISTINCT i.item_category) AS categorias
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name IN ('view_item','add_to_cart','begin_checkout','purchase')
AND i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
AND i.item_category IS NOT NULL AND i.item_category NOT IN ('(not set)','')
GROUP BY producto
)
SELECT categorias, COUNT(*) AS productos
FROM cat JOIN base USING (producto)
GROUP BY categorias
ORDER BY categorias
| categorías | productos |
|---|---|
| 1 | 1 |
| 2 a 4 | 28 |
| 5 a 7 | 130 |
| 8 | 71 |
| 9 a 12 | 166 |
| 13 a 17 | 24 |
Solo **1 de 421** productos tiene una única categoría. Lo habitual son ocho, y uno llega a diecisiete. Tal cual está, `item_category` no sirve para agrupar productos.
3.9 ¿Ocurre lo mismo en todos los eventos?
Un campo puede comportarse distinto según quién lo escribe. Antes de descartarlo conviene mirar evento por evento.
SELECT
event_name,
COUNT(*) AS productos,
COUNTIF(categorias = 1) AS con_una_sola,
MAX(categorias) AS maximo
FROM (
SELECT event_name, i.item_name AS producto, COUNT(DISTINCT i.item_category) AS categorias
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name IN ('view_item','add_to_cart','begin_checkout','purchase')
AND i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
AND i.item_category IS NOT NULL AND i.item_category NOT IN ('(not set)','')
GROUP BY event_name, producto
)
GROUP BY event_name
ORDER BY productos DESC
| evento | productos | con una sola categoría | máximo |
|---|---|---|---|
view_item |
420 | 6 | 17 |
add_to_cart |
412 | 10 | 15 |
purchase |
386 | 377 | 2 |
begin_checkout |
382 | 373 | 2 |
En purchase y begin_checkout la categoría sí es única por producto. En view_item y add_to_cart casi nunca.
Para saber por qué hay que mirar qué guarda de manera más frecuente cada uno:
SELECT
event_name,
i.item_category,
COUNT(*) AS filas
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name IN ('view_item','add_to_cart','begin_checkout','purchase')
AND i.item_category IS NOT NULL AND i.item_category NOT IN ('(not set)','')
GROUP BY event_name, i.item_category
QUALIFY ROW_NUMBER() OVER (PARTITION BY event_name ORDER BY filas DESC) <= 2
ORDER BY filas DESC
| evento | item_category | filas |
|---|---|---|
view_item |
Home/Apparel/Men's / Unisex/ |
462,115 |
view_item |
Home/Sale/ |
409,702 |
add_to_cart |
Home/Apparel/Men's / Unisex/ |
118,125 |
add_to_cart |
Home/Sale/ |
111,599 |
begin_checkout |
Apparel |
21,026 |
begin_checkout |
New |
7,524 |
purchase |
Apparel |
4,984 |
purchase |
Campus Collection |
1,497 |
view_item y add_to_cart guardan la ruta de navegación por la que llegó el usuario. Un producto alcanzable desde ofertas, desde hombre y desde Apparel acumula tres rutas, y ocho de media. begin_checkout y purchase guardan una etiqueta plana.
Queda una duda razonable antes de fiarse de la etiqueta plana: en la página de compra no hay ruta de navegación, así que quizá ese valor no describa el producto sino la página. Si fuera así, todos los productos de una misma compra tendrían la misma categoría.
WITH productos_comprados AS (
SELECT
CONCAT(user_pseudo_id, '-', CAST(event_timestamp AS STRING)) AS compra,
i.item_category AS categoria
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name = 'purchase'
AND i.item_category IS NOT NULL AND i.item_category NOT IN ('(not set)','')
),
por_compra AS (
SELECT
compra,
COUNT(*) AS productos,
COUNT(DISTINCT categoria) AS categorias
FROM productos_comprados
GROUP BY compra
)
SELECT
COUNT(*) AS compras_con_2_o_mas_productos,
COUNTIF(categorias = 1) AS todos_la_misma_categoria,
COUNTIF(categorias > 1) AS con_categorias_distintas,
MAX(categorias) AS maximo_en_una_compra
FROM por_compra
WHERE productos >= 2
| compras con 2+ productos | mima_categoría | categorías_distintas | máximo |
|---|---|---|---|
| 3,376 | 608 | 2,768 | 12 |
No es de la página: 2,768 compras llevan categorías distintas dentro del mismo evento, y una llega a doce. Si el valor viniera de la página, las 3,399 tendrían una sola.
`item_category` guarda **dos cosas distintas con el mismo nombre**. En `view_item` y `add_to_cart`, la ruta de navegación del usuario. En `begin_checkout` y `purchase`, la categoría del catálogo — y es del producto, no de la página.
3.10 ¿Todos los productos tienen categoría de catálogo?
La categoría buena solo aparece en begin_checkout y purchase. Un producto que se ve pero nunca llega a esas etapas no la tiene en ninguna parte.
WITH analizables AS (
SELECT DISTINCT i.item_name AS producto
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name = 'view_item'
AND i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
),
con_categoria AS (
SELECT DISTINCT i.item_name AS producto
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name IN ('begin_checkout','purchase')
AND i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
AND i.item_category IS NOT NULL AND i.item_category NOT IN ('(not set)','')
)
SELECT
(SELECT COUNT(*) FROM analizables) AS productos_analizables,
(SELECT COUNT(*) FROM analizables JOIN con_categoria USING (producto)) AS con_categoria_de_catalogo,
(SELECT COUNT(*) FROM analizables
WHERE producto NOT IN (SELECT producto FROM con_categoria)) AS sin_categoria_de_catalogo
| productos analizables | con categoría de catálogo | sin categoría de catálogo |
|---|---|---|
| 421 | 390 | 31 |
31 de los 421 productos no tienen categoría de catálogo en ningún evento. **Cualquier análisis por categoría se hace sobre 390 productos, no sobre 421.**
No se pueden recuperar desde estos datos. La mayoría sí tiene valores de categoría en item_category dentro de view_item, pero esos son rutas de navegación —la sección anterior lo demostró— y no describen al producto. No es una categoría que se esté desperdiciando: es otro campo que no sirve para esto.
Las vistas de producto
3.11 ¿Qué significa una vista?
Ya sabemos qué productos analizar. Falta comprobar qué mide el evento que va a hacer de denominador en todas las preguntas de conversión.
El supuesto es simple: view_item es alguien entrando a ver un producto. Si eso es cierto, cada evento debería traer exactamente un producto.
SELECT
ARRAY_LENGTH(items) AS productos_en_el_evento,
COUNT(*) AS eventos
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name = 'view_item'
GROUP BY productos_en_el_evento
ORDER BY productos_en_el_evento
| productos en el evento | eventos |
|---|---|
| 0 | 144,192 |
| 1 | 8,726 |
| 2 a 11 | 13,517 |
| 12 | 219,633 |
Dos cosas rompen el supuesto por separado.
Solo 8,726 eventos traen un producto, un 2% del total. En el 98% restante, o no hay ninguno o hay doce.
El tope está en 12 y ahí se acumula el 57%. Un número redondo repetido en 219,633 eventos no es comportamiento de usuario: es el tamaño de algo.
`view_item` no significa "alguien vio este producto". Un evento trae doce productos o ninguno, así que **contar sus filas no cuenta visitas a fichas de producto**.
4. Las respuestas
El diagnóstico deja una base clara y un límite duro. Estas son las tres preguntas del principio, con lo que se puede afirmar de cada una.
4.1 ¿Cuál es la conversión del negocio?
Sí se puede. Es la única de las tres que no necesita saber qué producto se vio, así que el defecto de view_item no la afecta.
Antes de calcularla hay que comprobar que el evento de compra no se repita, porque si se repite el numerador queda inflado.
WITH e AS (
SELECT
CONCAT(user_pseudo_id, '-', CAST((SELECT value.int_value FROM UNNEST(event_params)
WHERE key = 'ga_session_id') AS STRING)) AS sesion,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'transaction_id') AS transaccion
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name = 'purchase'
)
SELECT
COUNT(*) AS eventos_de_compra,
COUNT(DISTINCT transaccion) AS transacciones_distintas,
COUNTIF(transaccion IS NULL) AS sin_transaction_id
FROM e
| eventos de compra | transacciones distintas | sin transaction_id |
|---|---|---|
| 5,692 | 4,466 | 906 |
El evento de compra se dispara varias veces para la misma transacción — recargar la página de confirmación basta. De 5,692 eventos salen 4,466 transacciones. **Todo cálculo de ingresos o de compras hay que deduplicar por `transaction_id`.**
Los 906 eventos sin transaction_id no se pueden deduplicar y se conservan tal cual. Con esa regla quedan 5,372 transacciones.
Ya se puede calcular. Hay dos formas de expresarla y no dicen lo mismo:
WITH transacciones AS (
SELECT
CONCAT(user_pseudo_id, '-', CAST((SELECT value.int_value FROM UNNEST(event_params)
WHERE key = 'ga_session_id') AS STRING)) AS sesion,
COALESCE(
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'transaction_id'),
CONCAT(user_pseudo_id, '-', CAST(event_timestamp AS STRING))
) AS transaccion,
event_timestamp
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name = 'purchase'
QUALIFY ROW_NUMBER() OVER (PARTITION BY transaccion ORDER BY event_timestamp) = 1
),
sesiones AS (
SELECT CONCAT(user_pseudo_id, '-', CAST((SELECT value.int_value FROM UNNEST(event_params)
WHERE key = 'ga_session_id') AS STRING)) AS sesion
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
GROUP BY sesion
)
SELECT
(SELECT COUNT(*) FROM sesiones) AS sesiones,
(SELECT COUNT(*) FROM transacciones) AS transacciones,
(SELECT COUNT(DISTINCT sesion) FROM transacciones) AS sesiones_con_compra,
ROUND((SELECT COUNT(*) FROM transacciones) /
(SELECT COUNT(*) FROM sesiones) * 100, 2) AS a_transacciones_por_sesion,
ROUND((SELECT COUNT(DISTINCT sesion) FROM transacciones) /
(SELECT COUNT(*) FROM sesiones) * 100, 2) AS b_sesiones_que_compran
FROM sesiones LIMIT 1
| cálculo | resultado | |
|---|---|---|
| A. Transacciones por sesión | 5,372 / 360,129 | 1.49% |
| B. Sesiones que compran | 4,848 / 360,129 | 1.35% |
La A mide volumen: cuántas compras genera cada visita. Puede pasar de 100% si una sesión compra varias veces.
La B mide probabilidad: qué parte de las visitas acaba comprando. Nunca pasa de 100%, y es la que reporta GA4 como conversión de sesión y contra la que se comparan referencias del sector.
La diferencia entre las dos —1.49 frente a 1.35— son las 524 sesiones que hicieron más de una compra.
4.2 ¿Los productos que más facturan son los que mejor convierten?
Solo la mitad. La facturación por producto sale de purchase, que trae el ingreso y el producto, y no necesita vistas.
WITH compras AS (
SELECT
COALESCE(
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'transaction_id'),
CONCAT(user_pseudo_id, '-', CAST(event_timestamp AS STRING))
) AS transaccion,
event_timestamp, items
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name = 'purchase'
QUALIFY ROW_NUMBER() OVER (PARTITION BY transaccion ORDER BY event_timestamp) = 1
)
SELECT
i.item_name AS producto,
COUNT(*) AS unidades,
ROUND(SUM(IFNULL(i.item_revenue, 0))) AS ingresos
FROM compras, UNNEST(items) AS i
WHERE i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
GROUP BY producto
ORDER BY ingresos DESC
LIMIT 8
| producto | unidades | ingresos |
|---|---|---|
| Google Zip Hoodie F/C | 243 | 13,152 |
| Google Crewneck Sweatshirt Navy | 209 | 9,878 |
| Super G Unisex Joggers | 273 | 9,133 |
| Google Badge Heavyweight Pullover Black | 163 | 8,674 |
| Google Men's Tech Fleece Grey | 107 | 8,668 |
| Google Crewneck Sweatshirt Green | 160 | 7,579 |
| Google Sherpa Zip Hoodie Charcoal | 100 | 6,122 |
| Google Men's Puff Jacket Black | 59 | 5,750 |
Ya asoma algo: los joggers venden más unidades que nadie —273— y quedan terceros en ingresos. El Tech Fleece vende 107, cuatro veces menos, y se queda a 465 dólares de ellos. Unidades e ingresos no ordenan igual.
Lo que no se puede es la otra mitad. La conversión por producto necesita saber cuánta gente vio cada uno, y la sección 11 demostró que view_item no mide eso.
**La pregunta no tiene respuesta con la medición actual.** Se sabe qué productos facturan más; no se sabe cuáles convierten mejor, así que no se puede decir si coinciden.
4.3 ¿Qué categoría convierte mejor? ¿Es la que más factura?
Mismo reparto. La facturación por categoría se puede, sobre los 390 productos que tienen categoría de catálogo.
WITH compras AS (
SELECT
COALESCE(
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'transaction_id'),
CONCAT(user_pseudo_id, '-', CAST(event_timestamp AS STRING))
) AS transaccion,
event_timestamp, items
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name = 'purchase'
QUALIFY ROW_NUMBER() OVER (PARTITION BY transaccion ORDER BY event_timestamp) = 1
),
categoria_de_catalogo AS (
SELECT producto, categoria FROM (
SELECT
i.item_name AS producto,
i.item_category AS categoria,
ROW_NUMBER() OVER (PARTITION BY i.item_name ORDER BY COUNT(*) DESC) AS rn
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
AND event_name IN ('begin_checkout','purchase')
AND i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
AND i.item_category IS NOT NULL AND i.item_category NOT IN ('(not set)','')
GROUP BY producto, categoria
) WHERE rn = 1
),
ventas AS (
SELECT i.item_name AS producto, IFNULL(i.item_revenue, 0) AS ingreso
FROM compras, UNNEST(items) AS i
WHERE i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
)
SELECT
c.categoria,
COUNT(DISTINCT v.producto) AS productos,
COUNT(*) AS unidades,
ROUND(SUM(v.ingreso)) AS ingresos
FROM ventas v
JOIN categoria_de_catalogo c USING (producto)
GROUP BY c.categoria
ORDER BY ingresos DESC
LIMIT 8
| categoría | productos | unidades | ingresos |
|---|---|---|---|
| Apparel | 92 | 4,594 | 159,060 |
| New | 44 | 1,399 | 25,102 |
| Bags | 23 | 674 | 22,782 |
| Uncategorized Items | 13 | 679 | 21,139 |
| Campus Collection | 66 | 1,443 | 19,427 |
| Accessories | 41 | 1,224 | 17,161 |
| Shop by Brand | 17 | 811 | 17,142 |
| Drinkware | 11 | 673 | 14,976 |
Apparel factura seis veces más que la siguiente. Y aparece una categoría llamada Uncategorized Items que mueve 21,139 — el propio catálogo tiene productos sin clasificar, además de los 31 que no traen categoría en ningún sitio.
Misma respuesta parcial: se sabe qué categoría factura más, no cuál convierte mejor. Y la comparación se hace sobre 390 de los 421 productos, así que el reparto por categoría **no incluye a los 31 sin clasificar**.
5. Los hallazgos
Once comprobaciones, y cada una cambió algo. Se separan en dos grupos, porque el destinatario es distinto.
5.1 Lo que se resuelve analizando
Defectos del dato que se sortean eligiendo bien. Son decisiones del análisis y no requieren tocar nada del sitio.
| Hallazgo | Decisión |
|---|---|
item_id no identifica productos: 416 de 429 tienen varios, uno llega a catorce |
Agrupar por item_name. La base pasa de 1,392 a 429 |
item_category guarda dos cosas: la ruta de navegación en view_item, la etiqueta del catálogo en purchase |
Tomar la categoría solo de begin_checkout y purchase |
5.2 Lo que no se arregla desde SQL
Aquí el análisis no puede hacer nada. Son fallos de medición y quien los corrige es quien implementó el sitio.
| Hallazgo | Impacto |
|---|---|
view_item no mide vistas de producto. Un evento trae doce productos o ninguno; solo el 2% trae uno |
Bloquea la conversión por producto y por categoría. Es el más grave |
| 8 productos aparecen comprados sin haberse visto nunca | 645 compras, un 4% del total, sin producto atribuible |
| 31 productos no tienen categoría en ningún evento | El análisis por categoría cubre 390 de 421 productos |
5.3 Qué habría que arreglar, en orden
- Disparar
view_itemen la ficha de producto, con ese producto enitems. Es lo único que desbloquea la conversión por producto, que es la pregunta original. - Usar el mismo identificador de producto en las cuatro etapas. Hoy la mitad alta manda SKU y la baja un identificador por talla.
- Separar la categoría del catálogo de la ruta de navegación en dos campos distintos.
