ionvirgil.ro
  • Prezentare
  • Portofoliu
  • Despre mine
Studiu de caz despre analiza conversiei clienților unui business: funnel de vânzări, rata de conversie, abandon și customer journey într-o diagramă Sankey.
Keywords

analiza conversiei clienților, conversia clienților unui business, funnel de conversie, funnel de vânzări, rata de conversie, fluxul clienților, parcursul clientului, customer journey analytics, customer analytics, analiza abandonului, optimizarea conversiei, lead conversion, diagramă Sankey, vizualizare interactivă D3.js

← Înapoi la portofoliu

Studiu de caz 07

Customer Data Analytics

Analiza conversiei clienților unui business

Analiza urmărește drumul de la vizitator la client și arată unde se câștigă sau se pierde valoare în parcursul comercial. Diagrama Sankey evidențiază rata de conversie, abandonul și traseele alternative din funnel și poate fi automatizată pe datele oricărui business.

Observable OJS D3.js d3-sankey

Ce vrem să aflăm?

Unde apar cele mai importante pierderi între vizitarea site-ului, solicitarea unei discuții, transmiterea unei propuneri și finalizarea unui proiect? Pentru cele două categorii de servicii, analiză statistică în cercetare și data analytics în business prezentăm dacă procesul este similar sau dacă există diferențe.

Această analiză de customer journey arată volumul de lead-uri în fiecare etapă a funnelului de vânzări, astfel încât rata de conversie și punctele de abandon să poată fi comparate între cele două servicii.

d3 = require("d3@7")
d3Sankey = require("d3-sankey@0.12")
{
  const flows = [
    {source: "Vizitatori", target: "Vizitatorul este interesat!!", research: 780, business: 980},
    {source: "Vizitatori", target: "Vizitatorul nu este interesat de conținutul tău", research: 420, business: 570},
    {source: "Vizitatorul este interesat!!", target: "A trimis o cerere", research: 350, business: 420},
    {source: "Vizitatorul este interesat!!", target: "A revenit pe baza unei acțiuni anterioare", research: 280, business: 360},
    {source: "Vizitatorul este interesat!!", target: "dar după puțin timp s-a răzgândit", research: 150, business: 200},
    {source: "A trimis o cerere", target: "Vrea o discuție", research: 290, business: 350},
    {source: "A trimis o cerere", target: "S-a răzgândit", research: 60, business: 70},
    {source: "A revenit pe baza unei acțiuni anterioare", target: "Vrea o discuție", research: 95, business: 135},
    {source: "Vrea o discuție", target: "Propunere de colaborare", research: 305, business: 375},
    {source: "Vrea o discuție", target: "Nu a fost încântat de discuție", research: 80, business: 110},
    {source: "Propunere de colaborare", target: "Start proiect!", research: 228, business: 292},
    {source: "Propunere de colaborare", target: "Nu dorește să colaboreze", research: 77, business: 83},
    {source: "Start proiect!", target: "Proiect finalizat", research: 211, business: 274},
    {source: "Start proiect!", target: "În desfășurare", research: 17, business: 18}
  ];

  const wrapper = document.createElement("section");
  wrapper.className = "observable-project observable-project--sankey";
  wrapper.innerHTML = `
    <div class="observable-toolbar">
      <div>
        <p class="observable-eyebrow">Serviciu analizat</p>
        <div class="observable-pills" role="group" aria-label="Selectează segmentul">
          <button type="button" class="is-active" data-segment="total">Ambele servicii</button>
          <button type="button" data-segment="research">Analiză statistică în cercetare</button>
          <button type="button" data-segment="business">Data analytics în business</button>
        </div>
      </div>
      <div class="observable-live-metric" aria-live="polite"></div>
    </div>
    <div class="observable-stage"><div class="observable-chart"></div><div class="observable-tip" hidden></div></div>
    <p class="observable-instruction">Grosimea benzii reprezintă volumul, poți selecta un segment și poți explora fiecare traseu cu mouse-ul.</p>`;

  const chartHost = wrapper.querySelector(".observable-chart");
  const metric = wrapper.querySelector(".observable-live-metric");
  const tip = wrapper.querySelector(".observable-tip");
  const format = d3.format(",.0f");
  const width = 1040;
  const height = 610;
  const palette = ["#2563eb", "#22d3ee", "#94a3b8", "#8b5cf6", "#f97316", "#10b981", "#ef4444"];
  const color = d3.scaleOrdinal().domain(Array.from(new Set(flows.flatMap(d => [d.source, d.target])))).range(palette);

  const showTip = (event, html) => {
    const box = wrapper.querySelector(".observable-stage").getBoundingClientRect();
    tip.innerHTML = html;
    tip.hidden = false;
    tip.style.left = `${Math.min(event.clientX - box.left + 14, box.width - 230)}px`;
    tip.style.top = `${Math.max(12, event.clientY - box.top - 18)}px`;
  };

  function render(segment) {
    chartHost.replaceChildren();
    const links = flows.map(d => ({
      source: d.source,
      target: d.target,
      value: segment === "total" ? d.research + d.business : d[segment]
    }));
    const nodes = Array.from(new Set(links.flatMap(d => [d.source, d.target])), name => ({name}));
    const graph = d3Sankey.sankey()
      .nodeId(d => d.name)
      .nodeWidth(18)
      .nodePadding(17)
      .nodeAlign(d3Sankey.sankeyJustify)
      .extent([[22, 25], [width - 22, height - 28]])({nodes, links});

    const svg = d3.create("svg")
      .attr("viewBox", [0, 0, width, height])
      .attr("role", "img")
      .attr("aria-label", "Diagramă Sankey pentru parcursul clienților");

    const defs = svg.append("defs");
    graph.links.forEach((d, i) => {
      const gradient = defs.append("linearGradient")
        .attr("id", `sankey-gradient-${segment}-${i}`)
        .attr("gradientUnits", "userSpaceOnUse")
        .attr("x1", d.source.x1)
        .attr("x2", d.target.x0);
      gradient.append("stop").attr("stop-color", color(d.source.name));
      gradient.append("stop").attr("offset", 1).attr("stop-color", color(d.target.name));
    });

    const link = svg.append("g")
      .attr("fill", "none")
      .attr("stroke-opacity", 0.42)
      .selectAll("path")
      .data(graph.links)
      .join("path")
      .attr("d", d3Sankey.sankeyLinkHorizontal())
      .attr("stroke", (d, i) => `url(#sankey-gradient-${segment}-${i})`)
      .attr("stroke-width", d => Math.max(1, d.width))
      .style("mix-blend-mode", "multiply")
      .on("pointerenter", function(event, d) {
        d3.select(this).attr("stroke-opacity", 0.9);
        showTip(event, `<strong>${d.source.name} → ${d.target.name}</strong><span>${format(d.value)} oportunități</span>`);
      })
      .on("pointermove", event => showTip(event, tip.innerHTML))
      .on("pointerleave", function() {
        d3.select(this).attr("stroke-opacity", null);
        tip.hidden = true;
      });

    link.attr("stroke-dasharray", function() { return this.getTotalLength(); })
      .attr("stroke-dashoffset", function() { return this.getTotalLength(); })
      .transition().duration(900).ease(d3.easeCubicOut).attr("stroke-dashoffset", 0);

    const node = svg.append("g").selectAll("g").data(graph.nodes).join("g");
    node.append("rect")
      .attr("x", d => d.x0)
      .attr("y", d => d.y0)
      .attr("width", d => d.x1 - d.x0)
      .attr("height", d => Math.max(1, d.y1 - d.y0))
      .attr("rx", 5)
      .attr("fill", d => color(d.name))
      .attr("stroke", "#ffffff")
      .attr("stroke-width", 1.5);

    const lastLayer = d3.max(graph.nodes, d => d.layer);
    const placeLabelOnRight = d => d.layer < lastLayer;

    const labelMaxWidth = 112;
    const labelLineHeight = 14;
    const labelMeasure = document.createElement("canvas").getContext("2d");
    labelMeasure.font = "650 11.5px system-ui, sans-serif";

    node.append("text")
      .attr("x", d => placeLabelOnRight(d) ? d.x1 + 9 : d.x0 - 9)
      .attr("y", d => (d.y0 + d.y1) / 2)
      .attr("text-anchor", d => placeLabelOnRight(d) ? "start" : "end")
      .attr("fill", "#0f172a")
      .attr("font-size", 11.5)
      .attr("font-weight", 650)
      .attr("paint-order", "stroke")
      .attr("stroke", "#f8fafc")
      .attr("stroke-width", 3)
      .attr("stroke-linejoin", "round")
      .each(function(d) {
        const label = d3.select(this);
        const x = Number(label.attr("x"));
        const y = Number(label.attr("y"));
        const words = d.name.split(/\s+/);
        const lines = [];
        let line = [];

        words.forEach(word => {
          line.push(word);
          if (labelMeasure.measureText(line.join(" ")).width > labelMaxWidth && line.length > 1) {
            line.pop();
            lines.push(line.join(" "));
            line = [word];
          }
        });
        if (line.length) lines.push(line.join(" "));

        lines.forEach((text, index) => {
          label.append("tspan")
            .attr("x", x)
            .attr("y", y + (index - (lines.length - 1) / 2) * labelLineHeight)
            .text(text);
        });
      });

    const visitors = graph.nodes.find(d => d.name === "Vizitatori")?.value || 0;
    const completed = graph.nodes.find(d => d.name === "Proiect finalizat")?.value || 0;
    const conversion = visitors ? completed / visitors : 0;
    metric.innerHTML = `<strong>${d3.format(".2%")(conversion)}</strong><span>conversie finală</span>`;
    chartHost.append(svg.node());
  }

  wrapper.querySelectorAll("[data-segment]").forEach(button => {
    button.addEventListener("click", () => {
      wrapper.querySelectorAll("[data-segment]").forEach(item => item.classList.toggle("is-active", item === button));
      render(button.dataset.segment);
    });
  });

  render("total");
  return wrapper;
}

Interpretare

În scenariul de față blocajul apare înaintea discuției inițiale: o parte importantă a vizitatorilor explorează conținutul fără să trimită o cerere ceea ce înseamnă că ceva îi face să se răzgândească în a trimite o cerere. Cum ar fi și în cazul acestui website, nu trimiți o cerere deoarece poate nu ai suficientă încredere sau motivație, dar dacă citești fraza următoare afli că…

După transmiterea propunerii, conversia este mai bună datorită intervenției mele. După o discuție afli că de fapt te pot ajuta și putem duce discuția mai departe.

Pentru optimizarea conversiei este clar că trebuie îmbunătățit în primul rând modul în care site-ul comunica cu tine atunci când interacționezi cu el.

Proiecte conexe

  • Vizualizarea datelor din anchetele statistice
  • Intersecțiile competențelor ESCO–ISCO
  • Toate proiectele de analiză statistică și Business Intelligence
  • Servicii de analiză statistică, data science și vizualizare

← Înapoi la portofoliu

Intersecțiile ESCO–ISCO →

 
Cookie Preferences