こんにちは、hokkyokunです。
VBA学習者向けに
「Pythonいいって聞いたけど実際どうなの?」
という疑問に具体的な目的を処理するコードで比べてみます。
具体的な目的の処理
具体的な目的は以下です。
VOOの株価データを題材に、以下のような処理を行います。
- 2次元データを扱うオブジェクトを取得
- 2022年3月~2023年2月までの絞り込み
- 月末月初の値を取得 ←今回処理するのはこれ!
- 各月の月利(月当たりの価格変動)を計算して表で取得する
- 取得したデータで簡単なグラフを作成する
前回は①と②の処理を
PythonとVBAでそれぞれ書いてみました。
正直①②はPythonもVBAも大変さはあまり変わりませんが、
これ以降は、Pythonでは簡単に処理をしていくことができます!!
今回は③の月末月初の値の取得です。
VBAでのコード
前回は表をテーブル化して
2次元データをテーブルで取り扱う準備と
日付の絞り込みを行いました。
↓前回のコードです。
Sub VBA()
Dim ws As Worksheet
Dim table As ListObject
Dim maxRow As Long
'シートの取得
Set ws = Workbooks("VOO.csv").Worksheets(1)
'表をテーブル化して取得
'既にテーブル化している場合は、再度テーブル化するとエラーが出るので回避
Set table = ws.Cells(1, 1).ListObject
If table Is Nothing Then
Set table = ws.ListObjects.Add(xlSrcRange, ws.Cells(1, 1).CurrentRegion, , xlYes)
End If
'絞り込み
table.Range.AutoFilter field:=1, Criteria1:=">=2022/3/8", Operator:=xlAnd, Criteria2:="<=2023/2/28"
'最終行を取得し、値取得
maxRow = table.ListColumns("Date").DataBodyRange.SpecialCells(xlCellTypeVisible).Count
Debug.Print (table.ListColumns("Date").DataBodyRange(246))
'>>2023/02/28
End Sub
続きを書いていこうと思います。
絞ったテーブルから各月の月末月初の日付を
配列に格納します。
↓こんな感じで書いてみました。
Sub VBA2()
'========================前回までと同じ=================================================
Dim ws As Worksheet
Dim table As ListObject
Dim maxRow As Long
'シートの取得
Set ws = Workbooks("VOO.csv").Worksheets(1)
'表をテーブル化して取得
'既にテーブル化している場合は、再度テーブル化するとエラーが出るので回避
Set table = ws.Cells(1, 1).ListObject
If table Is Nothing Then
Set table = ws.ListObjects.Add(xlSrcRange, ws.Cells(1, 1).CurrentRegion, , xlYes)
End If
'=======================================================================================
Dim r As Range
Dim sDate, eDate As Date
Dim dateLists As Variant
Dim i As Long
For i = 1 To table.ListColumns("Date").DataBodyRange.SpecialCells(xlCellTypeVisible).Count
With table.ListColumns("Date")
'最初(2022年3月)の開始日は予め入れておく
'ここではまだ配列に入れない。
'2022年3月の終了日を取得出来てから配列に入れる
If i = 1 Then
sDate = .DataBodyRange(1)
'最後(2023年2月)の終了日は最終行の値を入れる
'2023年2月の開始日と終了日を配列に入れる
ElseIf i = .DataBodyRange.SpecialCells(xlCellTypeVisible).Count Then
eDate = .DataBodyRange(i).Value
Call add_Elm(dateLists, Array(sDate, eDate))
'日付を一つ一つ確認していき
'月が変われば(例えば2022年4月1日は一つ前が2022年3月31日。よって月が変わる)
'終了日を取得し、前回取得しておいた開始日とあわせて配列に格納する
'次の開始日をここで取得しておく
ElseIf Month(.DataBodyRange(i).Value) <> Month(.DataBodyRange(i - 1).Value) Then
eDate = .DataBodyRange(i - 1).Value
Call add_Elm(dateLists, Array(sDate, eDate))
sDate = .DataBodyRange(i).Value
End If
End With
Next
End Sub
これが唯一の回答ではないと思いますが、
オートフィルターとかは遅くなるので使いたくありませんでした。
正直、今回のデータ範囲だけなら大したことないですが、
銘柄を何十、何百、期間も何年もとなるとオートフィルターなんて使ってられません。
よって、上記のような処理を採用しました。
理屈が結構ややこしいので、少しだけ説明します。
基本的にはテーブルの「Date」列の値を一つ一つ見ていき、
月末月初の値を配列に格納していく作戦です。
「Date」列を上から見ていく際、
If文で三つの段階に分けて処理しています。
①最初の処理
②最後の処理
③それ以外の処理
わかりずらいので図示しました。
先ずこれでざっくり処理を見ていただければと思います。
①If i = 1 Thenの処理
テーブルの最初の値2022年3月8日をsDate(月初)にあらかじめ入れておきます。
これを入れておかないと、③の最初の処理で月初が空のまま処理してしまうことになります。
②ElseIf i = .DataBodyRange.SpecialCells(xlCellTypeVisible).Count Then の処理
テーブルの最後の値2023年3月7日をeDate(月末)に入れます
sDate(2023年3月1日)とともに配列に格納します。
③ElseIf Month(.DataBodyRange(i).Value) <> Month(.DataBodyRange(i – 1).Value) Then の処理
月が替わる瞬間にeDate(月末)に日付を入れ、
配列にsDateとeDateの組み合わせを格納します。
この後、sDateを更新します。
まあでもややこしいですね(笑)
Pythonでのコード
############前回と同じ#########################################################
import pandas as pd
import datetime as dt
#ファイルを開いてデータフレームを取得
df=pd.read_csv("./VOO.csv")
#日付のデータ型を日付型に変える
df["Date"]=pd.to_datetime(df["Date"],format="%Y-%m-%d")
#日付をインデックスにして検索や絞り込みをしやすくする
df=df.set_index("Date",drop=False)
#日付で絞る
df=df.loc[dt.datetime(2022,3,8):dt.datetime(2023,2,28)]
###############################################################################
#データフレームを月毎に区分けして
#それぞれの月初日のリスト→firsts 月末日のリスト→lasts に取得
firsts=df.resample("M").first()["Date"].tolist()
lasts=df.resample("M").last()["Date"].tolist()
#firstsとlastsをdatelistsに格納
datelists=[]
for first,last in zip(firsts,lasts):
datelists.append([first,last])
datelists
…Pythonだと実質5行で終了です。
resampleメソッドの説明を少しします
これはデータフレームを期間を区切って小分けにすることができるメソッドです。
引数を変えることで期間を色々変えれますが、
今回は月毎に区切りたいので引数を「”M”」とします。
さらにresample(“M”).first()で小分けした区分の最初の行
resample(“M”).last()で最後の行を取得できます。
これらの行のDate列の値をリスト化して格納します。
コーディングの違いは?
今回はコーディングの難易度が全然違います。
- VBAは18行、Pythonはわずか5行
- VBAのコードを読み解くのは結構大変
Pythonは簡単に何をしているかがわかる - 両者の違い
VBAは理論を自分で組み立ててコーディングしているのに対し
Pythonはすでに超優秀なエンジニアが既に作った理論にただ乗りしているだけ
Pythonは難しいように感じるかもしれませんが
むしろVBAの方がコーディングは難しいです。
なぜならだれもVBAでロジックを確立してくれていないので
自分で組むしかなくなるからです。
Pythonはすでに誰かが作ってあるので
それを使っていけばいいです。
非ITエンジニアはむしろPythonの方が楽にツールを作っていけるかもしれません。
VBAとPythonどちらを使えばよいか?
ケースバイケースですが
私は以下のように使い分けています。
VBAを使うケース
- エクセル、アウトルックの操作をする
- Pythonをコーディングする環境構築ができていない
- 人に配布する
エクセル、アウトルックの操作をする
エクセル、アウトルックの操作は
なんだかんだVBAの方が便利な場面もたくさんあります。
例えばPythonでもエクセルの操作はできますが、
セルの幅を自動調整するオートフィットは直接操作できません。
VBAなら一行ですね。
Pythonをコーディングする環境構築ができていない
また、環境構築ができていない場面ではVBAが気楽です。
なぜならエクセルが使える環境≒VBAが使える環境です(全部ではないですが…)
なので、ちょっとしたプログラムであれば、VBAでコーディングしちゃいます。
人に配布する
最後、人への配布ですが、
VBAのコードは読めるがPythonは無理って人は結構います。
なので、人に渡すものはVBAで渡した方が自分でメンテナンスしてくれる確率も高まります。
Pythonを使うケース
- 複雑なデータ処理
- Webから情報を取得する、操作をする
複雑なデータ処理
今回見たようにVBAだと処理が複雑になりますが
Pythonだと簡単にコーディングできたりします。
コーディングが楽だとメンテナンスも簡単になります。
Webから情報を取得する、操作をする
インターネット操作を自動化し、何らかの情報取得やデータ入力をする際は
Pythonの方が向いています。
VBAでもこれらの操作は可能ですが、
用意されているライブラリ(あらかじめ用意されたソースコード群)が脆弱です。
例えばグーグルクロームを操作するためには
ドライバーをインストールしなければいけないのですが、
VBAの場合はドライバーの自動更新は基本的にないので、
グーグルクロームがアップデートされるたびに
ドライバーをインストールしなおさなければいけません。
Pythonの場合はドライバーの更新を自動で行うことができるので
メンテナンスがめちゃくちゃ楽です。
結論
結論ですが、VBAはエクセル・アウトルックの自動化ツールとしては超優秀です。
が、それを超える範囲の処理(高度なデータ処理も含めます)は他言語に任せるべきです。
そして他言語の第一候補はPythonです。
理由は圧倒的に文法が理解しやすく、
また、書籍や動画解説のボリュームが他言語よりも明らかに多く、質が高いからです。
ぜひVBAをある程度学習出来たら
Pythonにチャレンジしてみてください。
次回
次回は月末月初の値を取得し、表形式にまとめたいと思います。