A/B test webu: osm chyb v měření, kvůli kterým se test nedá vyhodnotit

    Osm věcí, které tiše rozbijí vyhodnocení A/B testu nové verze webu. Zápis z případu, kde test už běžel a měřit šla jen jedna větev.

    GA4měřeníA/B testováníGoogle Tag Manager
    Grafický cover článku o měření A/B testu webu, dva pixelové sloupcové grafy vedle sebe v oranžové a hnědé

    Klient přepisoval katalogovou část webu. Ne barvu tlačítka, celou aplikaci: jiné komponenty, jiné URL, nový průvodce výběrem. Zadání dávalo smysl. Pustit novou verzi na malý díl provozu, porovnat ji se starou, a když vyjde líp, přepnout všechny.

    Den před tím, než se to mělo poprvé vyhodnocovat, jsem si k tomu sedl a prošel měření v náhledu Tag Manageru. Ručně, klikáním, s otevřenou datovou vrstvou. Za dvě hodiny jsem měl seznam šesti chyb. Každá jedna z nich stačila na to, aby se test nedal vyhodnotit.

    Nejhorší bylo, že ani jedna z nich nebyla vidět. Značky se spouštěly, událostí v GA4 přibývalo, grafy rostly. Jen ta čísla znamenala něco jiného, než jsme si mysleli.

    Takže krátká odpověď na otázku, co ověřit před A/B testem webu: obě větve musí posílat vlastní hodnotu příznaku varianty, ten příznak musí být v datové vrstvě dřív než Tag Manager, a nová verze musí posílat úplně stejné názvy událostí, parametrů i hodnot jako ta stará. Zbytek článku je o tom, jak se každá z těch tří věcí dá rozbít, aniž by si toho kdokoli všiml.

    Na čem A/B testy padají a proč to nebývá design

    O A/B testech se mluví jako o statistice a designu. Zatím jsem nezažil test, který by ztroskotal na tom, že varianta byla špatně navržená. Ztroskotávají na tom, že po měsíci běhu se zjistí, že data nejdou porovnat.

    Důvod je banální. A/B test potřebuje u každé události vědět, kterou verzi ten člověk viděl. Jeden parametr navíc. A kolem toho jednoho parametru se dá nasekat překvapivé množství chyb.

    1. Kontrolní skupina musí posílat příznak taky

    Tohle je zdaleka nejčastější chyba a stála za ní i tady.

    Vývojář udělá to, co intuitivně dává smysl: do nové verze přidá ab_variant: "test". Stará verze zůstane, jak byla. Logika zní „nová verze se pozná podle příznaku, zbytek je kontrola".

    Jenže „zbytek" vypadá v GA4 takhle:

    ab_variant konverzí
    (prázdné) 35
    (not set) 991
    test 19

    V té kategorii (not set) se sešly tři úplně různé věci. Konverze ze staré verze, konverze z doby před spuštěním testu a konverze lidí, u kterých se měření kvůli souhlasu s cookies vůbec nespustilo. Kontrolní skupinu z toho nevytáhnete žádným filtrem.

    Kontrolní větev proto musí posílat vlastní odlišnou hodnotu, typicky control. Ne prázdný řetězec, ne chybějící parametr.

    A rozhodnutí, kdo je v jaké větvi, musí padat ve vrstvě nad oběma verzemi: na proxy, na serveru, v edge funkci. Ne uvnitř nové aplikace. Nová aplikace o existenci kontrolní skupiny neví a vědět nemůže.

    2. Příznak zapište dřív, než se načte Tag Manager

    Pořadí v kódu vypadá jako detail. Není.

    <script>
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({ ab_variant: "control" });  // nejdřív tohle
    </script>
    <!-- teprve pak snippet Google Tag Manageru -->
    

    Důvody jsou dva a oba jsem si na tom projektu ověřil měřením.

    Zobrazení stránek u SPA neřídí Tag Manager. Aplikace běžela na Next.js a přechody mezi detaily nenačítaly stránku znovu. Zjistil jsem, že page_view u těch přechodů posílá přímo Google tag vlastní detekcí, mimo kontejner. Připojit k nim parametr jde jedině přes nastavení, které se čte jednou při startu měření. Když hodnota v tu chvíli není, přijdete o příznak u všech zobrazení stránek.

    Konverzní poměr potřebuje jmenovatel. Návštěva, kde jedinou událostí je zobrazení stránky, tedy odchod bez interakce, by bez včasného zápisu z testu vypadla úplně. A protože nová verze webu chování při odchodu skoro jistě mění, každá větev by přišla o jinak velký kus provozu. Výsledky by vyšly. Jen by lhaly.

    Jedna podmínka. Zápis před snippetem funguje jen tehdy, když je přiřazení do větve známé už na serveru. Pokud se varianta rozhoduje až v prohlížeči po načtení aplikace, potřebujete jiný postup a je dobré to zjistit předem, ne až při ladění.

    3. Nová verze skoro jistě přejmenovala události

    Když se aplikace píše znovu, píše se znovu i měření. A nový vývojář nemá důvod dodržet názvy, které kdysi vymyslel někdo jiný.

    Tady se to rozešlo na třech úrovních najednou:

    • Názvy událostí: pruvodce_oblasti se změnilo na pruvodce_oblast, pruvodce_typ_poskytovatele na pruvodce_typ
    • Názvy parametrů: rozsah na scope, idPoskytovatele na provider_id, pozice na position
    • Struktura: tři samostatné události zanikly a jejich obsah se přesunul do parametrů jiných událostí

    Ta třetí je nejzrádnější. Značky, které se spouštěly na zaniklé události, se prostě přestanou spouštět, přičemž v kontejneru vypadají naprosto v pořádku. A značky, které zůstaly, ale čtou přejmenované parametry, se spouštět budou a budou posílat prázdné hodnoty. V reportu to nepoznáte, dokud se nepodíváte na konkrétní dimenzi a nezarazí vás, kolik má prázdných řádků.

    Řešení je nudné a nedá se obejít. Projděte novou verzi klikáním s odposlechem datové vrstvy a porovnejte, co reálně chodí, proti tomu, co čte kontejner. Ne proti tabulce od vývojáře. Ta popisuje záměr, ne stav. V tomhle případě se tabulka od reality lišila u čtyř událostí ze třinácti.

    Když mají obě verze nějakou dobu běžet vedle sebe, hodí se v Tag Manageru pojistka: proměnná, která zkusí nový klíč a při jeho absenci sáhne po starém. Jedna značka pak obslouží obě větve a nemusíte držet dvě sady.

    Tohle celé předpokládá, že vám měření konverzí fungovalo už před testem. Pokud ho teprve stavíte, začněte nastavením konverzí v GA4 a A/B test řešte až nad hotovým základem.

    4. Tři tiché pasti v datové vrstvě

    Tyhle bych sám od stolu nevymyslel. Vylezly až z náhledu.

    Pole se v datové vrstvě slučují po prvcích

    Proměnná datové vrstvy v Tag Manageru nečte aktuální push. Čte sloučený model, který si kontejner drží od začátku návštěvy. A u polí to slučování probíhá po prvcích, ne přepisem celku.

    Konkrétně: první událost poslala values: ["montaz", "servis"]. O tři kroky později poslala jiná událost pod tímtéž klíčem values: ["expresni"]. Do GA4 odešlo tohle:

    expresni,servis
    

    Druhý prvek přetekl z předchozí události. Do dimenze „typ zakázky" tak tekla hodnota z úplně jiné otázky. Kdybych to nechytil, měli bychom v reportu kombinace, které nikdo nikdy nevybral, a vypadaly by naprosto věrohodně.

    Stačí, aby dvě různé události používaly stejný název klíče. Nebo aby se člověk v průvodci vrátil o krok zpátky a změnil výběr na kratší.

    Spolehlivé řešení je nepoužívat u polí proměnnou datové vrstvy a nahradit ji vlastní JavaScriptovou proměnnou, která si vezme hodnotu z posledního skutečného pushe:

    function() {
      try {
        var dl = window.dataLayer || [];
        for (var i = dl.length - 1; i >= 0; i--) {
          var x = dl[i];
          if (x && typeof x === 'object' && !Array.isArray(x) &&
              Object.prototype.hasOwnProperty.call(x, 'values')) {
            var v = x.values;
            if (v === undefined || v === null || v === '') return undefined;
            return Array.isArray(v) ? v.join(',') : String(v);
          }
        }
      } catch (e) {}
      return undefined;
    }
    

    Číslo a text se v GA4 chovají jinak

    Web posílal číslo kroku jako step: 1. V požadavku do GA4 se to objevilo takhle:

    epn.step=1
    

    To n je podstatné. ep. je parametr pro dimenzi, epn. pro metriku. Číselná hodnota vám vlastní dimenzi nenaplní, ani když ji v GA4 zaregistrujete. A trychtýř odpadu po krocích průvodce je přesně dimenze. Jako metrika by se čísla kroků sečetla, což nedává smysl.

    Týkalo se to pěti parametrů: čísla kroku, pozice ve výsledcích a počtů položek. Oprava je jednořádková, jen na to člověk musí přijít. V proměnné hodnotu obalit String().

    Mimochodem, tahle oprava zpětně spravila i starou verzi, která ID a pozici posílala jako čísla taky. Ty dimenze se u průvodce nejspíš neplnily roky a nikdo si toho nevšiml.

    Některé názvy parametrů si GA4 bere pro sebe

    Web posílal preferovaný jazyk jako language. Do GA4 nedorazil. Ne prázdný, vůbec.

    GA4 si název language bere pro vestavěnou dimenzi jazyka prohlížeče. V požadavku z něj zůstalo jen ul=cs a vlastní parametr se cestou zahodil.

    Veřejný seznam rezervovaných názvů, na který by se dalo spolehnout, neexistuje. Praktické pravidlo: u každého parametru se po nasazení podívejte, jestli v požadavku do GA4 skutečně je. Pojmenování typu jazyk, lead_origin nebo s prefixem podle projektu ten problém obchází dopředu.

    5. Číselníky musí být v obou verzích stejné

    Tohle vylezlo až úplně na konci, když jsem procházel starou verzi kvůli kontrole, že jsem nic nerozbil.

    Stará verze posílala:

    serviceType = "montáž,servis"
    

    Nová verze posílala do téže dimenze:

    serviceType = "installation,maintenance"
    

    Dimenze se naplní z obou. Ale hodnoty se nespárují a v reportu vzniknou dvě sady řádků, které spolu na první pohled nesouvisejí. Otázka typu „jak si v obou verzích vedou lidé, kteří hledají servis" pak vyžaduje ruční mapování v tabulce.

    Sjednotit číselníky je před spuštěním práce na dvacet minut. Po spuštění je to práce na půl dne a stejně to nebude čisté.

    6. Vlastní dimenze zaregistrujte před testem

    Vlastní dimenze v GA4 nefungují zpětně. Registrace neříká „ulož mi to, co už přišlo", ale „od teď to ukládej". Data, která dorazila předtím, se nedopočítají nikdy.

    K tomu se přidává zpoždění 24 až 48 hodin, než se dimenze začne objevovat v reportech. Když ji zakládáte v den spuštění testu, první dva dny testu jsou k ničemu.

    A pozor na kvótu. GA4 dává 50 vlastních dimenzí s rozsahem událost na vlastnost. Zní to hodně, dokud se nesejde starší měření s novým. Tady jsme se z 29 dostali na 43 a zbylo sedm volných.

    7. Spočítejte si, jestli test vůbec něco změří

    Nejčastější zadání zní „pustíme to na pět až deset procent provozu a uvidíme". To je legitimní cíl, jen je dobré si nahlas pojmenovat, že jde o ověření, že nová verze nepadá. Ne o měření dopadu na konverze.

    Při zhruba tisícovce konverzí měsíčně vychází potřebný objem takhle:

    Rozdíl, který chceme rozpoznat Konverzí na větev
    10 % ~1 600
    15 % ~700
    20 % ~400
    30 % ~180

    (Hladina významnosti 5 %, síla testu 80 %, jednotkou náhodného rozdělení je uživatel se stabilním přiřazením.)

    Při 7,5 % provozu připadne na novou variantu kolem 75 konverzí měsíčně. Rozpoznat dvacetiprocentní rozdíl by trvalo přes dva měsíce. Desetiprocentní zhruba tři čtvrtě roku, což je doba, za kterou se změní sortiment, sezóna i konkurence.

    Postup, který doporučuju:

    1. Pilot na 5 až 10 % na jeden až dva týdny. Ověření, že nová verze funguje, nic nepadá a měření sedí.
    2. Přepnutí na 50/50 na čtyři až šest týdnů. Teprve z téhle fáze se vyhodnocuje.

    Fáze nemíchat dohromady. A dvě hygienické podmínky navrch: délku testu stanovit předem a nevyhodnocovat průběžně při prvním „vypadá to nadějně". Kdo se dívá každý den a skončí ve chvíli, kdy vyjde signifikance, vyrobí si signifikanci prakticky vždycky.

    8. GA4 se s databází neshodne, a ani nemá

    Poslední věc, na kterou při testu narazíte, je otázka od klienta. Porovná si konverze v GA4 s tím, co má v databázi, a čísla nesedí. Tady GA4 ukazovalo za dva týdny něco přes 550 poptávek, v databázi jich bylo přes 970.

    Není to chyba měření. Skutečná chyba v datech vypadá jinak, třeba jako jedna objednávka za devět milionů, která se do účtu dostane omylem a přebije všechno ostatní. Rozdíl mezi GA4 a databází mají na svědomí tři věci, které GA4 nevidí ze zásady:

    • Souhlas s cookies. Kdo banner odmítne nebo ignoruje, poptávku pošle, ale v GA4 po něm nezůstane nic. V Česku běžně 20 až 35 %.
    • Blokátory reklam. Blokují doménu GA4 úplně. Dalších 10 až 20 %, částečně se to s předchozím překrývá.
    • Záznamy, které přes web neprošly. Poptávky zadané ručně, objednávky z jiných kanálů, testovací data.

    Pro A/B test je podstatné tohle: takový výpadek se dá tolerovat, pokud je v obou větvích stejný. Poměr mezi větvemi zůstane platný, i když absolutní čísla nesedí. Co tolerovat nejde, je situace, kdy se výpadek mezi větvemi liší. Třeba když nová verze načítá souhlasovou lištu jinak nebo dřív.

    Rychlá diagnostika zabere pět minut. Porovnejte poměr GA4 ku databázi po dnech. Stabilní poměr znamená souhlas a blokátory, s tím se nedá dělat nic kromě přepočtu. Kolísání nebo skok v nějakém datu znamená chybu, kterou stojí za to hledat.

    Kontrolní seznam před spuštěním A/B testu

    Než pustíte první procento provozu:

    • Obě větve posílají vlastní odlišnou hodnotu příznaku
    • Příznak se zapisuje při každém úplném načtení stránky, před snippetem Tag Manageru, bez podmínky na souhlas nebo přihlášení
    • Přiřazení do větve je stabilní na uživatele, ne náhodné při každém načtení
    • Existuje způsob, jak si větev vynutit pro testování
    • Nová verze posílá všechny události, na kterých stojí konverze, ověřeno klikáním a ne podle tabulky
    • Názvy událostí i parametrů sedí, nebo je v Tag Manageru pojistka pro obě verze
    • Číselné parametry jdou do GA4 jako text, pokud z nich mají být dimenze
    • Žádný parametr se nejmenuje tak, že si ho GA4 vezme pro sebe
    • Hodnoty číselníků jsou v obou verzích stejné
    • Vlastní dimenze jsou zaregistrované a mají za sebou aspoň 48 hodin
    • Filtr interního provozu je zapnutý
    • Export do BigQuery je zapnutý (je zdarma, nefunguje zpětně a při vyhodnocení na úrovni uživatelů se hodí)
    • Máte spočítáno, kolik konverzí na větev potřebujete a jak dlouho to potrvá

    A pak to celé projděte v náhledu ve třech stavech: nový návštěvník před udělením souhlasu, tentýž po jeho udělení a vracející se návštěvník s uloženým souhlasem. Ten poslední případ při povrchní kontrole vždycky projde a v ostrém provozu pak děruje data.

    Co bych udělal jinak

    Prošel bych to měření dřív, ne den před prvním vyhodnocováním. Ve chvíli, kdy jsem si sedl k náhledu, už test běžel na zhruba procentu provozu. Část dat, která se dala mít, prostě není a doplnit se nedá.

    Druhá věc: nespoléhat na dokumentaci od vývoje. Ne proto, že by ji psal někdo nepozorný, ale protože popisuje záměr v okamžiku psaní. Mezi tím a nasazením se ledacos změní a nikdo nemá důvod to hlásit. Dvě hodiny proklikávání s otevřenou konzolí ušetří měsíc dat.

    Když jste tenhle článek našli až ve chvíli, kdy vám test už běží, začněte bodem 1 a bodem 3. Ty dva se dají opravit i za pochodu a bez nich nemá smysl řešit zbytek.


    Chystáte A/B test a chcete mít jistotu, že z něj půjde něco vyčíst? Ozvěte se mi. Projít měření před spuštěním je otázka jednoho odpoledne a ušetří to celý běh testu. Když vás zajímá i to, co se mezitím děje v kampaních, je na to PPC audit.

    Časté otázky

    Potřebuju na A/B test webu speciální nástroj?

    Ne nutně. Pokud si rozdělení do větví řeší vývoj sama a příznak varianty se posílá do datové vrstvy, vyhodnotíte test v GA4 jako každou jinou vlastní dimenzi. Nástroje typu Optimizely nebo VWO se vyplatí tam, kde chcete testovat bez zásahu do kódu a mít statistiku hotovou za sebe. U přepsané aplikace, která se stejně nasazuje, bývá vlastní řešení jednodušší i levnější.

    Jak poznám, že příznak varianty skutečně chodí do GA4?

    V GA4 otevřete Průzkumy a Volný formulář. Do řádků dejte název události a vlastní dimenzi s příznakem, do hodnot počet událostí a přidejte filtr na konverzní událost. Uvidíte rozpad konverzí podle větví přímo z těch událostí. Nepoužívejte k tomu srovnání v běžných reportech, protože filtrují uživatele, ne události, a čísla pak znamenají něco jiného, než si myslíte.

    Kolik provozu mám do A/B testu pustit?

    Pokud chcete měřit dopad na konverze, tak co nejvíc, ideálně 50/50. Menší podíl dává smysl jen v první fázi, kdy ověřujete, že nová verze funguje a nic nepadá. Při pěti procentech provozu se rozdíl v konverzním poměru neprokáže ani za půl roku a do výsledku vám mezitím promluví sezónnost víc než samotná varianta.

    Proč GA4 ukazuje míň konverzí než databáze?

    Kvůli souhlasu s cookies (v Česku běžně 20 až 35 % návštěvníků), blokátorům reklam (dalších 10 až 20 %, částečně se to překrývá) a záznamům, které přes web vůbec neprošly. Pro A/B test to není zásadní problém, dokud je výpadek v obou větvích stejný. Poměr mezi větvemi zůstane platný, i když absolutní čísla nesedí.

    Co když se ukáže, že nová verze měří hůř než stará?

    Pak nemáte A/B test, ale dvě nesrovnatelná měření, a nová verze bude vypadat hůř, i kdyby byla lepší. Je to nejčastější způsob, jak si test tiše rozbít. Řešení je vždycky stejné: srovnat měření nejdřív a test spustit až potom.

    Další články na téma Metriky a měření PPC.

    Související články

    Potřebujete s PPC pomoct?

    Napište mi, co řešíte. Úvodní konzultace je nezávazná a zdarma — ozvu se do 24 hodin.