sugitora commited on
Commit
ec8bf77
·
verified ·
1 Parent(s): bc225f1

Upload 2 files

Browse files
Files changed (2) hide show
  1. Dockerfile +54 -11
  2. app.R +828 -48
Dockerfile CHANGED
@@ -1,14 +1,57 @@
1
- FROM rocker/r-base:latest
 
2
 
3
- WORKDIR /code
4
 
5
- RUN install2.r --error \
6
- shiny \
7
- dplyr \
8
- ggplot2 \
9
- readr \
10
- ggExtra
11
-
12
- COPY . .
 
 
 
 
 
 
13
 
14
- CMD ["R", "--quiet", "-e", "shiny::runApp(host='0.0.0.0', port=7860)"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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(bslib)
3
- library(dplyr)
4
  library(ggplot2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- df <- readr::read_csv("penguins.csv")
7
- # Find subset of columns that are suitable for scatter plot
8
- df_num <- df |> select(where(is.numeric), -Year)
9
-
10
- ui <- page_sidebar(
11
- theme = bs_theme(bootswatch = "minty"),
12
- title = "Penguins explorer",
13
- sidebar = sidebar(
14
- varSelectInput("xvar", "X variable", df_num, selected = "Bill Length (mm)"),
15
- varSelectInput("yvar", "Y variable", df_num, selected = "Bill Depth (mm)"),
16
- checkboxGroupInput("species", "Filter by species",
17
- choices = unique(df$Species), selected = unique(df$Species)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  ),
19
- hr(), # Add a horizontal rule
20
- checkboxInput("by_species", "Show species", TRUE),
21
- checkboxInput("show_margins", "Show marginal plots", TRUE),
22
- checkboxInput("smooth", "Add smoother"),
23
  ),
24
- plotOutput("scatter")
25
- )
26
-
27
- server <- function(input, output, session) {
28
- subsetted <- reactive({
29
- req(input$species)
30
- df |> filter(Species %in% input$species)
31
- })
32
-
33
- output$scatter <- renderPlot(
34
- {
35
- p <- ggplot(subsetted(), aes(!!input$xvar, !!input$yvar)) +
36
- theme_light() +
37
- list(
38
- theme(legend.position = "bottom"),
39
- if (input$by_species) aes(color = Species),
40
- geom_point(),
41
- if (input$smooth) geom_smooth()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  )
43
-
44
- if (input$show_margins) {
45
- margin_type <- if (input$by_species) "density" else "histogram"
46
- p <- p |> ggExtra::ggMarginal(
47
- type = margin_type, margins = "both",
48
- size = 8, groupColour = input$by_species, groupFill = input$by_species
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  )
50
- }
51
-
52
- p
53
- },
54
- res = 100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
57
 
58
- shinyApp(ui, server)
 
 
 
 
 
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)