アクセス解析で見るリニューアル効果:PV・直帰率はどう変わったか
モバイルページの読込時間が1秒から3秒に伸びると直帰確率が32%上がるというGoogle関連の分析(SOASTA, 2017)が広く知られています¹。さらにDeloitteの調査では、ロード時間を0.1秒短縮した小売サイトでコンバージョンが約8.4%向上したと報告されています²。数字は誤魔化せません。だからこそサイトリニューアルの成否は、好意的な声よりも、アクセス解析での変化量で判断すべきです。問題は、GA4時代におけるPVと直帰率の定義が変わり、単純比較が危ういという点にあります。現場では観測設計と統計処理を標準化しておくことで、意思決定のスピードと精度を両立しやすくなります。以下では、定義の正しさ、比較設計、実装、そしてビジネスインパクトの順に、今日から使える手順を共有します。
GA4時代の「PV」と「直帰率」を正しく定義する
まず言葉の前提を揃えます。PVはGA4では「page_viewイベント」のカウントであり、アプリ混在プロパティではスクリーンビューと区別されます。直帰率はUAの「単一ページビュー・0秒滞在」とは異なり、GA4ではエンゲージメント率の逆数として提供されます。具体的には、エンゲージメント率は「10秒以上の滞在」または「コンバージョン発生」または「2回以上の画面遷移」のいずれかを満たすセッションの比率で、直帰率は1 − エンゲージメント率です³。用語の感覚だけでも共有しておくと、分析の解像度が上がります(エンゲージメント率=“能動的なセッションの割合”という理解で概ね差し支えありません)。したがってUA時代の直帰率と数値水準が異なり、トレンド比較には同一プロパティ・同一タグ・同一除外条件を基本線とします³。
次に比較の設計です。リニューアル前後で28日を基準としたウィンドウを揃え、曜日と季節性を極力合わせます。キャンペーンやPR露出など外部ショックがある場合は、同期間の非対象セクション(例:ブログや採用ページなどデザイン非変更領域)をコントロール群として扱い、差の差(Difference-in-Differences:前後差の“差分”で外的要因を差し引く手法)で推定します。これにより単純な平均差よりも、外生的なトラフィック変動をキャンセルできます。セッション定義やコンバージョンイベントの変更がある場合は完全に同条件で再演できないため、旧新タグの併設期間を最低2週間確保し、パラレルランで乖離を補正します。
BigQueryで日次PV・直帰率を出す基本クエリ
GA4エクスポートを使うと柔軟に集計できます。以下はランディングページ単位で日次PVと直帰率(=1−エンゲージメント率)を計算する、再現性を重視した最小例です(タイムゾーンやプロジェクト名は適宜置換してください)。
-- Code 1: GA4 BigQuery 日次PVと直帰率(ランディングページ別)
DECLARE start_date DATE DEFAULT '2025-06-01';
DECLARE end_date DATE DEFAULT '2025-08-31';
-- 1) まずイベントを必要最小限の列に整形
WITH base AS (
SELECT
event_timestamp,
DATE(TIMESTAMP_MICROS(event_timestamp), 'Asia/Tokyo') AS dt,
user_pseudo_id,
CAST((SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS STRING) AS ga_session_id,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location') AS page_location,
(SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'engagement_time_msec') AS engagement_time_msec,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'session_engaged') AS session_engaged,
event_name
FROM `project.dataset.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', start_date)
AND FORMAT_DATE('%Y%m%d', end_date)
AND (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') IS NOT NULL
),
-- 2) セッションID(sid)とランディングページを決定
first_page_per_session AS (
SELECT
dt,
CONCAT(ga_session_id, '-', user_pseudo_id) AS sid,
page_location AS landing_page
FROM (
SELECT
dt, user_pseudo_id, ga_session_id, page_location, event_timestamp,
ROW_NUMBER() OVER (PARTITION BY ga_session_id, user_pseudo_id ORDER BY event_timestamp) AS rn
FROM base
WHERE event_name = 'page_view'
)
WHERE rn = 1
),
-- 3) セッション単位のエンゲージド判定
session_level AS (
SELECT
DATE(TIMESTAMP_MICROS(MIN(event_timestamp)), 'Asia/Tokyo') AS dt,
CONCAT(ga_session_id, '-', user_pseudo_id) AS sid,
-- 10秒以上の滞在 or 'user_engagement'発火 or 2PV以上 を満たせばエンゲージド
(SUM(COALESCE(engagement_time_msec, 0)) >= 10000)
OR (COUNTIF(event_name = 'user_engagement') > 0)
OR (COUNTIF(event_name IN ('page_view','screen_view')) >= 2) AS engaged
FROM base
GROUP BY sid
),
-- 4) ランディングページ別のセッション数と直帰率の計算
agg AS (
SELECT
f.dt,
f.landing_page,
COUNT(DISTINCT s.sid) AS sessions,
SUM(CASE WHEN s.engaged THEN 1 ELSE 0 END) AS engaged_sessions
FROM first_page_per_session f
JOIN session_level s USING (sid)
GROUP BY f.dt, f.landing_page
),
-- 5) ランディングページ自体のPV(日次)
pv_by_page AS (
SELECT
DATE(TIMESTAMP_MICROS(event_timestamp), 'Asia/Tokyo') AS dt,
page_location AS landing_page,
COUNTIF(event_name = 'page_view') AS pv
FROM base
GROUP BY dt, landing_page
)
SELECT
a.dt,
a.landing_page,
p.pv,
a.sessions,
SAFE_DIVIDE(engaged_sessions, sessions) AS engagement_rate,
1 - SAFE_DIVIDE(engaged_sessions, sessions) AS bounce_rate
FROM agg a
LEFT JOIN pv_by_page p USING (dt, landing_page)
ORDER BY a.dt, a.landing_page;
このクエリはランディングページ別に日次のPVと直帰率を並べます。リニューアル対象のURLパターンを正規表現でフィルタし、対象群とコントロール群に分けると差分推定の下準備が整います。なお、ランディングページの決定は“セッション内で最初に発生したpage_viewのpage_location”で一意に定めています(文字列の最小値ではなく、タイムスタンプに基づく先頭イベントを使用)。
Pythonで差の差分析を走らせる
BigQueryの結果を取り込み、差の差モデルで直帰率の改善を推定します。以下はstatsmodelsを使った最小例です(交互作用項=group:post が改善量を表します)。
# Code 2: 差の差(DiD)で直帰率の改善を推定
import pandas as pd
import statsmodels.formula.api as smf
# df: columns = ['dt','group','post','bounce_rate']
# group: 1=リニューアル対象, 0=コントロール
# post: 1=リニューアル後, 0=前
model = smf.ols('bounce_rate ~ group + post + group:post', data=df).fit(cov_type='HC1')
print(model.summary())
ate = model.params['group:post']
ci = model.conf_int().loc['group:post']
print({'DiD_effect': ate, '95%CI': (ci[0], ci[1])})
交互作用項の係数が改善量(負なら直帰率低下=改善)です。期間を28日から56日に広げても頑健性が保たれるか、感度分析で確認すると安心です。
前後比較の観測設計:ノイズを潰して信号を拾う
比較の精度を決めるのは設計です。まず、計測変更、トラフィック混入、配信施策の3点を先に潰します。計測ではConsent Mode v2の適用やサーバーサイドタグの導入タイミングが一致しているかを確認します⁴(Consent Modeはユーザー同意に応じて測定を補完する仕組み)。トラフィック混入は既知のボットや監視IPの除外、社内アクセスの新旧一致を確実にします。配信施策はメールや広告の同時実施で急峻な流入が起きていないかをログで追い、観測期間から外すかコントロール群で相殺します。
それでも残るのが季節性とチャネルミックスです。季節性は曜日整合の28日ウィンドウで影響を最小化できます。チャネルミックスはオーガニック・ダイレクト・リファラル・広告で反応が異なるため、流入チャネル×デバイスで分解し、加重平均で全体値に戻します。以下はチャネル別にPVと直帰率を集計するSQL例です(セッションのsource/mediumはsession_source/session_mediumパラメータを使用)。
-- Code 3: チャネル×デバイス別の集計(session_source/session_medium)
WITH base AS (
SELECT
DATE(TIMESTAMP_MICROS(event_timestamp), 'Asia/Tokyo') AS dt,
CONCAT(CAST((SELECT value.int_value FROM UNNEST(event_params) WHERE key='ga_session_id') AS STRING), '-', user_pseudo_id) AS sid,
device.category AS device_category,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key='session_source') AS session_source,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key='session_medium') AS session_medium,
(SELECT value.int_value FROM UNNEST(event_params) WHERE key='engagement_time_msec') AS engagement_time_msec,
event_name
FROM `project.dataset.analytics_123456789.events_*`
),
session_scoped AS (
SELECT
dt,
sid,
device_category,
COALESCE(session_source, '(direct)') AS source,
COALESCE(session_medium, '(none)') AS medium,
(SUM(COALESCE(engagement_time_msec,0)) >= 10000)
OR (COUNTIF(event_name = 'user_engagement') > 0)
OR (COUNTIF(event_name IN ('page_view','screen_view')) >= 2) AS engaged,
COUNTIF(event_name = 'page_view') AS pv
FROM base
GROUP BY dt, sid, device_category, source, medium
)
SELECT
dt,
device_category,
CASE
WHEN REGEXP_CONTAINS(LOWER(source), r'(google|bing|yahoo)') AND LOWER(medium) IN ('organic','(none)') THEN 'Organic'
WHEN LOWER(medium) IN ('cpc','ppc','paid','paidsearch') THEN 'Paid'
WHEN LOWER(source) = '(direct)' OR LOWER(medium) = '(none)' THEN 'Direct'
ELSE 'Referral'
END AS channel,
SUM(pv) AS pv,
COUNT(DISTINCT sid) AS sessions,
1 - SAFE_DIVIDE(SUM(CASE WHEN engaged THEN 1 ELSE 0 END), COUNT(DISTINCT sid)) AS bounce_rate
FROM session_scoped
GROUP BY dt, device_category, channel
ORDER BY dt;
デバイス差が大きい場合は、モバイルの描画シフトやCLS(Cumulative Layout Shift:視覚的なレイアウトのずれ)の改善が効いている可能性が高いので、Core Web VitalsのLCP(Largest Contentful Paint:主要コンテンツの描画完了)とINP(Interaction to Next Paint:次の描画までのインタラクション遅延)の実測値と並べて可視化します。たとえばLCPのP75が3.0秒から2.3秒に改善しているなら、行動指標の変化は速度起因と推定しやすくなります。速度指標と行動指標の相関を、ページ群ごとに散布図で確認すると良いでしょう⁶。なお、速度改善がコンバージョンに寄与する傾向は、業界別のリサーチや公開事例でも報告されています⁷⁸。
統計的に「差がある」と言えるかを手早く判定する
直帰率の差は二項比率の差の検定(2標本比率のz検定)で判定できます。Pythonなら数行です。
# Code 4: 直帰率の差の二項検定(2標本比率のz検定)
import numpy as np
from statsmodels.stats.proportion import proportions_ztest, proportion_confint
# before: bounces_b / sessions_b, after: bounces_a / sessions_a
count = np.array([bounces_a, bounces_b])
nobs = np.array([sessions_a, sessions_b])
stat, pval = proportions_ztest(count, nobs, alternative='smaller') # 直帰率が下がったか
ci_low_a, ci_high_a = proportion_confint(bounces_a, sessions_a, method='wilson')
ci_low_b, ci_high_b = proportion_confint(bounces_b, sessions_b, method='wilson')
print({'z': stat, 'p': pval, 'after_CI': (ci_low_a, ci_high_a), 'before_CI': (ci_low_b, ci_high_b)})
PVはポアソン近似(平均と分散がほぼ等しいとみなす)か、過分散が大きければ負の二項回帰(分散が平均より大きいカウントデータ向け)を使います。分布仮定に敏感な場合は、日次合計のブートストラップ(サンプル再抽出による信頼区間推定)で差の信頼区間を出すと頑健です。ここで有意差と実務上の重要差を分けて考えるのがポイントです。例えば直帰率が0.8ポイント低下しても、CVRやLTVとの連動が弱ければ優先度は低くなります。
ケーススタディ(想定例):28日比較でPV+約10〜20%、直帰率-約5〜7ptの内訳
例えば、B2Bサイトの製品LPを全面刷新したと仮定します。リニューアル前後28日でPVが**+10〜20%程度**、直帰率が**-5〜7ポイント程度**となる結果は、公開調査で示される速度×CVの弾性の範囲内で十分に現実的です²⁷⁸。算出はGA4の同一プロパティで、対象LP群のみを正規表現で抽出し、コントロールにはドキュメントセクションを用います。差の差モデルの交互作用係数が-0.05〜-0.07、95%信頼区間が負方向で安定していれば、改善の示唆として解釈できます。チャネル別ではオーガニックが最も寄与し、特にモバイルの改善が顕著になりやすい構造です。
この想定では、ファーストビューのDOM構造を見直してCLSを抑制し、LCPのP75を2.9秒から2.2秒に短縮したとします。上位10ページのうち多数で速度改善と直帰率低下が同方向に動き、相関係数が-0.6前後であれば、高速化が主要因であることを示唆します。一方、デスクトップでは差が小さく、ヒーロー画像の視認性向上とスクロール誘導のコピー差分が効くケースが多い。コピーのA/B実験の履歴と照合し、メッセージマッチの改善が補助的に効いていると読み解けます。
最後にビジネス指標への接続です。コンバージョン設定が「お問い合わせ」と「資料DL」のケースを想定すると、直帰率の改善に伴いセッションあたりページビューが+0.3前後増え、資料DL率が0.3〜0.5ポイント上がる、といった連鎖が起きることがあります。パイプラインの歩留まりを一定と仮定すると、月間の有望商談が数件〜十数件増え、平均受注単価を掛けて月次MQL価値が約1割前後伸びる、といった概算も可能です(外部施策や営業プロセスの変化は別途調整が必要)。行動指標からパイプライン価値までを繋いでおくと、次の投資判断に説得力が生まれます。
コンテンツタイプ別の分解で「次に効く箇所」を特定する
LP、ブログ、ドキュメントで行動は異なります。LPではファーストビューとCTA動線、ブログでは内部回遊とおすすめ枠、ドキュメントでは検索と目次の利便性が主要ドライバーです。GA4のカスタムディメンションにコンテンツタイプを付与しておくと、改善余地の大きいバケットが一目でわかります。例えばブログの直帰率が高止まりしているなら、関連記事レコメンドのアルゴリズムやレンダリングタイミングを調整し、CLSを抑えつつファーストペイント後1500ms以内に初回レコメンドを描画する、といった具体的施策に落とせます。
実装ガイド:データ取得からダッシュボードまで
運用可能な実装に落とすには、取得、保存、計算、可視化の一筆書きを作ります。GA4はData APIでもBigQueryでも取得できますが、粒度の柔軟性と再現性を重視してBigQueryを推奨します。以下はPythonで定期実行する最小コードです。
# Code 5: BigQueryから日次集計を取り込み、差の差まで一気通貫
from google.cloud import bigquery
import pandas as pd
import statsmodels.formula.api as smf
client = bigquery.Client()
query = open('sql/daily_landing_metrics.sql', encoding='utf-8').read()
df = client.query(query).result().to_dataframe()
# マーキング:対象群・コントロール群・前後(例)
df['group'] = df['landing_page'].str.contains('/products/').astype(int)
df['post'] = (pd.to_datetime(df['dt']) >= pd.Timestamp('2025-07-15')).astype(int)
# 差の差(直帰率)
model = smf.ols('bounce_rate ~ group + post + group:post', data=df).fit(cov_type='HC1')
print(model.params['group:post'], model.conf_int().loc['group:post'])
# ダッシュボード用に整形
out = df.groupby(['dt','group','post']).agg({'pv':'sum','sessions':'sum','bounce_rate':'mean'}).reset_index()
# 例:BigQueryへマート書き出し(認証と権限設定が必要)
# out.to_gbq('mart.renewal_daily', project_id='project', if_exists='replace')
ダッシュボードはLooker Studioで十分です。対象とコントロールを切り替えるスイッチ、前後期間の同期、チャネル×デバイスのスライサー、そして差の差の係数と信頼区間をカード表示にしておくと、経営会議でも一枚で説明できます。GA4のサンプリングを避けるため、Looker Studioは可能な限りBigQuery接続を使い、集約はSQLで事前計算しておくと快適です。
GA4 Data APIで軽量に済ませたい場合
BigQueryを使わない選択もあります。ページ別のPVとエンゲージメント率だけを取るなら、Data APIで十分です⁵。以下は安定版クライアント(v1)の例です。
# Code 6: GA4 Data APIでPVとエンゲージメント率を取得(v1)
from google.analytics.data_v1 import AnalyticsDataClient
from google.analytics.data_v1.types import DateRange, Dimension, Metric, RunReportRequest
property_id = 'properties/123456789'
client = AnalyticsDataClient()
request = RunReportRequest(
property=property_id,
date_ranges=[DateRange(start_date='2025-06-15', end_date='2025-08-15')],
dimensions=[Dimension(name='date'), Dimension(name='landingPagePlusQueryString')],
metrics=[Metric(name='screenPageViews'), Metric(name='engagementRate')]
)
response = client.run_report(request)
rows = [
{
'date': r.dimension_values[0].value,
'landing': r.dimension_values[1].value,
'pv': int(float(r.metric_values[0].value)),
'engagement_rate': float(r.metric_values[1].value)
} for r in response.rows
]
ただしイベントの再現性や加工の自由度はBigQueryに軍配が上がります。長期のベンチマークや最新のセッション定義変更に追随するには、原データに近い形で保持するほうが安全です。
ビジネスインパクトへ接続する:PV・直帰率の次に見るもの
PVや直帰率が改善しても、その先の価値に繋がらなければ施策の優先度は上げづらいのが現実です。中間KPIの連鎖を明文化しておくことを推奨します。例えば「直帰率低下 → セッションあたりPV増加 → 資料DL率上昇 → MQL増 → SQL/受注」といった連鎖を、部門横断で合意しておきます。各辺に期待弾性(1%変化あたりの反応度)を置き、範囲で見積もります。回帰で弾性を推定する最小コードは次の通りです。
# Code 7: 中間KPI連鎖の弾性を回帰で推定
import statsmodels.api as sm
# df: columns = ['pv_per_session','dl_rate','bounce_rate','controls...']
X = sm.add_constant(df[['pv_per_session','bounce_rate']])
y = df['dl_rate']
model = sm.OLS(y, X).fit(cov_type='HC1')
print(model.summary())
# 弾性からROI概算(例)
delta_bounce = -0.06 # 6pt改善を仮定
pv_lift = 0.32 # +0.32 PV/Sessionを仮定
dl_rate_lift = model.params['pv_per_session']*pv_lift + model.params['bounce_rate']*delta_bounce
print({'dl_rate_lift_estimate': dl_rate_lift})
ここで得られた弾性と営業パイプラインの歩留まりを掛け合わせると、今月のMQL増分や受注増分のレンジが出ます。インフラ費や開発稼働を控除すれば粗利ベースのROIに近づきます。リニューアルは一度きりのイベントではなく、観測・学習・改善のサイクルです。行動指標をビジネス指標へ翻訳する関数をチームで共有すると、次の一手が合意しやすくなります。
よくある落とし穴を先に塞ぐ
最後に現場で頻出する落とし穴をまとめておきます。まず、イベント名やコンバージョン定義をリニューアル直後に変更してしまうケースです。比較不可能になるため、最低2週間のパラレルランを経てから切替えましょう。次に、キャッシュやCDNの設定変更によって測定値が歪むケースがあります。エッジキャッシュのヒット率やHTMLのmax-ageを事前と同一に揃えておきます。また、Cookie同意バナーの挙動変更は、計測許諾率を通じて見かけの直帰率やPVに影響します。Consent Mode v2の各ステータスをログに残し、許諾別の補正を検討します⁴。最後に、UAとGA4の指標を横並びにしてしまうミスです。同じ名前でも定義が違えば別物です。GA4の直帰率はエンゲージメント率の補助指標であることを忘れないでください³。
まとめ:数字に静かな物語を語らせる
リニューアルの良し悪しは、PVや直帰率が教えてくれます。ただし定義を正し、条件を揃え、統計で確かめるという順番を守るほど、数字は静かに確かな物語を語り始めます。28日の前後比較にコントロールを添え、差の差で直帰率が下がっているなら、それはユーザー体験が良くなったサインです。そこから中間KPIの連鎖に沿ってパイプライン価値までつなげば、次の投資判断は迷いません。あなたのサイトでは、どのページ群が最も改善の余地を残していますか。今日、対象とコントロールを分けたビューを用意し、GA4とBigQueryで日次のトレンドを取りに行きましょう。参考として、Core Web Vitalsの基礎をまとめた解説や、GA4移行チェックリスト、実験設計の基本も併せてご覧ください。
参考文献
- ScientiaMobile. Mobile Site Visitors Abandon More Than 3 Seconds. https://www.scientiamobile.com/mobile-site-visitors-abandon-more-than-3-seconds/
- Deloitte. Milliseconds Make Millions. https://www.deloitte.com/ie/en/services/consulting/research/milliseconds-make-millions.html
- Google Analytics Help. About engagement rate and bounce rate in Google Analytics 4. https://support.google.com/analytics/answer/12195621
- Google Developers. Consent Mode. https://developers.google.com/tag-platform/devguides/consent
- Google Developers. Analytics Data API (GA4). https://developers.google.com/analytics/devguides/reporting/data/v1
- web.dev. Core Web Vitals. https://web.dev/vitals/
- Portent. Research: Site Speed Is Hurting Everyone’s Revenue. https://portent.com/blog/analytics/research-site-speed-hurting-everyones-revenue.htm
- web.dev. Case study: Renault increases conversions with PWA performance improvements. https://web.dev/case-studies/renault/