QualityGrowth / app.R
sugitora's picture
Update app.R
da7071b verified
# ============================================================================
# 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)