【簡単Excelマクロ・VBA】配列を使って繰り返し処理を高速化する方法|ワンポイントテクニック #003
いつもありがとうございます。
ノンプログラマー向け「Excelマクロ・VBAワンポイントテクニック解説シリーズ」へようこそ。
本稿では、「配列を使って繰り返し処理を高速化する方法」を解説いたします。
VBAで大規模なデータを処理する場合、通常のFor...Next ループなどの繰り返し処理でセルをひとつずつ見ていくような方法では時間がかかりすぎます。
つまり、VBAで大規模なデータを処理する場合には、処理速度を改善することが課題となります。
このような課題を解決するために有効な方法が、「配列」を使った繰り返し処理です。
本稿では、配列を使ってExcelの処理を高速化する方法とその仕組みについて解説します。
ことばの意味
- ノンプログラマー
プログラミングを本職としない人たちのことです。 - マクロ
VBAを使って作成される「機能」のことです。 - VBA
Visusal Basic for Application の略で、プログラミング言語のことです。
関連記事
配列とは?
配列とは、複数の値をまとめて扱うことができる、データの入れ物です。Excelで言うと、配列は連続したセル範囲に似ています。
通常、セルごとにひとつずつ値を操作しますが、配列を使えば複数の値を連続して格納し、一度に操作できます。
つまり、配列にデータを取り込むことで、VBA上でまとめてデータを操作できるため、セルをひとつずつ処理するよりも効率が上がるのです。
なぜ配列にすると処理速度が向上する?
VBAでセルをひとつずつ操作する場合、各処理ごとに「オーバーヘッド」と呼ばれる準備作業が発生します。これは、Excelのセルからデータを取得したり、書き込んだりする際に、Excelとの間で発生するやり取りのことです。こうした作業が繰り返されることで、全体の処理速度が低下します。
配列を使うと、一度にデータをメモリ上に読み込み、VBA内で操作を完結させられます。これによりExcelとのやり取りが減ります。つまり、準備作業(オーバーヘッド)も大幅に削減されるため、処理速度が向上するのです。
さらに、セルごとにデータを操作すると、そのたびに画面の描画(レンダリング)が発生します。Excelはセルの内容が更新されるたびに、画面に変化を反映させるため、描画の処理に時間がかかるのです。配列を使えば、VBA上で全ての操作を完了させた後にまとめてセルに戻せるため、描画(レンダリング)が一度で済み、処理速度が向上するのです。
つまり、配列を使うことで準備作業(オーバーヘッド)と画面描画(レンダリング)の負担が減り、処理速度が向上するのです。
VBAで配列を使って繰り返し処理を高速化する方法
シナリオ
A列の値を10倍にしてB列に書き出すコードを作成します。ワークシートのA列には、次のように10,000行分のランダムな数値が入力されています。
コード比較:配列なし vs 配列あり
「配列を使わない方法」と「配列を使う方法」のコードの比較をしていきます。
配列を使わない方法
最初に、配列を使わずに、セルを1つずつ走査する方法を見ていきましょう。
Sub ProcessWithoutArray()
' 変数宣言
Dim wb As Workbook ' 現在のブックを参照するオブジェクト変数
Dim ws As Worksheet ' 処理対象のワークシートを参照するオブジェクト変数
Dim row_idx As Long ' ループ用の行インデックス変数
' 現在のブックと最初のシートを変数に設定
Set wb = ThisWorkbook
Set ws = wb.Worksheets(1)
' 処理開始時刻を表示(処理時間の計測用)
Debug.Print "開始時刻: " & Now
' 各セルの値を10倍にしてB列に出力(セルを直接操作するためオーバーヘッドが発生)
For row_idx = 1 To 10000
ws.Cells(row_idx, 2).Value = ws.Cells(row_idx, 1).Value * 10 ' B列に書き出し
Next row_idx
' 処理終了時刻を表示(処理時間の計測用)
Debug.Print "終了時刻: " & Now
End Sub
この方法では、A列の値を1つずつ読み取り、10倍にしてB列に書き出しています。しかし、セルへのアクセスが1回ずつ行われるため、処理に時間がかかります。ここで発生するのが、Excelとのやり取りにかかる「オーバーヘッド」と、各セル操作後の「レンダリング(画面更新)」の負担です。
配列を使う方法
Sub ProcessWithArray()
' 変数宣言
Dim wb As Workbook ' 現在のブックを参照するオブジェクト変数
Dim ws As Worksheet ' 処理対象のワークシートを参照するオブジェクト変数
Dim data_array() As Variant ' データを一時的に格納する配列(Rangeから代入するため静的配列として扱われる)
Dim row_idx As Long ' ループ用の行インデックス変数
Dim target_range As Range ' 対象範囲を参照する変数
' 現在のブックと最初のシートを変数に設定
Set wb = ThisWorkbook
Set ws = wb.Worksheets(1)
' 対象範囲を変数に設定
Set target_range = ws.Range(ws.Cells(1, 1), ws.Cells(10000, 1))
' 処理開始時刻を表示(処理時間の計測用)
Debug.Print "開始時刻: " & Now
' 対象範囲のデータを配列に取り込む(Rangeから代入することでサイズが確定され、静的配列として扱われる)
data_array = target_range.Value
' 配列内の値を10倍にしてB列用の配列に準備
For row_idx = 1 To UBound(data_array, 1)
data_array(row_idx, 1) = data_array(row_idx, 1) * 10
Next row_idx
' 計算結果をB列に一括で書き出し
ws.Range(ws.Cells(1, 2), ws.Cells(10000, 2)).Value = data_array
' 処理終了時刻を表示(処理時間の計測用)
Debug.Print "終了時刻: " & Now
End Sub
この方法では、A列のデータを配列に一括で取り込み、メモリ上で計算を行います。計算結果は配列に保持され、処理が終わったら一度だけB列に書き戻すため、セルごとのやり取り(オーバーヘッド)が大幅に減り、レンダリングも最小限に抑えられます。
次のセクションで、それぞれのコードの詳細な解説をしていきます。
コードの詳細な解説
配列を使わない方法
繰り返しになりますが、まずコード全文を次に示します。
Sub ProcessWithoutArray()
' 変数宣言
Dim wb As Workbook ' 現在のブックを参照するオブジェクト変数
Dim ws As Worksheet ' 処理対象のワークシートを参照するオブジェクト変数
Dim row_idx As Long ' ループ用の行インデックス変数
' 現在のブックと最初のシートを変数に設定
Set wb = ThisWorkbook
Set ws = wb.Worksheets(1)
' 処理開始時刻を表示(処理時間の計測用)
Debug.Print "開始時刻: " & Now
' 各セルの値を10倍にしてB列に出力(セルを直接操作するためオーバーヘッドが発生)
For row_idx = 1 To 10000
ws.Cells(row_idx, 2).Value = ws.Cells(row_idx, 1).Value * 10 ' B列に書き出し
Next row_idx
' 処理終了時刻を表示(処理時間の計測用)
Debug.Print "終了時刻: " & Now
End Sub
解説していきます。
' 変数宣言
Dim wb As Workbook ' 現在のブックを参照するオブジェクト変数
Dim ws As Worksheet ' 処理対象のワークシートを参照するオブジェクト変数
Dim row_idx As Long ' ループ用の行インデックス変数
ここでは、コード内で使用する変数が宣言されています。変数は Dim 変数名 As データ型
の構文で宣言します。これにより、各変数は As
以降で指定したデータ型のデータを格納できるようになります。
ことばの意味
- 変数
「値」を入れておく「箱」のようなものです。変数は、その名の通りコード内の指示によって値を変えることができます。
' 現在のブックと最初のシートを変数に設定
Set wb = ThisWorkbook
Set ws = wb.Worksheets(1)
ここでは、オブジェクト変数 wb
と ws
に、それぞれワークブックオブジェクトとワークシートオブジェクトを代入しています。なお、ThisWorkbook
は現在のワークブック、つまりこのコードが記述されているワークブックを指します。Worksheets(1)
は、ブック内の最初のシートを指します。
ことばの意味
- オブジェクト
特定の役割を持つ道具のようなものです。
' 処理開始時刻を表示(処理時間の計測用)
Debug.Print "開始時刻: " & Now
ここでは、処理の開始時刻をイミディエイトウィンドウに出力しています。Debug.Print
は、デバッグ用の出力コマンドで、Now
を使って現在の日時を表示します。これにより、コードの実行時間を確認することができ、開始時刻と終了時刻の差から処理時間を把握できます。
これは、コードの機能には直接関係ありませんが、この記事をご覧の多くの方は処理時間を把握したいと考えていると思いますので、参考として追加しています。本番のコードでは不要ですので、削除もしくはコメントアウトしておきましょう。
なお、イミディエイトウィンドウとはデバッグ出力用の画面で、デフォルトでは画面下の方に表示されます。
表示メニューから表示させることができます。
' 各セルの値を10倍にしてB列に出力(セルを直接操作するためオーバーヘッドが発生)
For row_idx = 1 To 10000
ws.Cells(row_idx, 2).Value = ws.Cells(row_idx, 1).Value * 10 ' B列に書き出し
Next row_idx
ここでは、A列の各セルの値を10倍にし、その結果をB列に出力しています。
ループで1行目から10,000行目までを順に処理し、各行のA列の値を10倍にしてB列に書き込んでいます。このようにセルを1つずつ操作すると、Excelとのやり取りが繰り返されるため、オーバーヘッドが発生して処理速度が低下します。
' 処理終了時刻を表示(処理時間の計測用)
Debug.Print "終了時刻: " & Now
ここでは、処理の終了時刻をイミディエイトウィンドウに出力しています。Debug.Print
は、デバッグ用の出力コマンドで、Now
を使って現在の日時を表示します。これにより、コードの実行時間を確認することができ、開始時刻と終了時刻の差から処理時間を把握できます。
これは、コードの機能には直接関係ありませんが、この記事をご覧の多くの方は処理時間を把握したいと考えていると思いますので、参考として追加しています。本番のコードでは不要ですので、削除もしくはコメントアウトしておきましょう。
以上が、配列を使わないオーソドックスな方法です。シンプルですが、処理速度に課題がある場合には、次に解説する配列を活用した方法も検討してみてください。
配列を使う方法
コード全文を次に示します。
Sub ProcessWithArray()
' 変数宣言
Dim wb As Workbook ' 現在のブックを参照するオブジェクト変数
Dim ws As Worksheet ' 処理対象のワークシートを参照するオブジェクト変数
Dim data_array() As Variant ' データを一時的に格納する配列(Rangeから代入するため静的配列として扱われる)
Dim row_idx As Long ' ループ用の行インデックス変数
Dim target_range As Range ' 対象範囲を参照する変数
' 現在のブックと最初のシートを変数に設定
Set wb = ThisWorkbook
Set ws = wb.Worksheets(1)
' 対象範囲を変数に設定
Set target_range = ws.Range(ws.Cells(1, 1), ws.Cells(10000, 1))
' 処理開始時刻を表示(処理時間の計測用)
Debug.Print "開始時刻: " & Now
' 対象範囲のデータを配列に取り込む(Rangeから代入することでサイズが確定され、静的配列として扱われる)
data_array = target_range.Value
' 配列内の値を10倍にしてB列用の配列に準備
For row_idx = 1 To UBound(data_array, 1)
data_array(row_idx, 1) = data_array(row_idx, 1) * 10
Next row_idx
' 計算結果をB列に一括で書き出し
ws.Range(ws.Cells(1, 2), ws.Cells(10000, 2)).Value = data_array
' 処理終了時刻を表示(処理時間の計測用)
Debug.Print "終了時刻: " & Now
End Sub
解説していきます。
' 変数宣言
Dim wb As Workbook ' 現在のブックを参照するオブジェクト変数
Dim ws As Worksheet ' 処理対象のワークシートを参照するオブジェクト変数
Dim data_array() As Variant ' データを一時的に格納する配列(Rangeから代入するため静的配列として扱われる)
Dim row_idx As Long ' ループ用の行インデックス変数
Dim target_range As Range ' 対象範囲を参照する変数
ここでは、コード内で使用する変数が宣言されています。変数は Dim 変数名 As データ型
の構文で宣言します。これにより、各変数は As
以降で指定したデータ型のデータを格納できるようになります。
' 現在のブックと最初のシートを変数に設定
Set wb = ThisWorkbook
Set ws = wb.Worksheets(1)
ここでは、オブジェクト変数 wb
と ws
に、それぞれワークブックオブジェクトとワークシートオブジェクトを代入しています。
ThisWorkbook
は現在のワークブック、つまりこのコードが記述されているワークブックを指します。Worksheets(1)
は、ブック内の最初のシートを指します。
これで、ws
を使ってシート上のセルを操作できます。
' 対象範囲を変数に設定
Set target_range = ws.Range(ws.Cells(1, 1), ws.Cells(10000, 1))
ここでは、target_range
変数に対象のセル範囲(A列の1行目から10,000行目)を代入しています。ws.Range(ws.Cells(1, 1), ws.Cells(10000, 1))
は、ワークシートws
上のセル範囲を指定しており、A1セルからA10000セルまでの範囲を表しています。
この設定により、target_range
を通じて指定した範囲をまとめて扱えるようになります。このように範囲を変数として定義することで、コードが見やすくなり、範囲を柔軟に変更できる利点もあります。
' 処理開始時刻を表示(処理時間の計測用)
Debug.Print "開始時刻: " & Now
ここでは、処理の開始時刻をイミディエイトウィンドウに出力しています。Debug.Print
は、デバッグ用の出力コマンドで、Now
を使って現在の日時を表示します。これにより、コードの実行時間を確認することができ、開始時刻と終了時刻の差から処理時間を把握できます。
これは、コードの機能には直接関係ありませんが、この記事をご覧の多くの方は処理時間を把握したいと考えていると思いますので、参考として追加しています。本番のコードでは不要ですので、削除もしくはコメントアウトしておきましょう。
' 対象範囲のデータを配列に取り込む(Rangeから代入することでサイズが確定され、静的配列として扱われる)
data_array = target_range.Value
ここでは、target_range
(A列の1行目から10,000行目)のデータを、data_array
という配列に一括で取り込んでいます。これにより、Excelとやり取りすることなく、data_array
内でデータをメモリ上で処理することが可能になります。
なお、data_array
は、 Dim data_array()
で宣言しているので、一見すると動的配列のように見えますが、これは動的配列ではなく静的配列です。なぜならば、Range
からデータを直接代入することで、data_array
のサイズが自動的に決定されるためです。一度サイズが確定すると、data_array
は動的にサイズ変更できないため、静的配列として扱われます。
' 配列内の値を10倍にしてB列用の配列に準備
For row_idx = 1 To UBound(data_array, 1)
data_array(row_idx, 1) = data_array(row_idx, 1) * 10
Next row_idx
ここでは、data_array
内の各要素に10を掛ける処理を行っています。For
ループを使用して、data_array
の先頭から最後まで順に処理し、A列の値を10倍に変換しています。UBound(data_array, 1)
は、配列の最終行番号を表します。data_array(row_idx, 1)
のrow_idx
は配列の行番号のインデックス、1
は列番号のインデックスを表します。
このように、配列上で計算を行うことで、Excelシートとやり取りすることなく、メモリ上で効率的にデータを処理し、B列用のデータを作成しています。
' 計算結果をB列に一括で書き出し
ws.Range(ws.Cells(1, 2), ws.Cells(10000, 2)).Value = data_array
このコードでは、data_array
に格納した10倍の計算結果を、B列に一括で書き出しています。ws.Range(ws.Cells(1, 2), ws.Cells(10000, 2)).Value = data_array
により、B列の1行目から10,000行目までの範囲に配列data_array
のデータをまとめて出力します。
このように、一度に範囲を指定してデータを書き出すことで、各セルに個別に書き込む場合に比べ、処理速度が大幅に向上します。ExcelとVBAの間のやり取りを最小限に抑えることができるため、オーバーヘッドが減り、効率的なデータ操作が可能になります。
' 処理終了時刻を表示(処理時間の計測用)
Debug.Print "終了時刻: " & Now
ここでは、処理の終了時刻をイミディエイトウィンドウに出力しています。Debug.Print
は、デバッグ用の出力コマンドで、Now
を使って現在の日時を表示します。これにより、コードの実行時間を確認することができ、開始時刻と終了時刻の差から処理時間を把握できます。
これは、コードの機能には直接関係ありませんが、この記事をご覧の多くの方は処理時間を把握したいと考えていると思いますので、参考として追加しています。本番のコードでは不要ですので、削除もしくはコメントアウトしておきましょう。
以上が、配列を使ってVBAコードの処理速度を向上させる方法です。配列を活用することで、ExcelとVBA間のやり取りを最小限に抑え、大量データを効率的に処理できます。ぜひ参考にして、実際の業務でも活用してみてください。
以上で解説は終了です。ありがとうございました。
おわりに
ご覧いただきありがとうございました。
今回の記事では、セルの基本操作のうち、「配列を使って繰り返し処理を高速化する方法」を解説いたしました。
お問い合わせやご要望等ございましたら、「お問い合わせ/ご要望」またはコメントにて、ご連絡いただければ幸いでございます。
皆様の人生がより一層素晴らしいものになるよう、少しでもお役に立てれば幸いでございます。
なお、当サイトでは様々な情報を発信しております。もしよろしければ、トップページもご覧いただけると幸いでございます。
筆者の記事関連経験
- VBA使用経験約20年
実務に使用するマクロを多数作成してきました。 - Python 3 エンジニア認定基礎試験
経済産業省が定めたガイドライン「ITスキル標準(ITSS)」に掲載されている民間資格です。
VBAプログラミングスキルアップのための参考情報
ここでは参考図書を紹介いたしますが、これらに限らず自分に合うものを選ぶことが重要だと考えております。皆様の、より一層のご成功を心よりお祈りしております。
VBAプログラミングのスキルアップ
学習用としてもハンドブックとしても役立つ便利な書籍がこちらです。価格はやや高めですが、その内容は非常に充実しています。相応のスキルを身に付けるためには、こうしたしっかりとした書籍を一冊持っておくと良いでしょう。
入門書に関しては、どの書籍も大きな違いはありません。あまり迷うことに時間をかけるよりは、手頃なものを一冊選んでみると良いでしょう。VBAの入門書は数多く出版されていますので、興味がある方はぜひチェックしてみてください。
甲乙つけがたい場合、私はインプレス社の「いちばんやさしい」シリーズを選ぶことが多いです。
\チェックしてみよう/
\チェックしてみよう/
\チェックしてみよう/
VBAのプログラミング能力を客観的に証明したい場合には「VBAエキスパート試験」があります。この試験はVBAの知識を公式に認定するものです。VBAの総合的な能力獲得を目指す方に適しています。以下の公式テキストが販売されております。
プログラミングの一般教養
「独学プログラマー」というプログラミングの魅力を解説した書籍があります。これはVBAではなくPythonを題材としていますが、プログラミングの基本的な知識や思考法、仕事の進め方まで幅広く学べます。
こちらの記事でも紹介しております。もしよろしければご覧ください。