サンプリング周期が異なるデータの補間法
1.背景
世の中の様々な現象を把握・解析するには、その現象を目に見える様にする必要があります。そのための1つの方法として、センサーを通し時系列な定量値として現象を記録する手段があります。しかし各センサーにはサンプリング限界があり、また記録側の制約等により異なるサンプリングで計測せざるを得ない場合もあります。
また、例えばレーシングカーの性能計測で「車の振動・車速・外気温」を一緒に記録する場合、全項目を高速サンプリングする必要はありません。それぞれの現象の変化の仕方が違っているからです。もちろん各現象の重要度を考える必要もあります。
このように異なるサンプリングで取得したデータを1つの時系列データ(Excelデータ)にすると、サンプリングの遅い項目には空白セルが出来る事になります。サンプリングが遅いだけで別に間違っている訳では無いのですが、これを行単位で解析する時には空白セルは非常に困ることになります。
今回は、実データから計算で補間し、空白セル部分にデータを埋める方法について説明します。
2.概要
例えばA・B・Cの項目があったとします。それぞれのサンプリングは、BとCが1秒ごとなのに対し、Aは5秒ごとだとします。同じ時間軸上に並べると、図2-1の様に「Aだけ5つ置きに数値が入る」ことになります。
図2-1
Excelのグラフで表現すると、初期では「データの存在する位置にマークが点在する」状態ですが、グラフを掴み「デザイン」タブの「データ」グループから「データの選択」を選択すると図2-2のようなダイアログが現れ、左下の「非表示および空白のセル」をクリックするとその処理法が選択でき、「データ要素を線で結ぶ」で図2-1のような「線で結んだ状態」になります。
図2-2
しかし、これはグラフ上で点同士を結んだだけであり「データを補間」とは違います。
今回は、図2-1のB列の空白セルに値を埋めていく方法について、以下の3種類を紹介します。
(1)空白セルに、1区間ずつ数式を入れる
(2)全空白セルに、同じ数式を入れる
(3)マクロで計算し、空白セルに数値を入れる
なお、実測値の間をどのように結ぶのかは、色々考え方があります。曲線でなだらかに結ぶやり方もあるかと思いますが、今回は「実測値間は直線で結ぶ」考え方で行います。
3.(1)空白セルに数式を入れる(1区間ずつ)
3-1.絶対参照で数式を作る
まず、空白セルにどのような数式を入れるかを考えます。図3-1の左側のような表の場合、1区間目の空白セルは「B5~B8」の4つで、その両脇であるB4・B9に実測値が入力されています。考え易くするために、これをグラフにしてみたのが図3-1の右側です。
図3-1
実測値(黒丸)の間を直線で結び、空白セルに入れる部分を赤丸で示してあります。実測値の差は、数値で言えば「2.5 - 1 = 1.5」です。しかし数値が変わっても使えるようにセル番地で示すと「B9-B4」になります。
その間を5つに区切っていますので1つ1つのセルの間の差分は「(B9-B4)/5」になります。
では、B5セルに入れるべき数式を考えます。1つ前のセルとの差は「(B9-B4)/5」なのですから、1つ前のセルの値にその差を加えれば、B5セルに入れる値になるはずです。式で考えると「B4+(B9-B4)/5」です。
ということでB5セルには、数式を示す記号「=」を先頭につけて「 = B4 + (B9 - B4) / 5 」を入力します。
図3-2
図3-2のB5セルには「1.3」が表示されます。
次のB6セルには「 = B5 + (B9 - B4) / 5 」を入力することになりますが、B5セルとB6セルの数式の違いは1つ前のセルを示す「B4」「B5」の部分だけです。
ここで「B4」「B5」は連続していますので、図3-3の様に「セル右下の■印をクリックし、下方向にドラッグする(=オートフィル)」操作で数式のコピーが出来そうです。
図3-3
しかし、結果(図3-4)を見てみると、B6セルの結果は「1.04」(正しい値は、図3-2のように「1.6」)になってしまいました。
図3-4
数式としては、本当は「 = B5 + (B9 - B4) / 5 」にするつもりが、数式バーを見てみると「 = B5 + (B10 - B5) / 5 」となってしまい、「差分の元になっているセル位置もズレ」てしまっています。
これは「数式をコピーした場合、その中のセル位置情報は、コピー元~コピー先の相対位置分だけズレる」原理から、当然の結果なのです。ですので「ズレて欲しく無い」場合は、「セル位置の前に$印をつける(絶対参照と言います)」ことになっています。例えば「B9」を固定したい場合は「$B$9」としますが、今回の場合下方向のみにコピーしていますので「B列」であることは変わらないため「B$9」としても結果は同じです。
図3-5は、B5セルの数式を「 = B4 + (B$9 - B$4) / 5 」に変更してから、オートフィルで下方向にコピーしています。
図3-5
一番下のB8セルまでコピーした結果が図3-6です。
図3-6
B8セルの数式が数式バーに表示されており「一つ上のセル値+差分値」という正しい式になっています。また、右側のグラフを見ても、0秒と5秒の実測値を結ぶ直線の上に、計算値の丸印が並んでいます。
この作業を空白セル区間の数だけ繰り返すことで補間ができます。が、区間の数が多い場合にはとても大変です。
3-2.相対参照で数式を作る
そこで、上記の「$マークを付けて絶対参照」による方法を見直し、「$マークを付けない相対参照」でもう少し考えてみます。図3-7のように1つの空白セル区間の範囲だけ、相対参照($マークをつけない)で数式を入力します。この時、B5セルだけ数式を入れて、それをコピーするだけ・・・という訳には行きません。コピーしたとしても1セルずつ修正を加えるかの手間が必要です。
図3-7
1つの区間の数式が完成したら、図3-8のように「区間の数式(B5~B8)」を選択しコピーします。そして次の区間の先頭(B10)を選択し貼り付け(通常の貼付け、または数式貼付け)をします。
図3-8
すると、次の区間に対応した数式になり、正しい補間データを計算してくれます。図3-8の右側のグラフを見ても補間計算がうまくいっている事が分かると思います。
この「数式区間を一度コピー」し「下の区間の先頭を選択し貼付け」を繰り返していけば、データ全体の補間が出来ることになります。
尚、この方法は「空白セル区間のセル数が変わらない場合に有効」です。例えば「必ず5秒ごとにデータが取得されている」場合です。「サンプリング間隔はバラバラ」の場合には、もちろん使えません。
4.(2)空白セルに数式を入れる(全セル同一数式)
それでも手間が多すぎると言う場合、全てのセルに同一数式を入れる方法があります。この方法が有効であるのは、Excelの機能に「空白セルに一括入力できる手段」があるからです。4-1.空白セルへの一括入力方法
まず、空白セルへの入力方法について説明します。入力する数式については、あとで説明します。まず、①入力したい空白セルを含むセル範囲を選択します。その状態から「Ctrl + Gキー」を押す(又は、「ホーム」タグ→編集グループの「編集の検索と選択」→ジャンプ を選択)と、②ジャンプダイアログが表示されます。
図4-1
③その左下の「セル選択」ボタンをクリックすると、図4-2の選択オプションダイアログが表示されます。
④その中の「空白セル」をONにし ⑤「OK」ボタンをクリックすると、⑥空白セルのみが選択された状態になります。
図4-2
空白セルのみ選択状態では、先頭セルが白くアクティブになっています。(ここで空白セルのどこかをクリックしてしまうと、セル範囲が解除されてしまいますので、注意が必要です)
この状態のまま、⑦「数式バー」に値や数式を入力し、⑧Ctrl+Enterキーを押すことで、⑨選択された空白セルに値・数式が埋まります。
図4-3
尚、数式バーに入力するものが値であれば、選択した全空白セルに同一の値が入ります。また相対セル参照を含んだ数式であれば、一番上のセル(図4-3であればB5セル)を基準に相対的にズレたセル参照になります。
4-2.入力する数式(A1参照方式)
では、上記の⑦で「数式バー」に入力する数式を考えます。しかし、手入力した時のような数式「 = B4 + (B9 - B4) / 5 」や「 = B4 + (B$9 - B$4) / 5 」は使えません。
相対参照の数式では差分の式「(B9 - B4) / 5 」が空白セル区間内でズレてしまいますし、絶対参照では2つ目以降の区間では差分の値が違ってしまいます。
単一の数式では無理そうなので、今回は2つの数式の組み合わせで考えてみます。「入力できるのは1つの数式だけでしょ?」と思われると思いますが、セル位置を特徴付けられさえすれば、If文を使って2つの数式を使い分けることができます。
まずセル値の間の差分については、初めに算出しておく必要があります。ですので図4-4の様に「1番目の空白セル」で「差分を計算」する事にします。
その1番目の空白セルに値が入ると、その次の空白セルの目線で見ると「差分=1つ前の値 - 2つ前の値」ですので、それ以降の空白セルは「1つ前の値 +(1つ前の値 - 2つ前の値)」という同じ式で値が求まることが分かります。(図4-4の右側)
図4-4
では「1番目の空白セル」をどうやって知るか、です。「1つ前に数値が入っている」ではダメです。数式は一括で入力されますので、2つ目の空白セルにとって1つ目の空白セルは「計算されて、既に数値が入っている状態」だからです。
「数値」では無く、図4-4の左側のように「数式か否か」ではどうでしょう。1番目の空白セルにとって1つ前のセルは「数式ではありません」が、2番目の空白セルにとって1つ前のセルは「自分で入力した数式」です。
この「数式か否か」を調べる関数が「ISFORMULA(セル範囲)」で、これを使って数式を作ると以下のようになります。
=IF(ISFORMULA(B4),B4+(B4-B3),B4+(B9-B4)/5)
なお、この数式は「空白セルの先頭(B5セル)に貼り付ける数式」ですので、図4-5の様に1つ下にズレるたびに参照するセル位置も1つズレていきます。
図4-5
そして、区間内の一番上のセルでは「1つ上のセルは数式では無い」ので後ろ側の数式「B4+(B9-B4)/5」が実行され、それ以外のセルでは「1つ上のセルは数式」ですので前側の数式「B4+(B4-B3)」が実行されます。もちろんセル位置は相対参照していますので、それぞれのセルに合った数式になっています。
このようにして作成した数式を空白セルに一括挿入している場面が図4-6です。
図4-6
図4-1、図4-2の手順で空白セルを選択した状態で、⑦数式バーに数式を入力し、⑧Ctrl+Enterを押す事で⑨空白セルに数式が挿入され補間データとなります。
尚、ここまでは5個置きのデータの場合について説明してきましたが、10個置きの場合はどうでしょう。
図4-7の表2として10個置きのデータ表を作りましたので、この空白セルを埋めていく流れで説明します。
図4-7
今度の表2での空白セルの先頭セルは「G5セル」です。ここに入力すべき数式は以下の様になります。
=IF(ISFORMULA(G4),G4+(G4-G3),G4+(G14-G4)/10)
「5個置き」の数式と「10個置き」の数式との違いは、列が違う(B列→G列)ことを除くと、上記数式の赤字の部分だけです。
つまり「差分計算をする時の下側セルの行番地(9→14)」及び「区間の幅(5→10)」が異なっています。
ですので、5個置きとか10個置きとかに関わらず一定間隔でサンプリングしているデータであれば、数式のこの部分を変更すれば良いことになります。「5個置き」の数式と「10個置き」の数式との違いは、列が違う(B列→G列)ことを除くと、上記数式の赤字の部分だけです。
つまり「差分計算をする時の下側セルの行番地(9→14)」及び「区間の幅(5→10)」が異なっています。
また、複数列に空白セルとなるデータが有ったとしても、図4-8の様に1つの数式で複数列の補間が一括で可能です。
図4-8
なお、複数列で実データのタイミングが合っていない状態(図4-9)でも、空白セルの先頭位置に合わせて修正した数式を貼り付ければ補間データが得られます。
図4-9
但し、一部(図4-9ではL4・L5セル)のセルでエラーが発生する場合がありますが、これは与えられた条件だけでは計算出来ないためですので、部分的に手修正が必要となります。
4-3.入力する数式(R1C1参照方式)
しかし補間するデータの列や空白セルの先頭行が変わった場合には、その都度数式の列名や行番号を変更する必要があり、少し手間です。こういった時に有効なのが、今までの数式で使ってきた「A1参照方式」ではなく「R1C1参照方式」です。
4-3-1.R1C1参照とは
まず、Excel側の準備としてR1C1表示が出来る環境にする必要があります。(以下はExcel2016での手順です。バージョンにより多少の違いがあります。)Excelの「ファイル」タブの一番下の「オプション」を選択し、「数式」を選ぶと図4-10のようなダイアログになります。
図4-10
このダイアログの「数式の処理」の中の「R1C1参照形式を使用する」にチェックをし、一番下の「OK」ボタンをクリックすると、ワークシートは図4-11の様になります。
図4-11
R1C1参照形式は、セル番地をR(Row)とC(Column)のセットで表すもので、例えばB5セルは「R5C2」(5行目の2列目)となります。ちょうどVBAの「Worksheet1.cells(5,2)」と同じ使い方です。
また相対的な位置を表す際は、図4-12に示したようにある基準セルに対し、すぐ上のセルは「R[-1]C」となります。
図4-12
なお「C(列)は変わらないから「R[-1]C[0]」では?と考える方もいると思います。間違ってはいませんが、数式としての[0]は「Excel側が不要と判断し、除去した上で「R[-1]C」としてセルに保存されます。
4-3-2.R1C1参照形式での数式
今まで図4-6等でB列の先頭の空白セルに数式を入れ、Ctrl+Enterを押した(=下方向にコピーしたのと同じ操作)時には、各セルの数式は参照セル範囲が1つずつズレていました。しかしR1C1参照形式では、図4-11のように「一括入力した数式は、全て同じ式」となります。B列だけではありません、図4-8でのL列・N列に入れた数式も同じなのです。
ということは「R1C1参照形式であれば、入力セルの行位置・列位置を考慮し数式を修正しなくても良い」ことになるのです。
ということで、R1C1参照形式で数式を作ったのが図4-13になります。比較の為にA1参照形式も併せて載せました。
R1C1参照形式の指しているセル位置は、A1形式と同じ(図4-13の右表の太字部分)ことが分かります。
図4-13
R1C1参照形式は「R」と「C」ばかり目立つので、一見すると暗号のようにも見え苦手な方が多いと思いますが、うまく使うとすごく便利なものです。
このR1C1参照形式の数式を複数列の空白セルに一気に代入したのが図4-14です。
図4-14
なお、図4-14の7列目(A1参照形式ではG列)は10個置きのデータですので、5個置きデータ用の数式は使用できません。数式を貼り付ける方式では、R1C1参照形式でも「いくつ置きのデータか」で式を作り直す必要があります。
5個置きと10個置き用の数式を下記に掲載しておきます。
5個置きデータ用数式:=IF(ISFORMULA(R[-1]C),R[-1]C+(R[-1]C-R[-2]C),R[-1]C+(R[4]C-R[-1]C)/5)
10個置きデータ用数式:=IF( ISFORMULA(R[-1]C),R[-1]C+(R[-1]C-R[-2]C),R[-1]C+(R[9]C-R[-1]C)/10)
5.(3)マクロで補間値を計算する
今までは「数式で補間」する方法について説明してきましたが、マクロで計算し補間データを貼り付ける方法について説明します。5-1.ワークシート側の状況
サンプルファイルでは、空白セルの存在する表は「数式用」と「マクロ用」で分けておきました。マクロ用はSheet2です。データとしては数式用のSheet1と違いがありませんが、数式で対応できるのは「〇個置きのデータ」というようにサンプリング周期が一定のもののみです。しかしマクロであれば、サンプリングが不定期のものでも対応できます。
図5-1
Sheet2には、マクロ起動用のボタンを用意してありますが、アドイン等での起動も使い勝手が良いと思います。
5-2.マクロの使用法
マクロを起動すると図5-2のダイアログが現れますので、「時刻列の先頭データセル」をクリックします。図5-2では、時刻列はA4セルからスタートしていますので、A4を選択し「OKボタン」をクリックします。
図5-2
次に現れるダイアログでは、補間データの列を選択します。列全体を選択するため、列記号の部分をクリックします。
図5-3
補間データは1列のみでは無く、図5-4の様にCtrlキーを押しながら複数列を選択することも可能です。
また、「B列とC列」というように、マウスの左ボタンを押したままマウスを横に移動し、複数列を選択することも可能です。
図5-4
ダイアログの「OK」ボタンを押すことで、補間の計算がされデータが記入されます。数式では無く実数として書き込まれます。
図5-5
なお補間データ範囲の内、端のセル(図5-5では「G4セル」「I4セル」)は、値=「ゼロ」として計算をしています。端のデータは、前のデータの続きであったり、アイドリング状態だったりと状況が確定できませんので、今回はこのような計算方法としました。
使う方の状況に合わせて、端の部分の処理を行って下さい。
5-3.マクロのコード
Sheet2の「マクロ実行」ボタンから実行されるのが、図5-6の「Hokan」プロシージャです。- Sub Hokan()
- '========== ⇩① 変数宣言部 ====================
- Dim Time_Range As Range '←時刻列の先頭セル
- Dim Time_Start_Row As Long '←時刻データの先頭行
- Dim Time_End_Row As Long '←時刻データの最終行
- Dim Data_Range As Range '←データセル範囲
- Dim DataCol() As Long '←データセル範囲から読み取ったデータ列の配列
- Dim Col As Variant '←データ列の列番号
- Dim Sdata As Double '←実値間の上端の値
- Dim SdataRow As Long '←実値間の上端の行番号
- Dim Edata As Double '←実値間の下端の値
- Dim EdataRow As Long '←実値間の下端の行番号
- Dim Darea As Long '←複数Dataエリアの数
- Dim Dcol As Long '←複数DATA列の数
- Dim Dnum As Long '←エリアと列を合わせたData列数
- Dim TimeRow As Long '←計算の為の行カウンタ変数
- '========== ⇩② 解析する行・列の取得 ====================
- On Error Resume Next
- Set Time_Range = Application.InputBox(prompt:="時刻列の先頭行データを指定して下さい", Type:=8)
- If Not Err.Number = 0 Then Exit Sub
- Set Data_Range = Application.InputBox(prompt:="補間データの列を指定して下さい", Type:=8)
- If Not Err.Number = 0 Then Exit Sub
- On Error GoTo 0
- Time_Start_Row = Time_Range.Row
- Time_End_Row = Time_Range.End(xlDown).Row
- For Darea = 1 To Data_Range.Areas.Count
- For Dcol = 1 To Data_Range.Areas(Darea).Columns.Count
- Dnum = Dnum + 1
- ReDim Preserve DataCol(1 To Dnum)
- DataCol(Dnum) = Data_Range.Areas(Darea).Columns(Dcol).Column
- Next Dcol
- Next Darea
- '========== ⇩③ 列ごとに補間データ作成 ====================
- Application.ScreenUpdating = False
- With Data_Range.Parent
- For Each Col In DataCol
- If .Cells(Time_Start_Row, Col).Value = "" Then
- .Cells(Time_Start_Row, Col).Value = 0
- End If
- If .Cells(Time_End_Row, Col).Value = "" Then
- .Cells(Time_End_Row, Col).Value = 0
- End If
- For TimeRow = Time_Start_Row To Time_End_Row
- If Not .Cells(TimeRow, Col) = "" Then
- SdataRow = TimeRow
- Sdata = .Cells(TimeRow, Col)
- EdataRow = .Cells(TimeRow, Col).End(xlDown).Row
- Edata = .Cells(EdataRow, Col).Value
- Else
- .Cells(TimeRow, Col) = _
- (TimeRow - SdataRow) * (Edata - Sdata) / (EdataRow - SdataRow) + Sdata
- End If
- Next TimeRow
- Next Col
- End With
- Application.ScreenUpdating = True
- End Sub
5-3-1.変数宣言部
図5-6の3~16行目が変数宣言部になります。各変数の意味合いは、図5-7の様になります。図5-7
例えば、セル位置(Range変数)から行位置を計算するのは簡単ですが、今回は別々に変数を起こしています。これは処理時間を短くするために出来るだけセル(Range変数)そのものに触らないようにしている為と、コードを見易くする為です。ご了承下さい。
5-3-2.解析する行・列の取得
20行目・22行目は、Application.InputBoxメソッドを使用しセル範囲を取得しています。似たものにInputBox関数がありますので、まず比較をしてみます。主な違い | Application.InputBoxメソッド | InputBox関数 | |
---|---|---|---|
入力データのタイプ指定 | 可能(図5-9) | 不可能 | |
戻り値 | OKボタン | Variant型 | String型 |
Cancelボタン | False | ""(長さゼロの文字列) |
InputBox関数は、言ってみれば「文字列をプログラム中に取り込む」ものです。しかし Application.InputBoxメソッドの方は引数Typeに図5-9の値を指定することで、データ型を制限して取り込むだけでなく、様々なデータを取り込むことが出来ます。
値 | 説明 |
---|---|
0 | 数式 |
1 | 数値 |
2 | 文字列(テキスト) |
4 | 理論値(True 又は False) |
8 | セル参照(Rangeオブジェクト) |
16 | #N/Aなどのエラー値 |
64 | 値の配列 |
今回は、セル位置・列位置をRangeオブジェクトとして取得したかったので「Application.InputBoxメソッド」を使い、引数Typeに「8」を指定しました
19行目他のエラー処理については、あとで説明します。
まず20行目では「時刻列の先頭セル」をユーザーに指定してもらい、変数Time_Rangeに代入しています。
「先頭セル」の代わりに「時刻列」を指定してもらう方法も考えられますが、データの先頭行はマクロ内での計算に必須ですので、「先頭セル指定」より手間が一つ増えます。
その手間は結構面倒で、上から順に数値か時刻か日付が出てくるまで検索し、且つそれ以降の値が等間隔で並んでいるかを確認しないといけません。
ユーザーがセル指定の作業を嫌がらずにしてくれ、且つ間違えさえしなければ「先頭セル指定法」が楽です。しかし、もし間違えてタイトル行辺りを指定されてしまうと、エラーが発生しますので注意が必要です。
また、20行目のInputBoxメソッドには、複数のセル範囲やCtrlを使って複数のブロック(Area)も入力できます。
このような入力をされても先に進んでしまいますが、26・27行目の時刻列の先頭セルの処理では、「複数セル範囲」であれば左上のセル位置を、また複数のブロックであれば最初に選択したブロックの位置を使用します。
どこまで心配すれば良いかは難しいですが、説明書等を充実するのも1つです。
22行目は「補間するデータ列」をユーザーに指定してもらい、変数Data_Rangeに代入します。
ここで「補間するデータ範囲を指定して下さい」などと指示すると、ユーザーは「空白セルだけを指定」するのか「上下のデータも含める」のかを必ず迷いますし、先頭から最下行までをShiftキーなりCtrlキーなりを使って指定させるのは酷です。
今回は「列全体」を指定してもらい、時刻データの先頭行と最終行から、実測データを含んだデータ範囲を特定しています。
19・21・23・24行目のエラー処理について説明します。
20行目・22行目のInputBoxメソッドは、正しく入力し「OK」ボタンをクリックすればRangeオブジェクトが戻されます。(尚、間違った値(Range以外)を入れてOKボタンをクリックしても「違います」とアナウンスされ、ダイアログは終わりません)
一方「Cancel」ボタンをクリック(または、右上の×印をクリック)すると、図5-8で分かる通りBoolean型のFalseが戻ってきます。戻された値は、20行目であれば変数Time_Range、22行目であれば変数Data_Rangeに代入しようとします。
しかし、変数Time_Range・変数Data_Rangeは、3行目・6行目で宣言している通り「Range型」ですので、代入時にエラーが発生してしまいます。
そこで、エラーを無視させて次の行に移らせるために、19行目に「On Error Resume Next」を置いています。
もし20行目でエラーが発生(=Cancelボタンをクリック)したら、21行目に移りエラー番号を調べ、エラーが発生(Err.Numberがゼロ以外)しているので、マクロを終了(Exit Sub)します。
20行目は正しく入力された後、22行目でエラーが発生(=Cancelボタンをクリック)したら、23行目に移りエラー番号を調べ、エラーが発生(Err.Numberがゼロ以外)しているので、やはりマクロを終了(Exit Sub)します。
最後に、24行目の「On Error GoTo 0」で元に戻しています(それ以降のコードでエラー発生した時には、キチンとマクロ停止させるため)。
26行目では、「時刻列の先頭セル」の行位置を変数Time_Start_Rowに代入しています。
27行目では、「時刻列の先頭セル」からEnd+↓で得られる「最終時刻データの最終行セルの行位置」を変数Time_End_Rowに代入します。
この開始行・最終行の間にデータは存在するとして計算を進めていきます。
29~35行目は、データ列(Data_Range)の列位置を取り出す部分です。
列を指定する方法には2種類あります。図5-10の①のように「列を選択後、そのままマウスをスライドさせて隣の列も選択する」方法と、②のように「ある列を選択後、Ctrlキーを押しながら別の列を選択する」方法です。
図5-10
まず、Ctrlキーで選択したブロック(今回は列)は「Area」として数えます。図5-10では、B列C列は1つのAreaになりますので、全部で3つのAreaになります。
いくつAreaが存在するかは、29行目の「オブジェクト.Areas.Count」で得られます。
次に、マウスをスライドさせて選択した列が何列あるかは、各Areaの中で「Columns.Count」として数え、その列そのものは「Columns(番号)」として指定します。
ですので列位置を取り出すには、まずAreaを特定(29行目)し、次にその中の列を特定(30行目)という順序で列を絞り込んでいきます。
29行目では、選択したデータ範囲(列)のAreaがいくつ存在するかを調べ(Data_Range.Areas.Coount)、カウンタ変数Dareaで1Areaずつ調べて行きます。また30行目では、そのArea内にいくつ列が存在するかを調べ(Data_Range.Areas(Darea).Columns.Count)、カウンタ変数Dcolで1列ずつ調べる、という順序で進めます。
31行目の段階では、既に1列ずつに分離されており、その列は「Data_Range.Areas(Darea).Columns(Dcol)」で特定されます。空白セルに補間データを埋める工程では、この列の情報をどの様に保持するのが良いでしょうか。
保持する形については「オブジェクトのまま」か「列名=列番号」かの、どちらかの形が考えられます。
また、保管方法については「配列」か「Collection」「Dictionary」があります。
保管形と保管方法について、図5-11、図5-12で整理してみます。
オブジェクトのまま | 列全体を配列に取り込みメモリ上で計算するには便利そうだが、 約100万行分を配列に入れておくのは無駄が多い。 Resizeする方法もあるが、その場合オブジェクトのまま保管 しておく必要は無い。 |
---|---|
列名(列番号) | セル単位で操作する場合は「Cells(行,列)」でセル指定する。
→最初に列番号を求め、数値として保管した方が処理回数少ない。 |
配列 | ユーザーが選択した順で配列にデータが収まるが、解析順序は不問の為 For Eachで呼び出しても問題無し |
---|---|
Collection | 途中でデータ列の追加等は発生しない為、順序の得意なCollection機能は不要 |
Dictionary | 解析順序は不問の為、Dictionaryの特徴であるKey呼び出し機能は不要 |
以上の比較表から「列番号」を「配列」で保持することとしました。
31行目で要素数Dnumを1つ増やし、32行目のReDimで配列のサイズを大きくした後、33行目で配列の最後に列番号を代入しています。代入する列番号は「列オブジェクト.Column」で得られます。
2つのFor~Nextを抜けると、列番号が入った1次元配列が完成します。
なお今までの説明と重複する部分がありますが、複数列を選択する方法について図5-13で説明します。
1つは①の様にマウスをスライドさせて複数列を選択した場合、もう1つは②の様にCtrlキーで複数列を選択した場合です。たとえ隣同士の列を選択した場合でも①の方法か②の方法かで処理は変わります。
図5-13
列選択のInputBoxダイアログの値も異なっていることが確認できます。(カンマでデータが区切られるとAreaが分かれます。)
5-3-3.列ごとに補間データ作成
38行目の「Application.ScreenUpdating = False」について説明します。キー入力であろうとマクロであろうと、セルへ書き込むという事はワークシート上のセルの値を変える事です。セルの値が変更された場合、Excel にとっては現時点での姿を画面に反映させるのが使命ですので、画面更新を行います。
その画面更新には多くの処理時間が必要となりますが、キー入力であればそれほど気になる時間ではありません。しかし、マクロで極短時間に多くのセルへ書込みをしようとした場合は、気になる時間になります。
マクロで1つ書込みを行った結果をExcelが画面に反映し、その反映が終わってから次のマクロ処理を行う、という順序で進みますので、多くの書込みを行うには大量の時間が必要になるのです。
そこで「Application.ScreenUpdating = False」で画面更新を停止させておき、これ以降のコードでセルへの書込みを行っても画面更新をせずに次のコードに移ることで処理時間を短縮させているのです。
書込み処理が完了したあと、61行目で画面更新を再開(Application.ScreenUpdating = True)させ、セルへの書込み内容を画面に反映しています。
39行目は、補間データ列(Data_Range)の親オブジェクト(.Parent)をWithで省略記述できるようにしています。簡単に言うと、補間データ列がSheet2にあるのであれば「With Sheet2」と記述したことと同じになり、以降はSheet2を省略してコードを書ける(プロパティ等の前に、省略した印であるピリオドが必要)のです。
これを使う場面としては、同じオブジェクトに対するコードが複数行ある場合で、1つ1つのコードを短く記述できることが出来ます。但し、With~End With が次々に出てくるような書き方をすると、逆に分かり難くなりますので注意が必要です。
40行目のFor Each は、59行目のNextまでの間での繰り返しになります。
Inの後ろの「DataCol」は、ユーザーが指定したデータ列の列番号が収められている配列です。例えば図5-7のように「B列・G列・I列」を指定したとすると、配列DataColには「2,7,9(順序は違うかもしれません)」の3要素が入っています。
それを「For Each」で1つずつ取り出し、取り出した値を「変数Col」と置いてForで回しています。
41~43行目は、データ列の先頭セルの値を調べ「空白セルであれば0を代入」する処理をしています。
今回のマクロでは「実測値~実測値の間を1区間として、その間の空白セルに補間値を代入する」ことをしていますので、最初の区間である「列の先頭セル~最初の実測値」もその対象になります。しかし「列の先頭セル」に値が無ければ計算をすることが出来ません。
ですので「実測値は無いけれど、仮に端の値をゼロとして補間値を計算」しています。
もちろん端の部分をどう扱うかは、データの種類や場面で異なると思いますので、合わせてロジックを変えて下さい。
44~46行目は、データ列の最終セルの値を調べ「空白セルであれば0を代入」する処理をしています。理由は先頭セルの処理と同一です。
48行目はFor~Nextで先頭行(Time_Start_Row)から最終行(Time_End_Row)までを回しています。現在行は変数TimeRowになります。
49行目では、その現在行のセル(Cells(TimeRow, Col) )の値を調べ、空白セル(=""(空文字))でなければ50~53行目を実行し、空白セルであれば55~56行目を実行させます。
まず「空白セルでは無い場合」は、
50行目で、現在行の行位置を変数SdataRowに記憶させ、51行目で現在行の値を変数Sdateに記憶させます。
52行目では、現在行から下にジャンプ(End(xlDown))し、飛んだ先の行位置を変数EdataRowに記憶させ、53行目で飛んだ先の値をEdataとして記憶させます。
「空白セルの場合」には、55~56行目の数式を計算し、左辺である現在行セルに値を書き込みます。
これを時系列的に表現すると、図5-14のようになります。
現在行が変化するにつれて、変数(SdataRow、Sdata、EdataRow、Edata)の値、及び56行目の数式の右辺がどう変わっていくかを示したものです。なお、数式の右辺は①~④に分けてあります。
図5-14
図5-14の例で説明します。コードの48行目のFor~Nextで、現在行(TimeRow)がTime_Start_Rowから始まり下に降りてきます。Row10まで降りて来た時、49行目のIf文で「空白以外のセル」と判定されるので、50~53行目を実行します。
それぞれの変数(SdataRow、Sdata、EdataRow、Edata)に値が代入された後、現在行(TimeRow)はRow11に移ります。
Row11は空白セルですので、コードの55~56行目を実行します。Row10の時に各変数に代入した値、及びTimeRow値を使用して、56行目の数式右辺を作成します。①~④を計算すると「150」になりますので、それをRow11の空白セルに記入します。
続いてRow12に移動し、Row10の時に各変数に代入した値とTimeRow値を使用し「200」をRow12に記入します。
続いてRow13に移動し、Row10の時に各変数に代入した値とTimeRow値を使用し「250」をRow13に記入します。
Row14に移動すると「空白以外のセル」になりますので、変数(SdataRow、Sdata、EdataRow、Edata)に値を代入し、次の空白セル区間の計算に移っていきます。
以上を現行セルがTime_End_Rowになるまで続けます。
ですので「空白セル以外(=実測値があるセル)で変数に値を代入することで、空白セルの補間データの計算が出来る」ことになるので、この計算方法を使う上では先頭行・最終行に値が入っていることが必須なのです。
1つの列の処理が完了したら、40行目のFor Each で次の列に移り、同様の処理をしていきます。
6.最後に
実際の測定データは、何千行・何万行になるかと思います。そのような大量データでの動作確認は、今回のマクロは行っていませんので、どれだけの処理時間がかかるか分かりません。もし実用性が無いほどに遅いようでしたら「セルに1つずつ値を貼る」方法では無く、全データを一度配列にしメモリー上での計算作業のあと、再貼付けするようにして下さい。中途半端に「空白セルの区画ごとに配列を貼り付け」る方法では、繰り返しが多く良い効果が得られないような気がします。
また本マクロでは、空白セルに補間データを書き込んでしまいます。書き込んでしまうと「実データ」と「計算で求めた補間データ」の見分けがつかなくなってしまいます。
ですのでマクロでデータを埋める前に、図6-1の手段を使って実データのみを選択し、セルや数値に色を付けるなりしておくと、いざと言う時に役に立つと思います。
図6-1
サンプリング周期が異なるデータの補間法(it-036.xlsm)
セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始) 解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。 |