Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- Dockerfile +54 -11
- app.R +828 -48
Dockerfile
CHANGED
|
@@ -1,14 +1,57 @@
|
|
| 1 |
-
|
|
|
|
| 2 |
|
| 3 |
-
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dockerfile for Hugging Face Spaces - R Shiny App
|
| 2 |
+
# Quality Growth Strategy Dashboard
|
| 3 |
|
| 4 |
+
FROM rocker/shiny:4.3.2
|
| 5 |
|
| 6 |
+
# Install system dependencies
|
| 7 |
+
RUN apt-get update && apt-get install -y \
|
| 8 |
+
libcurl4-gnutls-dev \
|
| 9 |
+
libssl-dev \
|
| 10 |
+
libxml2-dev \
|
| 11 |
+
libfontconfig1-dev \
|
| 12 |
+
libfreetype6-dev \
|
| 13 |
+
libpng-dev \
|
| 14 |
+
libtiff5-dev \
|
| 15 |
+
libjpeg-dev \
|
| 16 |
+
libharfbuzz-dev \
|
| 17 |
+
libfribidi-dev \
|
| 18 |
+
pandoc \
|
| 19 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 20 |
|
| 21 |
+
# Install R packages
|
| 22 |
+
RUN R -e "install.packages(c(\
|
| 23 |
+
'shiny', \
|
| 24 |
+
'shinydashboard', \
|
| 25 |
+
'ggplot2', \
|
| 26 |
+
'dplyr', \
|
| 27 |
+
'tidyr', \
|
| 28 |
+
'plotly', \
|
| 29 |
+
'DT', \
|
| 30 |
+
'scales' \
|
| 31 |
+
), repos='https://cloud.r-project.org/')"
|
| 32 |
+
|
| 33 |
+
# Create app directory
|
| 34 |
+
RUN mkdir -p /srv/shiny-server/app
|
| 35 |
+
|
| 36 |
+
# Copy app files
|
| 37 |
+
COPY app.R /srv/shiny-server/app/
|
| 38 |
+
|
| 39 |
+
# Set permissions
|
| 40 |
+
RUN chown -R shiny:shiny /srv/shiny-server/app
|
| 41 |
+
|
| 42 |
+
# Expose port (Hugging Face uses 7860)
|
| 43 |
+
EXPOSE 7860
|
| 44 |
+
|
| 45 |
+
# Configure shiny-server to use port 7860
|
| 46 |
+
RUN echo "run_as shiny;\n\
|
| 47 |
+
server {\n\
|
| 48 |
+
listen 7860;\n\
|
| 49 |
+
location / {\n\
|
| 50 |
+
site_dir /srv/shiny-server/app;\n\
|
| 51 |
+
log_dir /var/log/shiny-server;\n\
|
| 52 |
+
directory_index on;\n\
|
| 53 |
+
}\n\
|
| 54 |
+
}" > /etc/shiny-server/shiny-server.conf
|
| 55 |
+
|
| 56 |
+
# Start shiny-server
|
| 57 |
+
CMD ["/usr/bin/shiny-server"]
|
app.R
CHANGED
|
@@ -1,58 +1,838 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
library(shiny)
|
| 2 |
-
library(
|
| 3 |
-
library(dplyr)
|
| 4 |
library(ggplot2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
),
|
| 19 |
-
|
| 20 |
-
checkboxInput("by_species", "Show species", TRUE),
|
| 21 |
-
checkboxInput("show_margins", "Show marginal plots", TRUE),
|
| 22 |
-
checkboxInput("smooth", "Add smoother"),
|
| 23 |
),
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
)
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
)
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
}
|
| 57 |
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================================
|
| 2 |
+
# Quality Growth Strategy Dashboard
|
| 3 |
+
# グローバルマーケティング戦略及びソートリーダーシップの高度化
|
| 4 |
+
# データドリブンな経営ナラティブによる「Quality Growth」の実現
|
| 5 |
+
# ============================================================================
|
| 6 |
+
|
| 7 |
library(shiny)
|
| 8 |
+
library(shinydashboard)
|
|
|
|
| 9 |
library(ggplot2)
|
| 10 |
+
library(dplyr)
|
| 11 |
+
library(tidyr)
|
| 12 |
+
library(plotly)
|
| 13 |
+
library(DT)
|
| 14 |
+
library(scales)
|
| 15 |
+
|
| 16 |
+
# ============================================================================
|
| 17 |
+
# データ生成(架空データ)
|
| 18 |
+
# ============================================================================
|
| 19 |
+
|
| 20 |
+
set.seed(2026)
|
| 21 |
+
|
| 22 |
+
# 1. 海外ブランディング時系列データ(24ヶ月)
|
| 23 |
+
create_branding_data <- function() {
|
| 24 |
+
n_months <- 24
|
| 25 |
+
dates <- seq(as.Date("2024-01-01"), by = "month", length.out = n_months)
|
| 26 |
+
|
| 27 |
+
# トレンド + 季節性 + ノイズ
|
| 28 |
+
trend <- seq(200, 1000, length.out = n_months)
|
| 29 |
+
seasonal <- 100 * sin(seq(0, 4*pi, length.out = n_months))
|
| 30 |
+
noise <- rnorm(n_months, 0, 50)
|
| 31 |
+
|
| 32 |
+
spend <- trend + seasonal + noise
|
| 33 |
+
search <- 0.8 * lag(spend, 1, default = 200) + 1.2 * lag(spend, 2, default = 200) + rnorm(n_months, 0, 30)
|
| 34 |
+
revenue <- 2000 + 3.5 * lag(spend, 2, default = 300) + 2.8 * lag(search, 1, default = 400) + rnorm(n_months, 0, 200)
|
| 35 |
+
|
| 36 |
+
data.frame(
|
| 37 |
+
Date = dates,
|
| 38 |
+
Spend = round(spend, 0),
|
| 39 |
+
Search = round(pmax(search, 100), 0),
|
| 40 |
+
Revenue = round(pmax(revenue, 2000), 0)
|
| 41 |
+
)
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
# 2. 地域別ROIデータ
|
| 45 |
+
create_regional_data <- function() {
|
| 46 |
+
regions <- c("US", "Europe", "Asia", "LATAM", "MEA")
|
| 47 |
+
n_per_region <- 12
|
| 48 |
+
|
| 49 |
+
regional_data <- do.call(rbind, lapply(regions, function(region) {
|
| 50 |
+
base_spend <- switch(region,
|
| 51 |
+
"US" = 800, "Europe" = 600, "Asia" = 400, "LATAM" = 200, "MEA" = 150
|
| 52 |
+
)
|
| 53 |
+
roi_mult <- switch(region,
|
| 54 |
+
"US" = 3.99, "Europe" = 3.53, "Asia" = 6.48, "LATAM" = 4.2, "MEA" = 5.1
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
spend <- base_spend + rnorm(n_per_region, 0, base_spend * 0.2)
|
| 58 |
+
search <- spend * runif(n_per_region, 1.2, 1.8) + rnorm(n_per_region, 0, 50)
|
| 59 |
+
revenue <- spend * roi_mult + rnorm(n_per_region, 0, spend * 0.3)
|
| 60 |
+
|
| 61 |
+
data.frame(
|
| 62 |
+
Region = region,
|
| 63 |
+
Month = seq(as.Date("2025-01-01"), by = "month", length.out = n_per_region),
|
| 64 |
+
Spend = round(spend, 0),
|
| 65 |
+
Search = round(search, 0),
|
| 66 |
+
Revenue = round(revenue, 0),
|
| 67 |
+
Tech_Score = round(runif(n_per_region, 55, 90), 1),
|
| 68 |
+
Design_Score = round(runif(n_per_region, 55, 90), 1),
|
| 69 |
+
Trust_Score = round(runif(n_per_region, 55, 90), 1)
|
| 70 |
+
)
|
| 71 |
+
}))
|
| 72 |
+
|
| 73 |
+
regional_data
|
| 74 |
+
}
|
| 75 |
|
| 76 |
+
# 3. ソートリーダーシップデータ
|
| 77 |
+
create_tl_data <- function() {
|
| 78 |
+
n_sessions <- 600
|
| 79 |
+
channels <- c("Organic", "Paid", "Social", "Direct", "Referral")
|
| 80 |
+
reports <- c(
|
| 81 |
+
"AI活用事例2025", "サステナビリティ報告",
|
| 82 |
+
"DX推進白書", "次世代インフラ展望", "グローバル市場分析"
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
channel_probs <- c(0.32, 0.19, 0.20, 0.19, 0.10)
|
| 86 |
+
|
| 87 |
+
tl_data <- data.frame(
|
| 88 |
+
session_id = paste0("S", sprintf("%04d", 1:n_sessions)),
|
| 89 |
+
channel = sample(channels, n_sessions, replace = TRUE, prob = channel_probs),
|
| 90 |
+
report = sample(reports, n_sessions, replace = TRUE),
|
| 91 |
+
duration_sec = NA,
|
| 92 |
+
scrolled = NA,
|
| 93 |
+
pages_viewed = NA
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
# チャネル別の行動パターン
|
| 97 |
+
for (i in 1:nrow(tl_data)) {
|
| 98 |
+
ch <- tl_data$channel[i]
|
| 99 |
+
if (ch == "Referral") {
|
| 100 |
+
tl_data$duration_sec[i] <- round(rnorm(1, 130, 40))
|
| 101 |
+
tl_data$scrolled[i] <- runif(1) < 0.85
|
| 102 |
+
tl_data$pages_viewed[i] <- rpois(1, 3) + 1
|
| 103 |
+
} else if (ch == "Organic") {
|
| 104 |
+
tl_data$duration_sec[i] <- round(rnorm(1, 105, 35))
|
| 105 |
+
tl_data$scrolled[i] <- runif(1) < 0.75
|
| 106 |
+
tl_data$pages_viewed[i] <- rpois(1, 2.5) + 1
|
| 107 |
+
} else if (ch == "Direct") {
|
| 108 |
+
tl_data$duration_sec[i] <- round(rnorm(1, 70, 30))
|
| 109 |
+
tl_data$scrolled[i] <- runif(1) < 0.60
|
| 110 |
+
tl_data$pages_viewed[i] <- rpois(1, 2) + 1
|
| 111 |
+
} else if (ch == "Social") {
|
| 112 |
+
tl_data$duration_sec[i] <- round(rnorm(1, 65, 35))
|
| 113 |
+
tl_data$scrolled[i] <- runif(1) < 0.50
|
| 114 |
+
tl_data$pages_viewed[i] <- rpois(1, 1.5) + 1
|
| 115 |
+
} else { # Paid
|
| 116 |
+
tl_data$duration_sec[i] <- round(rnorm(1, 35, 20))
|
| 117 |
+
tl_data$scrolled[i] <- runif(1) < 0.30
|
| 118 |
+
tl_data$pages_viewed[i] <- rpois(1, 1) + 1
|
| 119 |
+
}
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
tl_data$duration_sec <- pmax(tl_data$duration_sec, 5)
|
| 123 |
+
|
| 124 |
+
# TL成功判定: 滞在60秒以上 + スクロール + 2ページ以上閲覧
|
| 125 |
+
tl_data$is_tl_success <- with(tl_data,
|
| 126 |
+
duration_sec >= 60 & scrolled == TRUE & pages_viewed >= 2
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
tl_data
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
# データ生成
|
| 133 |
+
branding_data <- create_branding_data()
|
| 134 |
+
regional_data <- create_regional_data()
|
| 135 |
+
tl_data <- create_tl_data()
|
| 136 |
+
|
| 137 |
+
# ============================================================================
|
| 138 |
+
# UI定義
|
| 139 |
+
# ============================================================================
|
| 140 |
+
|
| 141 |
+
ui <- dashboardPage(
|
| 142 |
+
skin = "blue",
|
| 143 |
+
|
| 144 |
+
dashboardHeader(
|
| 145 |
+
title = tags$span(
|
| 146 |
+
tags$img(src = "", height = "30px", style = "margin-right: 10px;"),
|
| 147 |
+
"Quality Growth Dashboard"
|
| 148 |
),
|
| 149 |
+
titleWidth = 350
|
|
|
|
|
|
|
|
|
|
| 150 |
),
|
| 151 |
+
|
| 152 |
+
dashboardSidebar(
|
| 153 |
+
width = 280,
|
| 154 |
+
sidebarMenu(
|
| 155 |
+
id = "sidebar",
|
| 156 |
+
menuItem("Executive Summary", tabName = "summary", icon = icon("chart-line")),
|
| 157 |
+
menuItem("海外ブランディング分析", tabName = "branding", icon = icon("globe")),
|
| 158 |
+
menuItem("地域別ROI分析", tabName = "regional", icon = icon("map")),
|
| 159 |
+
menuItem("ソートリーダーシップ", tabName = "thought_leadership", icon = icon("lightbulb")),
|
| 160 |
+
menuItem("統計モデル", tabName = "models", icon = icon("calculator")),
|
| 161 |
+
|
| 162 |
+
hr(),
|
| 163 |
+
|
| 164 |
+
tags$div(
|
| 165 |
+
style = "padding: 15px; color: #b8c7ce; font-size: 12px;",
|
| 166 |
+
tags$p(tags$strong("プロジェクト情報")),
|
| 167 |
+
tags$p("Client: NTT DATA Corporation"),
|
| 168 |
+
tags$p("期間: 2026年1月19日〜3月15日")
|
| 169 |
+
)
|
| 170 |
+
)
|
| 171 |
+
),
|
| 172 |
+
|
| 173 |
+
dashboardBody(
|
| 174 |
+
tags$head(
|
| 175 |
+
tags$style(HTML("
|
| 176 |
+
.content-wrapper { background-color: #f4f6f9; }
|
| 177 |
+
.box { border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
| 178 |
+
.box-header { border-bottom: 2px solid #3c8dbc; }
|
| 179 |
+
.info-box { border-radius: 8px; }
|
| 180 |
+
.small-box { border-radius: 8px; }
|
| 181 |
+
.nav-tabs-custom > .nav-tabs > li.active { border-top-color: #3c8dbc; }
|
| 182 |
+
.skin-blue .main-header .logo { background-color: #1a3a5c; }
|
| 183 |
+
.skin-blue .main-header .navbar { background-color: #1a3a5c; }
|
| 184 |
+
.metric-card {
|
| 185 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 186 |
+
border-radius: 12px;
|
| 187 |
+
padding: 20px;
|
| 188 |
+
color: white;
|
| 189 |
+
margin-bottom: 15px;
|
| 190 |
+
}
|
| 191 |
+
.metric-card h3 { margin: 0; font-size: 2em; }
|
| 192 |
+
.metric-card p { margin: 5px 0 0; opacity: 0.9; }
|
| 193 |
+
"))
|
| 194 |
+
),
|
| 195 |
+
|
| 196 |
+
tabItems(
|
| 197 |
+
# Executive Summary タブ
|
| 198 |
+
tabItem(
|
| 199 |
+
tabName = "summary",
|
| 200 |
+
fluidRow(
|
| 201 |
+
column(12,
|
| 202 |
+
tags$div(
|
| 203 |
+
style = "background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
|
| 204 |
+
padding: 30px; border-radius: 12px; color: white; margin-bottom: 20px;",
|
| 205 |
+
tags$h2("グローバルマーケティング戦略及びソートリーダーシップの高度化",
|
| 206 |
+
style = "margin-top: 0;"),
|
| 207 |
+
tags$p("データドリブンな経営ナラティブによる「Quality Growth」の実現",
|
| 208 |
+
style = "font-size: 1.2em; opacity: 0.9;")
|
| 209 |
+
)
|
| 210 |
+
)
|
| 211 |
+
),
|
| 212 |
+
|
| 213 |
+
fluidRow(
|
| 214 |
+
valueBoxOutput("total_investment", width = 3),
|
| 215 |
+
valueBoxOutput("avg_roi", width = 3),
|
| 216 |
+
valueBoxOutput("tl_success_rate", width = 3),
|
| 217 |
+
valueBoxOutput("top_region", width = 3)
|
| 218 |
+
),
|
| 219 |
+
|
| 220 |
+
fluidRow(
|
| 221 |
+
box(
|
| 222 |
+
title = "Strategic Context: Quality Growthへの戦略的整合",
|
| 223 |
+
status = "primary", solidHeader = TRUE, width = 6,
|
| 224 |
+
tags$div(
|
| 225 |
+
style = "display: flex; gap: 20px;",
|
| 226 |
+
tags$div(
|
| 227 |
+
style = "flex: 1; padding: 15px; background: #f8f9fa; border-radius: 8px;",
|
| 228 |
+
tags$h4("Current Context (現状)", style = "color: #6c757d;"),
|
| 229 |
+
tags$ul(
|
| 230 |
+
tags$li("Volume Expansion(量的拡大)"),
|
| 231 |
+
tags$li("Technology-focused Context(技術文脈)")
|
| 232 |
+
),
|
| 233 |
+
tags$p("課題:経営層への認知・顧客化に最適化されていない",
|
| 234 |
+
style = "color: #dc3545; font-size: 0.9em;")
|
| 235 |
+
),
|
| 236 |
+
tags$div(
|
| 237 |
+
style = "flex: 1; padding: 15px; background: #e8f4f8; border-radius: 8px;",
|
| 238 |
+
tags$h4("Strategic Pivot (新方針)", style = "color: #007bff;"),
|
| 239 |
+
tags$ul(
|
| 240 |
+
tags$li("Quality Growth(質的成長)"),
|
| 241 |
+
tags$li("Management-focused Narrative(経営ナラティブ)"),
|
| 242 |
+
tags$li("Key Themes: Powered & Next Gen Infrastructure")
|
| 243 |
+
)
|
| 244 |
+
)
|
| 245 |
+
)
|
| 246 |
+
),
|
| 247 |
+
|
| 248 |
+
box(
|
| 249 |
+
title = "Project Scope: 2つの並行ワークストリーム",
|
| 250 |
+
status = "primary", solidHeader = TRUE, width = 6,
|
| 251 |
+
tags$div(
|
| 252 |
+
style = "display: flex; gap: 20px;",
|
| 253 |
+
tags$div(
|
| 254 |
+
style = "flex: 1; padding: 15px; background: #fff3cd; border-radius: 8px;",
|
| 255 |
+
tags$h4("Region A: 思想リーダーシップ", style = "color: #856404;"),
|
| 256 |
+
tags$p("Focus: 基準の再定義と経営ナラティブ"),
|
| 257 |
+
tags$ul(
|
| 258 |
+
tags$li("現状分析(回遊ログ・アクセス解析)"),
|
| 259 |
+
tags$li("競合ベンチマーク調査"),
|
| 260 |
+
tags$li("「質」を測る新KPIの定義")
|
| 261 |
+
)
|
| 262 |
+
),
|
| 263 |
+
tags$div(
|
| 264 |
+
style = "flex: 1; padding: 15px; background: #d4edda; border-radius: 8px;",
|
| 265 |
+
tags$h4("Region B: グローバルマーケティング", style = "color: #155724;"),
|
| 266 |
+
tags$p("Focus: 標準化と投資対効果(ROI)"),
|
| 267 |
+
tags$ul(
|
| 268 |
+
tags$li("拠点間横比較(市場規模 vs 成長率)"),
|
| 269 |
+
tags$li("ROI分析と投資効率の可視化"),
|
| 270 |
+
tags$li("レポートフォーマットの標準化")
|
| 271 |
+
)
|
| 272 |
+
)
|
| 273 |
+
)
|
| 274 |
+
)
|
| 275 |
+
),
|
| 276 |
+
|
| 277 |
+
fluidRow(
|
| 278 |
+
box(
|
| 279 |
+
title = "Schedule & Roadmap",
|
| 280 |
+
status = "info", solidHeader = TRUE, width = 12,
|
| 281 |
+
plotlyOutput("timeline_chart", height = "250px")
|
| 282 |
+
)
|
| 283 |
)
|
| 284 |
+
),
|
| 285 |
+
|
| 286 |
+
# 海外ブランディング分析 タブ
|
| 287 |
+
tabItem(
|
| 288 |
+
tabName = "branding",
|
| 289 |
+
fluidRow(
|
| 290 |
+
box(
|
| 291 |
+
title = "海外ブランディング指標の推移(24ヶ月)",
|
| 292 |
+
status = "primary", solidHeader = TRUE, width = 12,
|
| 293 |
+
plotlyOutput("branding_timeseries", height = "400px")
|
| 294 |
+
)
|
| 295 |
+
),
|
| 296 |
+
|
| 297 |
+
fluidRow(
|
| 298 |
+
box(
|
| 299 |
+
title = "相互相関分析: Spend vs Revenue",
|
| 300 |
+
status = "info", solidHeader = TRUE, width = 6,
|
| 301 |
+
plotOutput("ccf_plot", height = "300px"),
|
| 302 |
+
tags$p("タイムラグの特定:予算投下から効果が最大化するまでの期間を可視化",
|
| 303 |
+
style = "font-size: 0.9em; color: #6c757d;")
|
| 304 |
+
),
|
| 305 |
+
|
| 306 |
+
box(
|
| 307 |
+
title = "回帰分析結果",
|
| 308 |
+
status = "info", solidHeader = TRUE, width = 6,
|
| 309 |
+
verbatimTextOutput("regression_summary"),
|
| 310 |
+
tags$div(
|
| 311 |
+
style = "margin-top: 15px; padding: 10px; background: #e8f4f8; border-radius: 8px;",
|
| 312 |
+
tags$h5("解釈ポイント:"),
|
| 313 |
+
tags$ul(
|
| 314 |
+
tags$li("lag(Spend, 2)の係数: 2ヶ月前の投資が現在の売上に寄与"),
|
| 315 |
+
tags$li("lag(Search, 1)の係数: 指名検索数が売上をドライブ"),
|
| 316 |
+
tags$li("R-squared: モデルの説明力")
|
| 317 |
+
)
|
| 318 |
+
)
|
| 319 |
+
)
|
| 320 |
+
),
|
| 321 |
+
|
| 322 |
+
fluidRow(
|
| 323 |
+
box(
|
| 324 |
+
title = "投資効率サマリー",
|
| 325 |
+
status = "success", solidHeader = TRUE, width = 12,
|
| 326 |
+
fluidRow(
|
| 327 |
+
column(4,
|
| 328 |
+
tags$div(class = "metric-card",
|
| 329 |
+
tags$h3(textOutput("total_spend_text")),
|
| 330 |
+
tags$p("総投資額")
|
| 331 |
+
)
|
| 332 |
+
),
|
| 333 |
+
column(4,
|
| 334 |
+
tags$div(class = "metric-card", style = "background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);",
|
| 335 |
+
tags$h3(textOutput("total_revenue_text")),
|
| 336 |
+
tags$p("総売上額")
|
| 337 |
+
)
|
| 338 |
+
),
|
| 339 |
+
column(4,
|
| 340 |
+
tags$div(class = "metric-card", style = "background: linear-gradient(135deg, #ee0979 0%, #ff6a00 100%);",
|
| 341 |
+
tags$h3(textOutput("simple_roi_text")),
|
| 342 |
+
tags$p("単純ROI")
|
| 343 |
+
)
|
| 344 |
+
)
|
| 345 |
+
)
|
| 346 |
+
)
|
| 347 |
)
|
| 348 |
+
),
|
| 349 |
+
|
| 350 |
+
# 地域別ROI分析 タブ
|
| 351 |
+
tabItem(
|
| 352 |
+
tabName = "regional",
|
| 353 |
+
fluidRow(
|
| 354 |
+
box(
|
| 355 |
+
title = "地域別ROI比較",
|
| 356 |
+
status = "primary", solidHeader = TRUE, width = 6,
|
| 357 |
+
plotlyOutput("regional_roi_chart", height = "350px")
|
| 358 |
+
),
|
| 359 |
+
|
| 360 |
+
box(
|
| 361 |
+
title = "Brand Search vs Revenue",
|
| 362 |
+
status = "primary", solidHeader = TRUE, width = 6,
|
| 363 |
+
plotlyOutput("search_revenue_scatter", height = "350px")
|
| 364 |
+
)
|
| 365 |
+
),
|
| 366 |
+
|
| 367 |
+
fluidRow(
|
| 368 |
+
box(
|
| 369 |
+
title = "定性データ: ブランド認知スコア(0-100)",
|
| 370 |
+
status = "info", solidHeader = TRUE, width = 12,
|
| 371 |
+
plotlyOutput("qualitative_heatmap", height = "300px"),
|
| 372 |
+
tags$div(
|
| 373 |
+
style = "margin-top: 15px; padding: 10px; background: #fff3cd; border-radius: 8px;",
|
| 374 |
+
tags$h5("地域別インサイト:"),
|
| 375 |
+
tags$ul(
|
| 376 |
+
tags$li(tags$strong("Asia:"), " 技術力(Technology)が高評価 → 最新技術をアピール"),
|
| 377 |
+
tags$li(tags$strong("Europe:"), " 信頼度(Trust)が重要 → 認証取得・伝統の訴求"),
|
| 378 |
+
tags$li(tags$strong("US:"), " デザイン(Design)が先行指標 → クリエイティブ重視")
|
| 379 |
+
)
|
| 380 |
+
)
|
| 381 |
+
)
|
| 382 |
+
),
|
| 383 |
+
|
| 384 |
+
fluidRow(
|
| 385 |
+
box(
|
| 386 |
+
title = "地域別詳細データ",
|
| 387 |
+
status = "success", solidHeader = TRUE, width = 12,
|
| 388 |
+
DTOutput("regional_table")
|
| 389 |
+
)
|
| 390 |
+
)
|
| 391 |
+
),
|
| 392 |
+
|
| 393 |
+
# ソートリーダーシップ タブ
|
| 394 |
+
tabItem(
|
| 395 |
+
tabName = "thought_leadership",
|
| 396 |
+
fluidRow(
|
| 397 |
+
box(
|
| 398 |
+
title = "チャネル別TL成功率",
|
| 399 |
+
status = "primary", solidHeader = TRUE, width = 6,
|
| 400 |
+
plotlyOutput("tl_channel_chart", height = "350px"),
|
| 401 |
+
tags$p("TL成功条件: 滞在60秒以上 + スクロール発生 + 2ページ以上閲覧",
|
| 402 |
+
style = "font-size: 0.9em; color: #6c757d;")
|
| 403 |
+
),
|
| 404 |
+
|
| 405 |
+
box(
|
| 406 |
+
title = "コンテンツ別パフォーマンス",
|
| 407 |
+
status = "primary", solidHeader = TRUE, width = 6,
|
| 408 |
+
plotlyOutput("content_performance", height = "350px")
|
| 409 |
+
)
|
| 410 |
+
),
|
| 411 |
+
|
| 412 |
+
fluidRow(
|
| 413 |
+
box(
|
| 414 |
+
title = "チャネル別統計サマリー",
|
| 415 |
+
status = "info", solidHeader = TRUE, width = 12,
|
| 416 |
+
DTOutput("tl_summary_table")
|
| 417 |
+
)
|
| 418 |
+
),
|
| 419 |
+
|
| 420 |
+
fluidRow(
|
| 421 |
+
box(
|
| 422 |
+
title = "流入チャネルの「質」の評価",
|
| 423 |
+
status = "warning", solidHeader = TRUE, width = 6,
|
| 424 |
+
tags$div(
|
| 425 |
+
style = "padding: 15px;",
|
| 426 |
+
tags$h5("Referral / Organicの成功率が高い場合:"),
|
| 427 |
+
tags$p("外部メディアでの紹介や検索意図が、レポートの内容と合致しており、信頼獲得に成功している。"),
|
| 428 |
+
tags$hr(),
|
| 429 |
+
tags$h5("Paid / Socialの成功率が低い場合:"),
|
| 430 |
+
tags$p("「タイトル釣り」などで流入は稼げているが、中身が期待に沿っていない、あるいは「ながら読み」層が多い可能性がある。")
|
| 431 |
+
)
|
| 432 |
+
),
|
| 433 |
+
|
| 434 |
+
box(
|
| 435 |
+
title = "次のアクション提案",
|
| 436 |
+
status = "success", solidHeader = TRUE, width = 6,
|
| 437 |
+
tags$div(
|
| 438 |
+
style = "padding: 15px;",
|
| 439 |
+
tags$ul(
|
| 440 |
+
tags$li("スター・コンテンツ(High PV, High Success)の横展開"),
|
| 441 |
+
tags$li("ニッチ・リーダーシップコンテンツの広報強化"),
|
| 442 |
+
tags$li("Low Successコンテンツのトピック・ターゲット見直し"),
|
| 443 |
+
tags$li("GA4実データへの移行とスコアリングカスタマイズ")
|
| 444 |
+
)
|
| 445 |
+
)
|
| 446 |
+
)
|
| 447 |
+
)
|
| 448 |
+
),
|
| 449 |
+
|
| 450 |
+
# 統計モデル タブ
|
| 451 |
+
tabItem(
|
| 452 |
+
tabName = "models",
|
| 453 |
+
fluidRow(
|
| 454 |
+
box(
|
| 455 |
+
title = "Analytical Methodology: 統計的アプローチの採用",
|
| 456 |
+
status = "primary", solidHeader = TRUE, width = 12,
|
| 457 |
+
tags$div(
|
| 458 |
+
style = "display: flex; gap: 20px; flex-wrap: wrap;",
|
| 459 |
+
tags$div(
|
| 460 |
+
style = "flex: 1; min-width: 250px; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; color: white;",
|
| 461 |
+
tags$h4("Regression Analysis(回帰分析)"),
|
| 462 |
+
tags$p("成功に寄与している変数を統計的に切り分け、投資効率を算出する。")
|
| 463 |
+
),
|
| 464 |
+
tags$div(
|
| 465 |
+
style = "flex: 1; min-width: 250px; padding: 20px; background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); border-radius: 12px; color: white;",
|
| 466 |
+
tags$h4("Weighted Average Models(加重平均モデル)"),
|
| 467 |
+
tags$p("各国の市場環境の違いを考慮に入れた、公平な評価モデルの設計。")
|
| 468 |
+
),
|
| 469 |
+
tags$div(
|
| 470 |
+
style = "flex: 1; min-width: 250px; padding: 20px; background: linear-gradient(135deg, #ee0979 0%, #ff6a00 100%); border-radius: 12px; color: white;",
|
| 471 |
+
tags$h4("Statistical Tools(分析ツール)"),
|
| 472 |
+
tags$p("専門的な統計解析ソフトウェアを活用し、ブラックボックス化しない「説明可能なロジック」を構築。")
|
| 473 |
+
)
|
| 474 |
+
)
|
| 475 |
+
)
|
| 476 |
+
),
|
| 477 |
+
|
| 478 |
+
fluidRow(
|
| 479 |
+
box(
|
| 480 |
+
title = "地域別回帰分析結果",
|
| 481 |
+
status = "info", solidHeader = TRUE, width = 12,
|
| 482 |
+
selectInput("region_select", "地域を選択:",
|
| 483 |
+
choices = c("US", "Europe", "Asia", "LATAM", "MEA"),
|
| 484 |
+
selected = "US"),
|
| 485 |
+
verbatimTextOutput("regional_regression")
|
| 486 |
+
)
|
| 487 |
+
),
|
| 488 |
+
|
| 489 |
+
fluidRow(
|
| 490 |
+
box(
|
| 491 |
+
title = "モデル診断プロット",
|
| 492 |
+
status = "warning", solidHeader = TRUE, width = 6,
|
| 493 |
+
plotOutput("residual_plot", height = "300px")
|
| 494 |
+
),
|
| 495 |
+
|
| 496 |
+
box(
|
| 497 |
+
title = "予測 vs 実績",
|
| 498 |
+
status = "warning", solidHeader = TRUE, width = 6,
|
| 499 |
+
plotlyOutput("predicted_vs_actual", height = "300px")
|
| 500 |
+
)
|
| 501 |
+
)
|
| 502 |
+
)
|
| 503 |
+
)
|
| 504 |
)
|
| 505 |
+
)
|
| 506 |
+
|
| 507 |
+
# ============================================================================
|
| 508 |
+
# Server定義
|
| 509 |
+
# ============================================================================
|
| 510 |
+
|
| 511 |
+
server <- function(input, output, session) {
|
| 512 |
+
|
| 513 |
+
# --- Value Boxes ---
|
| 514 |
+
output$total_investment <- renderValueBox({
|
| 515 |
+
valueBox(
|
| 516 |
+
"¥10,500,000",
|
| 517 |
+
"Total Investment (税別)",
|
| 518 |
+
icon = icon("yen-sign"),
|
| 519 |
+
color = "blue"
|
| 520 |
+
)
|
| 521 |
+
})
|
| 522 |
+
|
| 523 |
+
output$avg_roi <- renderValueBox({
|
| 524 |
+
roi_summary <- regional_data %>%
|
| 525 |
+
group_by(Region) %>%
|
| 526 |
+
summarise(ROI = sum(Revenue) / sum(Spend)) %>%
|
| 527 |
+
summarise(Avg_ROI = mean(ROI))
|
| 528 |
+
|
| 529 |
+
valueBox(
|
| 530 |
+
sprintf("%.2fx", roi_summary$Avg_ROI),
|
| 531 |
+
"平均ROI(全地域)",
|
| 532 |
+
icon = icon("chart-line"),
|
| 533 |
+
color = "green"
|
| 534 |
+
)
|
| 535 |
+
})
|
| 536 |
+
|
| 537 |
+
output$tl_success_rate <- renderValueBox({
|
| 538 |
+
success_rate <- mean(tl_data$is_tl_success) * 100
|
| 539 |
+
valueBox(
|
| 540 |
+
sprintf("%.1f%%", success_rate),
|
| 541 |
+
"TL成功率",
|
| 542 |
+
icon = icon("lightbulb"),
|
| 543 |
+
color = "yellow"
|
| 544 |
+
)
|
| 545 |
+
})
|
| 546 |
+
|
| 547 |
+
output$top_region <- renderValueBox({
|
| 548 |
+
top <- regional_data %>%
|
| 549 |
+
group_by(Region) %>%
|
| 550 |
+
summarise(ROI = sum(Revenue) / sum(Spend)) %>%
|
| 551 |
+
arrange(desc(ROI)) %>%
|
| 552 |
+
slice(1)
|
| 553 |
+
|
| 554 |
+
valueBox(
|
| 555 |
+
top$Region,
|
| 556 |
+
sprintf("Top ROI Region (%.2fx)", top$ROI),
|
| 557 |
+
icon = icon("trophy"),
|
| 558 |
+
color = "purple"
|
| 559 |
+
)
|
| 560 |
+
})
|
| 561 |
+
|
| 562 |
+
# --- Timeline Chart ---
|
| 563 |
+
output$timeline_chart <- renderPlotly({
|
| 564 |
+
phases <- data.frame(
|
| 565 |
+
Phase = c("Phase 1: Mobilization", "Phase 2: Deep Analysis", "Phase 3: Reporting", "Phase 4: Close"),
|
| 566 |
+
Start = as.Date(c("2026-01-19", "2026-02-02", "2026-02-23", "2026-03-02")),
|
| 567 |
+
End = as.Date(c("2026-02-01", "2026-02-22", "2026-03-01", "2026-03-15")),
|
| 568 |
+
Color = c("#3498db", "#e74c3c", "#2ecc71", "#9b59b6")
|
| 569 |
+
)
|
| 570 |
+
|
| 571 |
+
plot_ly() %>%
|
| 572 |
+
add_segments(
|
| 573 |
+
data = phases,
|
| 574 |
+
x = ~Start, xend = ~End,
|
| 575 |
+
y = ~Phase, yend = ~Phase,
|
| 576 |
+
color = ~Phase,
|
| 577 |
+
colors = phases$Color,
|
| 578 |
+
line = list(width = 20),
|
| 579 |
+
showlegend = FALSE
|
| 580 |
+
) %>%
|
| 581 |
+
layout(
|
| 582 |
+
xaxis = list(title = "", tickformat = "%b %d"),
|
| 583 |
+
yaxis = list(title = "", categoryorder = "array",
|
| 584 |
+
categoryarray = rev(phases$Phase)),
|
| 585 |
+
margin = list(l = 150)
|
| 586 |
+
)
|
| 587 |
+
})
|
| 588 |
+
|
| 589 |
+
# --- Branding Time Series ---
|
| 590 |
+
output$branding_timeseries <- renderPlotly({
|
| 591 |
+
df_long <- branding_data %>%
|
| 592 |
+
pivot_longer(cols = c(Spend, Search, Revenue),
|
| 593 |
+
names_to = "Metric", values_to = "Value")
|
| 594 |
+
|
| 595 |
+
plot_ly(df_long, x = ~Date, y = ~Value, color = ~Metric,
|
| 596 |
+
type = 'scatter', mode = 'lines+markers',
|
| 597 |
+
colors = c("Revenue" = "#2ecc71", "Search" = "#e74c3c", "Spend" = "#3498db")) %>%
|
| 598 |
+
layout(
|
| 599 |
+
xaxis = list(title = ""),
|
| 600 |
+
yaxis = list(title = "Value (Standardized Units)"),
|
| 601 |
+
legend = list(orientation = "h", y = -0.1),
|
| 602 |
+
hovermode = "x unified"
|
| 603 |
+
)
|
| 604 |
+
})
|
| 605 |
+
|
| 606 |
+
# --- CCF Plot ---
|
| 607 |
+
output$ccf_plot <- renderPlot({
|
| 608 |
+
ccf_result <- ccf(branding_data$Spend, branding_data$Revenue,
|
| 609 |
+
lag.max = 10, plot = FALSE)
|
| 610 |
+
|
| 611 |
+
df_ccf <- data.frame(
|
| 612 |
+
Lag = ccf_result$lag,
|
| 613 |
+
ACF = ccf_result$acf
|
| 614 |
+
)
|
| 615 |
+
|
| 616 |
+
ggplot(df_ccf, aes(x = Lag, y = ACF)) +
|
| 617 |
+
geom_hline(yintercept = 0, color = "gray50") +
|
| 618 |
+
geom_hline(yintercept = c(-0.4, 0.4), linetype = "dashed", color = "blue", alpha = 0.5) +
|
| 619 |
+
geom_segment(aes(xend = Lag, yend = 0), color = "#3498db", size = 1) +
|
| 620 |
+
geom_point(color = "#3498db", size = 3) +
|
| 621 |
+
labs(title = "Cross-Correlation: Spend vs Revenue",
|
| 622 |
+
x = "Lag (months)", y = "ACF") +
|
| 623 |
+
theme_minimal(base_size = 14) +
|
| 624 |
+
theme(plot.title = element_text(hjust = 0.5, face = "bold"))
|
| 625 |
+
})
|
| 626 |
+
|
| 627 |
+
# --- Regression Summary ---
|
| 628 |
+
output$regression_summary <- renderPrint({
|
| 629 |
+
df <- branding_data
|
| 630 |
+
model <- lm(Revenue ~ lag(Spend, 2) + lag(Search, 1), data = df)
|
| 631 |
+
summary(model)
|
| 632 |
+
})
|
| 633 |
+
|
| 634 |
+
# --- Metric Cards ---
|
| 635 |
+
output$total_spend_text <- renderText({
|
| 636 |
+
paste0("¥", format(sum(branding_data$Spend) * 1000, big.mark = ","))
|
| 637 |
+
})
|
| 638 |
+
|
| 639 |
+
output$total_revenue_text <- renderText({
|
| 640 |
+
paste0("¥", format(sum(branding_data$Revenue) * 1000, big.mark = ","))
|
| 641 |
+
})
|
| 642 |
+
|
| 643 |
+
output$simple_roi_text <- renderText({
|
| 644 |
+
roi <- sum(branding_data$Revenue) / sum(branding_data$Spend)
|
| 645 |
+
sprintf("%.2fx", roi)
|
| 646 |
+
})
|
| 647 |
+
|
| 648 |
+
# --- Regional ROI Chart ---
|
| 649 |
+
output$regional_roi_chart <- renderPlotly({
|
| 650 |
+
roi_data <- regional_data %>%
|
| 651 |
+
group_by(Region) %>%
|
| 652 |
+
summarise(ROI = sum(Revenue) / sum(Spend)) %>%
|
| 653 |
+
arrange(desc(ROI))
|
| 654 |
+
|
| 655 |
+
plot_ly(roi_data, x = ~reorder(Region, ROI), y = ~ROI, type = 'bar',
|
| 656 |
+
marker = list(color = c("#2ecc71", "#3498db", "#e74c3c", "#9b59b6", "#f39c12"))) %>%
|
| 657 |
+
layout(
|
| 658 |
+
xaxis = list(title = "Region"),
|
| 659 |
+
yaxis = list(title = "ROI (Revenue / Spend)"),
|
| 660 |
+
title = list(text = "Revenue per 1 Unit of Spend")
|
| 661 |
+
)
|
| 662 |
+
})
|
| 663 |
+
|
| 664 |
+
# --- Search vs Revenue Scatter ---
|
| 665 |
+
output$search_revenue_scatter <- renderPlotly({
|
| 666 |
+
agg_data <- regional_data %>%
|
| 667 |
+
group_by(Region) %>%
|
| 668 |
+
summarise(
|
| 669 |
+
Avg_Search = mean(Search),
|
| 670 |
+
Avg_Revenue = mean(Revenue)
|
| 671 |
+
)
|
| 672 |
+
|
| 673 |
+
plot_ly(agg_data, x = ~Avg_Search, y = ~Avg_Revenue, color = ~Region,
|
| 674 |
+
type = 'scatter', mode = 'markers',
|
| 675 |
+
marker = list(size = 15),
|
| 676 |
+
text = ~Region) %>%
|
| 677 |
+
layout(
|
| 678 |
+
xaxis = list(title = "Brand Search Volume (Avg)"),
|
| 679 |
+
yaxis = list(title = "Revenue (Avg)")
|
| 680 |
+
)
|
| 681 |
+
})
|
| 682 |
+
|
| 683 |
+
# --- Qualitative Heatmap ---
|
| 684 |
+
output$qualitative_heatmap <- renderPlotly({
|
| 685 |
+
qual_data <- regional_data %>%
|
| 686 |
+
group_by(Region) %>%
|
| 687 |
+
summarise(
|
| 688 |
+
Technology = mean(Tech_Score),
|
| 689 |
+
Design = mean(Design_Score),
|
| 690 |
+
Trust = mean(Trust_Score)
|
| 691 |
+
) %>%
|
| 692 |
+
pivot_longer(cols = c(Technology, Design, Trust),
|
| 693 |
+
names_to = "Attribute", values_to = "Score")
|
| 694 |
+
|
| 695 |
+
plot_ly(qual_data, x = ~Attribute, y = ~Region, z = ~Score,
|
| 696 |
+
type = 'heatmap',
|
| 697 |
+
colorscale = list(c(0, "#f7fbff"), c(0.5, "#6baed6"), c(1, "#08306b")),
|
| 698 |
+
text = ~round(Score, 1),
|
| 699 |
+
texttemplate = "%{text}",
|
| 700 |
+
showscale = TRUE) %>%
|
| 701 |
+
layout(
|
| 702 |
+
xaxis = list(title = ""),
|
| 703 |
+
yaxis = list(title = "")
|
| 704 |
+
)
|
| 705 |
+
})
|
| 706 |
+
|
| 707 |
+
# --- Regional Table ---
|
| 708 |
+
output$regional_table <- renderDT({
|
| 709 |
+
summary_data <- regional_data %>%
|
| 710 |
+
group_by(Region) %>%
|
| 711 |
+
summarise(
|
| 712 |
+
`Total Spend` = sum(Spend),
|
| 713 |
+
`Total Revenue` = sum(Revenue),
|
| 714 |
+
`ROI` = round(sum(Revenue) / sum(Spend), 2),
|
| 715 |
+
`Avg Tech Score` = round(mean(Tech_Score), 1),
|
| 716 |
+
`Avg Design Score` = round(mean(Design_Score), 1),
|
| 717 |
+
`Avg Trust Score` = round(mean(Trust_Score), 1)
|
| 718 |
+
)
|
| 719 |
+
|
| 720 |
+
datatable(summary_data,
|
| 721 |
+
options = list(pageLength = 10, dom = 't'),
|
| 722 |
+
rownames = FALSE) %>%
|
| 723 |
+
formatCurrency(c('Total Spend', 'Total Revenue'), currency = "¥", digits = 0)
|
| 724 |
+
})
|
| 725 |
+
|
| 726 |
+
# --- TL Channel Chart ---
|
| 727 |
+
output$tl_channel_chart <- renderPlotly({
|
| 728 |
+
channel_summary <- tl_data %>%
|
| 729 |
+
group_by(channel) %>%
|
| 730 |
+
summarise(
|
| 731 |
+
sessions = n(),
|
| 732 |
+
success_count = sum(is_tl_success),
|
| 733 |
+
success_rate = mean(is_tl_success)
|
| 734 |
+
) %>%
|
| 735 |
+
arrange(desc(success_rate))
|
| 736 |
+
|
| 737 |
+
plot_ly(channel_summary, y = ~reorder(channel, success_rate), x = ~success_rate,
|
| 738 |
+
type = 'bar', orientation = 'h',
|
| 739 |
+
marker = list(color = c("#00bcd4", "#8bc34a", "#ff9800", "#e91e63", "#9c27b0"))) %>%
|
| 740 |
+
layout(
|
| 741 |
+
xaxis = list(title = "Success Rate", tickformat = ".0%"),
|
| 742 |
+
yaxis = list(title = "")
|
| 743 |
+
)
|
| 744 |
+
})
|
| 745 |
+
|
| 746 |
+
# --- Content Performance ---
|
| 747 |
+
output$content_performance <- renderPlotly({
|
| 748 |
+
content_summary <- tl_data %>%
|
| 749 |
+
group_by(report) %>%
|
| 750 |
+
summarise(
|
| 751 |
+
sessions = n(),
|
| 752 |
+
success_rate = mean(is_tl_success),
|
| 753 |
+
avg_duration = mean(duration_sec)
|
| 754 |
+
)
|
| 755 |
+
|
| 756 |
+
plot_ly(content_summary, x = ~sessions, y = ~success_rate,
|
| 757 |
+
size = ~avg_duration, color = ~report,
|
| 758 |
+
type = 'scatter', mode = 'markers',
|
| 759 |
+
marker = list(sizemode = 'diameter', sizeref = 2),
|
| 760 |
+
text = ~paste(report, "<br>Sessions:", sessions,
|
| 761 |
+
"<br>Success Rate:", scales::percent(success_rate),
|
| 762 |
+
"<br>Avg Duration:", round(avg_duration), "sec")) %>%
|
| 763 |
+
layout(
|
| 764 |
+
xaxis = list(title = "Sessions (PV)"),
|
| 765 |
+
yaxis = list(title = "TL Success Rate", tickformat = ".0%")
|
| 766 |
+
)
|
| 767 |
+
})
|
| 768 |
+
|
| 769 |
+
# --- TL Summary Table ---
|
| 770 |
+
output$tl_summary_table <- renderDT({
|
| 771 |
+
tl_data %>%
|
| 772 |
+
group_by(channel) %>%
|
| 773 |
+
summarise(
|
| 774 |
+
`Sessions` = n(),
|
| 775 |
+
`Success Count` = sum(is_tl_success),
|
| 776 |
+
`Success Rate` = scales::percent(mean(is_tl_success), accuracy = 0.1),
|
| 777 |
+
`Avg Duration (sec)` = round(mean(duration_sec), 1),
|
| 778 |
+
`Avg Pages Viewed` = round(mean(pages_viewed), 2)
|
| 779 |
+
) %>%
|
| 780 |
+
arrange(desc(`Success Count`)) %>%
|
| 781 |
+
datatable(options = list(pageLength = 10, dom = 't'), rownames = FALSE)
|
| 782 |
+
})
|
| 783 |
+
|
| 784 |
+
# --- Regional Regression ---
|
| 785 |
+
output$regional_regression <- renderPrint({
|
| 786 |
+
region_df <- regional_data %>% filter(Region == input$region_select)
|
| 787 |
+
model <- lm(Revenue ~ Tech_Score + Design_Score + Trust_Score, data = region_df)
|
| 788 |
+
summary(model)
|
| 789 |
+
})
|
| 790 |
+
|
| 791 |
+
# --- Residual Plot ---
|
| 792 |
+
output$residual_plot <- renderPlot({
|
| 793 |
+
df <- branding_data %>% filter(!is.na(lag(Spend, 2)) & !is.na(lag(Search, 1)))
|
| 794 |
+
model <- lm(Revenue ~ lag(Spend, 2) + lag(Search, 1), data = branding_data)
|
| 795 |
+
|
| 796 |
+
residuals_df <- data.frame(
|
| 797 |
+
Fitted = fitted(model),
|
| 798 |
+
Residuals = residuals(model)
|
| 799 |
+
)
|
| 800 |
+
|
| 801 |
+
ggplot(residuals_df, aes(x = Fitted, y = Residuals)) +
|
| 802 |
+
geom_point(color = "#3498db", size = 3, alpha = 0.7) +
|
| 803 |
+
geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
|
| 804 |
+
geom_smooth(method = "loess", se = FALSE, color = "#e74c3c") +
|
| 805 |
+
labs(title = "Residuals vs Fitted", x = "Fitted Values", y = "Residuals") +
|
| 806 |
+
theme_minimal(base_size = 14)
|
| 807 |
+
})
|
| 808 |
+
|
| 809 |
+
# --- Predicted vs Actual ---
|
| 810 |
+
output$predicted_vs_actual <- renderPlotly({
|
| 811 |
+
model <- lm(Revenue ~ lag(Spend, 2) + lag(Search, 1), data = branding_data)
|
| 812 |
+
|
| 813 |
+
pred_df <- data.frame(
|
| 814 |
+
Actual = branding_data$Revenue[3:24],
|
| 815 |
+
Predicted = predict(model, branding_data)[3:24]
|
| 816 |
+
)
|
| 817 |
+
|
| 818 |
+
plot_ly(pred_df) %>%
|
| 819 |
+
add_trace(x = ~Actual, y = ~Predicted, type = 'scatter', mode = 'markers',
|
| 820 |
+
marker = list(color = "#3498db", size = 10),
|
| 821 |
+
name = "Data Points") %>%
|
| 822 |
+
add_trace(x = c(min(pred_df$Actual), max(pred_df$Actual)),
|
| 823 |
+
y = c(min(pred_df$Actual), max(pred_df$Actual)),
|
| 824 |
+
type = 'scatter', mode = 'lines',
|
| 825 |
+
line = list(color = "red", dash = "dash"),
|
| 826 |
+
name = "Perfect Fit") %>%
|
| 827 |
+
layout(
|
| 828 |
+
xaxis = list(title = "Actual Revenue"),
|
| 829 |
+
yaxis = list(title = "Predicted Revenue")
|
| 830 |
+
)
|
| 831 |
+
})
|
| 832 |
}
|
| 833 |
|
| 834 |
+
# ============================================================================
|
| 835 |
+
# アプリ起動
|
| 836 |
+
# ============================================================================
|
| 837 |
+
|
| 838 |
+
shinyApp(ui = ui, server = server)
|