2つのExcelブックの内容を比較する

Excelブック(Aとします)で表が出来ていて、その表に適時に必要事項を入力して更新していくという作業があるとします。一定期間、たとえば月ごとで報告書なりの形式で出力し、Excelブックをそこで別名保存(Bとします)して、そのブックは更新を終了します。元のブック(A)は引き続き更新処理が継続されます。

このようにマスターとなるAブックから、一定期間でBブックを切り出して保存していく、という世代管理的手法は結構おこなわれているのではないかと思います。

また、前世代のBをコピーして変更したところだけ書き換えてB’として保存。これを繰り返していく方法も、実務あるあるではないでしょうか。

ところで、あるBと、その1つ前の世代のBでどこが変更されたのか知りたいならば、あなたはどうしますか?

目視は論外として、ワークシート関数やフィルターを使った方法をいくつか思いつきますが、毎回違うファイルが対象になるとしたら、手軽さや取り回しの良さは、それらの方法では期待できないことは想像に難くないでしょう。

ここはプログラムにやらせましょう。

VBAでブックの内容を比較する

やることは極めて単純で、対象のブックを両方開いて双方の同じセルアドレスの値をひたすら比較していくだけです。

サンプルとしてシートは1枚でA1セルから始まる10×1000の表を作成しました。値はわかりやすいように全部1にしておきます。

このブックをコピーして片方の値を一部書き換えます。

ロジックは単純明快なので早速コーディングします。処理するファイルとは切り離しておきたいので専用ブックを作成してそこへプロシージャを記述します。

Sub diff()
    Dim wb1 As Workbook
    Dim wb2 As Workbook
    Set wb1 = Workbooks.Open("C:\Users\ore\Desktop\1.xlsx")
    Set wb2 = Workbooks.Open("C:\Users\ore\Desktop\1111.xlsx")
    
    Dim r1 As Range
    Set r1 = wb1.Worksheets(1).Range("a1").CurrentRegion

    Dim r2 As Range
    Set r2 = wb2.Worksheets(1).Range("a1").CurrentRegion

    Dim row As Long
    Dim col As Long

    For row = 1 To r1.Rows.Count
        For col = 1 To r1.Columns.Count
            If Not r1(row, col).Value = r2(row, col).Value Then
                Debug.Print r1(row, col).Address
            End If
        Next
    Next
    
    wb1.Close
    wb2.Close
End Sub

実行結果は

ファイルパスを決め打ってますが、ここをセル値から引っ張ってくるようにしたり、IfがTrueのときの処理をワークシートから好きなように設定できるようにしたりすれば実用になるかと思います。

目的は達成しましたが、ここで終わってしまうわけにはいきません。このサイトのコンセプトExcel×Pythonを実践するまでは!

Pythonでブックの内容を比較する

同じ処理をPythonで書いてみます。ExcelファイルはOpenPyXLで扱います。

import openpyxl as excel

wb1 = excel.load_workbook(r'C:\Users\ore\Desktop\1.xlsx')
wb2 = excel.load_workbook(r'C:\Users\ore\Desktop\1111.xlsx')
ws1 = wb1.active
ws2 = wb2.active

for row in ws1:
    for cell in row:
        if ws1[cell.coordinate].value != ws2[cell.coordinate].value:
            print(cell.coordinate)

ものっすごいシンプルなコードです。神ツールOpenPyXLがよしなにやってくれていることや、Pythonの記法によるところも大きいですが、VBAは変数の宣言にかなりの文字数を費やさなければならないことがおわかりでしょう。

実行結果は

VBAでも変数宣言を省略できますが、絶対にやめましょう。理由はさんざんネット上に書かれていますのでググってくださいね。Pythonはそもそも宣言という概念がなく型の記述もありません。

素でメモ帳などにコーディングしていくとしたら、Pythonのこの仕様は案外キツいと思います。VBAで変数宣言をはしょるのがダメな理由が概ね当てはまります。しかし、この弱点はIDEが補完してくれます。IDEとPythonが組み合わさると、最強のシンプルコーディングが実現します。

VBAもエディタがもう少しまともに構文解析をしてくれるようになるか、IDEでコーディングできるようになれば変数宣言はする必要がなくなるでしょう。IDEについては別記事がありますのでぜひご覧ください。

VBAは遅いのか?~速度比較~

ここまでできると誰でも思うことがあります。「これどっちが速いの?」

VBAは他言語より遅いというイメージが世間一般には広まっている気がします。はたして本当でしょうか?比べてみましょう。

ネットで拾ってきたタイム計測コードを前述のコードに組み込み実行します。コードの先頭で計測開始、すべての処理が終わって計測終了します。

使用するPCのスペックは次の通りです。

ストレージはSSDです。数世代前のCPUとはいえ、事務所のPCといえばいまだにCeleronあたりが幅をきかせている有様なので、それよりはずっと速い環境なのはご承知置きください。

まずはVBA

0.59秒。

次にPython

0.25秒。

ええ、もう誤差の範囲です。どちらも待ち時間はないに等しいです。Pythonが圧勝かなと思っていましたが、意外にVBAが速かったです。

表は10×1000の大きさなので1万個のセルを総当たりしたことになります。これをもっと増やします。

10,000行まで拡張して計10万個のセルとしました。最後のセルまで走査しているかの確証のためJ10000のセルを違う値にしておきます。

VBA

1.28秒。

Python

あれ?

もう一回

2.75秒。

逆転されました。

実務で使う分にはまだまだ問題にはならない時間ですが、セルが多くなるとOpenPyXLの変換コストが高くなるということでしょうか。さすがに本家本元と同じようにはいかないようです。


ブック間で値が違うセルが大量にあったらどうなるか試してみます。

1~5000行目を違う値にしました。10×5000で5万回のログ生成&出力が加わります。

VBA

12.45秒。あからさまに遅くなりました。イミディエイトウィンドウに逐次出力されていくので、そこがボトルネックになっている模様。

Python

3.67秒。出力は最後にズドンなので、それほど速度に影響していないようです。

VBAの出力先をイミディエイトウィンドウではなくセルにしてみます。

Debug.Print r1(row, col).Address

ここを、値が違うセルがわかればいいのでセル背景色を赤にするコードへ書き換えて

r2(row, col).Interior.Color = 255

実行すると

こうなり

2.53秒でした。

イミディエイトウィンドウがやたら遅いということですね。

5万セルの書き換えでこの程度なら十分実用に耐えるのではないでしょうか。セルアドレスをログに取りたいならば、配列に入れておいて、最後にシートへ一発で書き出す方法で同程度の時間になるはずです。


ちなみにOpenPyXLでやるとすると

print(cell.coordinate)

これを

ws2[cell.coordinate].fill = PatternFill('solid', fgColor='ff0000')

こう書き換えて(関連モジュールのimportも必要)

6.36秒でした。

やっぱりOpenPyXLのセルアクセスでコストがかかっていますね。

まとめ

餅は餅屋に。

注意する点として、OpenPyXLに限らずVBA以外でExcelを扱うモジュールはどれもそうですが(昔さわったPHPでも同様でした)書式の扱いは苦手なようで、「見た目」の再現性はかなりいい加減です。

ファイルを開いて何もタッチしないで保存しただけでも、凝ったレイアウト(いわゆる神エクセル)であればあるほど原型をとどめません。あくまで値を処理するためのものと割り切った方がいいです。


おわり。

参考サイト

時間計測はそれぞれ次のサイトの方法を使用しました。

VBA

【ExcelVBA入門】処理時間を計測するためのTimer関数の使い方とは | 侍エンジニアブログ
この記事では「 【ExcelVBA入門】処理時間を計測するためのTimer関数の使い方とは 」といった内容について、誰でも理解できるように解説します。この記事を読めば、あなたの悩みが解決するだけじゃなく、新たな気付きも発見できることでしょう...

Python

おわり。