0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ほぼ自動化データ活用術 Excel VBA × LLMで「何が起きていて、何をすべきか」を一気に出す

Posted at

はじめに

データ分析において難しいのは、分析そのものよりも分析結果から何をすべきかを決めることである。
EDAやクロス集計ができても、「結局どこを改善するのか」「それによってどれほどの効果があるのか」を説明できないケースは多い。

本記事では、LLMに帳票設計を担わせることで、分析から活用までをほぼ自動化できるかを検証する。
対象はダミーの家計簿データであるが、業務データへの転用も前提とする。


背景と狙い

以前、GASを用いてExcel帳票を自動生成するプロンプトを公開した。
今回はアプローチを変え、以下を狙った。

  • LLMに分析設計そのものを考えさせる
  • Excel VBAで帳票を自動生成する
  • 人は元データを投入するだけにする

特に重視した問いは次の通りである。

何が起きていて、どうなっており、何をすべきで、その期待効果はいくらか

この問いに、Excel上の数式だけで答えられる構成を目指した。


技術スタック

  • ChatGPT-5.2(thinking)
    GeminiやCopilotを利用する企業も多いが、汎用性を重視しChatGPTを採用した。
    thinkingでなくとも動作するが、生成される帳票は簡略化される。

  • Excel VBA
    業務での利用人口が多く、追加ツールを必要としない点を評価した。


使用プロンプト

GPTsにて、下記の指示(システムプロンプト)を挿入する。
※会話UIをそのまま使ってしまうとほかのチャットに内容が引っ張られてしまい出力の精度が大きく下がる。GPTsにした方が安定する。

期待される出力としてはダミーデータが出現し、そこからやりたい内容を加えれば帳票を作成するVBAが出力される。
※項目が英語のままだったりするので、適宜「日本語にしろ」、と命令したり、元データを貼り付ければ出力されるようにしたい場合は項目とサンプル10件ほど入力プロンプトに入れればいい。元データをそのまま貼り付けると大きすぎてVBAがエラーを吐くので注意。

システムプロンプト:https://github.com/amenohabakiri-hue/gas_spreadsheet_prompt/blob/main/prompts_vba

入力した指示:

家計簿を作成し、データ分析をする。
・EDA
・クロス集計
・何をすべきか?
・その期待効果は?
※作成したシートから具体数値を数式で出す。
上記各帳票を作成せよ。
なお、列名と項目は日本語にする。

出力結果

プロンプトを入力すると、VBAコードが出力される。
これをExcelに貼り付けて実行すると、以下の帳票が自動生成される。

VBAコード
```vba
Option Explicit

' ==========================================
' メイン処理(エントリポイント)
' ==========================================
Sub Main()
    Dim wb As Workbook
    Set wb = ThisWorkbook

    Application.ScreenUpdating = False
    Application.DisplayAlerts = False
    Application.EnableEvents = False
    Application.Calculation = xlCalculationManual
    On Error GoTo ErrorHandler

    Call CreateKakeiboDataSheet(wb)
    Call CreateEDASheet(wb)
    Call CreateCrossTabSheet(wb)
    Call CreateActionSheet(wb)

    Application.Calculation = xlCalculationAutomatic
    Application.EnableEvents = True
    Application.DisplayAlerts = True
    Application.ScreenUpdating = True
    wb.Worksheets(1).Activate
    MsgBox "処理が正常に完了しました。", vbInformation
    Exit Sub

ErrorHandler:
    Application.Calculation = xlCalculationAutomatic
    Application.EnableEvents = True
    Application.DisplayAlerts = True
    Application.ScreenUpdating = True
    MsgBox "エラーが発生しました: " & Err.Description, vbCritical
End Sub

' ==========================================
' 1) 家計簿データ作成
' ==========================================
Sub CreateKakeiboDataSheet(wb As Workbook)
    Dim ws As Worksheet
    Set ws = ForceCreateSheet(wb, "家計簿_データ")

    Dim headers As Variant
    headers = Array("日付", "区分", "カテゴリ", "サブカテゴリ", "支払方法", "店名", "単価", "数量", "金額", "収支", "年", "月", "月初日", "週", "曜日")

    ws.Range("A1").Resize(1, UBound(headers) + 1).Value = headers

    Dim expenseCats As Variant
    expenseCats = Array("食費", "外食", "住居", "光熱費", "通信", "交通", "日用品", "医療", "教育", "娯楽", "交際", "衣服", "美容", "サブスク", "その他")

    Dim incomeCats As Variant
    incomeCats = Array("給与", "賞与", "副業", "臨時収入")

    Dim payMethods As Variant
    payMethods = Array("現金", "クレジット", "デビット", "電子マネー", "口座振替", "QR決済")

    Dim shops As Variant
    shops = Array("スーパー", "コンビニ", "ドラッグストア", "外食店", "ネット通販", "家賃", "電力会社", "ガス会社", "通信会社", "交通機関", "病院", "学校", "サブスク事業者", "その他")

    Dim startDate As Date
    startDate = DateSerial(2025, 1, 1)

    Dim endDate As Date
    endDate = DateSerial(2025, 12, 31)

    Dim rowCount As Long
    rowCount = 500

    Randomize

    Dim i As Long
    For i = 2 To rowCount + 1
        Dim d As Date
        d = startDate + Int((endDate - startDate + 1) * Rnd)

        Dim isExpense As Boolean
        isExpense = (Rnd < 0.85)

        Dim kbn As String
        Dim cat As String
        Dim subCat As String
        Dim pm As String
        Dim shop As String
        Dim unitPrice As Long
        Dim qty As Long

        If isExpense Then
            kbn = "支出"
            cat = expenseCats(Int((UBound(expenseCats) + 1) * Rnd))
            subCat = PickExpenseSubCategory(cat)
            pm = payMethods(Int((UBound(payMethods) + 1) * Rnd))
            shop = PickShopByCategory(cat, shops)
            unitPrice = PickExpenseUnitPrice(cat)
            qty = PickExpenseQty(cat)
        Else
            kbn = "収入"
            cat = incomeCats(Int((UBound(incomeCats) + 1) * Rnd))
            subCat = PickIncomeSubCategory(cat)
            pm = "振込"
            shop = "勤務先"
            unitPrice = PickIncomeUnitPrice(cat)
            qty = 1
        End If

        ws.Cells(i, 1).Value = d
        ws.Cells(i, 2).Value = kbn
        ws.Cells(i, 3).Value = cat
        ws.Cells(i, 4).Value = subCat
        ws.Cells(i, 5).Value = pm
        ws.Cells(i, 6).Value = shop
        ws.Cells(i, 7).Value = unitPrice
        ws.Cells(i, 8).Value = qty

        ' 計算列は必ず数式
        ws.Cells(i, 9).FormulaR1C1 = "=RC[-2]*RC[-1]" ' 金額
        ws.Cells(i, 10).FormulaR1C1 = "=IF(RC[-8]=""支出"",-RC[-1],RC[-1])" ' 収支
        ws.Cells(i, 11).FormulaR1C1 = "=YEAR(RC[-10])" ' 年
        ws.Cells(i, 12).FormulaR1C1 = "=MONTH(RC[-11])" ' 月
        ws.Cells(i, 13).FormulaR1C1 = "=DATE(RC[-2],RC[-1],1)" ' 月初日
        ws.Cells(i, 14).FormulaR1C1 = "=WEEKNUM(RC[-13],2)" ' 週
        ws.Cells(i, 15).FormulaR1C1 = "=TEXT(RC[-14],""aaa"")" ' 曜日
    Next i

    ' 書式
    ws.Columns("A").NumberFormatLocal = "yyyy/mm/dd"
    ws.Columns("G").NumberFormatLocal = "#,##0"
    ws.Columns("H").NumberFormatLocal = "0"
    ws.Columns("I").NumberFormatLocal = "#,##0"
    ws.Columns("J").NumberFormatLocal = "#,##0"
    ws.Columns("K").NumberFormatLocal = "0"
    ws.Columns("L").NumberFormatLocal = "0"
    ws.Columns("M").NumberFormatLocal = "yyyy/mm/dd"

    Call ApplyBasicFormatting(ws)

    ' 既定は日付昇順
    ws.Range("A1").CurrentRegion.Sort _
        Key1:=ws.Range("A2"), Order1:=xlAscending, Header:=xlYes

    ' ウィンドウ枠固定
    ws.Activate
    ws.Range("A2").Select
    ActiveWindow.FreezePanes = True
End Sub

' ==========================================
' 2) EDA(探索的データ分析)作成
' ==========================================
Sub CreateEDASheet(wb As Workbook)
    Dim ws As Worksheet
    Set ws = ForceCreateSheet(wb, "EDA")

    Dim dataWs As Worksheet
    Set dataWs = wb.Worksheets("家計簿_データ")

    Dim lastRow As Long
    lastRow = dataWs.Cells(dataWs.Rows.Count, 1).End(xlUp).Row
    If lastRow < 2 Then lastRow = 2

    Dim rngDate As String, rngKbn As String, rngCat As String, rngPay As String, rngAmt As String, rngMonthStart As String
    rngDate = "'家計簿_データ'!$A$2:$A$" & CStr(lastRow)
    rngKbn = "'家計簿_データ'!$B$2:$B$" & CStr(lastRow)
    rngCat = "'家計簿_データ'!$C$2:$C$" & CStr(lastRow)
    rngPay = "'家計簿_データ'!$E$2:$E$" & CStr(lastRow)
    rngAmt = "'家計簿_データ'!$I$2:$I$" & CStr(lastRow)
    rngMonthStart = "'家計簿_データ'!$M$2:$M$" & CStr(lastRow)

    ws.Range("A1").Value = "EDA(探索的データ分析)"
    ws.Range("A1").Font.Bold = True
    ws.Range("A1").Font.Size = 14

    ' サマリー
    ws.Range("A3").Value = "集計期間(最小日付)"
    ws.Range("B3").Formula = "=MIN(" & rngDate & ")"
    ws.Range("A4").Value = "集計期間(最大日付)"
    ws.Range("B4").Formula = "=MAX(" & rngDate & ")"

    ws.Range("A6").Value = "総収入(円)"
    ws.Range("B6").Formula = "=SUMIFS(" & rngAmt & "," & rngKbn & ",""収入"")"
    ws.Range("A7").Value = "総支出(円)"
    ws.Range("B7").Formula = "=SUMIFS(" & rngAmt & "," & rngKbn & ",""支出"")"
    ws.Range("A8").Value = "収支差額(円)"
    ws.Range("B8").Formula = "=B6-B7"

    ws.Range("A10").Value = "取引件数(件)"
    ws.Range("B10").Formula = "=COUNTA(" & rngDate & ")"
    ws.Range("A11").Value = "支出平均(円/件)"
    ws.Range("B11").Formula = "=AVERAGEIFS(" & rngAmt & "," & rngKbn & ",""支出"")"

    ws.Range("A12").Value = "支出中央値(円/件)"
    ws.Range("B12").FormulaArray = "=MEDIAN(IF(" & rngKbn & "=""支出""," & rngAmt & "))"

    ws.Range("A13").Value = "支出最大(円/件)"
    ws.Range("B13").FormulaArray = "=MAX(IF(" & rngKbn & "=""支出""," & rngAmt & "))"
    ws.Range("A14").Value = "支出最小(円/件)"
    ws.Range("B14").FormulaArray = "=MIN(IF(" & rngKbn & "=""支出""," & rngAmt & "))"

    ws.Range("A15").Value = "支出のばらつき(標準偏差)"
    ws.Range("B15").FormulaArray = "=STDEV(IF(" & rngKbn & "=""支出""," & rngAmt & "))"

    ws.Columns("A").ColumnWidth = 24
    ws.Columns("B").ColumnWidth = 18
    ws.Range("B3:B4").NumberFormatLocal = "yyyy/mm/dd"
    ws.Range("B6:B8").NumberFormatLocal = "#,##0"
    ws.Range("B10:B15").NumberFormatLocal = "#,##0"

    ' 月別推移
    ws.Range("A18").Value = "月別推移(支出/収入/差額)"
    ws.Range("A18").Font.Bold = True

    ws.Range("A19").Value = "月初日"
    ws.Range("B19").Value = "支出(円)"
    ws.Range("C19").Value = "収入(円)"
    ws.Range("D19").Value = "差額(円)"

    Dim baseYear As Long
    baseYear = 2025

    Dim m As Long
    For m = 1 To 12
        ws.Cells(19 + m, 1).Value = DateSerial(baseYear, m, 1)
        ws.Cells(19 + m, 2).Formula = "=SUMIFS(" & rngAmt & "," & rngKbn & ",""支出""," & rngMonthStart & ",A" & CStr(19 + m) & ")"
        ws.Cells(19 + m, 3).Formula = "=SUMIFS(" & rngAmt & "," & rngKbn & ",""収入""," & rngMonthStart & ",A" & CStr(19 + m) & ")"
        ws.Cells(19 + m, 4).Formula = "=C" & CStr(19 + m) & "-B" & CStr(19 + m)
    Next m

    ws.Range("A20:A31").NumberFormatLocal = "yyyy/mm/dd"
    ws.Range("B20:D31").NumberFormatLocal = "#,##0"

    ' 月別推移チャート(差額)
    Dim chartRng As Range
    Set chartRng = ws.Range("A19:D31")

    Call AddSafeChart(ws, chartRng, "月別推移(支出/収入/差額)", 380, 40, xlLineMarkers)

    ' 年間カテゴリ支出(上位抽出用の台)
    ws.Range("A34").Value = "年間支出(カテゴリ別)"
    ws.Range("A34").Font.Bold = True
    ws.Range("A35").Value = "カテゴリ"
    ws.Range("B35").Value = "年間支出(円)"

    Dim expenseCats As Variant
    expenseCats = Array("食費", "外食", "住居", "光熱費", "通信", "交通", "日用品", "医療", "教育", "娯楽", "交際", "衣服", "美容", "サブスク", "その他")

    Dim c As Long
    For c = 0 To UBound(expenseCats)
        ws.Cells(36 + c, 1).Value = CStr(expenseCats(c))
        ws.Cells(36 + c, 2).Formula = "=SUMIFS(" & rngAmt & "," & rngKbn & ",""支出""," & rngCat & ",A" & CStr(36 + c) & ")"
    Next c

    ws.Range("B36:B" & CStr(36 + UBound(expenseCats))).NumberFormatLocal = "#,##0"

    ' 降順ソート(年間支出の多い順)
    Dim sortRng As Range
    Set sortRng = ws.Range("A35").Resize(UBound(expenseCats) + 2, 2)
    sortRng.Sort Key1:=ws.Range("B36"), Order1:=xlDescending, Header:=xlYes

    ' 上位3カテゴリ表示
    ws.Range("D34").Value = "支出上位(カテゴリ)"
    ws.Range("D34").Font.Bold = True
    ws.Range("D35").Value = "順位"
    ws.Range("E35").Value = "カテゴリ"
    ws.Range("F35").Value = "年間支出(円)"

    ws.Range("D36").Value = "1位"
    ws.Range("D37").Value = "2位"
    ws.Range("D38").Value = "3位"
    ws.Range("E36").Formula = "=A36"
    ws.Range("E37").Formula = "=A37"
    ws.Range("E38").Formula = "=A38"
    ws.Range("F36").Formula = "=B36"
    ws.Range("F37").Formula = "=B37"
    ws.Range("F38").Formula = "=B38"
    ws.Range("F36:F38").NumberFormatLocal = "#,##0"

    Call ApplyBasicFormatting(ws)
End Sub

' ==========================================
' 3) クロス集計作成(SUMIFSで作る安全なクロス)
' ==========================================
Sub CreateCrossTabSheet(wb As Workbook)
    Dim ws As Worksheet
    Set ws = ForceCreateSheet(wb, "クロス集計")

    Dim dataWs As Worksheet
    Set dataWs = wb.Worksheets("家計簿_データ")

    Dim lastRow As Long
    lastRow = dataWs.Cells(dataWs.Rows.Count, 1).End(xlUp).Row
    If lastRow < 2 Then lastRow = 2

    Dim rngKbn As String, rngCat As String, rngPay As String, rngAmt As String, rngMonthStart As String
    rngKbn = "'家計簿_データ'!$B$2:$B$" & CStr(lastRow)
    rngCat = "'家計簿_データ'!$C$2:$C$" & CStr(lastRow)
    rngPay = "'家計簿_データ'!$E$2:$E$" & CStr(lastRow)
    rngAmt = "'家計簿_データ'!$I$2:$I$" & CStr(lastRow)
    rngMonthStart = "'家計簿_データ'!$M$2:$M$" & CStr(lastRow)

    ws.Range("A1").Value = "クロス集計(SUMIFS)"
    ws.Range("A1").Font.Bold = True
    ws.Range("A1").Font.Size = 14

    Dim baseYear As Long
    baseYear = 2025

    ' ---- クロス1: カテゴリ × 月(支出) ----
    ws.Range("A3").Value = "【支出】カテゴリ × 月"
    ws.Range("A3").Font.Bold = True

    ws.Range("A4").Value = "カテゴリ"
    Dim m As Long
    For m = 1 To 12
        ws.Cells(4, 1 + m).Value = DateSerial(baseYear, m, 1)
    Next m
    ws.Cells(4, 14).Value = "年間合計"

    ws.Range("B4:M4").NumberFormatLocal = "yyyy/mm/dd"

    Dim expenseCats As Variant
    expenseCats = Array("食費", "外食", "住居", "光熱費", "通信", "交通", "日用品", "医療", "教育", "娯楽", "交際", "衣服", "美容", "サブスク", "その他")

    Dim r As Long
    For r = 0 To UBound(expenseCats)
        ws.Cells(5 + r, 1).Value = CStr(expenseCats(r))
        For m = 1 To 12
            ws.Cells(5 + r, 1 + m).Formula = "=SUMIFS(" & rngAmt & "," & rngKbn & ",""支出""," & rngCat & ",$A" & CStr(5 + r) & "," & rngMonthStart & ",B$4)"
        Next m
        ws.Cells(5 + r, 14).FormulaR1C1 = "=SUM(RC[-12]:RC[-1])"
    Next r

    ' 合計行
    ws.Cells(5 + UBound(expenseCats) + 1, 1).Value = "合計"
    For m = 1 To 12
        ws.Cells(5 + UBound(expenseCats) + 1, 1 + m).FormulaR1C1 = "=SUM(R[-" & CStr(UBound(expenseCats) + 1) & "]C:R[-1]C)"
    Next m
    ws.Cells(5 + UBound(expenseCats) + 1, 14).FormulaR1C1 = "=SUM(RC[-12]:RC[-1])"

    Dim cross1EndRow As Long
    cross1EndRow = 5 + UBound(expenseCats) + 1

    ws.Range("B5:N" & CStr(cross1EndRow)).NumberFormatLocal = "#,##0"

    ' ---- クロス2: 支払方法 × カテゴリ(支出) ----
    Dim startRow2 As Long
    startRow2 = cross1EndRow + 4

    ws.Cells(startRow2, 1).Value = "【支出】支払方法 × カテゴリ(年間)"
    ws.Cells(startRow2, 1).Font.Bold = True

    ws.Cells(startRow2 + 1, 1).Value = "支払方法"
    Dim c As Long
    For c = 0 To UBound(expenseCats)
        ws.Cells(startRow2 + 1, 2 + c).Value = CStr(expenseCats(c))
    Next c
    ws.Cells(startRow2 + 1, 2 + UBound(expenseCats) + 1).Value = "合計"

    Dim payMethods As Variant
    payMethods = Array("現金", "クレジット", "デビット", "電子マネー", "口座振替", "QR決済", "振込")

    Dim pr As Long
    For pr = 0 To UBound(payMethods)
        ws.Cells(startRow2 + 2 + pr, 1).Value = CStr(payMethods(pr))
        For c = 0 To UBound(expenseCats)
            ws.Cells(startRow2 + 2 + pr, 2 + c).Formula = "=SUMIFS(" & rngAmt & "," & rngKbn & ",""支出""," & rngPay & ",$A" & CStr(startRow2 + 2 + pr) & "," & rngCat & ",B$" & CStr(startRow2 + 1) & ")"
        Next c
        ws.Cells(startRow2 + 2 + pr, 2 + UBound(expenseCats) + 1).FormulaR1C1 = "=SUM(RC[-" & CStr(UBound(expenseCats) + 1) & "]:RC[-1])"
    Next pr

    Dim cross2EndRow As Long
    cross2EndRow = startRow2 + 2 + UBound(payMethods)

    ws.Range(ws.Cells(startRow2 + 2, 2), ws.Cells(cross2EndRow, 2 + UBound(expenseCats) + 1)).NumberFormatLocal = "#,##0"

    Call ApplyBasicFormatting(ws)

    ws.Columns.AutoFit
End Sub

' ==========================================
' 4) 何をすべきか?/期待効果(数式で具体数値)
' ==========================================
Sub CreateActionSheet(wb As Workbook)
    Dim ws As Worksheet
    Set ws = ForceCreateSheet(wb, "改善提案")

    Dim dataWs As Worksheet
    Set dataWs = wb.Worksheets("家計簿_データ")

    Dim lastRow As Long
    lastRow = dataWs.Cells(dataWs.Rows.Count, 1).End(xlUp).Row
    If lastRow < 2 Then lastRow = 2

    Dim rngKbn As String, rngCat As String, rngAmt As String
    rngKbn = "'家計簿_データ'!$B$2:$B$" & CStr(lastRow)
    rngCat = "'家計簿_データ'!$C$2:$C$" & CStr(lastRow)
    rngAmt = "'家計簿_データ'!$I$2:$I$" & CStr(lastRow)

    ws.Range("A1").Value = "何をすべきか?(施策)と期待効果(数式で算出)"
    ws.Range("A1").Font.Bold = True
    ws.Range("A1").Font.Size = 14

    ws.Range("A3").Value = "施策"
    ws.Range("B3").Value = "現状(年間支出・円)"
    ws.Range("C3").Value = "削減率"
    ws.Range("D3").Value = "目標(年間支出・円)"
    ws.Range("E3").Value = "期待効果(年間削減・円)"
    ws.Range("F3").Value = "期待効果(月間削減・円)"
    ws.Range("G3").Value = "狙い(メモ)"

    ' 施策1: 外食
    ws.Range("A4").Value = "外食を10%削減(回数/単価の見直し)"
    ws.Range("B4").Formula = "=SUMIFS(" & rngAmt & "," & rngKbn & ",""支出""," & rngCat & ",""外食"")"
    ws.Range("C4").Value = 0.1
    ws.Range("D4").Formula = "=B4*(1-C4)"
    ws.Range("E4").Formula = "=B4-D4"
    ws.Range("F4").Formula = "=E4/12"
    ws.Range("G4").Value = "外食頻度を減らす/ランチ上限を決める"

    ' 施策2: サブスク
    ws.Range("A5").Value = "サブスクを20%削減(未使用解約/プラン変更)"
    ws.Range("B5").Formula = "=SUMIFS(" & rngAmt & "," & rngKbn & ",""支出""," & rngCat & ",""サブスク"")"
    ws.Range("C5").Value = 0.2
    ws.Range("D5").Formula = "=B5*(1-C5)"
    ws.Range("E5").Formula = "=B5-D5"
    ws.Range("F5").Formula = "=E5/12"
    ws.Range("G5").Value = "利用実績が低い契約を棚卸し"

    ' 施策3: 通信費
    ws.Range("A6").Value = "通信費を15%削減(プラン/回線の見直し)"
    ws.Range("B6").Formula = "=SUMIFS(" & rngAmt & "," & rngKbn & ",""支出""," & rngCat & ",""通信"")"
    ws.Range("C6").Value = 0.15
    ws.Range("D6").Formula = "=B6*(1-C6)"
    ws.Range("E6").Formula = "=B6-D6"
    ws.Range("F6").Formula = "=E6/12"
    ws.Range("G6").Value = "データ容量/セット割の再設計"

    ' 施策4: 光熱費
    ws.Range("A7").Value = "光熱費を5%削減(節電/契約アンペア見直し)"
    ws.Range("B7").Formula = "=SUMIFS(" & rngAmt & "," & rngKbn & ",""支出""," & rngCat & ",""光熱費"")"
    ws.Range("C7").Value = 0.05
    ws.Range("D7").Formula = "=B7*(1-C7)"
    ws.Range("E7").Formula = "=B7-D7"
    ws.Range("F7").Formula = "=E7/12"
    ws.Range("G7").Value = "ピーク時間帯の使用量削減"

    ' 施策5: 交際
    ws.Range("A8").Value = "交際費を10%削減(上限ルール化)"
    ws.Range("B8").Formula = "=SUMIFS(" & rngAmt & "," & rngKbn & ",""支出""," & rngCat & ",""交際"")"
    ws.Range("C8").Value = 0.1
    ws.Range("D8").Formula = "=B8*(1-C8)"
    ws.Range("E8").Formula = "=B8-D8"
    ws.Range("F8").Formula = "=E8/12"
    ws.Range("G8").Value = "月予算を決めて超過を抑える"

    ' 期待効果合計
    ws.Range("A10").Value = "合計(期待効果)"
    ws.Range("E10").Formula = "=SUM(E4:E8)"
    ws.Range("F10").Formula = "=SUM(F4:F8)"
    ws.Range("G10").Value = "年間/月間の削減見込み合計"

    ' 施策の優先順位(期待効果の大きい順)
    ws.Range("A12").Value = "優先順位(期待効果が大きい順)"
    ws.Range("A12").Font.Bold = True
    ws.Range("A13").Value = "順位"
    ws.Range("B13").Value = "施策"
    ws.Range("C13").Value = "年間削減(円)"

    ws.Range("B14").Formula = "=A4"
    ws.Range("C14").Formula = "=E4"
    ws.Range("B15").Formula = "=A5"
    ws.Range("C15").Formula = "=E5"
    ws.Range("B16").Formula = "=A6"
    ws.Range("C16").Formula = "=E6"
    ws.Range("B17").Formula = "=A7"
    ws.Range("C17").Formula = "=E7"
    ws.Range("B18").Formula = "=A8"
    ws.Range("C18").Formula = "=E8"

    ws.Range("A14").Value = "1"
    ws.Range("A15").Value = "2"
    ws.Range("A16").Value = "3"
    ws.Range("A17").Value = "4"
    ws.Range("A18").Value = "5"

    ' 年間削減列でソート(降順)
    Dim sortRng As Range
    Set sortRng = ws.Range("A13").Resize(6, 3)
    sortRng.Sort Key1:=ws.Range("C14"), Order1:=xlDescending, Header:=xlYes

    ws.Range("B4:B10").NumberFormatLocal = "#,##0"
    ws.Range("D4:F10").NumberFormatLocal = "#,##0"
    ws.Range("C4:C8").NumberFormatLocal = "0%"
    ws.Range("C14:C18").NumberFormatLocal = "#,##0"

    Call ApplyBasicFormatting(ws)

    ws.Columns.AutoFit
End Sub

' ==========================================
' 共通ユーティリティ(必須・変更禁止)
' ==========================================

' シートを強制的に新規作成
Function ForceCreateSheet(wb As Workbook, sheetName As String) As Worksheet
    Dim ws As Worksheet
    On Error Resume Next
    Set ws = wb.Worksheets(sheetName)
    If Not ws Is Nothing Then ws.Delete
    On Error GoTo 0

    Set ws = wb.Worksheets.Add(After:=wb.Worksheets(wb.Worksheets.Count))
    ws.Name = sheetName
    Set ForceCreateSheet = ws
End Function

' 基本的な見栄えを整える
Sub ApplyBasicFormatting(ws As Worksheet)
    If ws.Cells(ws.Rows.Count, 1).End(xlUp).Row < 1 Then Exit Sub

    Dim rng As Range
    Set rng = ws.UsedRange

    With rng.Borders
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With

    ' 先頭行に見出しがある場合の装飾(A1行)
    If ws.Cells(1, 1).Value <> "" Then
        With ws.Range(ws.Cells(1, 1), ws.Cells(1, rng.Columns.Count))
            .Interior.Color = RGB(220, 220, 220)
            .Font.Bold = True
            .HorizontalAlignment = xlLeft
        End With
    End If

    ' 3行目が表ヘッダーの場合も多いので装飾(A3行)
    If ws.Cells(3, 1).Value <> "" And ws.Cells(3, 2).Value <> "" Then
        With ws.Range(ws.Cells(3, 1), ws.Cells(3, rng.Columns.Count))
            .Interior.Color = RGB(235, 235, 235)
            .Font.Bold = True
            .HorizontalAlignment = xlCenter
        End With
    End If

    ws.Columns.AutoFit

    ' オートフィルタ(A1起点の表がある場合)
    On Error Resume Next
    If Not ws.AutoFilterMode Then
        If ws.Range("A1").Value <> "" Then ws.Range("A1").CurrentRegion.AutoFilter
    End If
    On Error GoTo 0
End Sub

' 安全なグラフ作成(AddChart2禁止対応)
Sub AddSafeChart(ws As Worksheet, dataRng As Range, title As String, leftPos As Double, topPos As Double, chartType As XlChartType)
    Dim co As ChartObject
    Set co = ws.ChartObjects.Add(leftPos, topPos, 520, 260)

    With co.Chart
        .SetSourceData dataRng
        .ChartType = chartType
        .HasTitle = True
        .ChartTitle.Text = title
        .HasLegend = True
    End With
End Sub

' ==========================================
' データ生成用ヘルパー
' ==========================================

Private Function PickExpenseSubCategory(cat As String) As String
    Dim arr As Variant
    Select Case cat
        Case "食費": arr = Array("米・パン", "肉・魚", "野菜", "飲料", "調味料")
        Case "外食": arr = Array("ランチ", "ディナー", "カフェ", "デリバリー")
        Case "住居": arr = Array("家賃", "管理費", "修繕", "家具")
        Case "光熱費": arr = Array("電気", "ガス", "水道")
        Case "通信": arr = Array("携帯", "固定回線", "端末分割")
        Case "交通": arr = Array("電車", "バス", "タクシー", "ガソリン")
        Case "日用品": arr = Array("洗剤", "紙類", "雑貨")
        Case "医療": arr = Array("病院", "薬", "検診")
        Case "教育": arr = Array("学費", "教材", "習い事")
        Case "娯楽": arr = Array("映画", "ゲーム", "旅行", "本")
        Case "交際": arr = Array("飲み会", "贈答", "冠婚葬祭")
        Case "衣服": arr = Array("服", "靴", "小物")
        Case "美容": arr = Array("化粧品", "美容院", "ケア用品")
        Case "サブスク": arr = Array("動画", "音楽", "クラウド", "アプリ")
        Case Else: arr = Array("その他")
    End Select
    PickExpenseSubCategory = CStr(arr(Int((UBound(arr) + 1) * Rnd)))
End Function

Private Function PickIncomeSubCategory(cat As String) As String
    Dim arr As Variant
    Select Case cat
        Case "給与": arr = Array("基本給", "残業", "手当")
        Case "賞与": arr = Array("夏", "冬")
        Case "副業": arr = Array("業務委託", "物販", "配達")
        Case Else: arr = Array("その他")
    End Select
    PickIncomeSubCategory = CStr(arr(Int((UBound(arr) + 1) * Rnd)))
End Function

Private Function PickShopByCategory(cat As String, shops As Variant) As String
    Select Case cat
        Case "住居": PickShopByCategory = "家賃"
        Case "光熱費": PickShopByCategory = CStr(Array("電力会社", "ガス会社")(Int(2 * Rnd)))
        Case "通信": PickShopByCategory = "通信会社"
        Case "交通": PickShopByCategory = "交通機関"
        Case "医療": PickShopByCategory = "病院"
        Case "サブスク": PickShopByCategory = "サブスク事業者"
        Case "外食": PickShopByCategory = "外食店"
        Case "日用品": PickShopByCategory = CStr(Array("ドラッグストア", "スーパー")(Int(2 * Rnd)))
        Case Else
            PickShopByCategory = CStr(shops(Int((UBound(shops) + 1) * Rnd)))
    End Select
End Function

Private Function PickExpenseUnitPrice(cat As String) As Long
    Dim v As Long
    Select Case cat
        Case "住居"
            v = (Int(6 * Rnd) + 7) * 10000 ' 70,000 - 120,000
        Case "光熱費"
            v = (Int(8 * Rnd) + 3) * 1000 ' 3,000 - 10,000
        Case "通信"
            v = (Int(8 * Rnd) + 3) * 1000 ' 3,000 - 10,000
        Case "交通"
            v = (Int(20 * Rnd) + 2) * 100 ' 200 - 2,100
        Case "医療"
            v = (Int(40 * Rnd) + 5) * 100 ' 500 - 4,400
        Case "サブスク"
            v = (Int(15 * Rnd) + 5) * 100 ' 500 - 1,900
        Case "外食"
            v = (Int(60 * Rnd) + 5) * 100 ' 500 - 6,400
        Case "食費"
            v = (Int(60 * Rnd) + 1) * 100 ' 100 - 6,000
        Case "娯楽"
            v = (Int(80 * Rnd) + 2) * 100 ' 200 - 8,100
        Case "交際"
            v = (Int(100 * Rnd) + 5) * 100 ' 500 - 10,400
        Case Else
            v = (Int(80 * Rnd) + 1) * 100 ' 100 - 8,000
    End Select
    PickExpenseUnitPrice = v
End Function

Private Function PickExpenseQty(cat As String) As Long
    Dim q As Long
    Select Case cat
        Case "住居", "光熱費", "通信", "サブスク"
            q = 1
        Case "外食"
            q = Int(3 * Rnd) + 1
        Case "交通"
            q = Int(10 * Rnd) + 1
        Case Else
            q = Int(5 * Rnd) + 1
    End Select
    PickExpenseQty = q
End Function

Private Function PickIncomeUnitPrice(cat As String) As Long
    Dim v As Long
    Select Case cat
        Case "給与"
            v = (Int(21 * Rnd) + 20) * 10000 ' 200,000 - 400,000
        Case "賞与"
            v = (Int(41 * Rnd) + 10) * 10000 ' 100,000 - 500,000
        Case "副業"
            v = (Int(31 * Rnd) + 3) * 10000 ' 30,000 - 330,000
        Case Else
            v = (Int(51 * Rnd) + 1) * 1000 ' 1,000 - 51,000
    End Select
    PickIncomeUnitPrice = v
End Function

1. 家計簿_データ

  • ダミーデータを含む元データシート
  • 金額、収支、年月、週などはすべて数式
  • 実データに差し替えても壊れない構成

image.png

2. EDA(探索的データ分析)

  • 集計期間、総収入・総支出、差額
  • 支出の平均、中央値、ばらつき
  • 月別の支出・収入・差額推移

image.png

数式も入ることがわかる。たまに間違えることもあるのでどちらかというとこのテンプレを基礎としてピボットテーブルにした方がよい。

3. クロス集計

  • カテゴリ × 月(支出)
  • 支払方法 × カテゴリ(支出)

image.png

見やすいようにしたのと、数式がおかしい部分があったので修正をかけている。

4. 改善提案(何をすべきか/期待効果)

  • 削減率を入力するだけで
    年間・月間の削減効果を数式で算出
  • 施策の優先順位を金額ベースで整理

image.png

元データから数字をとり、そこからどの程度数字が改善されるかがわかる。


活用の視点

全体像の把握

EDAシートを見ることで、毎月どのカテゴリにどの程度支出しているかを俯瞰できる。
特に支出・収入・差額を並べることで、黒字月と赤字月の構造が明確になる。

固定費が重いのか、変動費が膨らんでいるのかを初期段階で切り分けられる点は有効である。


無意識で出している支出はなにか?

クロス集計を確認すると、

  • 特定の月だけ突出しているカテゴリ
  • 特定の支払方法に偏った支出
  • 毎月なんとなく発生している支出

といった「意識されにくい支出」が可視化される。
単純な合計表では得られない示唆である。


行動への接続

改善提案シートでは、

  • 外食費を10%削減すると年間いくらか
  • サブスクを整理すると月いくら改善するか

といった行動と金額の対応関係が明示される。
これにより、効果の小さい施策に時間を使わず、
費用対効果の高い施策から着手できる。


検証結果のまとめ

  • 説明可能性の観点では、十分に実用的な分析テンプレートである
  • 一方で、「なぜそうなっているのか」という仮説の根拠までは自動化できない
    (これは業務知識や文脈理解の領域である)
  • しかし、テンプレートとしての完成度は高く、LLMの種類に依存しにくい
  • 元データを差し替えるだけで、分析設計から実装までを効率化できる点は大きな利点である

おわりに

高度な分析をする手法ではないが、
分析に不慣れな人が、まず「使える形」に到達するための補助線としては有効になる。
また分析に慣れている人にとっては、詳細な分析設計を指示することで、より詳細に成果物をイメージできることが見込める。

Excelという既存資産とLLMを組み合わせることで、
「分析できない」ではなく「どう使うかを考える」段階に進める。

業務データへの応用も容易であり、
データ活用の初動を加速させる手段として有効であると考える。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?