2023/06/29

VBAの配列で使うFrequencyメソッド




1.シート上でのFREQUENCY関数の使い方

ワークシート関数の1つに「FREQUENCY関数」というものがあります。図01のように、あるセル範囲内の値(図01の緑線枠)を区間(赤線枠)に従って「範囲内の個数をカウント」するという機能です。
単列値の度数計算
図01

この関数は「区間」という複数セルを対象にした関数(配列関数)のため、数式入力範囲(図01だとE2~E13セル)を選択後、上部の数式バーに数式(図01の場合「=FREQUENCY(A2:A11,D2:D12)」)を入力したのち「Ctrl+Shift+Enter」キーを押して確定します。すると入力範囲の数式が「{}(波カッコ)」で囲まれ、各区間の度数(個数)が表示されます。
今までは上記のような操作での入力だったのですが、Microsoft365・Excel2021ではスピル機能が追加となりました。先頭セル(図01ではI2セル)に数式「=FREQUENCY(A2:A11,H2:H12)」を入力すると、区間に指定したセル範囲に対応して自動的に数式が展開(スピルと言うそうです)されます。
この方式は動的配列数式と呼ばれ、後から区間を変更しても数式は追従してくれるのが利点です。
当然ながら従来の配列数式と結果は同じですし、Microsoft365・Excel2021でも配列数式の入力方法(Ctrl+Shift+Enter)も可能です。
なお区間の値は「以下」を表しています。例えば図01のD2セル「0」は「ゼロ以下」、D12セル「100」は「100以下」となります。しかしこのままでは100を超えた値があった場合にはカウントできませんので、E13セルに「100超」の度数が入ります。配列数式では手動で「区間の1つ下のセル」まで選択する必要がありますが、動的配列数式では1つ下まで自動的に数式が入ります。
図01は単列データでしたが、図02のように複数列データでも使い方は同じです。
複数列値の度数計算
図02

2.VBAでのFrequencyメソッドの使い方

Frequencyは、WorksheetFunctionオブジェクト内にも存在します。使い方はワークシート関数(図01)と同様で、引数として「データのセル範囲」・「区間のセル範囲」の代わりに「配列」も指定できるところが異なります。
VBAで処理をする場合、何らかの配列データが度数計算元データとなることが多いと思います。ですので元データは全てシートのセル範囲から取得する形とし、以下では「区間データ」の取得方法別、及び出力別でプログラムを分けてみました(図03)。もちろんどれを組み合わせてもOKです。むしろ出力せずに度数計算結果をその後の処理に繋げる事の方が多いかもしれません。
ボタンコード元データ区間出力
1図05単列固定配列シート上に書き込み
2図08単列セル範囲メッセージボックス
3図10複数列自動計算フォーム上のListBox
図03

2ー1.ワークシートの準備

サンプルファイルでは、図04のように2つのワークシートを用意しました。Sheet1は単列データ、Sheet2は複数列データとし、データのセル範囲・区間のセル範囲は図の通りです。なお、元データ(緑色背景セル)は0~100の整数を乱数により発生させていますので、再計算やセル入力をすると値が変わります。
ワークシート上の準備
図04

Sheet1には「ボタン1」「ボタン2」の2個のボタンを、Sheet2には「ボタン3」を貼り付けています。それぞれのボタンには、標準モジュールの「vbaFreq1」「vbaFreq2」「vbaFreq3」をマクロ登録しています。

2ー2.区間固定配列での度数計算

Sheet1のボタン1から呼び出されるのが図05です。
  1. '========== ⇩(1) 区間を固定配列で取得し度数計算 ============
  2. Sub vbaFreq1()
  3.  Dim Data As Variant    '←元データの配列
  4.  Dim Rank As Variant    '←区間データの配列
  5.  Dim Count As Variant   '←度数の配列
  6.  Dim i As Integer      '←カウンタ変数(区間数)
  7.  Data = Sheet1.Range("A2:A11").Value
  8.  Rank = Array(0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
  9.  Count = WorksheetFunction.Frequency(Data, Rank)
  10.  ReDim Preserve Rank(0 To UBound(Rank, 1) + 1)
  11.  ReDim Preserve Count(1 To UBound(Count, 1), 1 To 2)
  12.  For i = 1 To UBound(Count, 1)
  13.   Count(i, 2) = Count(i, 1)
  14.   Count(i, 1) = Rank(i - 1)
  15.  Next i
  16.  Sheet1.Range("K2").Resize(UBound(Count, 1), UBound(Count, 2)) = Count
  17. End Sub
図05

07行目「Data = Sheet1.Range("A2:A11").Value」では、ワークシート上のデータ値を取得し、配列の形で変数Dataに代入しています。アプリ内で度数処理をする場合、その元データは様々な場所から取得すると思いますが、処理直前には配列の形となっている事が多いと考え、このような配列取得方法としました。
08行目「Rank = Array(0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100)」では、Array関数を使って「区間データの配列」を作成しています。単純なArray関数ですのでインデックスはゼロ始まりの一次元配列となります。
10行目「Count = WorksheetFunction.Frequency(Data, Rank)」で、Frequencyメソッドの第一引数に元データの配列、第二引数に区間データの配列を指定することで、ワークシート関数と同様に度数計算の結果が配列として得られます。
なお戻り値である配列の特徴は、以下の通りです。
 ・二次元配列(複数行 × 1列)
 ・大きさは、区間配列よりも1要素多い
 ・インデックスは1スタート
特徴からも分かるように、ワークシート上での計算結果がそのまま配列の形になっていると考えても良いと思います。
なおFrequencyメソッドの引数は、配列以外に「セル範囲」も可です。ですので10行目は「Count = WorksheetFunction.Frequency(Sheet1.Range("A2:A11"), Rank)」としてもOKです。その場合は07行目は不要ということになります。
Frequencyメソッドの戻り値が取得できれば、あとは区間(今回の場合はRank配列)と組み合わせて処理を続けていけばよいのですが、ここでは度数と区間を1つの配列にまとめています。手順は以下のようにしました。
配列の結合方法
図06

最終的にまとめる配列は、度数側の配列(配列Count)とします。
まず、区間データの配列(配列Rank)は要素数が度数側よりも1つ少ないため、行を1つ増やしておきます①。
度数側の配列(配列Count)には、区間データを入れるスペースを作るために、列数を1つ増やします②。次に度数の値が入っている1列目から2列目にデータを移します③。なお1列目は区間のデータで「上書き」されますので、値のCopyで十分です。
最後に区間データを配列Countの1列目にデータCopy④します。その際、各配列のインデックスがズレているので注意します。
12行目「ReDim Preserve Rank(0 To UBound(Rank, 1) + 1)」では、配列Rankの行数を1つ増やしています(図06の①)。
13行目「ReDim Preserve Count(1 To UBound(Count, 1), 1 To 2)」では、配列Countの列数を1つ増やしています(図06の②)
15行目「For i = 1 To UBound(Count, 1)」はカウンタ変数iを配列Countの行数分だけ回します。配列Countはインデックスが1始まりのため、カウンタ変数iも1始まりとしています。
16行目「Count(i, 2) = Count(i, 1)」では、度数値を1列目から2列目にコピー(図06の③)しています。
17行目「Count(i, 1) = Rank(i - 1)」では、配列Rankから区間データを配列Countの1列目にコピー(図06の④)しています。なお、インデックスがズレているため、右辺は「Rank(i - 1)」と「 -1 」させています。
区間データと度数データが1つの配列(配列Count)となりましたので、20行目「Sheet1.Range("K2").Resize(UBound(Count, 1), UBound(Count, 2)) = Count」で、K2セルを左上セルとするセル範囲にデータを貼り付けます。この際、データのサイズと貼り付けセル範囲のサイズが合っている必要があるため、Resizeプロパティでセル範囲変更をしています。
出力された様子は図07のようになります。
セルへの出力の様子
図07

この「区間と度数」を使えば、グラフを作成する 等の処理が行えます。
なお、ワークシート関数での度数結果と値が異なっていますが、これは元データを「Rand関数」でランダムに計算させているからで、出力データを貼り付けた時に再計算が行われ「元データの値が変更」されてしまったためです。通常の処理時には、このような事は無いと思ったので、このままとしました。

2ー3.区間をセル範囲で取得し度数計算

Sheet1のボタン2から呼び出されるのが図08です。
  1. '========== ⇩(2) 区間をセル範囲で取得し度数計算 ============
  2. Sub vbaFreq2()
  3.  Dim Data As Variant    '←元データの配列
  4.  Dim Rank As Variant    '←区間データの配列
  5.  Dim Count As Variant   '←度数の配列
  6.  Dim i As Integer      '←カウンタ変数(区間数)
  7.  Dim S As String      '←メッセージボックス用の文字列
  8.  Data = Sheet1.Range("A2:A11").Value
  9. ' Rank = Array(0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
  10.  Rank = Sheet1.Range("D2:D12").Value
  11.  Count = WorksheetFunction.Frequency(Data, Rank)
  12.  Rank = WorksheetFunction.Transpose(Rank)
  13.  ReDim Preserve Rank(1 To UBound(Rank, 1) + 1)
  14.  ReDim Preserve Count(1 To UBound(Count, 1), 1 To 2)
  15.  For i = 1 To UBound(Count, 1)
  16.   Count(i, 2) = Count(i, 1)
  17.   Count(i, 1) = Rank(i)
  18.  Next i
  19.  For i = 1 To UBound(Count, 1)
  20.   S = S & Count(i, 1) & vbTab & Count(i, 2) & vbNewLine
  21.  Next i
  22.  MsgBox S
  23. End Sub
図08

38行目「Data = Sheet1.Range("A2:A11").Value」では、ワークシート上のデータ値を取得し、配列の形で変数Dataに代入しています。この部分は図05の07行目と全く同じです。
40行目「Rank = Sheet1.Range("D2:D12").Value」では、内部で区間データを作る代わりに、ワークシート上のセル範囲から区間データを取得しています。セル範囲から取得しているため、その配列(配列Rank)は「二次元配列」で「インデックスは1始まり」となります。
42行目「Count = WorksheetFunction.Frequency(Data, Rank)」で、Frequencyメソッドに元データ(配列Data)と区間データ(配列Rank)を指定することで、度数計算の結果が配列として得られます。引数として与える配列は「1次元配列でも2次元配列でも」OKですし、また「インデックスが何から始まっても」OKです。
なおFrequencyメソッドの引数は、図05でも説明したように、配列以外に「セル範囲」も可です。しかし42行目を「Count = WorksheetFunction.Frequency(Sheet1.Range("A2:A11"), Sheet1.Range("D2:D12"))」とし、40行目を無くしてしまうと、44行目以降での配列の結合時に改めて区間データを取得する必要が出てきます。配列の結合等が不要な状況であれば、直接セル範囲を指定するのも有りだと思います。
44~51行目は図06の「配列の結合処理」を行っている部分ですが、今回は配列Rankが「二次元配列」であり、また「インデックスは1始まり」のため、図05の12~18行目の時とは少し変わります。
まず図06の①の処理(配列Rankの要素を1つ増やす)ですが、配列Rankは二次元配列で「延ばしたい方向の次元が1次元目(行方向)」です。そのためReDimでは、そのまま延ばす訳にはいきません(二次元配列の1次元目を延ばそうとするとエラーとなります)。ですので44行目「Rank = WorksheetFunction.Transpose(Rank) 」で、配列の縦横を回転させています。この場合回転後の配列は横方向となりますので「一次元配列」に変換されます。
44行目でTransposeを使って縦横回転をさせた為、配列のインデックスは「1始まり(元々、セル範囲からの取得なので1始まり)」となるので、45行目「ReDim Preserve Rank(1 To UBound(Rank, 1) + 1)」のReDimも「1始まり」で、要素を1つ増やしています。
46行目「ReDim Preserve Count(1 To UBound(Count, 1), 1 To 2)」では、配列Countの列数を1つ増やしています。
48行目「For i = 1 To UBound(Count, 1)」は、カウンタ変数iを配列Countの行数分だけ回します。
49行目「Count(i, 2) = Count(i, 1)」では、度数値を1列目から2列目にコピーしています。
50行目「Count(i, 1) = Rank(i)」では、配列Rankから区間データを配列Countにコピーしています。なお、今回はインデックスのズレが無い(配列Countも配列Rankもインデックスは1始まり)ため、図05の17行目とは異なり、右辺は「Rank( i )」としています。
53~55行目では、配列のデータをメッセージボックス用の文字列に変換させています。
53行目「For i = 1 To UBound(Count, 1)」では、カウンタ変数iを配列Countの行数分だけ回します。
54行目「S = S & Count(i, 1) & vbTab & Count(i, 2) & vbNewLine」では「区間データ + TAB + 度数データ + 改行」を1行とするデータを作っています。先頭に「S &」を入れることで、どんどんと下につなげています。
文字列が完成したら57行目「MsgBox S」でメッセージ表示させています。
なお、48行目と53行目のFor文は全く同じなので、54行目の処理は50行目の下に入れ込む事は可能です。
メッセージボックスが表示された様子は図09のようになります。
メッセージボックスへの出力の様子
図09

2ー4.区間を最大最小値から自動計算し度数計算

Sheet2のボタン3から呼び出されるのが図10です。
  1. '========== ⇩(3) 区間を最大最小値から自動計算し度数計算 ============
  2. Sub vbaFreq3()
  3.  Dim Data As Variant    '←元データの配列
  4.  Dim Rank As Variant    '←区間データの配列
  5.  Dim Count As Variant   '←度数の配列
  6.  Dim i As Integer      '←カウンタ変数(区間数)
  7.  Data = Sheet2.Range("A2:E11").Value
  8.  Rank = makeFreq(WorksheetFunction.Min(Data), WorksheetFunction.Max(Data))
  9.  Count = WorksheetFunction.Frequency(Data, Rank)
  10.  ReDim Preserve Rank(1 To UBound(Rank, 1) + 1)
  11.  ReDim Preserve Count(1 To UBound(Count, 1), 1 To 2)
  12.  For i = 1 To UBound(Count, 1)
  13.   Count(i, 2) = Count(i, 1)
  14.   Count(i, 1) = Rank(i)
  15.  Next i
  16.  UserForm1.ListBox1.ColumnCount = 2
  17.  UserForm1.ListBox1.List = Count
  18.  UserForm1.Show
  19. End Sub
図10

77行目「Data = Sheet2.Range("A2:E11")」では、ワークシート上のデータ値を取得し、配列の形で変数Dataに代入しています。今回は複数列のデータを取得していますが、この後の処理には変更はありません。
79行目「Rank = makeFreq(WorksheetFunction.Min(Data), WorksheetFunction.Max(Data))」では、図13のユーザー定義関数makeFreqを呼び出し、区間データを作成しています。引数には「データの最小値」「データの最大値」を指定するため、ワークシート関数のMaxとMinを使っています。
今回のmakeFreq関数で戻される区間配列は「一次元配列」で「インデックスは1始まり」としました。
81行目「Count = WorksheetFunction.Frequency(Data, Rank)」で、Frequencyメソッドに元データ(配列Data)と区間データ(配列Rank)を指定することで、度数計算の結果が配列として得られます。
83行目「ReDim Preserve Rank(1 To UBound(Rank, 1) + 1)」では、配列Rankの要素を1つ増やしています。
84行目「ReDim Preserve Count(1 To UBound(Count, 1), 1 To 2)」では、配列Countの列を1つ増やしています。
86行目「For i = 1 To UBound(Count, 1)」は、カウンタ変数iを配列Countの行数分だけ回します。
87行目「Count(i, 2) = Count(i, 1)」では、度数値を1列目から2列目にコピーしています。
88行目「Count(i, 1) = Rank(i)」では、配列Rankから区間データを配列Countにコピーしています。
区間と度数を合わせた配列は、今回はユーザーフォームのリストボックスにデータ表示させています。
今回のデータは「2列(区間+度数)」ですので、リストボックスも2列対応させるため、91行目「UserForm1.ListBox1.ColumnCount = 2」でColumnCountプロパティを変更させます。もちろんUserFormのフォームモジュールで、InitializeやActivateイベントを利用して変更してもOKです。
92行目「UserForm1.ListBox1.List = Count」では、ListBoxに配列データを直接貼り付けています。
93行目「UserForm1.Show」では、ユーザーフォームを表示させています。
ユーザーフォームを表示させた状態は、図11のようになります。
ユーザーフォームを表示させた様子
図11

2ー4ー1.ユーザーフォームの準備

今回使用したユーザーフォームは、図12のようにフォーム上にListBoxを1つ置いただけのものです。
フォームモジュールも今回は特に使用しませんでした。
ユーザーフォームのレイアウト
図12

2ー4ー2.区間を自動計算するユーザー定義関数

図10の79行目から呼び出されるユーザー定義関数が図13で、引数にはMin値とMax値を指定します。「指定したMin・Max値を含む範囲を、適当な間隔で区切った区間配列」を戻します。
  1. '========== ⇩(4) 区間を自動計算する関数 ============
  2. Function makeFreq(Mn As Double, Mx As Double) As Variant
  3.  Dim Bas As Variant    '←キリの良い値の配列
  4.  Dim Diff As Double    '←差(最大 - 最小)
  5.  Dim n As Integer    '←移動した桁数
  6.  Dim j As Integer    '←キリの良い値のインデックス値
  7.  Dim k As Integer    '←戻す配列のインデックス値
  8.  Dim temp As Variant    '←処理中の配列。最終的に戻り値になる
  9.  Dim buf As Double    '←引数を逆転するための一時変数
  10.  Dim Ratio As Double    '←区間の幅
  11.  Const Part As Integer = 10    '←目標の分割数
  12.  Bas = Array(2, 5, 10)    '←キリの良い値の配列
  13.  If Mx < Mn Then
  14.   buf = Mx
  15.   Mx = Mn
  16.   Mn = buf
  17.  End If
  18.  Diff = Mx - Mn
  19.  Diff = Diff / Part
  20.  Select Case Diff
  21.   Case Is > 10
  22.    For n = 1 To 10
  23.     Diff = Diff / 10
  24.     If Diff <= 10 Then Exit For
  25.    Next n
  26.   Case Is <= 1
  27.    For n = -1 To -10 Step -1
  28.     Diff = Diff * 10
  29.     If Diff > 1 Then Exit For
  30.    Next n
  31.  End Select
  32.  For j = 0 To UBound(Bas, 1)
  33.   If Diff <= Bas(j) Then Exit For
  34.  Next j
  35.  Ratio = Bas(j) * 10 ^ n
  36.  Mx = -1 * (Int(-1 * Mx / Ratio) * Ratio)
  37.  Mn = Int(Mn / Ratio) * Ratio
  38.  ReDim temp(1 To (Mx - Mn) / Ratio + 1)
  39.  For k = 1 To UBound(temp, 1)
  40.   temp(k) = Mn + ((k - 1) * Ratio)
  41.  Next k
  42.  makeFreq = temp
  43. End Function
図13

この関数考え方は図14のようになります。引数として受け取るのがMin値(コード内ではMn)とMax値(同Mx)の2つで、その値を加工していきます。なお、図14内の黄色部分「目標の分割数(同Part)」「キリの良い値の配列(同Bas)」は、内部定数相当です。
考え方のフローチャート
図14

まず引数として受け取る最小値(図14でのMin)・最大値(同Max)を使って差を求め、目標の分割数で割ります。この値が「区間の間隔」に近い値になるはずです。
但し区間の間隔値は何でも良い訳ではありません。例えば0~19の間を「3飛び」で刻むとすると「0,3,6,9,12,15,18,21」となり、桁が変わる時にキリの良い値になってくれません。それよりは「2飛び(0,2,4,6,8,10,12,14,16,18,20)」や「5飛び(0,5,10,15,20)」で刻んだ方が分かり易そうです。そこで今回は「10の約数(1, 2, 5, 10 )」をキリの良い数字としました。
しかし区間とすれば「5飛び」だけでなく「50飛び」「500飛び」等があり得ますから、区間の間隔を10nで乗算・除算をして整数部分を1桁の値(1超~10以下)にしてから、キリの良い数字と比較するようにしています。
キリの良い数字が見つかったら、その数値になるように最小値・最大値を丸め(元の最小値・最大値を含むような範囲にする)、最後に丸めた最小値・区間幅・最大値を使って配列の形に並べ、戻り値とします。
111行目「Const Part As Integer = 10」は、目標分割数です。この値を大きくすると細かく分割されます。
112行目「Bas = Array(2, 5, 10)」は、キリの良い数字を配列の形にしています。今回は「1超の値」に加工してから比較を行っていますので、10の約数とは言っても1は除いています。
114~118行目は、引数の最小値・最大値を逆に指定されてしまった時に、引数値を入れ替えています。もし逆のままだと、処理後の最小値・最大値の値がおかしくなり、また戻り値の配列作成時(145行目)にエラーが発生します。
114行目「If Mx < Mn Then」で逆転有無を確認し、逆転している場合は115行目「buf = Mx」で一時変数bufに第二引数値(本当は最小値)を格納します。その後116行目「Mx = Mn」で、変数Mxに第一引数(本当は最大値)をコピーし、117行目「Mn = buf」で変数Mnに一時変数値(本当の最小値)を入れ直します。
120行目「Diff = Mx - Mn」では、引数の最小値・最大値から、その差を計算し変数Diffに代入しています。
121行目「Diff = Diff / Part」では、その差を「目標の分割数」で除算し、その結果を変数Diffに入れ直しています。
123~134行目では10nで乗算・除算をして整数部分が1桁(1超~10以下)になるように処理をしています。なお、その処理に使ったn値(何乗したか)は、140行目で区間幅の計算式に使います。
123行目「Select Case Diff」では変数Diffの大きさを調べています。10を超えている場合は125~128行目を実行し、1以下の場合は130~133行目を実行します。
10を超えている場合(124行目「Case Is > 10」)は、125行目「For n = 1 To 10」でカウンタ変数nを回しています。
126行目「Diff = Diff / 10」では、変数Diffを10で割り、変数Diff値を置き換えています。そして127行目「If Diff <= 10 Then Exit For」で10以下になったか否かを調べ、まだ10超ならば再び126行目の10で割る処理を行います。一方10以下ならばFor文を抜け出します。この抜け出した時のn値が「元のDiff値」との比という事になります。10回で終了してしまうため、ゼロが10個の「999億」が差の上限ということになります。
一方、1以下の場合(129行目「Case Is <= 1」)は、130行目「For n = -1 To -10 Step -1」でカウンタ変数nを-1から-10まで逆方向に回します。125行目の1から10までを回すのとは別にしているのは、「n値のみで元のDiff値の桁を減らしているのか増やしているのか」が分かるようにするためです。
131行目「Diff = Diff * 10」では、変数Diffに10を掛けて、変数Diff値を置き換えています。132行目「If Diff > 1 Then Exit For」ではDiff値が1を超えた時にFor文を抜け出しています。
こちらも10回でFor~Nextを終了させているので、差の下限は「0.000000001」という事になります。
変数Diffの整数部分が1桁(1超~10以下)になったら、136~138行目で「最適なキリの良い数字」を見つけます。
136行目「For j = 0 To UBound(Bas, 1)」で、配列Bas(キリの良い数字の配列)の要素数だけ、カウンタ変数jを回します。
137行目「If Diff <= Bas(j) Then Exit For」で、適するキリの良い値が見つかったら、For文を抜け出します。抜け出した時の「Bas(j)値」がキリの良い値となります。
140行目「Ratio = Bas(j) * 10 ^ n」では、123~134行目のカウンタ変数nと、136~138行目で選択したキリの良い値 Bas(j) を使い「区間の幅」を計算します。
142~143行目は、引数で得た最小値・最大値を、切り上げ・切り捨てをして「キリの良い数値」に丸めます。この「切り上げ」「切り捨て」の計算にはちょっと工夫が必要です。
まず今回の目的は、引数で得た「最小値・最大値を含む範囲」を取得することです。図15で言えば、最小値・最大値を広げ(≒切り上げ・切り捨て)て、黄色い範囲を決めます。これは、最大値・最小値がプラス側でもマイナス側でも同じです。
最小値・最大値を含む範囲を取得する
図15

この「切り上げ」「切り捨て」に良く使われる関数は「RoundUp」「RoundDown」です。「切り捨て」として使われる「Int関数」も併せて、その特性を図16に並べました。
切り上げ・切り捨て関数の挙動
図16

図16でも分かるように、各関数に値を指定した場合の移動方向は以下の通りです。
RoundUpゼロから離れる方向に動く
RoundDownゼロに近づく方向に動く
Int値が減る方向(-∞の方向)に動く
最小値・最大値のプラスマイナスを調べてから処理の分岐をすれば、どの関数でも使えますが、場合分けをせずに図15の動きに合わせる方法が、142~143行目のInt関数を使う方法です。
142行目「Mx = -1 * (Int(-1 * Mx / Ratio) * Ratio)」では、一旦最大値のプラスマイナスを逆転させた上でInt計算をしています。最大値がプラスだった場合は絶対値が大きい側に動き、最後に-1を掛けることでプラス側の大きい側(+∞側)となります。
一方最大値がマイナスの場合は絶対値が小さい側に動き、最後に-1を掛けることで、やはり+∞側に動くことになります。
なお変数Ratioで割ったり掛けたりしているのは、動きを「区間の幅の単位で動かす」ためです。
また143行目「Mn = Int(Mn / Ratio) * Ratio」では、Intで値を単純に左方向(-∞側)に動かすことで、最小値を含む範囲となるようにしています。
最小値・最大値がキリの良い数値となりましたので、145行目「ReDim temp(1 To (Mx - Mn) / Ratio + 1)」では、戻り値となる配列を作ります。最小値と最大値の差を区間の幅(変数Ratio)で割った値が要素数になります。
147~149行目では、作った配列に区間値を入れていきます。
147行目「For k = 1 To UBound(temp, 1)」では、カウンタ変数kを配列tempの要素数だけ回します。
148行目「temp(k) = Mn + ((k - 1) * Ratio)」では、最小値(変数Mn)からスタートし、区間幅(変数Ratio)を加えていきます。最後の要素は最大値(変数Mx)と同じ値が入ります。
配列tempが完成したら、151行目「makeFreq = temp」で、関数プロシージャの戻り値に設定し終了します。

3.まとめ

データ分析として区間別に度数分布グラフを作る手法は基本です。しかしその元データをExcelで作るには、Frequency関数を作り「Ctrl+Shift+Enterキー」という非常にやっかいな手順が必要でした。私も最初にその手順を教えてもらった時には「なんでこんなに面倒なの?」と感じたことを覚えています。
最新Excelではスピル機能により、ようやく入力が楽になってきましたが、Excelのバージョンを上げる必要もあるので、浸透するにはまだまだ時間がかかりそうです。
今回のVBA内で使用するFrequencyメソッドは、そんなやっかいさを少しでも解消したいという思いもあり紹介しました。グラフ化がとりあえずの目標であれば、最初からワークシート上のみで完結させた方が良いのかもしれません。しかしVBAを含めた様々な手法の知識があれば、少しでも使い易くなったり、更なる改善が出来たりするのでは と思います。
また、自動で区間データを作る関数も紹介しましたが、最終的には人が目的的に「最小・最大・区間幅」を決めるものだと思います。それでも初回は「まずはデータの様子を見る」ことからスタートするはずですので、その一助になればと思います。

アプリ実例・関連する項目

設備の稼働状態記録とデータ集計

サンプルファイル

VBAの配列で使うFrequencyメソッド(its-040.xlsm)
セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始)
解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。

サンプルファイルのシート上には、動的配列数式も書き込んでいますが、スピル機能の無いバージョン(Microsoft365・Excel2021以前)で開くと「通常の配列数式」として見えることになります。機能上は同じです。