Pythonコードのアップデートをしたので、ランニングデータの集計と分析方法についてまとめておきます。
長らく走っていなかったメタボのアラフィフおじさんのランニング記録です。走り始めて79日、走った回数は32回になりました。
歩いているのか走っているのか、微妙なデータが並ぶことをご了承ください。
データ収集から分析まで
Apple Watchで収集したランニングデータを、iPhoneからCSVエクスポートして、PCのPythonで集計しています。
flowchart TB node_1["走る"] node_2["iPhoneフィットネスアプリ"] node_3["Runmeter"] node_4["OneDrive"] node_5["Jupter Lab"] node_6["グラフ作成"] node_7["Googleカレンダー"] node_1 =="Apple Watchで計測"==> node_2 node_2 =="インポート"==> node_3 node_3 =="統計情報をエクスポート"==> node_4 node_4 ==> node_5 node_5 =="CSVに統合・グラフ作成"==> node_6 node_3 =="カレンダー同期"==> node_7
週/月ごとの下記のデータを継続的に追っています。
- 合計距離
- 平均距離
- ランニング時間
関連性を見るための二軸性のグラフはiPhoneのアプリでは表示できないので、Pythonでグラフを作成しています。
Jupyter Lab(Python)でグラフと表を作成
Pythonのコード作成はChatGPTを使用しています。使いながら少しづつ改変しています。今回は下記の変更を行いました。
ランニングのデータを月ごと・週ごとにグラフ化・テーブル化するPythonコード
import pandas as pd import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties import os from datetime import datetime from tabulate import tabulate # CSVファイルのパス file_path = 'your_data_path/run2024.csv' # 日本語フォントのパスを設定 font_path = 'your_font_path/ipaexg.ttf' japanese_font = FontProperties(fname=font_path) # データの読み込み running_data = pd.read_csv(file_path) # 重複行を削除する前の行数 initial_row_count = len(running_data) # 重複行を削除する running_data.drop_duplicates(inplace=True) # 重複行を削除した後の行数 final_row_count = len(running_data) # 重複行が削除されたか確認 if final_row_count < initial_row_count: # 現在のタイムスタンプを取得 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") # バックアップファイルのパスを作成 backup_file_path = f"{file_path.rsplit('.', 1)[0]}_{timestamp}.csv" # 元のCSVファイルをバックアップファイルにコピー os.rename(file_path, backup_file_path) # 新しいCSVファイルとして保存 running_data.to_csv(file_path, index=False) print(f"重複行を削除し、新しいファイルを作成しました。元のファイルは {backup_file_path} にバックアップされました。") else: print("重複行は見つかりませんでした。バックアップファイルは作成されません。") # 時間列をdatetime形式に変換してインデックスに設定 running_data['時間'] = pd.to_datetime(running_data['時間']) running_data.set_index('時間', inplace=True) # ランタイムを秒から分に変換し、1kmあたりのペースを計算 running_data['ランタイム (分)'] = running_data['ランタイム (秒)'] / 60 running_data['ペース (分/km)'] = running_data['ランタイム (分)'] / running_data['距離 (km)'] # 週単位の分析 running_data['ISO週番号'] = running_data.index.isocalendar().week weekly_data = running_data.groupby('ISO週番号').agg({ 'ペース (分/km)': 'mean', '距離 (km)': ['sum', 'count'] }) weekly_data.columns = ['週間ペース (分/km)', '週間総走行距離', '週間ワークアウト回数'] weekly_data['週間平均走行距離'] = weekly_data['週間総走行距離'] / weekly_data['週間ワークアウト回数'] # 月単位の分析 running_data['年月'] = running_data.index.to_period('M') monthly_data = running_data.groupby('年月').agg({ 'ペース (分/km)': 'mean', '距離 (km)': ['sum', 'count'] }) monthly_data.columns = ['月間ペース (分/km)', '月間総走行距離', '月間ワークアウト回数'] monthly_data['月間平均走行距離'] = monthly_data['月間総走行距離'] / monthly_data['月間ワークアウト回数'] # 日本語フォントの設定 plt.rcParams['font.family'] = japanese_font.get_name() # 週単位のグラフ作成 fig, ax1 = plt.subplots(figsize=(16, 9)) ax1.set_xlabel('ISO週番号', fontproperties=japanese_font) ax1.set_ylabel('走行距離 (km)', fontproperties=japanese_font) ax1.bar(weekly_data.index, weekly_data['週間総走行距離'], color='lightblue', width=0.4, label='週間総走行距離', align='center', zorder=1) ax1.bar(weekly_data.index + 0.4, weekly_data['週間平均走行距離'], color='green', width=0.4, label='週間平均走行距離', align='center', zorder=2) ax1.tick_params(axis='x', labelsize=8) ax1.tick_params(axis='y') ax2 = ax1.twinx() ax2.set_ylabel('週間ペース (分/km)', fontproperties=japanese_font) ax2.plot(weekly_data.index, weekly_data['週間ペース (分/km)'], color='red', label='週間ペース (分/km)', zorder=3) ax2.tick_params(axis='y', labelcolor='red') fig.legend(loc='upper left', bbox_to_anchor=(0.1, 0.9), prop=japanese_font) plt.title('ISO週番号別 ランニングパフォーマンス', fontproperties=japanese_font) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") weekly_output_path = f"WeeklyAnalysis-{timestamp}.png" plt.savefig(weekly_output_path) plt.show() # 月単位のグラフ作成 fig, ax1 = plt.subplots(figsize=(16, 9)) bar_width = 0.35 months = range(len(monthly_data)) bars1 = ax1.bar(months, monthly_data['月間総走行距離'], width=bar_width, color='lightblue', label='月間総走行距離', align='center') bars2 = ax1.bar([x + bar_width for x in months], monthly_data['月間平均走行距離'], width=bar_width, color='green', label='月間平均走行距離', align='center') for bar in bars1: yval = bar.get_height() ax1.text(bar.get_x() + bar.get_width()/2, yval + 0.5, round(yval, 1), va='bottom', ha='center', fontsize=12, color='black') for bar in bars2: yval = bar.get_height() ax1.text(bar.get_x() + bar.get_width()/2, yval + 0.5, round(yval, 1), va='bottom', ha='center', fontsize=12, color='black') month_labels = [f'{m+1}月' for m in monthly_data.index.month - 1] ax1.set_xticks([x + bar_width / 2 for x in months]) ax1.set_xticklabels(month_labels) ax1.set_xlabel('月', fontproperties=japanese_font) ax1.set_ylabel('走行距離 (km)', fontproperties=japanese_font) ax1.tick_params(axis='x', labelsize=8) ax1.tick_params(axis='y') ax2 = ax1.twinx() ax2.set_ylabel('月間ペース (分/km)', fontproperties=japanese_font) ax2.plot(months, monthly_data['月間ペース (分/km)'], color='red', label='月間ペース (分/km)') ax2.tick_params(axis='y', labelcolor='red') fig.legend(loc='upper left', bbox_to_anchor=(0.1, 0.9), prop=japanese_font) plt.title('年月別 ランニングパフォーマンス', fontproperties=japanese_font) monthly_output_path = f"MonthlyAnalysis-{timestamp}.png" plt.savefig(monthly_output_path) plt.show() # Markdownのコード形式で表をテキストファイルに書き出し weekly_markdown = tabulate(weekly_data, headers='keys', tablefmt='pipe', showindex=True) monthly_markdown = tabulate(monthly_data, headers='keys', tablefmt='pipe', showindex=True) markdown_output_path = f"Markdown-table-{timestamp}.txt" with open(markdown_output_path, 'w', encoding='utf-8') as f: f.write("### 週ごとのランニングデータ\n") f.write(weekly_markdown) f.write("\n\n### 月ごとのランニングデータ\n") f.write(monthly_markdown) print("\n### 週ごとのランニングデータ\n") print(weekly_markdown) print("\n### 月ごとのランニングデータ\n") print(monthly_markdown) print(f"週単位のグラフは {weekly_output_path} に保存されました。") print(f"月単位のグラフは {monthly_output_path} に保存されました。") print(f"Markdown形式の表は {markdown_output_path} に保存されました。")
作成したグラフ
データを蓄積させて、Pythonのファイルを実行すれば自動的に下記のグラフが作成されます。
Markdownで表を作成
こちらもPythonコードを実行すれば、自動的にテキストファイルに書き出すように変更しました。
週ごとのランニングデータ
ISO週番号 | 週間ペース (分/km) | 週間総走行距離 | 週間ワークアウト回数 | 週間平均走行距離 |
---|---|---|---|---|
9 | 8.24618 | 1.09 | 1 | 1.09 |
11 | 10.5731 | 1.55 | 1 | 1.55 |
12 | 8.54601 | 1.92 | 1 | 1.92 |
13 | 8.91069 | 2.09 | 1 | 2.09 |
14 | 9.27627 | 1.84 | 1 | 1.84 |
15 | 9.65168 | 4.5 | 3 | 1.5 |
16 | 8.66516 | 9.15 | 5 | 1.83 |
17 | 8.37914 | 11.94 | 6 | 1.99 |
18 | 8.43324 | 14.97 | 6 | 2.495 |
19 | 8.61574 | 12.35 | 5 | 2.47 |
20 | 8.20327 | 5.24 | 2 | 2.62 |
月ごとのランニングデータ
年月 | 月間ペース (分/km) | 月間総走行距離 | 月間ワークアウト回数 | 月間平均走行距離 |
---|---|---|---|---|
2024-02 | 8.24618 | 1.09 | 1 | 1.09 |
2024-03 | 9.34327 | 5.56 | 3 | 1.85333 |
2024-04 | 8.7799 | 32.26 | 17 | 1.89765 |
2024-05 | 8.42348 | 27.73 | 11 | 2.52091 |
現状
心拍数をコントロールする必要がなくなった
3~4週前は走る距離を伸ばすために心拍数を上げすぎないように走っていました。息が上がってしんどかったからですが、この2週間は息が上がるよりも、ふくらはぎの張りの有無がランニングの距離とペースを左右するようになっている。
上記のグラフでは折れ線グラフのランニングペースが早くなっているものの、心拍数(最大平均)も下がっている。客観的にも心肺機能への負担が減っているが、筋肉の張りのようなランニングフォーム由来の問題が大きくなってきた。
走り方を考えよう
今後は負担が少ないランニングフォームを考えていきたいと思います。
まとめ
ランニングのログを可視化するためのPythonコードをアップデートしました。1コード1機能で運用していましたが、機能も固まってきたので複数の機能を統合したコードに変更しました。
ちょうどChatGPTもGPT-4oにバージョンアップしたところで、長いコードも途切れることなく作ってくれるようになりました。
遅々としているもののランニング能力は改善してています。ただ体重がほとんど変わりません。これは10年前のマラソンの時もそうでした。
これまでは食いしん坊のせいかと思っていましたが、昨日男性ホルモン(テストステロン)の分泌量が人よりも少ない可能性が出てきました。男性ホルモンが減ると太りやすくなるそうです。
男性ホルモン走ることに加えて、筋トレも組み合わせていきたいですが、ブログにランニング、トレーニングまで追加すると時間が問題になりますね。