t-hom’s diary

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

Python Pandasで週ごとの集計~週間運動時間グラフ表示

前回は摂取カロリー記録に使用しているM5 Stackに運動時間を入力する仕組みを追加した。
thom.hateblo.jp

入力されたデータをWifiでラズパイに飛ばして記録するところまでは完成している。

今回は記録した運動時間をmatplotlibでグラフ化して表示させる部分を作成した。

動作イメージ

週単位で運動時間を集計し、棒グラフがピンクの斜線のエリアに到達すればその週のノルマは達成したという意味になる。
f:id:t-hom:20210225003552p:plain

もともと紙で記録していた運動時間をシステム化したものであるが、実際の運動量を入力したところ2週間前の運動時間が150分に満たないことが判明。
紙の運用で△を全部潰したので達成!って思ってたんだけど、そういえば最低1つ〇を消し込んで達成というルールで設計したのを忘れていた。
f:id:t-hom:20210225003750p:plain

電子記録でグラフ化すればこんな間違いも防げる。

コード

カロリー記録よりもシンプルに見えて、実際には今回の方が難しかった。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime, date, timedelta
df = pd.read_csv("/home/pi/test_exercise.csv", header=None, names=["timestamp", "exercise_time"], parse_dates=["timestamp"])

print('\n--print dataframe which generated from csv--')
print(df)


base_day = datetime.now()
base_day = base_day.replace(hour=0, minute=0, second=0, microsecond=0)
print(base_day)
while base_day.weekday() != 5:
    base_day = base_day+timedelta(days=1)

dummy_data = []
for i in range(0, 7*7)[::-1]:
    sublist = []
    sublist.append((base_day -timedelta(days=i)))
    sublist.append(0)
    dummy_data.append(sublist)

dummy_columns = ["timestamp","exercise_time"]
dummy_df = pd.DataFrame(data=dummy_data,columns=dummy_columns)

print('\n--print dummy dataframe for data completeness--')
print(dummy_df)

df = df.append(dummy_df,ignore_index=True)

df["date"] = df["timestamp"].dt.strftime("%Y-%m-%d")
df = df.groupby(["date"]).agg({"exercise_time":"sum"}).reset_index()

print('\n--print completed data--')
print(df)

df["week"] = df['date'].apply(lambda x: -1 * ((base_day - datetime.strptime(x, '%Y-%m-%d')).days // 7))
df = df.groupby(['week']).agg({"exercise_time":"sum"}).reset_index()
print(df)


print('\n--print tailed data--')
df = df.tail(7)
print(df)


week = df["week"].values
exercise_time = df["exercise_time"].values
minimum = np.array([150 for i in df.index])
maximum = np.array([300 for i in df.index])
 
plt.title("Exercise Record", fontsize = 22)
plt.xlabel("Week", fontsize = 22)
plt.ylabel("Time", fontsize = 22)
plt.grid(True)
 

plt.bar(week, maximum-minimum, width=0.25, bottom = minimum, tick_label = week, align="center", label="Guideline", color = "none", edgecolor="#ff1493", lw=0, hatch="/////")

plt.bar(week, exercise_time, width=0.2, tick_label = week, align="center", label="Breakfast", color = "#dc143c")

plt.show()


悩んだポイントは「どうやって週単位のデータに変換するか」である。

1週間の定義を日曜~土曜としたいのだが、集計日を基準にしてしまうと1週間の範囲が日々変動することになる。

そこで、基準日(base_day)に一旦Nowを代入した後、その曜日(Weekday)が土曜になるまでbase_dayを進める。
こうしていつ集計しても当週の土曜が最終データになるようにダミーデータが作られ、基準日からの日数を7で割った整数に-1を掛けることで「何週前」を表現することにした。

今回新たに覚えた技は、データフレームのシリーズに対する加工である。
シリーズというのはデータ列のことで、例えば日付もシリーズとして格納されている。

シリーズ同士の計算はpythonが勝手にやってくれるんだけど、シリーズと単一データの計算は普通にやってもうまくいかない。
例えば次のような計算は失敗する。

df['week']  = base_day - df['date']

正しく計算するには、計算をラムダ関数にしてシリーズに適用させれば良いことが分かった。

df["week"] = df['date'].apply(lambda x: -1 * ((base_day - datetime.strptime(x, '%Y-%m-%d')).days // 7))

ああなるほど、関数型言語だなぁと思った。
また一つ、pandasの扱いが上達した気がして満足である。

おわりに

これで体重記録・摂取カロリー記録・運動量記録が揃った。
あとダイエット関連で足りないのは筋トレか。これは種目ごとの積み上げグラフとかにしたいなぁと思うけど、そもそも筋トレは始めてすらいないので紙での記録開始がまず先決かな。

開発するなら、ついでにメトロノームも実装してしまおうかと思う。

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