はじめに

広告の良し悪しを決めるとき、多くの管理画面やレポートは「最後にクリックされた広告」に成果を全部つけます。いわゆるラストクリックです。たしかにシンプルで分かりやすいのですが、これだけで予算配分を決めると、けっこう危うい判断になります。

たとえば、あるお客さんが最初にInstagram広告で商品を知り、数日後にディスプレイ広告でもう一度思い出し、最後に「ブランド名」で検索して購入したとします。ラストクリックの世界では、この購入はすべて「検索(指名)」の手柄になります。でも実際にきっかけを作ったのはInstagram広告でした。指名検索ばかりが優秀に見え、認知を作っていた上流のチャネルが過小評価される。これがラストクリック偏重の典型的な落とし穴です。

この記事では、GA4のBigQueryエクスポートを使って、購入に至るまでのチャネル接触を時系列に並べ、ラストクリック以外の物差しで貢献度を配り直す方法を解説します。完璧な正解を出すというより、「最後の接触だけ」とは違う見え方を一本持つことがゴールです。

なお、広告費とGA4の購入を結合してROASを見直す話は「BigQueryでGoogle広告×GA4データを結合してキーワード別の「真のROAS」を計算する」で、受注データと突き合わせて売上を正確に紐づける話は「BigQueryでEC受注データとGA4を結合して売上のアトリビューションを正す」で扱っています。あわせて読むと全体像がつかめます。

データドリブンアトリビューションの考え方

アトリビューションとは、ひとつのコンバージョン(ここでは購入)を、そこに至るまでの複数の接触にどう割り振るか、という話です。割り振り方にはいくつかルールがあります。

  • ラストクリック: 最後の接触に100%
  • ファーストクリック: 最初の接触に100%
  • 線形: すべての接触に均等配分
  • 位置ベース: 最初と最後を厚めに、間を薄く配分
  • データドリブン(DDA): 実際のデータから「あるチャネルが経路にあると、どれだけコンバージョンしやすくなるか」を推定して配分

DDAの考え方は、ざっくり言えば「そのチャネルがあった経路と、なかった経路を比べて、どれだけ違いを生んだか」を成果とみなすものです。Googleの管理画面でも自動のDDAが提供されていますが、その配分ロジックは中身が見えませんし、自社の受注データとは別物です。

そこでまずは、中身が完全に見える線形・位置ベースを自分のデータで実装してみます。仕組みが透明なので、「なぜこの数字になったか」を自分で説明できるのが利点です。

GA4のチャネル接触をBigQueryで並べる

最初にやることは、ユーザーごとに「購入までにどのチャネルに触れたか」を時系列に並べることです。

ここで重要なのが流入元の取り方です。GA4のBigQueryエクスポートでは、source / medium / チャネルといった流入元情報は event_params の中ではなく、専用フィールドに入っています。具体的には、イベント単位の collected_traffic_source.* と、セッション単位の session_traffic_source.*(および session_traffic_source_last_click.*)です。チャネルを扱うときは必ずこちらを参照してください。event_params を探しても流入元は見つかりません。なお Direct 判定は、manual_source(direct) のケースと NULL(未設定)のケースの両方を拾うようにしておくと、計測漏れの取りこぼしが減ってより堅くなります。

まずは接触イベント(セッション開始や広告クリックに相当するもの)を、チャネル区分つきで取り出します。

-- ユーザー × タイムスタンプ で「接触」を並べる
WITH touches AS (
  SELECT
    user_pseudo_id,
    -- event_timestamp は INT64(マイクロ秒)。TIMESTAMP に変換して扱う
    TIMESTAMP_MICROS(event_timestamp) AS ts,
    -- イベント単位の流入元を使う場合
    LOWER(COALESCE(collected_traffic_source.manual_source, '(direct)')) AS source,
    LOWER(COALESCE(collected_traffic_source.manual_medium, '(none)')) AS medium,
    -- ざっくりチャネルに丸める
    CASE
      WHEN collected_traffic_source.manual_medium IN ('cpc', 'ppc', 'paid') THEN 'Paid Search'
      WHEN collected_traffic_source.manual_medium = 'organic' THEN 'Organic Search'
      WHEN collected_traffic_source.manual_medium IN ('social', 'paid-social') THEN 'Social'
      WHEN collected_traffic_source.manual_medium IN ('email', 'e-mail', 'newsletter') THEN 'Email'
      WHEN collected_traffic_source.manual_medium = 'referral' THEN 'Referral'
      WHEN collected_traffic_source.manual_source = '(direct)' OR collected_traffic_source.manual_medium IS NULL THEN 'Direct'
      ELSE 'Other'
    END AS channel
  FROM `your_project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20260701' AND '20260731'
    AND event_name IN ('session_start', 'page_view')
)
SELECT * FROM touches
ORDER BY user_pseudo_id, event_timestamp

次に、購入イベントを抜き出します。

-- 購入イベント(売上金額つき)
WITH purchases AS (
  SELECT
    user_pseudo_id,
    -- 同じく INT64 を TIMESTAMP に変換
    TIMESTAMP_MICROS(event_timestamp) AS purchase_ts,
    ecommerce.purchase_revenue AS revenue
  FROM `your_project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20260701' AND '20260731'
    AND event_name = 'purchase'
)
SELECT * FROM purchases

user_pseudo_id は端末・ブラウザ単位の識別子なので、別端末をまたぐと別人扱いになる点には注意してください。ログインIDを持っているECなら、後述のように user_id で寄せると精度が上がります。

線形・位置ベースで貢献度を配分する

接触と購入がそろったら、購入の直前にあった一連の接触(ルックバック期間内)を集めて、配分ルールを当てはめます。ここでは購入前30日を見ることにします。

-- 購入ごとに、直前30日の接触を集めて配分する
WITH touches AS (
  SELECT
    user_pseudo_id,
    -- INT64(マイクロ秒)を TIMESTAMP に変換しておき、比較を TIMESTAMP 同士でそろえる
    TIMESTAMP_MICROS(event_timestamp) AS ts,
    CASE
      WHEN collected_traffic_source.manual_medium IN ('cpc', 'ppc', 'paid') THEN 'Paid Search'
      WHEN collected_traffic_source.manual_medium = 'organic' THEN 'Organic Search'
      WHEN collected_traffic_source.manual_medium IN ('social', 'paid-social') THEN 'Social'
      WHEN collected_traffic_source.manual_medium IN ('email', 'e-mail', 'newsletter') THEN 'Email'
      WHEN collected_traffic_source.manual_medium = 'referral' THEN 'Referral'
      ELSE 'Direct/Other'
    END AS channel
  FROM `your_project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20260601' AND '20260731'
    AND event_name IN ('session_start', 'page_view')
),
purchases AS (
  SELECT
    user_pseudo_id,
    -- こちらも TIMESTAMP に変換しておく
    TIMESTAMP_MICROS(event_timestamp) AS purchase_ts,
    ecommerce.purchase_revenue AS revenue
  FROM `your_project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20260701' AND '20260731'
    AND event_name = 'purchase'
),
-- 購入に、直前30日かつ購入時刻より前の接触を結合
paths AS (
  SELECT
    p.user_pseudo_id,
    p.purchase_ts,
    p.revenue,
    t.channel,
    t.ts AS touch_ts,
    -- 経路内での接触順(最初=1)
    ROW_NUMBER() OVER (
      PARTITION BY p.user_pseudo_id, p.purchase_ts ORDER BY t.ts
    ) AS pos,
    COUNT(*) OVER (
      PARTITION BY p.user_pseudo_id, p.purchase_ts
    ) AS n_touches
  FROM purchases p
  JOIN touches t
    ON t.user_pseudo_id = p.user_pseudo_id
   -- ts / purchase_ts はどちらも CTE で TIMESTAMP に変換済みなので、そのまま比較できる
   AND t.ts < p.purchase_ts
   AND t.ts >= TIMESTAMP_SUB(p.purchase_ts, INTERVAL 30 DAY) -- 30日のルックバック
)
SELECT
  channel,
  -- 線形配分: 1接触あたり revenue / 接触数
  ROUND(SUM(revenue / n_touches), 0) AS linear_revenue,
  -- 位置ベース配分: 最初40% / 最後40% / 中間を均等に20%
  ROUND(SUM(
    revenue * CASE
      WHEN n_touches = 1 THEN 1.0
      WHEN pos = 1 THEN 0.4
      WHEN pos = n_touches THEN 0.4
      ELSE 0.2 / (n_touches - 2)
    END
  ), 0) AS position_based_revenue
FROM paths
GROUP BY channel
ORDER BY linear_revenue DESC

ポイントは2つです。線形配分は revenue / n_touches で接触1回あたりに等分しているだけです。位置ベースは、最初と最後の接触にそれぞれ40%、残り20%を中間の接触で割っています(接触が1回なら全額、2回なら最初と最後で50%ずつになるよう分岐を足してもよいです)。

これをラストクリック(最後の接触に100%)の集計と並べると、「ラストクリックでは小さく見えていたが、線形・位置ベースでは存在感が出るチャネル」が浮かび上がります。たいていSNSやディスプレイなど、認知の入り口になっているチャネルです。

ログインIDがあるECなら、touchespurchasesuser_id ベースで作り直すと、端末をまたいだ経路もつながり、より実態に近くなります。

厳密なDDAは高度で、限界もある

⚠️ ここまでの線形・位置ベースは「ルールで配るだけ」なので透明ですが、データから貢献度を推定しているわけではありません。本来のデータドリブンアトリビューションは、シャープレイ値やマルコフ連鎖といった手法で「そのチャネルが経路にあることで、どれだけコンバージョン確率が上がったか」を計算します。

ただ、これを自前でやるのはかなり高度です。シャープレイ値は接触の組み合わせ(部分集合)すべてを評価するため、チャネル数が増えると計算量が一気に膨らみます。マルコフ連鎖は経路を状態遷移としてモデル化し、各チャネルを「除いたら」コンバージョンがどれだけ減るか(除去効果)で貢献度を出しますが、遷移確率の推定には十分なデータ量が要ります。取引が月に数十件しかないような規模だと、推定がブレて安定しません。

加えて、どの手法を使っても次の限界はついて回ります。

  • 計測できた接触しか入らない: クッキー削除、別端末、アプリ内ブラウザ、計測漏れがあると経路は欠けます
  • 相関であって因果ではない: 「あった経路は売れている」は、必ずしも「そのチャネルが売らせた」を意味しません
  • オフラインや口コミは映らない: 店頭・友人の紹介などは経路に出てきません

ですので、厳密なDDAは「Googleの自動DDAや専門ツールに任せ、その出力を疑いながら使う」くらいが現実的です。自前実装するなら、まずは透明な線形・位置ベースで複数の物差しを持ち、ラストクリックとの差分を見ることをおすすめします。差分そのものが「ラストクリックで見落としていたもの」を教えてくれます。

まとめ

ラストクリックは分かりやすい反面、購入を後押しした上流の接触を見えなくします。GA4のBigQueryエクスポートを使えば、流入元を collected_traffic_source.* / session_traffic_source.* から取り出し、購入までの接触を時系列に並べて、線形・位置ベースで貢献度を配り直せます。

大事なのは、ひとつのモデルを「正解」と思い込まないことです。ラストクリック・線形・位置ベースを並べ、その差を眺める。差が大きいチャネルこそ、評価を見直すべき候補です。厳密なDDAは魅力的ですが計算もデータ要件もハードルが高く、限界もあるので、まずは透明なルールベースで複数の視点を持つところから始めるのが堅実です。