Pandasの groupby + unstack の話を、**Excel VBA(マクロ)**の世界に置き換えた記事ドラフトを作成しました。
VBAでピボットテーブル機能(PivotCaches.Create など)を使わずにクロス集計を行う場合、**「連想配列(Dictionary)」**を使うのが定石です。これがPandasでいうIndex操作に相当します。
「ピボットテーブル機能は重い」「書式が勝手に変わるのが嫌」という層に刺さる、実務的な内容にしています。
記事タイトル案
【Excel VBA】ピボットテーブルは使わない。Dictionary(連想配列)で高速にクロス集計するテクニック
記事本文(ドラフト)
はじめに
Excel VBAでクロス集計を行う際、マクロの記録で生成された PivotCaches.Create や CreatePivotTable をそのまま使っていませんか?
もちろん標準機能は強力ですが、自動化の文脈では以下のような悩みが生じがちです。
ファイルサイズ肥大化: ピボットキャッシュが残ってファイルが重くなる。
書式崩れ: 更新のたびに列幅や罫線がリセットされるのを防ぐコードが必要。
配布時のトラブル: バージョン間の互換性や、ユーザーが誤ってピボットを操作してしまうリスク。
今回は、ピボットテーブル機能(オブジェクト)に頼らず、**「配列」と「連想配列(Dictionary)」**を使って、純粋な値としてクロス集計結果を算出する方法を紹介します。Python Pandasでいう groupby 処理をVBAで自作するイメージです。
ロジックの考え方:Index + Key
Pandasなどのデータフレーム操作に慣れている方なら、以下の対比が分かりやすいでしょう。
Pandas: groupby(['Date', 'Category'])['Value'].sum()
VBA: Dictionary オブジェクトの Key に 'Date_Category' という複合キーを持たせて加算する
VBAでは Dictionary をインデックス(検索キー)として利用することで、高速な集計が可能になります。
コード解説
以下のサンプルデータ(Sheet1)を集計します。
A列: 日付 (Date)
B列: カテゴリ (Category)
C列: 数値 (Value)
サンプルコード
参照設定で「Microsoft Scripting Runtime」にチェックを入れるか、以下の通りLate Binding(CreateObject)で使用します。
VBA
Sub ManualPivotAggregation()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Sheet1")
' 1. データを配列に取り込む(高速化の基本)
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
Dim dataArr As Variant
' A列~C列をまとめて取得
dataArr = ws.Range("A2:C" & lastRow).Value
' 2. Dictionaryで集計(Group By)
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Dim i As Long
Dim key As String
Dim dateVal As String, catVal As String, amount As Double
For i = 1 To UBound(dataArr, 1)
dateVal = dataArr(i, 1)
catVal = dataArr(i, 2)
amount = dataArr(i, 3)
' 複合キーを作成(例: "2024/01/01|A")
key = dateVal & "|" & catVal
' 集計ロジック:キーが存在すれば加算、なければ新規登録
If dict.Exists(key) Then
dict(key) = dict(key) + amount
Else
dict(key) = amount
End If
Next i
' 3. 結果の出力(Unstack的な処理)
' ここでは簡易的に別シートのリストとして出力します
Dim outWs As Worksheet
Set outWs = ThisWorkbook.Sheets.Add
' ヘッダー
outWs.Range("A1:C1").Value = Array("Date", "Category", "Sum_Value")
Dim k As Variant
Dim parts() As String
Dim r As Long
r = 2
' Dictionaryの中身を展開
For Each k In dict.Keys
parts = Split(k, "|") ' キーを分解
outWs.Cells(r, 1).Value = parts(0) ' Date
outWs.Cells(r, 2).Value = parts(1) ' Category
outWs.Cells(r, 3).Value = dict(k) ' Sum
r = r + 1
Next k
MsgBox "集計完了"
End Sub
なぜこの方法が良いのか?
-
圧倒的に高速
セルを1つずつループして処理すると遅いですが、上記のように Range.Value で配列に一度取り込んでからメモリ上で計算すれば、数万〜数十万行のデータでも数秒以内で完了します。ピボットテーブルの更新処理よりも速いケースが多いです。 -
「完全な値」として出力できる
ピボットテーブルはあくまで「表示機能」を持ったオブジェクトですが、この方法なら純粋な「値(文字列・数値)」としてセルに出力されます。
他のブックへのコピペが容易
書式設定(塗りつぶしや罫線)が完全に自由
配布先でピボットの操作ミスが発生しない
- 複雑な条件分岐が可能
ピボットテーブルの「集計フィールド」では難しいような条件(例:「AかつBの場合は係数0.8を掛けて集計」など)も、If 文一つで自由に組み込めます。
実務での応用(マトリクス表にするには?)
上記のコードはリスト形式(縦持ち)での出力ですが、クロス表(マトリクス)にしたい場合は、辞書を2つ用意する方法が一般的です。
RowDict: 行見出し(日付など)の管理
ColDict: 列見出し(カテゴリなど)の管理
ResultArr: (RowDict.Count, ColDict.Count) の2次元配列を用意して集計値を格納
これらを組み合わせることで、ピボットテーブルと全く同じ見た目を、VBAだけで自在に生成・制御できるようになります。
まとめ
手軽な分析 → 標準のピボットテーブル
定型レポートの自動化・配布 → VBA配列 + Dictionary
VBAで「ピボットテーブルの操作」と格闘して消耗している方は、一度「自前で集計してしまう」というアプローチを検討してみてはいかがでしょうか。処理速度とメンテナンス性が劇的に向上するはずです。