このレポートでは、PI Web APIを使用してPIシステムからデータを取得し、Rを用いて可視化する一連の流れを示します。 今回は、以下のタグのデータを取得対象とします: M2TI4405.PV, M2PI4407.PV, M2PI4410W.PV, M2PI4411W.PV, M2FC4403.PV, M2CI2901.CPV
APIリクエストには httr、JSONの解析には
jsonlite、データの整形・可視化には dplyr,
lubridate, ggplot2 を使用します。
library(httr)
library(jsonlite)
library(ggplot2)
library(dplyr)
library(lubridate)
library(tidyr)
library(DT)
PI Web APIへの接続パラメータを設定します。 ※
URL等の設定値はリポジトリ内の設定ファイル
(expressions.tmdl) を参照しています。
# PIシステムの設定
PI_WEB_API_BASE_URL <- "https://hinuta105.ccit.ad.sharedom.net/piwebapi"
PI_SERVER_NAME <- "HINUTA101C"
TAG_NAMES <- c("M2TI4405.PV", "M2PI4407.PV", "M2PI4410W.PV", "M2PI4411W.PV", "M2FC4403.PV", "M2CI2901.CPV")
# 認証設定
# 'auth_config.R' から認証情報を読み込みます
if (file.exists("auth_config.R")) {
source("auth_config.R")
auth_config <- authenticate(AUTH_USER, AUTH_PASS)
} else {
stop("認証設定ファイル 'auth_config.R' が見つかりません。'auth_config_template.R' をコピーして設定してください。")
}
ssl_config <- config(ssl_verifypeer = 0) # 社内サーバー等のためSSL検証をスキップ
APIと対話するためのヘルパー関数を定義します。 1.
get_web_id: タグ名からWebIDを取得する関数 2.
get_interpolated_data:
WebIDを使用して指定期間・周期の補間データ(Interpolated
Data)を取得する関数
get_web_id <- function(tag_name) {
path <- sprintf("\\\\%s\\%s", PI_SERVER_NAME, tag_name)
url <- paste0(PI_WEB_API_BASE_URL, "/points")
response <- GET(url, query = list(path = path), auth_config, ssl_config)
if (status_code(response) != 200) {
warning(paste("WebIDの取得に失敗しました:", tag_name))
return(NULL)
}
content_json <- fromJSON(content(response, "text", encoding = "UTF-8"))
return(content_json$WebId)
}
get_interpolated_data <- function(web_id, tag_name, start_time = "2021-01-01T00:00:00+09:00", end_time = "*", interval = "1d") {
url <- paste0(PI_WEB_API_BASE_URL, "/streams/", web_id, "/interpolated")
response <- GET(url, query = list(startTime = start_time, endTime = end_time, interval = interval),
auth_config, ssl_config)
if (status_code(response) != 200) {
warning(paste("データの取得に失敗しました:", tag_name))
return(NULL)
}
content_json <- fromJSON(content(response, "text", encoding = "UTF-8"))
items <- content_json$Items
if (is.null(items) || length(items) == 0) return(NULL)
# Handle complex value (e.g. Digital State object or System Error)
vals <- items$Value
if (is.data.frame(vals)) {
# If it's a data frame, we want the "Value" column if it exists, otherwise the first column.
# We must NOT unlist the entire data frame as that concatenates columns.
if ("Value" %in% names(vals)) {
vals <- vals$Value
} else {
vals <- vals[[1]]
}
} else if (is.list(vals)) {
# If it's a list of objects (e.g. from JSON), extract the Value field or the first element
# Use sapply to ensure we get a vector of the same length, handling NULLs
vals <- sapply(vals, function(x) {
if (is.list(x) || is.data.frame(x)) {
if ("Value" %in% names(x)) return(x$Value)
if (length(x) > 0) return(x[[1]])
return(NA)
} else {
return(x)
}
})
}
data.frame(
Timestamp = items$Timestamp,
Value = vals,
Tag = tag_name,
stringsAsFactors = FALSE
)
}
定義したタグのリストをループ処理し、WebIDを取得した後、2021年以降の日次データ(interval = "1d")を取得します。
all_data <- data.frame()
for (tag in TAG_NAMES) {
web_id <- get_web_id(tag)
if (!is.null(web_id)) {
tag_data <- get_interpolated_data(web_id, tag)
if (!is.null(tag_data)) {
all_data <- rbind(all_data, tag_data)
}
}
}
# データの先頭を表示
head(all_data)
## Timestamp Value Tag
## 1 2020-12-31T15:00:00Z 93.50242 M2TI4405.PV
## 2 2021-01-01T15:00:00Z 93.54465 M2TI4405.PV
## 3 2021-01-02T15:00:00Z 93.48495 M2TI4405.PV
## 4 2021-01-03T15:00:00Z 93.54973 M2TI4405.PV
## 5 2021-01-04T15:00:00Z 93.57597 M2TI4405.PV
## 6 2021-01-05T15:00:00Z 93.48778 M2TI4405.PV
取得したデータのタイムスタンプを日付型に変換し、値を数値型に変換した後、グラフを描画します。
if (nrow(all_data) > 0) {
# 型変換とタイムゾーン変換(日本時間)
all_data$Timestamp <- with_tz(ymd_hms(all_data$Timestamp), tzone = "Asia/Tokyo")
all_data$Value <- as.numeric(all_data$Value)
# 数値変換でNAになったデータ(システムステート等)を除去
all_data <- all_data %>% filter(!is.na(Value))
# プロット
ggplot(all_data, aes(x = Timestamp, y = Value, color = Tag)) +
geom_line() +
labs(title = "PIデータトレンド",
subtitle = paste(TAG_NAMES, collapse = ", "),
x = "日時",
y = "値") +
theme_minimal()
} else {
print("表示可能なデータがありません。")
}
取得したデータから、U_Calculation_Method.md に基づいて総括伝熱係数
U を算出します。
if (nrow(all_data) > 0) {
# データをタグごとに横持ち(ワイドフォーマット)に変換
wide_data <- all_data %>%
select(Timestamp, Tag, Value) %>%
# 重複がある場合は平均をとるか最初の値をとる(日次データなので基本は1日1行)
group_by(Timestamp, Tag) %>%
summarise(Value = mean(Value, na.rm = TRUE), .groups = 'drop') %>%
pivot_wider(names_from = Tag, values_from = Value)
# 必要なタグが全て存在するか確認
required_tags <- c("M2FC4403.PV", "M2TI4405.PV", "M2PI4410W.PV", "M2PI4407.PV", "M2PI4411W.PV")
if (all(required_tags %in% names(wide_data))) {
# U値の計算プロセス
calc_data <- wide_data %>%
mutate(
# ステップ 1: 熱量 q の算出
q = M2FC4403.PV * 2257.05 * 1000 / 3600 / 1000,
# ステップ 2: ポンプ流量 pump_flow の算出
pump_inlet = (M2PI4407.PV / 760 * 101.325) + (950 * 9.81 * (1.4 + 6.125) / 1000),
pump_outlet = M2PI4411W.PV + 101.325,
lifting_height = (pump_outlet - pump_inlet) * 1000 / 9.81 / 950,
# 二次方程式の適用
pump_flow = (-0.6857 * lifting_height^2 + 0.8571 * lifting_height + 43.589) * 60 * 950 / 1000,
# ステップ 3: 温度差の算出
t1 = M2TI4405.PV,
t2 = t1 + q * 1000 / (pump_flow * 1000 / 3600) / 2.09,
# LN は R では log()
t3_t4 = 27.838 * log(M2PI4410W.PV + 101.325) - 28.559,
# ステップ 4: 対数平均温度差 delta_tlm (LMTD)
dT1 = t3_t4 - t1,
dT2 = t3_t4 - t2,
delta_tlm = ifelse(
dT1 > 0 & dT2 > 0 & dT1 != dT2,
(dT1 - dT2) / log(dT1 / dT2),
NA # 計算不可またはゼロ割りの場合
),
# 最終的な U の算出
U = ifelse(
!is.na(delta_tlm) & delta_tlm != 0,
(q * 1000 * 1000 / 367) / delta_tlm,
NA
)
) %>%
select(Timestamp, M2PI4410W = M2PI4410W.PV, q, pump_flow, t1, t2, t3_t4, delta_tlm, U) %>%
arrange(desc(Timestamp)) # 最新のデータを上に
# 計算結果の一部をコンソールに表示
head(calc_data)
} else {
print("U値の計算に必要なタグのデータが揃っていません。")
}
}
## # A tibble: 6 × 9
## Timestamp M2PI4410W q pump_flow t1 t2 t3_t4 delta_tlm
## <dttm> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 2026-04-23 00:00:00 -0.771 0 2388. 18.7 18.7 99.8 NA
## 2 2026-04-22 00:00:00 -0.781 0 1127. 24.4 24.4 99.8 NA
## 3 2026-04-21 00:00:00 -0.678 0 2497. 25.3 25.3 99.8 NA
## 4 2026-04-20 00:00:00 -0.701 0 1014. 35.5 35.5 99.8 NA
## 5 2026-04-19 00:00:00 -0.699 0 1017. 39.7 39.7 99.8 NA
## 6 2026-04-18 00:00:00 -0.715 0 1023. 45.5 45.5 99.8 NA
## # ℹ 1 more variable: U <dbl>
計算された U値 および各種中間指標を DT::datatable
を用いて見やすいテーブルとして表示します。
if (exists("calc_data") && nrow(calc_data) > 0) {
datatable(
calc_data,
options = list(
pageLength = 10,
autoWidth = TRUE,
scrollX = TRUE,
dom = 'Bfrtip'
),
caption = "総括伝熱係数 (U) および関連パラメータの計算結果",
rownames = FALSE
) %>%
formatRound(columns = c("M2PI4410W", "q", "pump_flow", "t1", "t2", "t3_t4", "delta_tlm", "U"), digits = 2) %>%
formatDate("Timestamp", method = "toLocaleString")
} else {
print("表示できる計算結果がありません。")
}
Campaign_Evaluation_Methodology.md
に基づき、各キャンペーンの「初期」と「末期」における最大ジャケット圧力時のデータを抽出し、U値との相関を確認します。
if (exists("calc_data") && nrow(calc_data) > 0 && file.exists("campaign_periods.csv")) {
# キャンペーン期間データの読み込み
campaigns <- read.csv("campaign_periods.csv", stringsAsFactors = FALSE)
campaigns$Start_Date <- as.Date(campaigns$Start_Date)
campaigns$End_Date <- as.Date(campaigns$End_Date)
campaign_results <- data.frame()
for (i in 1:nrow(campaigns)) {
period <- campaigns$Period[i]
start_d <- campaigns$Start_Date[i]
end_d <- campaigns$End_Date[i]
if (is.na(start_d)) next
# 初期 (Beginning): 開始日から1ヶ月間
beg_end_d <- start_d + months(1)
beg_data <- calc_data %>% filter(as.Date(Timestamp) >= start_d & as.Date(Timestamp) <= beg_end_d)
if (nrow(beg_data) > 0) {
max_row <- beg_data[which.max(beg_data$M2PI4410W), ]
# Use check.names=FALSE style or matching the literal name
col_beg <- grep("詰まり本数_前半", names(campaigns), value=TRUE)[1]
nos_val <- as.numeric(campaigns[i, col_beg])
campaign_results <- rbind(campaign_results, data.frame(
Period = period,
Type = "Beginning",
PeriodType = paste0(period, "_Beginning"),
Timestamp = max_row$Timestamp,
Max_Jacket_P = max_row$M2PI4410W,
U_Value = max_row$U,
Flow_Rate = max_row$pump_flow,
NoS = nos_val
))
}
# 末期 (End): 終了日の1ヶ月前から終了日まで
if (!is.na(end_d)) {
end_start_d <- end_d - months(1)
end_data <- calc_data %>% filter(as.Date(Timestamp) >= end_start_d & as.Date(Timestamp) <= end_d)
if (nrow(end_data) > 0) {
max_row <- end_data[which.max(end_data$M2PI4410W), ]
col_end <- grep("詰まり本数_後半", names(campaigns), value=TRUE)[1]
nos_val <- as.numeric(campaigns[i, col_end])
campaign_results <- rbind(campaign_results, data.frame(
Period = period,
Type = "End",
PeriodType = paste0(period, "_End"),
Timestamp = max_row$Timestamp,
Max_Jacket_P = max_row$M2PI4410W,
U_Value = max_row$U,
Flow_Rate = max_row$pump_flow,
NoS = nos_val
))
}
}
}
# 結果のテーブル表示
if (nrow(campaign_results) > 0) {
library(htmltools)
dt <- datatable(
campaign_results,
options = list(pageLength = 10, autoWidth = TRUE, scrollX = TRUE),
caption = "キャンペーン期間ごとの最大ジャケット圧力抽出結果",
rownames = FALSE
) %>%
formatRound(columns = c("Max_Jacket_P", "U_Value"), digits = 2) %>%
formatDate("Timestamp", method = "toLocaleString")
print(tagList(dt))
# 散布図の作成 1 (最大ジャケット圧力 vs U値)
p_campaign_u <- ggplot(campaign_results, aes(x = Max_Jacket_P, y = U_Value)) +
geom_point(aes(color = Type, size = NoS), alpha = 0.7) +
geom_smooth(method = "lm", se = TRUE, color = "blue", fill = "lightblue", alpha = 0.3) +
labs(
title = "キャンペーン期間ごとの最大ジャケット圧力とU値の相関",
subtitle = "各キャンペーンの初期(1ヶ月)・末期(1ヶ月)における最大ジャケット圧力時のデータ",
x = "最大ジャケット圧力 (M2PI4410W.PV)",
y = "総括伝熱係数 (U値)",
color = "期間",
size = "詰まり本数(NoS)"
) +
theme_minimal()
print(p_campaign_u)
# 相関分析結果を保存するリスト
model_stats <- data.frame(
Parameter = character(),
R2 = numeric(),
p_value = numeric(),
Equation = character(),
stringsAsFactors = FALSE
)
# 散布図の作成 2 (最大ジャケット圧力 vs 詰まり本数)
valid_nos_p <- campaign_results %>% filter(!is.na(NoS), !is.na(Max_Jacket_P))
if(nrow(valid_nos_p) > 2) {
model_p <- lm(NoS ~ Max_Jacket_P, data = valid_nos_p)
sum_p <- summary(model_p)
r2_p <- sum_p$r.squared
pval_p <- sum_p$coefficients[2, 4]
eq_p <- sprintf("y = %.2fx %s %.2f", coef(model_p)[2], ifelse(coef(model_p)[1] >= 0, "+", "-"), abs(coef(model_p)[1]))
stats_str_p <- sprintf("回帰式: %s | R^2 = %.3f | p-value = %.3f", eq_p, r2_p, pval_p)
model_stats <- rbind(model_stats, data.frame(Parameter = "ジャケット圧力", R2 = r2_p, p_value = pval_p, Equation = eq_p))
p_campaign_nos_p <- ggplot(valid_nos_p, aes(x = Max_Jacket_P, y = NoS)) +
geom_point(aes(color = Type), size = 3, alpha = 0.7) +
geom_smooth(method = "lm", se = TRUE, color = "darkred", fill = "pink", alpha = 0.3) +
labs(
title = "キャンペーン期間ごとの最大ジャケット圧力と詰まり本数の相関",
subtitle = paste("各キャンペーンの初期(1ヶ月)・末期(1ヶ月)における最大ジャケット圧力時のデータ\n", stats_str_p),
x = "最大ジャケット圧力 (M2PI4410W.PV)",
y = "詰まり本数 (NoS)",
color = "期間"
) +
theme_minimal()
print(p_campaign_nos_p)
}
# 散布図の作成 3 (U値 vs 詰まり本数)
valid_nos_u <- campaign_results %>% filter(!is.na(NoS), !is.na(U_Value))
if(nrow(valid_nos_u) > 2) {
model_u <- lm(NoS ~ U_Value, data = valid_nos_u)
sum_u <- summary(model_u)
r2_u <- sum_u$r.squared
pval_u <- sum_u$coefficients[2, 4]
eq_u <- sprintf("y = %.2fx %s %.2f", coef(model_u)[2], ifelse(coef(model_u)[1] >= 0, "+", "-"), abs(coef(model_u)[1]))
stats_str_u <- sprintf("回帰式: %s | R^2 = %.3f | p-value = %.3f", eq_u, r2_u, pval_u)
model_stats <- rbind(model_stats, data.frame(Parameter = "U値", R2 = r2_u, p_value = pval_u, Equation = eq_u))
p_campaign_nos_u <- ggplot(valid_nos_u, aes(x = U_Value, y = NoS)) +
geom_point(aes(color = Type), size = 3, alpha = 0.7) +
geom_smooth(method = "lm", se = TRUE, color = "darkgreen", fill = "lightgreen", alpha = 0.3) +
labs(
title = "キャンペーン期間ごとのU値と詰まり本数の相関",
subtitle = paste("各キャンペーンの初期(1ヶ月)・末期(1ヶ月)における最大ジャケット圧力時のデータ\n", stats_str_u),
x = "総括伝熱係数 (U値)",
y = "詰まり本数 (NoS)",
color = "期間"
) +
theme_minimal()
print(p_campaign_nos_u)
}
# 散布図の作成 4 (ポンプ流量 vs 詰まり本数)
valid_nos_f <- campaign_results %>% filter(!is.na(NoS), !is.na(Flow_Rate))
if(nrow(valid_nos_f) > 2) {
model_f <- lm(NoS ~ Flow_Rate, data = valid_nos_f)
sum_f <- summary(model_f)
r2_f <- sum_f$r.squared
pval_f <- sum_f$coefficients[2, 4]
eq_f <- sprintf("y = %.2fx %s %.2f", coef(model_f)[2], ifelse(coef(model_f)[1] >= 0, "+", "-"), abs(coef(model_f)[1]))
stats_str_f <- sprintf("回帰式: %s | R^2 = %.3f | p-value = %.3f", eq_f, r2_f, pval_f)
model_stats <- rbind(model_stats, data.frame(Parameter = "ポンプ流量", R2 = r2_f, p_value = pval_f, Equation = eq_f))
p_campaign_nos_f <- ggplot(valid_nos_f, aes(x = Flow_Rate, y = NoS)) +
geom_point(aes(color = Type), size = 3, alpha = 0.7) +
geom_smooth(method = "lm", se = TRUE, color = "purple", fill = "thistle", alpha = 0.3) +
labs(
title = "キャンペーン期間ごとのポンプ流量と詰まり本数の相関",
subtitle = paste("各キャンペーンの初期(1ヶ月)・末期(1ヶ月)における最大ジャケット圧力時のデータ\n", stats_str_f),
x = "ポンプ流量 (pump_flow)",
y = "詰まり本数 (NoS)",
color = "期間"
) +
theme_minimal()
print(p_campaign_nos_f)
}
# 詰まり本数との相関モデル比較の表示
if (nrow(model_stats) > 0) {
model_stats <- model_stats %>% arrange(desc(R2))
print(datatable(
model_stats,
options = list(pageLength = 5, autoWidth = TRUE, dom = 't'),
caption = "詰まり本数との相関強度の比較 (R^2 降順)",
rownames = FALSE
) %>%
formatRound(columns = c("R2", "p_value"), digits = 3))
}
} else {
print("キャンペーン評価用のデータが抽出できませんでした。")
}
} else {
print("キャンペーン評価に必要なデータ(calc_data)または campaign_periods.csv が存在しません。")
}
## <div class="datatables html-widget html-fill-item" id="htmlwidget-b8384fd0f1eb692f436c" style="width:100%;height:auto;"></div>
## <script type="application/json" data-for="htmlwidget-b8384fd0f1eb692f436c">{"x":{"filter":"none","vertical":false,"caption":"<caption>キャンペーン期間ごとの最大ジャケット圧力抽出結果<\/caption>","data":[[2103,2103,2109,2109,2206,2206,2302,2302,2310,2310,2404,2404,2502,2502],["Beginning","End","Beginning","End","Beginning","End","Beginning","End","Beginning","End","Beginning","End","Beginning","End"],["2103_Beginning","2103_End","2109_Beginning","2109_End","2206_Beginning","2206_End","2302_Beginning","2302_End","2310_Beginning","2310_End","2404_Beginning","2404_End","2502_Beginning","2502_End"],["2021-04-13T15:00:00Z","2021-09-08T15:00:00Z","2021-10-26T15:00:00Z","2022-02-06T15:00:00Z","2022-07-26T15:00:00Z","2023-01-13T15:00:00Z","2023-03-15T15:00:00Z","2023-09-18T15:00:00Z","2023-11-17T15:00:00Z","2024-03-12T15:00:00Z","2024-04-14T15:00:00Z","2025-06-27T15:00:00Z","2025-07-30T15:00:00Z","2025-12-23T15:00:00Z"],[12.09471,13.94503,4.7961,18.06892,3.6427,-0.5194795,-1.391797,-0.5811917,-0.6167568,-0.9513991000000001,-1.175398,3.71782637,1.37394071,1.12084067],[448.6526527294358,372.7256412851982,526.9320277444729,351.2972468028649,433.386667005834,519.1924538640029,null,464.490907696674,null,null,null,480.9218610433566,479.5211867889088,593.7199198328791],[1925.796003595965,1570.825104049807,1804.486510953861,1771.142357478077,1780.787263384056,2369.987806467864,2079.520604753937,2245.628717555061,2097.86518490855,98.31618093169857,2435.517765740384,2385.43878695637,2139.425577945138,2009.563149209568],[138,196,123,162,0,9,9,18,18,26,null,null,26,null]],"container":"<table class=\"display\">\n <thead>\n <tr>\n <th>Period<\/th>\n <th>Type<\/th>\n <th>PeriodType<\/th>\n <th>Timestamp<\/th>\n <th>Max_Jacket_P<\/th>\n <th>U_Value<\/th>\n <th>Flow_Rate<\/th>\n <th>NoS<\/th>\n <\/tr>\n <\/thead>\n<\/table>","options":{"pageLength":10,"autoWidth":true,"scrollX":true,"columnDefs":[{"targets":3,"render":"function(data, type, row, meta) {\n return type !== 'display' ? data : DTWidget.formatDate(data, \"toLocaleString\");\n }"},{"targets":4,"render":"function(data, type, row, meta) {\n return type !== 'display' ? data : DTWidget.formatRound(data, 2, 3, \",\", \".\", null);\n }"},{"targets":5,"render":"function(data, type, row, meta) {\n return type !== 'display' ? data : DTWidget.formatRound(data, 2, 3, \",\", \".\", null);\n }"},{"className":"dt-right","targets":[0,4,5,6,7]},{"name":"Period","targets":0},{"name":"Type","targets":1},{"name":"PeriodType","targets":2},{"name":"Timestamp","targets":3},{"name":"Max_Jacket_P","targets":4},{"name":"U_Value","targets":5},{"name":"Flow_Rate","targets":6},{"name":"NoS","targets":7}],"order":[],"orderClasses":false}},"evals":["options.columnDefs.0.render","options.columnDefs.1.render","options.columnDefs.2.render"],"jsHooks":[]}</script>
2310以前のデータを訓練データ(学習データ)として線形回帰モデルを構築し、2502期(テストデータ)の詰まり本数を予測します。実際の2502期の詰まり本数(48本)と比較し、モデルの予測精度を評価します。
if (exists("campaign_results") && nrow(campaign_results) > 0) {
# 訓練データ: Periodが2310以下、かつNoSに欠損値がない行
train_data <- campaign_results %>%
filter(as.numeric(Period) <= 2310, !is.na(NoS))
# テストデータの抽出: 2502期のデータ
test_data_base <- campaign_results %>% filter(Period == "2502")
# テストデータの予測変数を取得 (初期と末期で分かれている場合は、予測したいフェーズに合わせますが、ここでは最大値を持つ行を採用するか、テストデータ群として扱います。依頼の意図としては2502期のある特定時点での予測値と実測値の比較と考えられますので、テストデータとして利用可能な行ごとに予測を行います。)
if (nrow(train_data) > 2 && nrow(test_data_base) > 0) {
# 実際の値
actual_nos_2502 <- 47
# 訓練データでのモデル構築
mod_p <- lm(NoS ~ Max_Jacket_P, data = train_data)
mod_u <- lm(NoS ~ U_Value, data = train_data)
mod_f <- lm(NoS ~ Flow_Rate, data = train_data)
eval_results <- data.frame()
for(i in 1:nrow(test_data_base)) {
row_data <- test_data_base[i, ]
# 予測の実行
pred_p <- predict(mod_p, newdata = row_data)
pred_u <- predict(mod_u, newdata = row_data)
pred_f <- predict(mod_f, newdata = row_data)
# 評価指標 (絶対誤差と絶対誤差率 MAPE)
error_p <- abs(pred_p - actual_nos_2502)
error_u <- abs(pred_u - actual_nos_2502)
error_f <- abs(pred_f - actual_nos_2502)
mape_p <- (error_p / actual_nos_2502) * 100
mape_u <- (error_u / actual_nos_2502) * 100
mape_f <- (error_f / actual_nos_2502) * 100
eval_results <- rbind(eval_results, data.frame(
PeriodType = row_data$PeriodType,
Actual_NoS = actual_nos_2502,
Predictor = c("ジャケット圧力", "U値", "ポンプ流量"),
Predicted_NoS = c(pred_p, pred_u, pred_f),
Absolute_Error = c(error_p, error_u, error_f),
MAPE_Percent = c(mape_p, mape_u, mape_f)
))
}
# 評価結果の列を追加
eval_results <- eval_results %>% mutate(
Evaluation = case_when(
MAPE_Percent < 10 ~ "非常に良い予測",
MAPE_Percent >= 10 & MAPE_Percent < 20 ~ "良い予測",
MAPE_Percent >= 20 & MAPE_Percent < 50 ~ "妥当な予測",
TRUE ~ "不正確な予測"
)
)
# 列名を日本語に変更してわかりやすくする
eval_results_display <- eval_results %>%
rename(
`期間` = PeriodType,
`実際の詰まり本数` = Actual_NoS,
`予測に使用した指標` = Predictor,
`予想本数` = Predicted_NoS,
`誤差(絶対値)` = Absolute_Error,
`誤差率 (MAPE %)` = MAPE_Percent,
`評価結果` = Evaluation
)
# MAPEで昇順ソートして最も精度が良いものを上に
eval_results_display <- eval_results_display %>% arrange(`誤差率 (MAPE %)`)
# 評価コメントの自動生成 (表の前に出力)
best_model <- eval_results %>% arrange(MAPE_Percent) %>% slice(1)
cat(sprintf("\n**評価コメント:**\n一般的な評価指標(MAPE: 平均絶対パーセント誤差)に照らすと、%sの時の '%s' を使ったモデルが最も誤差が少なく、予測値は約 %.1f 本(誤差 %.1f%%、評価: %s)となりました。\n※ 評価基準: MAPEが10%%未満で「非常に良い」、10%%〜20%%未満で「良い」、20%%〜50%%未満で「妥当」、50%%以上で「不正確」としています。\n\n",
best_model$PeriodType, best_model$Predictor, best_model$Predicted_NoS, best_model$MAPE_Percent, best_model$Evaluation))
datatable(
eval_results_display,
options = list(pageLength = 10, autoWidth = TRUE, scrollX = TRUE),
caption = "2502期の予測モデル評価結果 (実際の詰まり本数: 47本)",
rownames = FALSE
) %>%
formatRound(columns = c("予想本数", "誤差(絶対値)", "誤差率 (MAPE %)"), digits = 2)
} else {
print("モデルの訓練に必要な2310以前のデータ、または2502期のテストデータが不足しています。")
}
}
##
## **評価コメント:**
## 一般的な評価指標(MAPE: 平均絶対パーセント誤差)に照らすと、2502_Beginningの時の 'ジャケット圧力' を使ったモデルが最も誤差が少なく、予測値は約 37.0 本(誤差 21.2%、評価: 妥当な予測)となりました。
## ※ 評価基準: MAPEが10%未満で「非常に良い」、10%〜20%未満で「良い」、20%〜50%未満で「妥当」、50%以上で「不正確」としています。