t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

ローソク足チャートでダイエットの経過報告

前回ローソク足で体重管理するという記事を書いた。
今回はプログラミングと全く関係ないけど、まぁ前回記事を書いてからそろそろ2週間だし経過報告しとかないとどうせ三日坊主だろとか思われるものアレなので。。

てな感じで現在のグラフがこちら。
f:id:t-hom:20191103184638p:plain

1カ月で1キロ落とすみたいなことを書いといてなんだけど、予定外にすごいペースで減っている。
ちょっと早すぎて怖いけど、まぁ今太りすぎて脂肪肝という健康被害が既に出てるので、とりあえずそれを脱するまでは特に気にしなくて良いかなと考えている。

※ちなみに11/2は急激に減ってるけど、これは夜勤であまり計測できなかったため、たまたま最低値がその日の最終計測になっただけ。

やってること

朝食はシマヤの雑炊(約100kcal)と、小分けのミックスナッツ1袋(約200kcal)

昼食は大抵外食で欲望のままにドカ食い。

夕食はウェルネスダイニングの冷凍おかず(300kcal未満)と、マンナンごはん(168kcal)。

運動は室内ウォーカー45分(時速2.3km、傾斜5度)。電動の昇降デスクを使ってるので、高さを最大にすればウォーキングしながらPCで遊べる。

あとあんまり関係ないと思うけどシックスパッドのアブスベルトを毎日25分くらい(レベル13)。

今のところ順調に進んでる要因

標準体重68kgなので、現時点で成功と呼ぶのは気が早いんだけど、減量ペース的にはまぁ順調と言える。

朝と夜のカロリー合計がだいたい固定で100+200+300+ 168 = 768kcalで、運動しない場合の代謝が2600kcalくらいなので、体重維持に必要な昼食のカロリーは1832kcal。
昼食を好きなだけ食べたところでまぁ1000kcalってところなので、毎日800kcalくらいは足りない計算。
少し運動もしてるけど、摂取カロリーからすると運動してもしなくても勝手に痩せていく仕組みを構築したのが大きい。

あとはコンビニに行くとついつい色々とおいしそうなものを買ってしまうんだけど、以前ほど立ち寄らなくなった。平日のルーチンを決めたことで遊ぶ時間が減ってしまったので早く帰宅できる日なんかはコンビニに立ち寄る時間も勿体ないと感じる。19時になるとシステムから夕食を取れというお達しがあるので、空腹になりすぎて衝動的にがっつりメシを買いに行くなんてことも減った。

ただし一番大きいのはやはりローソク足チャートで体重変化をタイムリーにつかめるようになったこと。コンビニに立ち寄ってついつい夕食でドカ食いした日とか、ポテチを食った日にはすぐにグラフが上昇に転じるので分かりやすい。一方で予め定義した食生活を守るとサクッと減少が始まる。実際には食べたものが排泄されるまで24H~48Hとのことなので、すぐに体重に出るというよりは胃腸に滞留している食物量が変化してるということだろうけど、まぁ細かいことはおいといて結果がすぐに反映されるのは嬉しい。減ったと勘違いしてモチベーションが維持されて、それにより長期的に見ても本当に減ってるなら別に結果が即反映されていると考えても実際そんなに間違いはないかなと。

ということで報告おわり。
またしばらくしたら報告しようかと思う。
特に最近コーディングのネタも無いので。

ローソク足チャートで体重管理

先日、別の記事で紹介した自動体重記録システムだが、このなかにローソク足チャートを表示させたいというものがある。
f:id:t-hom:20191012202211p:plain

今回はプログラミング関係ないけど、これをちょっと説明してみようかと。

ローソク足チャートについて

ローソク足チャートは主に株の値動きを表すために使用されるが、上下に変動が激しいデータの傾向を掴むのに向いているので体重管理にも使える。

下図がここ一週間ちょいの体重の変動だ。(後述するが、緑がDown、赤がUpと、海外の株価で一般的な配色と逆にしている。)
f:id:t-hom:20191019204326p:plain

どうやって見るか、図で示すとこんな感じ。
f:id:t-hom:20191019210344p:plain

今回のチャートではローソク1本 = 1日なので、このチャートは日足(ひあし)と呼ばれる。
上記の場合、朝のOpenに対して夜のCloseの方が値が小さいため、陰線(いんせん)と呼ばれる。

逆に朝と夜で体重が逆転した場合、箱の色が変わる。
f:id:t-hom:20191019210836p:plain
こちらは陽線(ようせん)と呼ばれる。

HighからLowへ延びる線をヒゲと呼び、OpenからCloseまでの箱を実体と呼ぶ。
つまり実体(箱)のうち、上辺と下辺のどっちがOpenでどっちがCloseかは、実体の色で判断するのだ。

カラー版のローソク足チャートでは、以下の表記が一般的だ。

陰線(下降) 陽線(上昇)
主に日本で
主に海外で

私の中では、OK=緑、NG=赤のイメージがあるので海外版の方が分かりやすい。
会社にとっては株価が下がれば赤(Bad)、上がれば緑(Good)だから。

ただ、体重となると話は別。
ただいま絶賛減量中なので、体重が減れば緑(Good)、増えれば赤(Bad)なのだ。

ということで、私のチャートはあえて配色を海外版ローソク足の逆にしている。

陰線(下降) 陽線(上昇)
私の体重チャート

なぜローソク足を使うのか

冒頭のチャートを見て頂いたら分かるように、1日の中でも体重はかなり変動する。
だから、普通は体重管理するときは、決まった時間に、同じくらいの空腹度合いで、同じような服装で行うのが良い。

ただこれ、すこぶる面倒くさい。
冬は寒いのに着こんでる服をわざわざ脱いで量るのが嫌だったり、量り忘れて食事を取ってしまったりと、色々と億劫になり続かない。

そこで考えたのが、「服装もタイミングも自由。そのかわり頻繁に測る」だ。
直近三日の計測頻度はこれくらい↓。

日時 体重
2019-10-17 06:26:07 82.6
2019-10-17 07:25:24 82.8
2019-10-17 18:44:45 83.0
2019-10-17 20:46:38 83.2
2019-10-17 21:59:52 82.8
2019-10-17 22:01:45 82.3
2019-10-17 22:19:24 82.5
2019-10-18 06:42:27 83.5
2019-10-18 07:16:53 83.2
2019-10-18 07:31:21 83.0
2019-10-18 08:21:09 83.0
2019-10-18 19:34:31 82.7
2019-10-18 20:00:20 82.7
2019-10-18 21:04:20 83.3
2019-10-19 06:20:28 83.6
2019-10-19 07:04:41 83.6
2019-10-19 08:02:07 83.6
2019-10-19 11:35:56 82.4
2019-10-19 14:24:17 83.6
2019-10-19 14:40:30 83.9
2019-10-19 16:01:43 83.8
2019-10-19 17:02:15 83.7
2019-10-19 17:16:41 83.7
2019-10-19 17:28:02 83.6
2019-10-19 19:51:01 83.6
2019-10-19 19:53:26 83.3
2019-10-19 20:18:34 83.8
2019-10-19 21:35:22 83.8

妙に近い時間帯があるのは、「そういや何キロって言ってたっけ?」的な確認か、トイレ行く前と後か、食事を取る前と後か、水を飲む前と後かそんな感じ。

ただ、こんな頻繁にデータを取ってもそのままではギザギザの折れ線になってよく分からないので、傾向を掴むための特徴的な値を抽出する。それがローソク足チャートの説明で示したOpen、High、Low、Closeの4つだ。

傾向の掴み方

体重を日足で表した場合、OpenとCloseの値にはそんなに分析価値は無い。せめて週足くらいになると、週始めと週終わりの体重変動が分かるのだが。

そこで、完全に自己流だけど、HighとLowに注目して傾向を掴むことにした。
まず各日のHighと、各日のLowの推移を分かりやすく矢印で結んでみた。上昇は赤、下降は緑、横ばいはグレーとしている。
f:id:t-hom:20191019220012p:plain

HighとLowが両方上昇していれば、これはもう確実に「太った」ということであり、両方下降していれば「痩せた」と言えるのではないかと。
(専門的には恐らく移動平均線とかそういうので傾向を掴むのではと推測。なんとなく、字面から、HighとLowの平均をとった値じゃね?とか思ってるけど壮大な勘違いの可能性があるので、知らないことは喋らないでおく。)

そして、ここ数日の体重変化は、見事に思い当たる節あり。。
f:id:t-hom:20191019221310p:plain

ここまで顕著に傾向が出ると嬉しい。とはいえ実際の変動はもっと緩やかだと思う。

なぜなら体重1キロあたり7200kcalということは、代謝エネルギー2600の人が3日間ほぼ絶食してようやく1キロなのだ。
ふつうに食ってて3日で1キロの変動はあり得ない。よって宿便が出たか、水分量が減ったか、服装による変動か、胃の中身によるブレだと思う。それでも、増量トレンドなのか、減量トレンドなのか、大まかな傾向は反映されるのでモチベーションに繋がる。

もし1日1回しか計測しなければ、体重はHighからLowまでのどの値を取るか分からないので、一喜一憂するだけで終わってしまう。
f:id:t-hom:20191019222140p:plain

これだと努力が結果に反映されるまでに長いタイムスパンが必要になるので、ダイエットが辛くなる。

まだ1週間しかデータが無いので、きちんと傾向をとらえられる仕組みなのかどうかはもう少し様子見が必要だけど、しばらくこれでやってみようと思う。

以上

VBA 数値をふりがなに変換するマクロ

今回のマクロは、数値をふりがなに変換するマクロの紹介。
例えば1なら「いち」、290なら「に / ひゃく / きゅう / じゅう」という具合だ。

これの何が難しいかというと、単純に数値の読み「1~9」と位の読み「じゅう・ひゃく・せん」を組み合わせればできるというものでもないってところ。

たとえば8は「はち」。
800は?

はちひゃくと呼んでも意味は伝わるけど、普通は「は / ゃく」だ。
2000は、「に / せん」。
3000は?

「さん & ぜん」と読む。

色々整理したところ、例外も含めた規則が分かってきたのでコードに落とし込んでみた。

規則

  • 1の位は普通に読む。
  • 1の位以外の位に「1」が出てきたら「いち」は読まずに位だけを読む。
  • ある位に「0」が出てきたら、読まずに次の位に移る。
  • 百の位が3の場合、位は「びゃく」と読む
  • 百の位が6の場合、6は「ろっ」と読み、位は「ぴゃく」と読む。
  • 百の位が8の場合、8は「はっ」と読み、位は「ぴゃく」と読む。
  • 千の位が3の場合、位は「ぜん」と読む。
  • 千の位が8の場合、8は「はっ」と読む。
  • 万より上の位は普通。ただし一万を超える値において千の位に「1」が来た場合、「いっ」と読んでも良い。

コード

出力シートのオブジェクト名はOutputSheetに変更したうえで、標準モジュールに次のコードを書いて実行する。

Option Explicit
Sub NumberToPronounciation()
    Dim numWord: numWord = Split("ぜろ いち に さん よん ご ろく なな はち きゅう")
    
    Dim i
    For i = 0 To 9999
        Dim numAsText: numAsText = Format(i, "0000")
        Dim thousandsPlace: thousandsPlace = Int(Mid(numAsText, 1, 1))
        Dim hundredsPlace: hundredsPlace = Int(Mid(numAsText, 2, 1))
        Dim tensPlace: tensPlace = Int(Mid(numAsText, 3, 1))
        Dim onesPlace: onesPlace = Int(Mid(numAsText, 4, 1))
        
        Dim c As Collection: Set c = New Collection
        Select Case thousandsPlace
        Case 0
            'Do nothing
        Case 1
            c.Add "せん"
        Case 3
            c.Add "さん"
            c.Add "ぜん"
        Case 8
            c.Add "はっ"
            c.Add "せん"
        Case Else
            c.Add numWord(thousandsPlace)
            c.Add "せん"
        End Select
        
        Select Case hundredsPlace
        Case 0
            'Do nothing
        Case 1
            c.Add "ひゃく"
        Case 3
            c.Add "さん"
            c.Add "びゃく"
        Case 6
            c.Add "ろっ"
            c.Add "ぴゃく"
        Case 8
            c.Add "はっ"
            c.Add "ぴゃく"
        Case Else
            c.Add numWord(hundredsPlace)
            c.Add "ひゃく"
        End Select

        Select Case tensPlace
        Case 0
            'Do nothing
        Case 1
            c.Add "じゅう"
        Case Else
            c.Add numWord(tensPlace)
            c.Add "じゅう"
        End Select
        
        Select Case onesPlace
        Case 0
            If c.Count = 0 Then
                c.Add "ぜろ"
            End If
        Case Else
            c.Add numWord(onesPlace)
        End Select
        
        Dim result As String: result = ""
        Dim j
        For j = 1 To c.Count
            result = result & c(j)
        Next
        OutputSheet.Cells(i + 1, 1).Value = i
        OutputSheet.Cells(i + 1, 2).Value = result
    Next
End Sub

実行結果

こんな感じで出力される。
f:id:t-hom:20191013215453p:plain
※ブログ用に画像加工で横に並べてますが出力はA列とB列だけです。

実際にやりたかったこと

実際にやりたいのは、Raspberry Piを使った数値の音声読み上げである。1.wav~9.wav, jyu.wav, hyaku.wav, sen.wavのように予め発話用のwavファイルを用意しておき、それらを組み合わせて数値を発音させる。

「体重データをもとに、必要エネルギーを計算します。あなたの体重は」
「はち」
「じゅう」
「よん」
「てん」
「に」
「キロです。」
「1カ月あたり1キロやせたい場合、本日の摂取エネルギーの上限は」
「に」
「せん」
「さん」
「びゃく」
「ろく」
「じゅう」
「なな」
「キロカロリーです。」

そんなことしなくてもLinuxにはフリーの音声合成もあるので、リアルタイムで数字そのまま読み上げさせることもできるんだけど、どうにも声が気に入らないし、購入済のVOICEROIDで作ったwavファイルと連続で読み上げさせるので、数値だけ別の声というのも違和感がある。それで検討した結果、wav組み合わせ方式を採用することにした。

一応、得意なVBAでの検証が済んだのでこのあとPythonコードに翻訳し、「いち」や「に」等の出力の代わりに「1.wav」「2.wav」を再生するように改変して出来上がりの予定。

VBA 減量のためのカロリー計算

ちょっと最近太りぎみなのでそろろそマジでダイエットでもしようかと思い立ったところ、世の中には種々様々なダイエット法が存在していて、一体どうすれば良いのか混乱する。

ただ基本的には、シンプルにこういう式で表すことができる。
 摂取エネルギー > 消費エネルギー →太る
 摂取エネルギー < 消費エネルギー →痩せる
 摂取エネルギー = 消費エネルギー →維持

それで、変なダイエット法に当たるくらいなら、ちゃんとカロリー計算した方が良いなと思って、マクロにしてみた。

データを集める

厚労省の資料から

WHOとかの研究データをもとに書かれてるので信頼できる。
https://www.mhlw.go.jp/file/05-Shingikai-10901000-Kenkoukyoku-Soumuka/0000083871.pdf

P54 3─2─3.目標とする BMI の範囲

ここには、統計データから最も死亡率の低いBMI値が掲載されている。
対象年齢ごとに目標BMIが異なるが、全年齢をカバーするのが21.5~24.93であることが分かった。

よって、その中間値 23.215 を目標に定めることにした。

達成後も21.5~24.93からはみ出ないように23.215に向けて調整し続ければ良い。

P56・P57

ここにとても重要なことが書かれている。

なお、脂肪細胞 1 gが7 kcal を有すると仮定すれば、100 kcal/日のエネルギー摂取量の減少は14.3 g/日の体重減少、つまり、5.21 kg/年の体重減少が期待できるが、上記のようにそうはならない。これは、主として、体重の減少に伴ってエネルギー消費量も減少するためであると考えられる。体重の変化(減少)は徐々に起こるため、それに呼応してエネルギー消費量も徐々に減少する。そのため、時間経過に対する体重の減少率は徐々に緩徐になり、やがて、体重は減少しなくなる。

~中略~

f:id:t-hom:20191012194257p:plain

つまり、体重変化の度に、必要エネルギー量は計算し直さないといけないのだ。

P65 4─2.基礎代謝基準値

ここには基礎代謝を求める計算式が書かれている。

年齢、性別、身長、体重を用いた下記の日本人の基礎代謝量の推定式 170)は、BMI が 30 kg/m2 程度までならば体重による系統誤差を生じないことが示されており 35)、BMI が 25~29.9 kg/m2 の肥満者では、この推定式で基礎代謝量の推定が可能である。

基礎代謝(kcal/日)=〔0.0481×体重(kg)+0.0234×身長(cm)-0.0138×年齢(歳)-定数(男性:
0.4235、女性:0.9708)〕×1000/4.186

P67~69

ここには活動レベルⅠ~Ⅲの定義と年齢を考慮した活動量がある。
ただ成人してからはそんなに変わらないので、Ⅰが1.5、Ⅱが1.75、Ⅲが2と覚えればよさげ。
私はデスクワーク中心なので、意識的に運動しない日は1.5、運動する日は1.75ということにした。

P70

最後に、推定エネルギー必要量(kcal/日)=基礎代謝量(kcal/日)×身体活動レベルという計算式が出てくる。
これでPDFから取得する材料はおしまい。

その他の情報

ググってみると、脂肪1キロあたりのカロリーは7200kcalとのこと。
また、痩せるスピードとしては1月で1キロくらいがベストらしい。
つまり30日で7200kcalのマイナスなので、推定エネルギー必要量よりも1日あたり240kcal少ない食事量にすれば、1キロ/月ペースで痩せることができる。

コーディング

シートモジュール

まずシミュレーション結果を表示させるシートのオブジェクト名をSimulationSheetに変更する。
f:id:t-hom:20191012195622p:plain

コードは次のとおり。

Private Cursor As Long
Private Enum Col
    日付 = 1
    想定体重
    活動レベル
    摂取カロリー目安
    BMI
End Enum
Private Const HEADER_ROW = 1

Sub Init()
    Me.Cells.Delete
    Cursor = HEADER_ROW + 1
    Me.Cells(HEADER_ROW, Col.日付) = "日付"
    Me.Cells(HEADER_ROW, Col.想定体重) = "想定体重"
    Me.Cells(HEADER_ROW, Col.活動レベル) = "活動レベル"
    Me.Cells(HEADER_ROW, Col.摂取カロリー目安) = "摂取カロリー目安"
    Me.Cells(HEADER_ROW, Col.BMI) = "BMI"
End Sub

Sub WriteRecord(day, weight, activity_level, cal_intake, bmi_)
    Me.Cells(Cursor, Col.日付) = day
    Me.Cells(Cursor, Col.想定体重) = weight
    Me.Cells(Cursor, Col.活動レベル) = activity_level
    Me.Cells(Cursor, Col.摂取カロリー目安) = cal_intake
    Me.Cells(Cursor, Col.BMI) = bmi_
    Cursor = Cursor + 1
End Sub

標準モジュール

標準モジュールは次のとおり。

'パラメーター(自分のデータを入れる)
Const 初期体重 = 85
Const 身長 = 176.8
Const 運動頻度 = 3  ' 日に1回
Const 減量目標 = 30 ' 日ごとに1キロ
Const 性別 = "男"
Const 開始日 = #10/12/2019#

'固定係数(基本さわらない)
Const 体重係数 = 0.0481
Const 身長係数 = 0.0234
Const 年齢係数 = 0.0138
Const 男性係数 = 0.4235
Const 女性係数 = 0.9708
Const 理想BMI = 23.215
Const 脂肪1に対するCal = 7200

Sub Main()
    Dim 体重 As Double: 体重 = 初期体重
    Dim 経過日数 As Long: 経過日数 = 0
    SimulationSheet.Init
    Do
        Dim 身体活動レベル As Double: 身体活動レベル _
            = IIf(経過日数 Mod 運動頻度 = 0, 1.75, 1.5)
        
        Dim 体重維持エネルギー As Double: 体重維持エネルギー _
            = 基礎代謝(体重, 身長, #6/28/1984#, 性別) * 身体活動レベル
            
        Dim BMI As Double: BMI = 体重 / (身長 / 100) ^ 2
        SimulationSheet.WriteRecord _
            day:=開始日 + 経過日数, _
            weight:=Round(体重, 1), _
            activity_level:=身体活動レベル, _
            cal_intake:=Round(体重維持エネルギー - (脂肪1に対するCal / 減量目標)), _
            bmi_:=Round(BMI, 1)

        体重 = 体重 - (1 / 減量目標)
        経過日数 = 経過日数 + 1
    Loop While BMI > 理想BMI
End Sub

Function 基礎代謝(体重 As Double, 身長 As Double, 生年月日 As Date, 性別 As String)
    Dim 年齢 As Double
    年齢 = CLng(Date - 生年月日) / 365
    
    Dim 性別補正 As Double
    Select Case 性別
        Case "男"
            性別補正 = 男性係数
        Case "女"
            性別補正 = 女性係数
    End Select
    
    基礎代謝 = ((体重 * 体重係数) + (身長 * 身長係数) - (年齢 * 年齢係数) - 性別補正) * 1000 / 4.186
End Function

使い方

標準モジュールのMainを実行するとシミュレーションシートにこんな感じで出力される。
f:id:t-hom:20191012200558p:plain

活動レベルが1.75の日は運動するっていう意味。運動したらその分は食うというコードになってるので、このとおりに食べた場合、運動による消費カロリーが直接ダイエットスピードを速めることにはならない。ただ筋力が付くのでその分基礎代謝は上がるだろうけど。

摂取カロリー目安の減少を見るにはややこしいので、殆ど運動しない設定に変えてみる。
※パラメータの運動頻度を999とかにすると最初の1回だけ1.75であとは1.5になる。

Mainを実行すると次のように出た。最初の1回は活動レベル1.75になるので無視したとして、明日から月末にかけて摂取カロリー目安が11キロカロリー減少しているのがわかる。
f:id:t-hom:20191012201319p:plain

つまりダイエット成功のためには、食う量を、固定で減らすんじゃなくて、減らし続けなければならないということがこのシミュレーションから分かる。

今後の応用

個人的な話で恐縮だけど、最近体重計に乗るだけでCSVに体重データを蓄積できるシステムを作った。
構成図としてはこんな感じで、できてるのはCSV書き込みのweght_watcher.pyまで。
f:id:t-hom:20191012202211p:plain

今回調べた計算式を用いれば、図のcalorie_adviser.pyが作れるので、デイリーのカロリー計算に役立つかなと思っている。

VBA 電波の図形を作成するマクロ

今回は電波の図形を作成するマクロを作った。
といってもサイン派みたいなのではなく、イラストでよくある電波でてますよーというアイコンみたいなもの。
f:id:t-hom:20191009121201p:plain

下図のように基本図形の「円弧」を使って作画するのだが、手動でやるとどうにも綺麗にできないのでプログラムで作ることにした。
f:id:t-hom:20191009122549p:plain

完成したシェイプはパワポにコピーして使う。

コード

以下コードのMainを実行すると、アクティブシートに電波が綺麗に描画される。
実行するとアクティブシートの既存シェイプが消えるので注意。このコードはまっさらなシートで実行する想定。

Sub Main()
    Call DeleteAllShape
    Call DrawRadioWave( _
        directivity:=40, _
        color:=RGB(255, 100, 100), _
        frequency:=10, _
        acceleration:=1.2, _
        intensity:=1)
    ActiveSheet.Shapes.SelectAll
    Selection.ShapeRange.Group
End Sub

Sub DrawRadioWave(directivity, color, frequency, acceleration, intensity)
    Dim sh As Shape, sh2 As Shape
    pos = 10 * acceleration ^ (freqency - 1)
    Set sh = ActiveSheet.Shapes.AddShape(msoShapeArc, pos, pos, 10, 10)
    sh.Adjustments.Item(1) = -90 - directivity / 2
    sh.Adjustments.Item(2) = -90 + directivity / 2
    For t = 1 To frequency - 1
        sh.Line.Weight = intensity
        sh.Line.ForeColor.RGB = color
        Set sh2 = sh.Duplicate
        sh2.Top = sh.Top
        sh2.Left = sh.Left
        sh2.ScaleWidth acceleration, msoFalse, msoScaleFromMiddle
        sh2.ScaleHeight acceleration, msoFalse, msoScaleFromMiddle
        Set sh = sh2
    Next
End Sub

Sub DeleteAllShape()
    For Each sh In ActiveSheet.Shapes
        sh.Delete
    Next
End Sub

パラメータの解説

DrawRadioWave関数に渡すパラメータ(引数)によって描画される電波が変化する。

    Call DrawRadioWave( _
        directivity:=40, _
        color:=RGB(255, 100, 100), _
        freqency:=10, _
        acceleration:=1.2, _
        intensity:=1)

directivity(指向性)

電波の描画角度を表す。角度は向きじゃなくて、幅の意味なので注意。
f:id:t-hom:20191009123639p:plain

color(色)

色。そのまま。

frequency(周波数)

なんちゃって周波数。単に線の数。
これを増やす場合、accelerationを下げないとでかくなる。
でかくなると描画位置がだいぶ右にズレる。なぜなら円弧は表示範囲だけでなく円周分の幅を取るので。

acceleration(加速度)

電波が広がる度にサイズがどれくらい大きくなるかを元の倍率で表す。
1.2~1.5くらいが適正。これを上げる場合、frequencyを下げないとでかくなる。

intensity(強度)

電波強度。単に線の太さ。
1~3くらいが適正。

以上

PythonでGUI版 筋トレメトロノームを作成 その2

前回は筋トレ用メトロノームをGUI化した。
thom.hateblo.jp

なるべく紹介済の機能に絞って書いたのでコードがとても冗長だ。
前回GUIパーツをそれぞれ変数に入れて扱っていたため、今回はPythonのデータ型(リスト、タプル、ディクショナリ)を使ってもう少しスッキリさせてた。

Pythonの基礎でこれらのデータ型が出てくるので、「何に使うねんこれ」と思うかもしれないけど、そのイメージを掴んでもらうためにも良いと思う。

コード

import tkinter as tk
import winsound
from time import sleep

root_window = tk.Tk()
root_window.geometry("200x160")

def start_clicked():
    replay = int(items["Replay"][1].get())
    count = int(items["Count"][1].get())
    high_tone = int(items["High tone"][1].get())
    low_tone = int(items["Low tone"][1].get())
    duration = int(items["Duration"][1].get())
    for i in range(replay):
        for j in range(count):
            winsound.Beep(55*2**low_tone,duration)
            sleep(1)
        winsound.Beep(55*2**high_tone,duration*2)
        sleep(1)

item_names = ["Replay", "Count", "High tone", "Low tone", "Duration"]
initials = [5, 3, 5, 3, 200]

items = {}

for item_name in item_names:
    items[item_name] = (
            tk.Label(
                master=root_window,
                text=item_name),
            tk.Entry(
                master=root_window,
                width=10))

btn = tk.Button(
    master=root_window,
    text="Start",
    command=start_clicked
    )

for i in range(5):
    lbl, ent = items[item_names[i]]
    lbl.place(x=5,y=5+(25*i))
    ent.place(x=70,y=5+(25*i))
    ent.insert(tk.END,initials[i])

btn.place(x=70,y=130)

root_window.mainloop()

リストについて

リストはVBAでいうコレクションのようなものだが、Pythonの場合[]の中にカンマ区切りで書くことで初期値を設定できる。

VBAの場合

初期値を設定する方法が無いので以下のように一つずつ要素を追加する。

Dim item_names As Collection
Set item_names = New Collection
item_names.Add "Replay"
item_names.Add "Count"
item_names.Add "High tone"
item_names.Add "Low tone"
item_names.Add "Duration"

Pythonの場合

このように一つずつ追加しても良いが、

item_names = list()
item_names.append("Replay")
item_names.append("Count")
item_names.append("High tone")
item_names.append("Low tone")
item_names.append("Duration")

以下のように単に[]内にカンマ区切りで同じリストを作成できる。

item_names = ["Replay", "Count", "High tone", "Low tone", "Duration"]

また、空のリストを作りたければitem_names=list()とも書けるし、item_names=[]とも書ける。

アンパックについて

Pythonではリストの内容を個別の変数に入れたいとき、次のようにインデックス指定で入れることもできるが、

a = item_names[0]
b = item_names[1]
c = item_names[2]
d = item_names[3]
e = item_names[4]

アンパックという記述方法があり、次のように書くとリストのそれぞれの要素が変数に入る。

a, b, c, d, e = item_names

これ、とても楽。

ディクショナリについて

VBAのディクショナリと同じく、キーと値の対応付けデータである。
空のディクショナリを作っているのがこちら。

items = {}

文字列でキーを指定して値を入れることで追加・更新ができる。

items["キー"] = 値

タプルについて

基本的にリストみたいなもの。ただし追加や削除などの操作ができない。
リストが主に同種データを扱うのに対し、タプルは異なる型のデータを纏める用途でよく使われるらしい。
ということで、ラベルとテキストボックスを纏めてみた。

コード中に登場する以下のループ内では、item_namesリストから取り出したitem_nameをキーとしてディクショナリitemsに、ラベルとテキストボックスのタプルを格納している。

for item_name in item_names:
    items[item_name] = (
            tk.Label(
                master=root_window,
                text=item_name),
            tk.Entry(
                master=root_window,
                width=10))

そして配置する際は、ディクショナリitemsからitem_namesリストのi番目のキーでタプルを取り出し、lblとentにアンパックしている。

for i in range(5):
    lbl, ent = items[item_names[i]]
    lbl.place(x=5,y=5+(25*i))
    ent.place(x=70,y=5+(25*i))
    ent.insert(tk.END,initials[i])

一方でテキストボックス読み出しの際は、ラベルを取り出す必要はないのでアンパックではなく[1]を指定してゲット。
※タプルの[0]にラベル、[1]にテキストボックスが入っている。

def start_clicked():
    replay = int(items["Replay"][1].get())
    count = int(items["Count"][1].get())
    high_tone = int(items["High tone"][1].get())
    low_tone = int(items["Low tone"][1].get())
    duration = int(items["Duration"][1].get())

使用したデータの組み合わせ図

f:id:t-hom:20190929133438p:plain

おわりに

データ型を覚えるとコードの冗長性が無くなってコーディングが楽になる。
今回は前回の半分くらいのコード量になった。
また、変数名を大量に管理する必要がなくなり、変更にも強くなる。

今回の記事で、あまりついてこれなかった方も、基礎をしっかり学ぶとより短い労力でコードを書けるということは伝わったかと思う。

ということで、学習がんばりましょう。

やさしいPython (「やさしい」シリーズ)

やさしいPython (「やさしい」シリーズ)

PythonでGUI版 筋トレメトロノームを作成

前回の記事ではPythonで筋トレ用メトロノームを作成した。
thom.hateblo.jp

前々回ではTkinterを使ったGUIのサンプルを紹介した。
thom.hateblo.jp

今回は、これらを組み合わせて、GUI版の筋トレ用メトロノームを作成してみよう。

動作イメージ

動作は前回のCUI版と同じだが、設定用のテキストボックスとスタートボタンを配置した。

ちょっと右の余白がダサいんだけど、これ以上横幅を削るとタイトルバーで掴める場所がなくなって、ウインドウ移動に支障をきたすので。。

コード

import tkinter as tk
import winsound
from time import sleep

root_window = tk.Tk()
root_window.geometry("200x160")

def start_clicked():
    replay = int(ent_replay.get())
    count = int(ent_count.get())
    high_tone = int(ent_high_tone.get())
    low_tone = int(ent_low_tone.get())
    duration = int(ent_duration.get())
    for i in range(replay):
        for j in range(count):
            winsound.Beep(55*2**low_tone,duration)
            sleep(1)
        winsound.Beep(55*2**high_tone,duration*2)
        sleep(1)

lbl_replay = tk.Label(
    master=root_window,
    text="Replay")

lbl_count = tk.Label(
    master=root_window,
    text="Count")

lbl_high_tone = tk.Label(
    master=root_window,
    text="High tone")

lbl_low_tone = tk.Label(
    master=root_window,
    text="Low tone")

lbl_duration = tk.Label(
    master=root_window,
    text="Duration")

ent_replay = tk.Entry(
    master=root_window,
    width = 10)

ent_count = tk.Entry(
    master=root_window,
    width = 10)

ent_high_tone = tk.Entry(
    master=root_window,
    width = 10)

ent_low_tone = tk.Entry(
    master=root_window,
    width = 10)

ent_duration = tk.Entry(
    master=root_window,
    width = 10)

btn = tk.Button(
    master=root_window,
    text="Start",
    command=start_clicked
    )


lbl_replay.place(x=5,y=5)
ent_replay.place(x=70,y=5)

lbl_count.place(x=5,y=30)
ent_count.place(x=70,y=30)

lbl_high_tone.place(x=5,y=55)
ent_high_tone.place(x=70,y=55)

lbl_low_tone.place(x=5,y=80)
ent_low_tone.place(x=70,y=80)

lbl_duration.place(x=5,y=105)
ent_duration.place(x=70,y=105)

btn.place(x=70,y=130)

ent_replay.insert(tk.END,"5")
ent_count.insert(tk.END,"3")
ent_high_tone.insert(tk.END,"5")
ent_low_tone.insert(tk.END,"3")
ent_duration.insert(tk.END,"200")

root_window.mainloop()

解説

GUIとビープ音の解説は前回・前々回で説明が終わっているので省略。
今回新登場した要素は、テキストボックスだ。

tk.Entryというクラスで作成することができる。
プログラムで文字を入力するには、insert命令で、最初の引数 tk.END はテキストボックスの最後に挿入するという意味で、2番目の引数が実際に入力する値。

startボタンを押したときにはgetメソッドで内容を取り出して、int関数で整数に直してから変数に格納し、あとはループ中で利用している。

テキストボックスの使い方はこちらのサイトが分かりやすいのでご参考までに。
pg-chain.com

今回のコードは実際にはかなり冗長なので、次回もう少しスマートにしてみようと思う。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。