GA4のデフォルトの直帰率はエンゲージメント率の逆数であり、ユニバーサル アナリティクス(UA)の直帰率とは異なるものとなっている。UAの直帰率はランディングページから次ページへ遷移せずに離脱した割合のことを指す。UAも過去の産物となり、UA定義の直帰率の馴染み深さもなくなっていくかもしれないが、ランディングページのみの1ページのみで離脱したという定義はGA4になってもサイト分析に大いに活用できるものであるため、エンゲージメント率の逆数という同義の指標とは別に、いつでもUA定義の直帰率を指標として扱えるようにしておいた方がよいだろう。UAの直帰率の定義について公式ヘルプにおける記載は以下を参照。
より厳密にUA定義の直帰率を算出
UAにおける直帰とは、セッションの中で1ページしか閲覧されなかった場合に直帰とみなされる。このような直帰率を集計する際、よくセッションの中でpage_viewイベントが1か、それ以上かを判定しているのをみかけるが、単純にpage_viewイベントがセッションの中で1か否かを直帰の判定基準としてしまうと、1ページの中で複数回page_viewイベントが付いた場合には直帰判定とはならなくなる問題点が生じる。通常、同一ページでpage_viewイベントが複数回カウントされたとしても、同じページであればページ遷移とみなさないと考えるのが普通であるし、同一ページでpage_viewイベントが複数回カウントされ、離脱した場合は直帰としてカウントするべきである。
問題点を解決した唯一の直帰率の算出方法を開発
これまで述べたように同一ページの中のpage_viewイベントを「ページ遷移」とみなさない処理をする必要があるが、その方法について他で解説しているのを見たことがないため、当記事で解説する直帰率の算出方法が唯一、正確かつ厳密な直帰率を計算できる方法であるといえる。この正しい直帰率の計算方法により、同一ページの複数回のpage_viewイベントをページ遷移とみなし直帰とはみなさない方法と比べて、直帰率は上がるものと想定されるが、本来の正しい直帰率となる。
再帰CTEによる次ページの取得【非推奨】
※追記(24年07月30日)再帰CTEによる処理は再帰処理が500を超えるとエラーが生じるため、以下再帰クエリは非推奨。改良版を作成したため、さらに下部を参照。
別記事でも既に解説したが、次ページが見つかるか離脱するまでに再帰的な処理により直帰判定を行っている。つまりセッションの中で、同一ページを除く次の遷移先ページが見つからなければ直帰となる。これにより正確に直帰率の判定が可能になる。

GA4のページ遷移の取得に以下のようなLEAD関数が用いられることが多く、この方法でユーザーが遷移した2ページ目を取得することが可能になる。 LEAD(page_location) OVER(PARTITION BY ga_session_id ORDER BY event_timestamp ...
再帰CTEによるUA定義の直帰率を算出するクエリ
CREATE TEMP FUNCTION date_from() RETURNS STRING AS ('20240401');
WITH RECURSIVE
SessionData AS (
SELECT
ssid,
ymd,
event_timestamp,
page_location,
LEAD(page_location) OVER(PARTITION BY ssid ORDER BY event_timestamp ASC) AS next_page_location,
LEAD(event_timestamp) OVER(PARTITION BY ssid ORDER BY event_timestamp ASC) AS next_event_timestamp,
entrances,
1 AS depth
FROM (
SELECT
CONCAT(user_pseudo_id, '-', CAST((SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS STRING)) AS ssid,
PARSE_DATE("%Y%m%d", event_date) AS ymd,
event_timestamp,
REGEXP_REPLACE((SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location'), r"\?.*", "") AS page_location,
(SELECT value.int_value FROM UNNEST(event_params) p WHERE p.key = 'entrances') AS entrances
FROM
`<project>.<dataset>.events_*`
WHERE
_TABLE_SUFFIX BETWEEN date_from() AND FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY))
AND
event_name = 'page_view'
)
),
RecursivePages AS (
SELECT
ssid,
ymd,
event_timestamp,
page_location,
next_page_location,
next_event_timestamp,
entrances,
depth
FROM
SessionData
UNION ALL
SELECT
rp.ssid,
rp.ymd,
rp.event_timestamp,
rp.page_location,
sd.next_page_location,
sd.next_event_timestamp,
rp.entrances,
rp.depth + 1
FROM
RecursivePages rp
INNER JOIN
SessionData sd
ON
rp.ssid = sd.ssid
AND
rp.ymd = sd.ymd
AND
rp.next_event_timestamp = sd.event_timestamp
WHERE
rp.page_location = rp.next_page_location
),
Master_01 AS (
SELECT
ssid,
ymd,
event_timestamp,
page_location AS Landing_page,
CASE
WHEN next_page_location IS NULL THEN 'Exit'
ELSE next_page_location
END AS final_next_page,
entrances
FROM
RecursivePages
WHERE (
page_location != next_page_location
OR
next_page_location IS NULL
)
AND
entrances = 1
ORDER BY
page_location,
ymd
),
CalcBounce_01 AS (
SELECT
ymd,
Landing_page,
IF(final_next_page = 'Exit',1, 0) AS _exit,
entrances AS _entrances
FROM
Master_01
),
CalcBounce_02 AS (
SELECT
Landing_page,
SUM(_exit) AS _exit,
SUM(_entrances) AS _entrances,
ROUND( (SUM(_exit) / SUM(_entrances) * 100), 1) AS _BounceRate
FROM
CalcBounce_01
GROUP BY
Landing_page
)
SELECT
*
FROM
CalcBounce_02
クエリ解説
直帰率の計算結果までをBigQuery内で表示させるために計算結果まで求めているが、通常はこのような割合の計算はBIツール側で行う。その際は1つ前のCTEである「CalcBounce_01」までのクエリ結果を求めBIツールと連携し直帰率を求める。
新規ユーザーの直帰率を算出するクエリ
ここでの新規ユーザーの定義は、初回訪問したその日のセッションは全て初回訪問扱いとしている。新規ユーザーについては別記事にて解説済み

GA4の分析において新規とリピーターを分けて分析したいケースがよくある。GA4の新規ユーザーの定義では初回訪問は新規ユーザーとしてカウントされるが、その日の2回目以降のセッションではリピーターとしてカウントされてしまう。ここでは初回訪問であれば、その日は何回セッションが変わったとしても初回訪問した新...
UA定義の直帰率を算出するクエリ【改良版】
UAの直帰率の定義と同様の1セッションの中で1ページビューのみであったセッションを直帰とみなす。
CREATE TEMP FUNCTION date_from() RETURNS STRING AS ('20240401');
WITH
BounceRate AS (
SELECT
ymd,
Landing_page,
IF(final_next_page = 'Exit',1, 0) AS _exit,
_entrances
FROM (
SELECT
ssid,
ymd,
event_timestamp,
page_location AS Landing_page,
CASE
WHEN next_page_location IS NULL THEN 'Exit'
ELSE next_page_location
END AS final_next_page,
IF (
entrances = 1
OR
entrances_01 = 1
,1,0
) AS _entrances
FROM (
SELECT
*,
IF (
LAG(entrances) OVER(PARTITION BY ssid ORDER BY event_timestamp ASC) = 1
AND
LAG(dup_page) OVER(PARTITION BY ssid ORDER BY event_timestamp ASC) = 1
,1,0
) AS entrances_01
FROM (
SELECT
ssid,
ymd,
event_timestamp,
page_location,
next_page_location,
IF(page_location = next_page_location,1, 0) AS dup_page,
entrances
FROM (
SELECT
ssid,
ymd,
event_timestamp,
page_location,
LEAD(page_location) OVER(PARTITION BY ssid ORDER BY event_timestamp ASC) AS next_page_location,
entrances
FROM (
SELECT
CONCAT(user_pseudo_id, '-', CAST((SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS STRING)) AS ssid,
PARSE_DATE("%Y%m%d", event_date) AS ymd,
event_timestamp,
REGEXP_REPLACE((SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location'), r"\?.*", "") AS page_location,
(SELECT value.int_value FROM UNNEST(event_params) p WHERE p.key = 'entrances') AS entrances
FROM
`<project>.<dataset>.events_*`
WHERE
_TABLE_SUFFIX BETWEEN date_from() AND FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY))
AND
event_name = 'page_view'
)
)
)
WHERE
entrances = 1
OR
dup_page = 0
)
WHERE
(dup_page = 0 AND entrances = 1)
OR
entrances_01 = 1
)
)
SELECT
*
FROM
BounceRate