Spaces:
Sleeping
Sleeping
| # ============================================================================ | |
| # Quality Growth Strategy Dashboard | |
| # グローバルマーケティング戦略及びソートリーダーシップの高度化 | |
| # データドリブンな経営ナラティブによる「Quality Growth」の実現 | |
| # ============================================================================ | |
| library(shiny) | |
| library(shinydashboard) | |
| library(ggplot2) | |
| library(dplyr) | |
| library(tidyr) | |
| library(plotly) | |
| library(DT) | |
| library(scales) | |
| # ============================================================================ | |
| # データ生成(架空データ) | |
| # ============================================================================ | |
| set.seed(2026) | |
| # 1. 海外ブランディング時系列データ(24ヶ月) | |
| create_branding_data <- function() { | |
| n_months <- 24 | |
| dates <- seq(as.Date("2024-01-01"), by = "month", length.out = n_months) | |
| # トレンド + 季節性 + ノイズ | |
| trend <- seq(200, 1000, length.out = n_months) | |
| seasonal <- 100 * sin(seq(0, 4*pi, length.out = n_months)) | |
| noise <- rnorm(n_months, 0, 50) | |
| spend <- trend + seasonal + noise | |
| search <- 0.8 * lag(spend, 1, default = 200) + 1.2 * lag(spend, 2, default = 200) + rnorm(n_months, 0, 30) | |
| revenue <- 2000 + 3.5 * lag(spend, 2, default = 300) + 2.8 * lag(search, 1, default = 400) + rnorm(n_months, 0, 200) | |
| data.frame( | |
| Date = dates, | |
| Spend = round(spend, 0), | |
| Search = round(pmax(search, 100), 0), | |
| Revenue = round(pmax(revenue, 2000), 0) | |
| ) | |
| } | |
| # 2. 地域別ROIデータ | |
| create_regional_data <- function() { | |
| regions <- c("US", "Europe", "Asia", "LATAM", "MEA") | |
| n_per_region <- 12 | |
| regional_data <- do.call(rbind, lapply(regions, function(region) { | |
| base_spend <- switch(region, | |
| "US" = 800, "Europe" = 600, "Asia" = 400, "LATAM" = 200, "MEA" = 150 | |
| ) | |
| roi_mult <- switch(region, | |
| "US" = 3.99, "Europe" = 3.53, "Asia" = 6.48, "LATAM" = 4.2, "MEA" = 5.1 | |
| ) | |
| spend <- base_spend + rnorm(n_per_region, 0, base_spend * 0.2) | |
| search <- spend * runif(n_per_region, 1.2, 1.8) + rnorm(n_per_region, 0, 50) | |
| revenue <- spend * roi_mult + rnorm(n_per_region, 0, spend * 0.3) | |
| data.frame( | |
| Region = region, | |
| Month = seq(as.Date("2025-01-01"), by = "month", length.out = n_per_region), | |
| Spend = round(spend, 0), | |
| Search = round(search, 0), | |
| Revenue = round(revenue, 0), | |
| Tech_Score = round(runif(n_per_region, 55, 90), 1), | |
| Design_Score = round(runif(n_per_region, 55, 90), 1), | |
| Trust_Score = round(runif(n_per_region, 55, 90), 1) | |
| ) | |
| })) | |
| regional_data | |
| } | |
| # 3. ソートリーダーシップデータ | |
| create_tl_data <- function() { | |
| n_sessions <- 600 | |
| channels <- c("Organic", "Paid", "Social", "Direct", "Referral") | |
| reports <- c( | |
| "AI活用事例2025", "サステナビリティ報告", | |
| "DX推進白書", "次世代インフラ展望", "グローバル市場分析" | |
| ) | |
| channel_probs <- c(0.32, 0.19, 0.20, 0.19, 0.10) | |
| tl_data <- data.frame( | |
| session_id = paste0("S", sprintf("%04d", 1:n_sessions)), | |
| channel = sample(channels, n_sessions, replace = TRUE, prob = channel_probs), | |
| report = sample(reports, n_sessions, replace = TRUE), | |
| duration_sec = NA, | |
| scrolled = NA, | |
| pages_viewed = NA | |
| ) | |
| # チャネル別の行動パターン | |
| for (i in 1:nrow(tl_data)) { | |
| ch <- tl_data$channel[i] | |
| if (ch == "Referral") { | |
| tl_data$duration_sec[i] <- round(rnorm(1, 130, 40)) | |
| tl_data$scrolled[i] <- runif(1) < 0.85 | |
| tl_data$pages_viewed[i] <- rpois(1, 3) + 1 | |
| } else if (ch == "Organic") { | |
| tl_data$duration_sec[i] <- round(rnorm(1, 105, 35)) | |
| tl_data$scrolled[i] <- runif(1) < 0.75 | |
| tl_data$pages_viewed[i] <- rpois(1, 2.5) + 1 | |
| } else if (ch == "Direct") { | |
| tl_data$duration_sec[i] <- round(rnorm(1, 70, 30)) | |
| tl_data$scrolled[i] <- runif(1) < 0.60 | |
| tl_data$pages_viewed[i] <- rpois(1, 2) + 1 | |
| } else if (ch == "Social") { | |
| tl_data$duration_sec[i] <- round(rnorm(1, 65, 35)) | |
| tl_data$scrolled[i] <- runif(1) < 0.50 | |
| tl_data$pages_viewed[i] <- rpois(1, 1.5) + 1 | |
| } else { # Paid | |
| tl_data$duration_sec[i] <- round(rnorm(1, 35, 20)) | |
| tl_data$scrolled[i] <- runif(1) < 0.30 | |
| tl_data$pages_viewed[i] <- rpois(1, 1) + 1 | |
| } | |
| } | |
| tl_data$duration_sec <- pmax(tl_data$duration_sec, 5) | |
| # TL成功判定: 滞在60秒以上 + スクロール + 2ページ以上閲覧 | |
| tl_data$is_tl_success <- with(tl_data, | |
| duration_sec >= 60 & scrolled == TRUE & pages_viewed >= 2 | |
| ) | |
| tl_data | |
| } | |
| # データ生成 | |
| branding_data <- create_branding_data() | |
| regional_data <- create_regional_data() | |
| tl_data <- create_tl_data() | |
| # ============================================================================ | |
| # UI定義 | |
| # ============================================================================ | |
| ui <- dashboardPage( | |
| skin = "blue", | |
| dashboardHeader( | |
| title = tags$span( | |
| tags$img(src = "", height = "30px", style = "margin-right: 10px;"), | |
| "Quality Growth Dashboard" | |
| ), | |
| titleWidth = 350 | |
| ), | |
| dashboardSidebar( | |
| width = 280, | |
| sidebarMenu( | |
| id = "sidebar", | |
| menuItem("Executive Summary", tabName = "summary", icon = icon("chart-line")), | |
| menuItem("海外ブランディング分析", tabName = "branding", icon = icon("globe")), | |
| menuItem("地域別ROI分析", tabName = "regional", icon = icon("map")), | |
| menuItem("ソートリーダーシップ", tabName = "thought_leadership", icon = icon("lightbulb")), | |
| menuItem("統計モデル", tabName = "models", icon = icon("calculator")), | |
| hr(), | |
| tags$div( | |
| style = "padding: 15px; color: #b8c7ce; font-size: 12px;", | |
| tags$p(tags$strong("プロジェクト情報")) | |
| ) | |
| ) | |
| ), | |
| dashboardBody( | |
| tags$head( | |
| tags$style(HTML(" | |
| .content-wrapper { background-color: #f4f6f9; } | |
| .box { border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } | |
| .box-header { border-bottom: 2px solid #3c8dbc; } | |
| .info-box { border-radius: 8px; } | |
| .small-box { border-radius: 8px; } | |
| .nav-tabs-custom > .nav-tabs > li.active { border-top-color: #3c8dbc; } | |
| .skin-blue .main-header .logo { background-color: #1a3a5c; } | |
| .skin-blue .main-header .navbar { background-color: #1a3a5c; } | |
| .metric-card { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| border-radius: 12px; | |
| padding: 20px; | |
| color: white; | |
| margin-bottom: 15px; | |
| } | |
| .metric-card h3 { margin: 0; font-size: 2em; } | |
| .metric-card p { margin: 5px 0 0; opacity: 0.9; } | |
| ")) | |
| ), | |
| tabItems( | |
| # Executive Summary タブ | |
| tabItem( | |
| tabName = "summary", | |
| fluidRow( | |
| column(12, | |
| tags$div( | |
| style = "background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%); | |
| padding: 30px; border-radius: 12px; color: white; margin-bottom: 20px;", | |
| tags$h2("グローバルマーケティング戦略及びソートリーダーシップの高度化", | |
| style = "margin-top: 0;"), | |
| tags$p("データドリブンな経営ナラティブによる「Quality Growth」の実現", | |
| style = "font-size: 1.2em; opacity: 0.9;") | |
| ) | |
| ) | |
| ), | |
| fluidRow( | |
| valueBoxOutput("total_investment", width = 3), | |
| valueBoxOutput("avg_roi", width = 3), | |
| valueBoxOutput("tl_success_rate", width = 3), | |
| valueBoxOutput("top_region", width = 3) | |
| ), | |
| fluidRow( | |
| box( | |
| title = "Strategic Context: Quality Growthへの戦略的整合", | |
| status = "primary", solidHeader = TRUE, width = 6, | |
| tags$div( | |
| style = "display: flex; gap: 20px;", | |
| tags$div( | |
| style = "flex: 1; padding: 15px; background: #f8f9fa; border-radius: 8px;", | |
| tags$h4("Current Context (現状)", style = "color: #6c757d;"), | |
| tags$ul( | |
| tags$li("Volume Expansion(量的拡大)"), | |
| tags$li("Technology-focused Context(技術文脈)") | |
| ), | |
| tags$p("課題:経営層への認知・顧客化に最適化されていない", | |
| style = "color: #dc3545; font-size: 0.9em;") | |
| ), | |
| tags$div( | |
| style = "flex: 1; padding: 15px; background: #e8f4f8; border-radius: 8px;", | |
| tags$h4("Strategic Pivot (新方針)", style = "color: #007bff;"), | |
| tags$ul( | |
| tags$li("Quality Growth(質的成長)"), | |
| tags$li("Management-focused Narrative(経営ナラティブ)"), | |
| tags$li("Key Themes: Powered & Next Gen Infrastructure") | |
| ) | |
| ) | |
| ) | |
| ), | |
| box( | |
| title = "Project Scope: 2つの並行ワークストリーム", | |
| status = "primary", solidHeader = TRUE, width = 6, | |
| tags$div( | |
| style = "display: flex; gap: 20px;", | |
| tags$div( | |
| style = "flex: 1; padding: 15px; background: #fff3cd; border-radius: 8px;", | |
| tags$h4("Region A: 思想リーダーシップ", style = "color: #856404;"), | |
| tags$p("Focus: 基準の再定義と経営ナラティブ"), | |
| tags$ul( | |
| tags$li("現状分析(回遊ログ・アクセス解析)"), | |
| tags$li("競合ベンチマーク調査"), | |
| tags$li("「質」を測る新KPIの定義") | |
| ) | |
| ), | |
| tags$div( | |
| style = "flex: 1; padding: 15px; background: #d4edda; border-radius: 8px;", | |
| tags$h4("Region B: グローバルマーケティング", style = "color: #155724;"), | |
| tags$p("Focus: 標準化と投資対効果(ROI)"), | |
| tags$ul( | |
| tags$li("拠点間横比較(市場規模 vs 成長率)"), | |
| tags$li("ROI分析と投資効率の可視化"), | |
| tags$li("レポートフォーマットの標準化") | |
| ) | |
| ) | |
| ) | |
| ) | |
| ), | |
| fluidRow( | |
| box( | |
| title = "Schedule & Roadmap", | |
| status = "info", solidHeader = TRUE, width = 12, | |
| plotlyOutput("timeline_chart", height = "250px") | |
| ) | |
| ) | |
| ), | |
| # 海外ブランディング分析 タブ | |
| tabItem( | |
| tabName = "branding", | |
| fluidRow( | |
| box( | |
| title = "海外ブランディング指標の推移(24ヶ月)", | |
| status = "primary", solidHeader = TRUE, width = 12, | |
| plotlyOutput("branding_timeseries", height = "400px") | |
| ) | |
| ), | |
| fluidRow( | |
| box( | |
| title = "相互相関分析: Spend vs Revenue", | |
| status = "info", solidHeader = TRUE, width = 6, | |
| plotOutput("ccf_plot", height = "300px"), | |
| tags$p("タイムラグの特定:予算投下から効果が最大化するまでの期間を可視化", | |
| style = "font-size: 0.9em; color: #6c757d;") | |
| ), | |
| box( | |
| title = "回帰分析結果", | |
| status = "info", solidHeader = TRUE, width = 6, | |
| verbatimTextOutput("regression_summary"), | |
| tags$div( | |
| style = "margin-top: 15px; padding: 10px; background: #e8f4f8; border-radius: 8px;", | |
| tags$h5("解釈ポイント:"), | |
| tags$ul( | |
| tags$li("lag(Spend, 2)の係数: 2ヶ月前の投資が現在の売上に寄与"), | |
| tags$li("lag(Search, 1)の係数: 指名検索数が売上をドライブ"), | |
| tags$li("R-squared: モデルの説明力") | |
| ) | |
| ) | |
| ) | |
| ), | |
| fluidRow( | |
| box( | |
| title = "投資効率サマリー", | |
| status = "success", solidHeader = TRUE, width = 12, | |
| fluidRow( | |
| column(4, | |
| tags$div(class = "metric-card", | |
| tags$h3(textOutput("total_spend_text")), | |
| tags$p("総投資額") | |
| ) | |
| ), | |
| column(4, | |
| tags$div(class = "metric-card", style = "background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);", | |
| tags$h3(textOutput("total_revenue_text")), | |
| tags$p("総売上額") | |
| ) | |
| ), | |
| column(4, | |
| tags$div(class = "metric-card", style = "background: linear-gradient(135deg, #ee0979 0%, #ff6a00 100%);", | |
| tags$h3(textOutput("simple_roi_text")), | |
| tags$p("単純ROI") | |
| ) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ), | |
| # 地域別ROI分析 タブ | |
| tabItem( | |
| tabName = "regional", | |
| fluidRow( | |
| box( | |
| title = "地域別ROI比較", | |
| status = "primary", solidHeader = TRUE, width = 6, | |
| plotlyOutput("regional_roi_chart", height = "350px") | |
| ), | |
| box( | |
| title = "Brand Search vs Revenue", | |
| status = "primary", solidHeader = TRUE, width = 6, | |
| plotlyOutput("search_revenue_scatter", height = "350px") | |
| ) | |
| ), | |
| fluidRow( | |
| box( | |
| title = "定性データ: ブランド認知スコア(0-100)", | |
| status = "info", solidHeader = TRUE, width = 12, | |
| plotlyOutput("qualitative_heatmap", height = "300px"), | |
| tags$div( | |
| style = "margin-top: 15px; padding: 10px; background: #fff3cd; border-radius: 8px;", | |
| tags$h5("地域別インサイト:"), | |
| tags$ul( | |
| tags$li(tags$strong("Asia:"), " 技術力(Technology)が高評価 → 最新技術をアピール"), | |
| tags$li(tags$strong("Europe:"), " 信頼度(Trust)が重要 → 認証取得・伝統の訴求"), | |
| tags$li(tags$strong("US:"), " デザイン(Design)が先行指標 → クリエイティブ重視") | |
| ) | |
| ) | |
| ) | |
| ), | |
| fluidRow( | |
| box( | |
| title = "地域別詳細データ", | |
| status = "success", solidHeader = TRUE, width = 12, | |
| DTOutput("regional_table") | |
| ) | |
| ) | |
| ), | |
| # ソートリーダーシップ タブ | |
| tabItem( | |
| tabName = "thought_leadership", | |
| fluidRow( | |
| box( | |
| title = "チャネル別TL成功率", | |
| status = "primary", solidHeader = TRUE, width = 6, | |
| plotlyOutput("tl_channel_chart", height = "350px"), | |
| tags$p("TL成功条件: 滞在60秒以上 + スクロール発生 + 2ページ以上閲覧", | |
| style = "font-size: 0.9em; color: #6c757d;") | |
| ), | |
| box( | |
| title = "コンテンツ別パフォーマンス", | |
| status = "primary", solidHeader = TRUE, width = 6, | |
| plotlyOutput("content_performance", height = "350px") | |
| ) | |
| ), | |
| fluidRow( | |
| box( | |
| title = "チャネル別統計サマリー", | |
| status = "info", solidHeader = TRUE, width = 12, | |
| DTOutput("tl_summary_table") | |
| ) | |
| ), | |
| fluidRow( | |
| box( | |
| title = "流入チャネルの「質」の評価", | |
| status = "warning", solidHeader = TRUE, width = 6, | |
| tags$div( | |
| style = "padding: 15px;", | |
| tags$h5("Referral / Organicの成功率が高い場合:"), | |
| tags$p("外部メディアでの紹介や検索意図が、レポートの内容と合致しており、信頼獲得に成功している。"), | |
| tags$hr(), | |
| tags$h5("Paid / Socialの成功率が低い場合:"), | |
| tags$p("「タイトル釣り」などで流入は稼げているが、中身が期待に沿っていない、あるいは「ながら読み」層が多い可能性がある。") | |
| ) | |
| ), | |
| box( | |
| title = "次のアクション提案", | |
| status = "success", solidHeader = TRUE, width = 6, | |
| tags$div( | |
| style = "padding: 15px;", | |
| tags$ul( | |
| tags$li("スター・コンテンツ(High PV, High Success)の横展開"), | |
| tags$li("ニッチ・リーダーシップコンテンツの広報強化"), | |
| tags$li("Low Successコンテンツのトピック・ターゲット見直し"), | |
| tags$li("GA4実データへの移行とスコアリングカスタマイズ") | |
| ) | |
| ) | |
| ) | |
| ) | |
| ), | |
| # 統計モデル タブ | |
| tabItem( | |
| tabName = "models", | |
| fluidRow( | |
| box( | |
| title = "Analytical Methodology: 統計的アプローチの採用", | |
| status = "primary", solidHeader = TRUE, width = 12, | |
| tags$div( | |
| style = "display: flex; gap: 20px; flex-wrap: wrap;", | |
| tags$div( | |
| style = "flex: 1; min-width: 250px; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; color: white;", | |
| tags$h4("Regression Analysis(回帰分析)"), | |
| tags$p("成功に寄与している変数を統計的に切り分け、投資効率を算出する。") | |
| ), | |
| tags$div( | |
| style = "flex: 1; min-width: 250px; padding: 20px; background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); border-radius: 12px; color: white;", | |
| tags$h4("Weighted Average Models(加重平均モデル)"), | |
| tags$p("各国の市場環境の違いを考慮に入れた、公平な評価モデルの設計。") | |
| ), | |
| tags$div( | |
| style = "flex: 1; min-width: 250px; padding: 20px; background: linear-gradient(135deg, #ee0979 0%, #ff6a00 100%); border-radius: 12px; color: white;", | |
| tags$h4("Statistical Tools(分析ツール)"), | |
| tags$p("専門的な統計解析ソフトウェアを活用し、ブラックボックス化しない「説明可能なロジック」を構築。") | |
| ) | |
| ) | |
| ) | |
| ), | |
| fluidRow( | |
| box( | |
| title = "地域別回帰分析結果", | |
| status = "info", solidHeader = TRUE, width = 12, | |
| selectInput("region_select", "地域を選択:", | |
| choices = c("US", "Europe", "Asia", "LATAM", "MEA"), | |
| selected = "US"), | |
| verbatimTextOutput("regional_regression") | |
| ) | |
| ), | |
| fluidRow( | |
| box( | |
| title = "モデル診断プロット", | |
| status = "warning", solidHeader = TRUE, width = 6, | |
| plotOutput("residual_plot", height = "300px") | |
| ), | |
| box( | |
| title = "予測 vs 実績", | |
| status = "warning", solidHeader = TRUE, width = 6, | |
| plotlyOutput("predicted_vs_actual", height = "300px") | |
| ) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ) | |
| # ============================================================================ | |
| # Server定義 | |
| # ============================================================================ | |
| server <- function(input, output, session) { | |
| # --- Value Boxes --- | |
| output$total_investment <- renderValueBox({ | |
| valueBox( | |
| "¥10,500,000", | |
| "Total Investment (税別)", | |
| icon = icon("yen-sign"), | |
| color = "blue" | |
| ) | |
| }) | |
| output$avg_roi <- renderValueBox({ | |
| roi_summary <- regional_data %>% | |
| group_by(Region) %>% | |
| summarise(ROI = sum(Revenue) / sum(Spend)) %>% | |
| summarise(Avg_ROI = mean(ROI)) | |
| valueBox( | |
| sprintf("%.2fx", roi_summary$Avg_ROI), | |
| "平均ROI(全地域)", | |
| icon = icon("chart-line"), | |
| color = "green" | |
| ) | |
| }) | |
| output$tl_success_rate <- renderValueBox({ | |
| success_rate <- mean(tl_data$is_tl_success) * 100 | |
| valueBox( | |
| sprintf("%.1f%%", success_rate), | |
| "TL成功率", | |
| icon = icon("lightbulb"), | |
| color = "yellow" | |
| ) | |
| }) | |
| output$top_region <- renderValueBox({ | |
| top <- regional_data %>% | |
| group_by(Region) %>% | |
| summarise(ROI = sum(Revenue) / sum(Spend)) %>% | |
| arrange(desc(ROI)) %>% | |
| slice(1) | |
| valueBox( | |
| top$Region, | |
| sprintf("Top ROI Region (%.2fx)", top$ROI), | |
| icon = icon("trophy"), | |
| color = "purple" | |
| ) | |
| }) | |
| # --- Timeline Chart --- | |
| output$timeline_chart <- renderPlotly({ | |
| phases <- data.frame( | |
| Phase = c("Phase 1: Mobilization", "Phase 2: Deep Analysis", "Phase 3: Reporting", "Phase 4: Close"), | |
| Start = as.Date(c("2026-01-19", "2026-02-02", "2026-02-23", "2026-03-02")), | |
| End = as.Date(c("2026-02-01", "2026-02-22", "2026-03-01", "2026-03-15")), | |
| Color = c("#3498db", "#e74c3c", "#2ecc71", "#9b59b6") | |
| ) | |
| plot_ly() %>% | |
| add_segments( | |
| data = phases, | |
| x = ~Start, xend = ~End, | |
| y = ~Phase, yend = ~Phase, | |
| color = ~Phase, | |
| colors = phases$Color, | |
| line = list(width = 20), | |
| showlegend = FALSE | |
| ) %>% | |
| layout( | |
| xaxis = list(title = "", tickformat = "%b %d"), | |
| yaxis = list(title = "", categoryorder = "array", | |
| categoryarray = rev(phases$Phase)), | |
| margin = list(l = 150) | |
| ) | |
| }) | |
| # --- Branding Time Series --- | |
| output$branding_timeseries <- renderPlotly({ | |
| df_long <- branding_data %>% | |
| pivot_longer(cols = c(Spend, Search, Revenue), | |
| names_to = "Metric", values_to = "Value") | |
| plot_ly(df_long, x = ~Date, y = ~Value, color = ~Metric, | |
| type = 'scatter', mode = 'lines+markers', | |
| colors = c("Revenue" = "#2ecc71", "Search" = "#e74c3c", "Spend" = "#3498db")) %>% | |
| layout( | |
| xaxis = list(title = ""), | |
| yaxis = list(title = "Value (Standardized Units)"), | |
| legend = list(orientation = "h", y = -0.1), | |
| hovermode = "x unified" | |
| ) | |
| }) | |
| # --- CCF Plot --- | |
| output$ccf_plot <- renderPlot({ | |
| ccf_result <- ccf(branding_data$Spend, branding_data$Revenue, | |
| lag.max = 10, plot = FALSE) | |
| df_ccf <- data.frame( | |
| Lag = ccf_result$lag, | |
| ACF = ccf_result$acf | |
| ) | |
| ggplot(df_ccf, aes(x = Lag, y = ACF)) + | |
| geom_hline(yintercept = 0, color = "gray50") + | |
| geom_hline(yintercept = c(-0.4, 0.4), linetype = "dashed", color = "blue", alpha = 0.5) + | |
| geom_segment(aes(xend = Lag, yend = 0), color = "#3498db", size = 1) + | |
| geom_point(color = "#3498db", size = 3) + | |
| labs(title = "Cross-Correlation: Spend vs Revenue", | |
| x = "Lag (months)", y = "ACF") + | |
| theme_minimal(base_size = 14) + | |
| theme(plot.title = element_text(hjust = 0.5, face = "bold")) | |
| }) | |
| # --- Regression Summary --- | |
| output$regression_summary <- renderPrint({ | |
| df <- branding_data | |
| model <- lm(Revenue ~ lag(Spend, 2) + lag(Search, 1), data = df) | |
| summary(model) | |
| }) | |
| # --- Metric Cards --- | |
| output$total_spend_text <- renderText({ | |
| paste0("¥", format(sum(branding_data$Spend) * 1000, big.mark = ",")) | |
| }) | |
| output$total_revenue_text <- renderText({ | |
| paste0("¥", format(sum(branding_data$Revenue) * 1000, big.mark = ",")) | |
| }) | |
| output$simple_roi_text <- renderText({ | |
| roi <- sum(branding_data$Revenue) / sum(branding_data$Spend) | |
| sprintf("%.2fx", roi) | |
| }) | |
| # --- Regional ROI Chart --- | |
| output$regional_roi_chart <- renderPlotly({ | |
| roi_data <- regional_data %>% | |
| group_by(Region) %>% | |
| summarise(ROI = sum(Revenue) / sum(Spend)) %>% | |
| arrange(desc(ROI)) | |
| plot_ly(roi_data, x = ~reorder(Region, ROI), y = ~ROI, type = 'bar', | |
| marker = list(color = c("#2ecc71", "#3498db", "#e74c3c", "#9b59b6", "#f39c12"))) %>% | |
| layout( | |
| xaxis = list(title = "Region"), | |
| yaxis = list(title = "ROI (Revenue / Spend)"), | |
| title = list(text = "Revenue per 1 Unit of Spend") | |
| ) | |
| }) | |
| # --- Search vs Revenue Scatter --- | |
| output$search_revenue_scatter <- renderPlotly({ | |
| agg_data <- regional_data %>% | |
| group_by(Region) %>% | |
| summarise( | |
| Avg_Search = mean(Search), | |
| Avg_Revenue = mean(Revenue) | |
| ) | |
| plot_ly(agg_data, x = ~Avg_Search, y = ~Avg_Revenue, color = ~Region, | |
| type = 'scatter', mode = 'markers', | |
| marker = list(size = 15), | |
| text = ~Region) %>% | |
| layout( | |
| xaxis = list(title = "Brand Search Volume (Avg)"), | |
| yaxis = list(title = "Revenue (Avg)") | |
| ) | |
| }) | |
| # --- Qualitative Heatmap --- | |
| output$qualitative_heatmap <- renderPlotly({ | |
| qual_data <- regional_data %>% | |
| group_by(Region) %>% | |
| summarise( | |
| Technology = mean(Tech_Score), | |
| Design = mean(Design_Score), | |
| Trust = mean(Trust_Score) | |
| ) %>% | |
| pivot_longer(cols = c(Technology, Design, Trust), | |
| names_to = "Attribute", values_to = "Score") | |
| plot_ly(qual_data, x = ~Attribute, y = ~Region, z = ~Score, | |
| type = 'heatmap', | |
| colorscale = list(c(0, "#f7fbff"), c(0.5, "#6baed6"), c(1, "#08306b")), | |
| text = ~round(Score, 1), | |
| texttemplate = "%{text}", | |
| showscale = TRUE) %>% | |
| layout( | |
| xaxis = list(title = ""), | |
| yaxis = list(title = "") | |
| ) | |
| }) | |
| # --- Regional Table --- | |
| output$regional_table <- renderDT({ | |
| summary_data <- regional_data %>% | |
| group_by(Region) %>% | |
| summarise( | |
| `Total Spend` = sum(Spend), | |
| `Total Revenue` = sum(Revenue), | |
| `ROI` = round(sum(Revenue) / sum(Spend), 2), | |
| `Avg Tech Score` = round(mean(Tech_Score), 1), | |
| `Avg Design Score` = round(mean(Design_Score), 1), | |
| `Avg Trust Score` = round(mean(Trust_Score), 1) | |
| ) | |
| datatable(summary_data, | |
| options = list(pageLength = 10, dom = 't'), | |
| rownames = FALSE) %>% | |
| formatCurrency(c('Total Spend', 'Total Revenue'), currency = "¥", digits = 0) | |
| }) | |
| # --- TL Channel Chart --- | |
| output$tl_channel_chart <- renderPlotly({ | |
| channel_summary <- tl_data %>% | |
| group_by(channel) %>% | |
| summarise( | |
| sessions = n(), | |
| success_count = sum(is_tl_success), | |
| success_rate = mean(is_tl_success) | |
| ) %>% | |
| arrange(desc(success_rate)) | |
| plot_ly(channel_summary, y = ~reorder(channel, success_rate), x = ~success_rate, | |
| type = 'bar', orientation = 'h', | |
| marker = list(color = c("#00bcd4", "#8bc34a", "#ff9800", "#e91e63", "#9c27b0"))) %>% | |
| layout( | |
| xaxis = list(title = "Success Rate", tickformat = ".0%"), | |
| yaxis = list(title = "") | |
| ) | |
| }) | |
| # --- Content Performance --- | |
| output$content_performance <- renderPlotly({ | |
| content_summary <- tl_data %>% | |
| group_by(report) %>% | |
| summarise( | |
| sessions = n(), | |
| success_rate = mean(is_tl_success), | |
| avg_duration = mean(duration_sec) | |
| ) | |
| plot_ly(content_summary, x = ~sessions, y = ~success_rate, | |
| size = ~avg_duration, color = ~report, | |
| type = 'scatter', mode = 'markers', | |
| marker = list(sizemode = 'diameter', sizeref = 2), | |
| text = ~paste(report, "<br>Sessions:", sessions, | |
| "<br>Success Rate:", scales::percent(success_rate), | |
| "<br>Avg Duration:", round(avg_duration), "sec")) %>% | |
| layout( | |
| xaxis = list(title = "Sessions (PV)"), | |
| yaxis = list(title = "TL Success Rate", tickformat = ".0%") | |
| ) | |
| }) | |
| # --- TL Summary Table --- | |
| output$tl_summary_table <- renderDT({ | |
| tl_data %>% | |
| group_by(channel) %>% | |
| summarise( | |
| `Sessions` = n(), | |
| `Success Count` = sum(is_tl_success), | |
| `Success Rate` = scales::percent(mean(is_tl_success), accuracy = 0.1), | |
| `Avg Duration (sec)` = round(mean(duration_sec), 1), | |
| `Avg Pages Viewed` = round(mean(pages_viewed), 2) | |
| ) %>% | |
| arrange(desc(`Success Count`)) %>% | |
| datatable(options = list(pageLength = 10, dom = 't'), rownames = FALSE) | |
| }) | |
| # --- Regional Regression --- | |
| output$regional_regression <- renderPrint({ | |
| region_df <- regional_data %>% filter(Region == input$region_select) | |
| model <- lm(Revenue ~ Tech_Score + Design_Score + Trust_Score, data = region_df) | |
| summary(model) | |
| }) | |
| # --- Residual Plot --- | |
| output$residual_plot <- renderPlot({ | |
| df <- branding_data %>% filter(!is.na(lag(Spend, 2)) & !is.na(lag(Search, 1))) | |
| model <- lm(Revenue ~ lag(Spend, 2) + lag(Search, 1), data = branding_data) | |
| residuals_df <- data.frame( | |
| Fitted = fitted(model), | |
| Residuals = residuals(model) | |
| ) | |
| ggplot(residuals_df, aes(x = Fitted, y = Residuals)) + | |
| geom_point(color = "#3498db", size = 3, alpha = 0.7) + | |
| geom_hline(yintercept = 0, linetype = "dashed", color = "red") + | |
| geom_smooth(method = "loess", se = FALSE, color = "#e74c3c") + | |
| labs(title = "Residuals vs Fitted", x = "Fitted Values", y = "Residuals") + | |
| theme_minimal(base_size = 14) | |
| }) | |
| # --- Predicted vs Actual --- | |
| output$predicted_vs_actual <- renderPlotly({ | |
| model <- lm(Revenue ~ lag(Spend, 2) + lag(Search, 1), data = branding_data) | |
| pred_df <- data.frame( | |
| Actual = branding_data$Revenue[3:24], | |
| Predicted = predict(model, branding_data)[3:24] | |
| ) | |
| plot_ly(pred_df) %>% | |
| add_trace(x = ~Actual, y = ~Predicted, type = 'scatter', mode = 'markers', | |
| marker = list(color = "#3498db", size = 10), | |
| name = "Data Points") %>% | |
| add_trace(x = c(min(pred_df$Actual), max(pred_df$Actual)), | |
| y = c(min(pred_df$Actual), max(pred_df$Actual)), | |
| type = 'scatter', mode = 'lines', | |
| line = list(color = "red", dash = "dash"), | |
| name = "Perfect Fit") %>% | |
| layout( | |
| xaxis = list(title = "Actual Revenue"), | |
| yaxis = list(title = "Predicted Revenue") | |
| ) | |
| }) | |
| } | |
| # ============================================================================ | |
| # アプリ起動 | |
| # ============================================================================ | |
| shinyApp(ui = ui, server = server) | |