sugitora's picture
Update app.R
6a0fbf7 verified
library(shiny)
library(ggplot2)
library(dplyr)
library(tidyr)
library(corrplot)
library(plotly)
library(gridExtra)
ui <- fluidPage(
# タイトルと概要
titlePanel("経営指標分析ダッシュボード"),
# スタイルの設定
tags$head(
tags$style(HTML("
body { font-family: 'Arial', sans-serif; }
.nav-tabs > li > a { color: #333; }
.tab-pane { padding: 20px; }
h3 { color: #2c3e50; margin-top: 20px; }
.well { background-color: #ecf0f1; }
"))
),
# タブの構成
tabsetPanel(
# タブ1: 相関分析
tabPanel("相関分析",
fluidRow(
column(12,
h3("指標間の相関係数(ヒートマップ)"),
p("各ビジネス部門の売上、原価、粗利、営業利益、KPIなど全指標間の相関を表示します。")
)
),
fluidRow(
column(8, plotOutput("correlation_heatmap", height = "600px")),
column(4,
wellPanel(
h4("相関分析の概要"),
verbatimTextOutput("correlation_summary")
)
)
)
),
# タブ2: 回帰分析
tabPanel("回帰分析",
fluidRow(
column(12, h3("KPIと売上・利益の関係分析"))
),
fluidRow(
column(6,
wellPanel(
h4("回帰分析の設定"),
selectInput("x_var", "説明変数(X軸)",
c("整備_車検件数" = "x25",
"整備_整備・板金数" = "x31",
"運送_自社ドライバー数" = "x67",
"運送_他社ドライバー数" = "x73",
"レンタル_レンタル台数" = "x91")),
selectInput("y_var", "被説明変数(Y軸)",
c("整備_売上" = "x2",
"整備_営業利益" = "x9",
"商品_売上" = "x37",
"商品_営業利益" = "x41",
"運送_売上" = "x52",
"運送_営業利益" = "x56",
"レンタル_売上" = "x76",
"レンタル_営業利益" = "x80")),
actionButton("update_regression", "更新", class = "btn-primary")
)
)
),
fluidRow(
column(6, plotlyOutput("regression_scatter")),
column(6,
wellPanel(
h4("回帰分析結果"),
verbatimTextOutput("regression_results")
)
)
)
),
# タブ3: 時系列分析
tabPanel("時系列分析",
fluidRow(
column(12, h3("各部門の営業利益推移"))
),
fluidRow(
column(12, plotlyOutput("timeseries_profit", height = "500px"))
),
fluidRow(
column(12, h3("売上高の推移"))
),
fluidRow(
column(12, plotlyOutput("timeseries_sales", height = "500px"))
),
fluidRow(
column(12, h3("KPI推移"))
),
fluidRow(
column(12, plotlyOutput("timeseries_kpi", height = "500px"))
)
),
# タブ4: 統計サマリー
tabPanel("統計サマリー",
fluidRow(
column(12, h3("基本統計量"))
),
fluidRow(
column(6,
wellPanel(
h4("整備部門"),
verbatimTextOutput("summary_seisetsubi")
)
),
column(6,
wellPanel(
h4("商品部門"),
verbatimTextOutput("summary_shohin")
)
)
),
fluidRow(
column(6,
wellPanel(
h4("運送部門"),
verbatimTextOutput("summary_unsou")
)
),
column(6,
wellPanel(
h4("レンタル部門"),
verbatimTextOutput("summary_rental")
)
)
)
),
# タブ5: 詳細な散布図分析
tabPanel("詳細分析",
fluidRow(
column(12, h3("主要指標の散布図行列"))
),
fluidRow(
column(12,
wellPanel(
h4("分析対象指標の選択(複数選択可)"),
checkboxGroupInput("scatter_vars", "変数を選択",
c("整備_売上" = "x2",
"整備_粗利" = "x7",
"整備_営業利益" = "x9",
"商品_売上" = "x37",
"商品_粗利" = "x39",
"商品_営業利益" = "x41",
"運送_売上" = "x52",
"運送_粗利" = "x54",
"運送_営業利益" = "x56",
"レンタル_売上" = "x76",
"レンタル_粗利" = "x78",
"レンタル_営業利益" = "x80"),
selected = c("x2", "x9", "x37", "x52"))
)
)
),
fluidRow(
column(12, plotOutput("scatter_matrix", height = "800px"))
)
)
)
)
server <- function(input, output, session) {
# データの読み込み
df <- reactive({
# ファイルパスを設定(Docker対応)
possible_paths <- c(
"DummyData.csv", # Docker環境
"SampleData.csv", # ローカル環境
file.path(getwd(), "DummyData.csv"),
file.path(getwd(), "SampleData.csv")
)
for (path in possible_paths) {
if (file.exists(path)) {
# CSVを読み込み
data <- read.csv(path, row.names = 1, check.names = FALSE)
# 年月を列として追加
data <- data %>%
mutate(年月 = rownames(.))
# すべての数値列を明示的に数値型に変換
numeric_cols <- colnames(data)[colnames(data) != "年月"]
for (col in numeric_cols) {
data[[col]] <- as.numeric(as.character(data[[col]]))
}
return(data)
}
}
# ファイルが見つからない場合は警告
showNotification("DummyData.csv または SampleData.csv が見つかりません", type = "error")
return(NULL)
})
# 列名のマッピング(全28列)
get_column_label <- function(col_name) {
labels <- list(
"x2" = "整備_売上",
"x3" = "整備_一般車検",
"x4" = "整備_整備・板金",
"x5" = "整備_その他",
"x6" = "整備_原価",
"x7" = "整備_粗利",
"x8" = "整備_販管費",
"x9" = "整備_営業利益",
"x25" = "整備KPI_車検件数",
"x31" = "整備KPI_整備・板金数",
"x37" = "商品_売上",
"x38" = "商品_原価",
"x39" = "商品_粗利",
"x40" = "商品_販管費",
"x41" = "商品_営業利益",
"x52" = "運送_売上",
"x53" = "運送_原価",
"x54" = "運送_粗利",
"x55" = "運送_販管費",
"x56" = "運送_営業利益",
"x67" = "運送KPI_自社ドライバー数",
"x73" = "運送KPI_他社ドライバー数",
"x76" = "レンタル_売上",
"x77" = "レンタル_原価",
"x78" = "レンタル_粗利",
"x79" = "レンタル_販管費",
"x80" = "レンタル_営業利益",
"x91" = "レンタルKPI_レンタル台数"
)
return(labels[[col_name]] %||% col_name)
}
# タブ1: 相関ヒートマップ
output$correlation_heatmap <- renderPlot({
if (is.null(df())) return(NULL)
tryCatch({
# 数値列だけを選択
numeric_data <- df() %>%
select(-年月) %>%
select_if(is.numeric)
# 完全なケースのみを使用(欠損値やNaN、Infを除外)
numeric_data_clean <- numeric_data[complete.cases(numeric_data), ]
if (nrow(numeric_data_clean) < 3) {
plot(1, 1, type="n", xlab="", ylab="", main="エラー:分析可能なデータが不足しています")
text(1, 1, "欠損値やNaN値が多すぎます", cex=1.5)
return()
}
# 相関行列を計算
corr_matrix <- cor(numeric_data_clean, use = "complete.obs", method = "pearson")
# NaN値を0に置き換え
corr_matrix[is.na(corr_matrix)] <- 0
# ヒートマップを作成
corrplot(corr_matrix, method = "color", type = "full",
tl.cex = 0.7, tl.col = "black",
addCoef.col = "white", number.cex = 0.6,
title = "相関係数ヒートマップ", mar = c(0, 0, 2, 0))
}, error = function(e) {
plot(1, 1, type="n", xlab="", ylab="", main="エラー")
text(1, 1, paste("エラー:", e$message), cex=1.2, col="red")
})
})
# 相関分析の概要
output$correlation_summary <- renderPrint({
if (is.null(df())) return(NULL)
tryCatch({
numeric_data <- df() %>%
select(-年月) %>%
select_if(is.numeric)
numeric_data_clean <- numeric_data[complete.cases(numeric_data), ]
corr_matrix <- cor(numeric_data_clean, use = "complete.obs")
corr_matrix[is.na(corr_matrix)] <- 0
# 相関が強い組み合わせを抽出
upper_tri <- upper.tri(corr_matrix)
corr_pairs <- data.frame(
var1_code = rownames(corr_matrix)[row(corr_matrix)[upper_tri]],
var2_code = colnames(corr_matrix)[col(corr_matrix)[upper_tri]],
correlation = corr_matrix[upper_tri]
) %>%
arrange(desc(abs(correlation))) %>%
head(10)
# 日本語ラベルを追加
corr_pairs$var1_label <- sapply(corr_pairs$var1_code, get_column_label)
corr_pairs$var2_label <- sapply(corr_pairs$var2_code, get_column_label)
cat("相関係数が強い上位10の組み合わせ:\n\n")
for (i in 1:nrow(corr_pairs)) {
cat(sprintf("%s (%s) と %s (%s): %.3f\n",
corr_pairs$var1_label[i],
corr_pairs$var1_code[i],
corr_pairs$var2_label[i],
corr_pairs$var2_code[i],
corr_pairs$correlation[i]))
}
}, error = function(e) {
cat("エラーが発生しました:", e$message)
})
})
# タブ2: 回帰分析
regression_data <- reactive({
if (is.null(df())) return(NULL)
x_var <- input$x_var
y_var <- input$y_var
data_for_reg <- df() %>%
select(年月, !!x_var, !!y_var) %>%
rename(X = !!x_var, Y = !!y_var) %>%
filter(!is.na(X), !is.na(Y), X != 0, is.finite(X), is.finite(Y))
if (nrow(data_for_reg) < 3) {
return(NULL)
}
return(data_for_reg)
}) %>% debounce(500)
output$regression_scatter <- renderPlotly({
if (is.null(regression_data())) return(NULL)
data <- regression_data()
model <- lm(Y ~ X, data = data)
x_label <- get_column_label(input$x_var)
y_label <- get_column_label(input$y_var)
p <- ggplot(data, aes(x = X, y = Y)) +
geom_point(size = 3, color = "steelblue", alpha = 0.6) +
geom_smooth(method = "lm", se = TRUE, color = "red", fill = "red", alpha = 0.2) +
labs(title = paste(y_label, "vs", x_label),
subtitle = sprintf("(%s) vs (%s)", input$y_var, input$x_var),
x = x_label,
y = y_label) +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold"))
ggplotly(p)
})
output$regression_results <- renderPrint({
if (is.null(regression_data())) return(NULL)
data <- regression_data()
model <- lm(Y ~ X, data = data)
x_label <- get_column_label(input$x_var)
y_label <- get_column_label(input$y_var)
cat(sprintf("回帰分析: %s = β₀ + β₁ × %s\n", y_label, x_label))
cat(sprintf(" (%s) = β₀ + β₁ × (%s)\n\n", input$y_var, input$x_var))
print(summary(model))
cat("\n相関係数 (r):", format(cor(data$X, data$Y), digits = 4))
cat("\n決定係数 (R²):", format(summary(model)$r.squared, digits = 4))
cat("\n観測数:", nrow(data), "\n")
})
# タブ3: 時系列分析
output$timeseries_profit <- renderPlotly({
if (is.null(df())) return(NULL)
data <- df() %>%
select(年月, x9, x41, x56, x80) %>%
rename("整備営業利益" = x9,
"商品営業利益" = x41,
"運送営業利益" = x56,
"レンタル営業利益" = x80) %>%
gather(key = "部門", value = "営業利益", -年月)
p <- ggplot(data, aes(x = factor(年月, levels = unique(年月)),
y = 営業利益,
color = 部門,
group = 部門)) +
geom_line(size = 1) +
geom_point(size = 2) +
labs(title = "各部門の営業利益推移",
x = "年月",
y = "営業利益(円)",
color = "部門") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
plot.title = element_text(hjust = 0.5, size = 14, face = "bold"))
ggplotly(p)
})
output$timeseries_sales <- renderPlotly({
if (is.null(df())) return(NULL)
data <- df() %>%
select(年月, x2, x37, x52, x76) %>%
rename("整備売上" = x2,
"商品売上" = x37,
"運送売上" = x52,
"レンタル売上" = x76) %>%
gather(key = "部門", value = "売上", -年月)
p <- ggplot(data, aes(x = factor(年月, levels = unique(年月)),
y = 売上,
color = 部門,
group = 部門)) +
geom_line(size = 1) +
geom_point(size = 2) +
labs(title = "各部門の売上高推移",
x = "年月",
y = "売上(円)",
color = "部門") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
plot.title = element_text(hjust = 0.5, size = 14, face = "bold"))
ggplotly(p)
})
output$timeseries_kpi <- renderPlotly({
if (is.null(df())) return(NULL)
data <- df() %>%
select(年月, x25, x31, x67, x73, x91) %>%
rename("車検件数" = x25,
"整備・板金数" = x31,
"自社ドライバー数" = x67,
"他社ドライバー数" = x73,
"レンタル台数" = x91) %>%
gather(key = "KPI", value = "件数", -年月)
p <- ggplot(data, aes(x = factor(年月, levels = unique(年月)),
y = 件数,
color = KPI,
group = KPI)) +
geom_line(size = 1) +
geom_point(size = 2) +
labs(title = "KPI推移",
x = "年月",
y = "件数・台数",
color = "KPI") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
plot.title = element_text(hjust = 0.5, size = 14, face = "bold"))
ggplotly(p)
})
# タブ4: 統計サマリー
output$summary_seisetsubi <- renderPrint({
if (is.null(df())) return(NULL)
cat("整備部門の基本統計量\n")
cat("==================\n\n")
cat("売上:\n")
print(summary(df()$x2))
cat("\n営業利益:\n")
print(summary(df()$x9))
cat("\n車検件数:\n")
print(summary(df()$x25))
cat("\n整備・板金数:\n")
print(summary(df()$x31))
})
output$summary_shohin <- renderPrint({
if (is.null(df())) return(NULL)
cat("商品部門の基本統計量\n")
cat("==================\n\n")
cat("売上:\n")
print(summary(df()$x37))
cat("\n原価:\n")
print(summary(df()$x38))
cat("\n粗利:\n")
print(summary(df()$x39))
cat("\n営業利益:\n")
print(summary(df()$x41))
})
output$summary_unsou <- renderPrint({
if (is.null(df())) return(NULL)
cat("運送部門の基本統計量\n")
cat("==================\n\n")
cat("売上:\n")
print(summary(df()$x52))
cat("\n営業利益:\n")
print(summary(df()$x56))
cat("\n自社ドライバー数:\n")
print(summary(df()$x67))
cat("\n他社ドライバー数:\n")
print(summary(df()$x73))
})
output$summary_rental <- renderPrint({
if (is.null(df())) return(NULL)
cat("レンタル部門の基本統計量\n")
cat("======================\n\n")
cat("売上:\n")
print(summary(df()$x76))
cat("\n原価:\n")
print(summary(df()$x77))
cat("\n粗利:\n")
print(summary(df()$x78))
cat("\n営業利益:\n")
print(summary(df()$x80))
cat("\nレンタル台数:\n")
print(summary(df()$x91))
})
# タブ5: 散布図行列
output$scatter_matrix <- renderPlot({
if (is.null(df())) return(NULL)
tryCatch({
selected_vars <- input$scatter_vars
if (length(selected_vars) == 0) {
plot(1, 1, type="n", xlab="", ylab="", main="変数を選択してください")
text(1, 1, "左側のチェックボックスから分析対象の指標を選択してください", cex=1.2)
return()
}
data <- df() %>%
select(all_of(selected_vars)) %>%
select_if(is.numeric)
# 完全なケースのみを使用
data_clean <- data[complete.cases(data), ]
if (nrow(data_clean) < 3) {
plot(1, 1, type="n", xlab="", ylab="")
text(1, 1, "データが不足しています", cex=1.5)
return()
}
# 列名を日本語に変更
colnames(data_clean) <- sapply(colnames(data_clean), get_column_label)
# 散布図行列を作成
pairs(data_clean,
panel = function(x, y, ...) {
points(x, y, col = "steelblue", cex = 0.7)
abline(lm(y ~ x), col = "red", lty = 2)
},
main = "変数間の散布図行列")
}, error = function(e) {
plot(1, 1, type="n", xlab="", ylab="")
text(1, 1, paste("エラー:", e$message), cex=1.2, col="red")
})
})
}
# アプリの実行
shinyApp(ui = ui, server = server)