🦔

PythonでロードバイクのFITファイルを分析・可視化する

に公開

こんにちは!ロードバイクに乗る皆さん、日々の走行データ、どう活用していますか? Garmin Connect や Strava などのサービスも素晴らしいですが、「自分でデータをいじってみたい!」「もっと自由に分析したい!」と思ったことはありませんか?
この記事では、Pythonを使ってロードバイクの走行データ(FITファイル)を読み込み、基本的な情報を分析・視覚化する方法を解説します。プログラミングで、あなたのサイクリングライフをもっと豊かにしてみませんか。

この記事を読むとできるようになること:

• FITファイルから走行データをPythonで読み込む方法
• pandas を使ってデータを扱いやすい形にする方法
• matplotlib を使って走行データ(距離、標高など)を時系列グラフにする方法
• 分析したデータをCSVファイルとして保存する方法

なぜPython? なぜFITファイル?

• FIT (Flexible and Interoperable Data Transfer) ファイル:
 Garminデバイスなどで広く使われている、GPS座標、速度、心拍数、パワー、ケイデンスなど、豊富な走行データを含むファイル形式です。
• Python: 
データ分析や科学計算の分野で非常に人気のあるプログラミング言語です。
pandas や matplotlib といった強力なライブラリを使えば、複雑なデータ処理や視覚化も比較的簡単に行えます。自動化やカスタマイズも自由自在!

準備するもの

1. Python環境: まだの方は、Python公式サイト からダウンロードしてインストールしてください。

2. 必要なライブラリ: ターミナル(コマンドプロンプト)で以下のコマンドを実行して、ライブラリをインストールします。

  pip install fitdecode pandas matplotlib
  
3. FITファイル: あなた自身の走行データ(.fit 拡張子のファイル)を用意してください。
Garmin Connectなどからダウンロードできます。コードと同じフォルダに置くか、コード内のファイルパスを適切に設定してください。

コード解説:FITファイルを読み込んでグラフにしよう!

それでは、実際にコードを見ていきましょう。以下のコードは、FITファイルを読み込み、いくつかのデータを時系列でプロットし、結果をCSVファイルに保存します。fitファイルの読み込み等については、YMTLabさん、Markのらいふつくーるさんのブログ記事を参考にしました。

import fitdecode
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import sys # エラー終了用にインポート
from pathlib import Path # ファイルパス操作用にインポート
import warnings
import japanize_matplotlib 

# --- 設定 ---
# 読み込むFITファイル名を指定
fit_file_path = Path('##########.fit')
# 出力するCSVファイル名を指定
output_csv_path = Path('analyzed_ride_data.csv')
# グラフに表示するデータ列を指定 (ここでは距離、標高、スピードを指定)
columns_to_plot = ['distance', 'altitude', 'speed'] 
# --- 関数定義 ---
def read_fit_file(filepath: Path) -> pd.DataFrame:
    """
    FITファイルを読み込み、レコードデータをPandas DataFrameとして返す。
    """
    records = []
    try:
        with fitdecode.FitReader(filepath) as fit:
            print(f"FITファイルを読み込んでいます: {filepath}")
            for frame in fit:
                if isinstance(frame, fitdecode.FitDataMessage) and frame.name == 'record':
                    record_data = {}
                    for field in frame.fields:
                        record_data[field.name] = field.value
                        record_data[field.name + '_units'] = field.units
                        
                    records.append(record_data) 
                    
    except FileNotFoundError:
        print(f"エラー: ファイルが見つかりません: {filepath}", file=sys.stderr)
        sys.exit(1) # プログラム終了
    
    except Exception as e:
        print(f"予期せぬエラーが発生しました: {e}", file=sys.stderr)
        sys.exit(1)

    if not records:
        print("エラー: FITファイルから有効なレコードデータを読み込めませんでした。", file=sys.stderr)
        sys.exit(1)

    # Pandas DataFrameに変換
    df = pd.DataFrame(records)
    df = df.dropna(how='all', axis=1)
    
    return df

def plot_ride_data(df: pd.DataFrame, columns: list):
    """
    指定された列のデータを時系列でプロットする。
    存在しない列はスキップする。
    """
    available_columns = [col for col in columns if col in df.columns]

    if not available_columns:
        print("警告: プロット可能なデータ列が見つかりませんでした。")
        return

    num_plots = len(available_columns)
    fig, axes = plt.subplots(num_plots, 1, figsize=(10, 3 * num_plots), sharex=True)

    # 単一プロットの場合、axesが配列にならないため対応
    if num_plots == 1:
        axes = [axes]

    fig.suptitle('走行データ時系列プロット', fontsize=16)

    for i, col in enumerate(available_columns):
        ax = axes[i]
        ax.plot(df.index, df[col], label=col)
        ax.set_ylabel(f"{col.replace('_', ' ').title()} ({df.get(col+'_units', [''])[0]})") # 単位も表示試行
        ax.grid(True)
        ax.legend(loc='upper left')

        # X軸のフォーマット設定 (時間)
        plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha="right") # ラベルを回転

    axes[-1].set_xlabel('Time') # 最後のグラフにX軸ラベルを設定
    fig.tight_layout(rect=[0, 0.03, 1, 0.97]) # タイトルとの重なりを調整
    plt.show()

def save_to_csv(df: pd.DataFrame, filepath: Path):
    """DataFrameを指定されたパスにCSVファイルとして保存する。"""
    try:
        
        df.to_csv(filepath)
        print(f"データをCSVファイルに保存しました: {filepath}")
    except Exception as e:
        print(f"エラー: CSVファイルへの保存中にエラーが発生しました: {e}", file=sys.stderr)

# --- メイン処理 ---
if __name__ == "__main__":
    # 1. FITファイルの読み込み
    ride_df = read_fit_file(fit_file_path)

    # 2. データの視覚化
    plot_ride_data(ride_df, columns_to_plot)

    # 3. CSVファイルへの保存
    save_to_csv(ride_df, output_csv_path)


コードのポイント解説

1. 設定 (fit_file_path, output_csv_path, columns_to_plot): スクリプトの最初で、読み込むFITファイル名、出力するCSVファイル名、グラフにしたいデータ項目(列名)を指定します。fit_file_path はご自身のファイル名に必ず変更してください。 columns_to_plot も、自分のデータに含まれる項目(speed, heart_rate, cadence, power など)に合わせて調整してください。
2. read_fit_file 関数:
    ◦ fitdecode.FitReader でFITファイルを開き、for frame in fit: ループでデータフレームを一つずつ読み込みます。
    ◦ frame.name == 'record' で、1秒ごとの詳細データ(レコードメッセージ)を抽出しています。
    ◦ 各レコードからフィールド名 (field.name) と値 (field.value) を辞書 record_data に格納します。 最後に pd.DataFrame(records) でリストに溜めた辞書からデータフレームを作成。
    
3. plot_ride_data 関数:
    ◦ plt.subplots(num_plots, 1, ...) で、指定された列の数だけ縦に並んだグラフ領域を作成します。sharex=True でX軸(時間軸)を共有します。
    ◦ ループ内で各 ax (Axesオブジェクト、個々のグラフ描画領域) に対して ax.plot(df.index, df[col]) で時系列データをプロットします。df.index がX軸、df[col] がY軸(指定したデータ列)になります。
    ◦ plt.show() でグラフを表示します。
4. save_to_csv 関数:
    ◦ df.to_csv(filepath) は、データフレームの内容を指定したファイルパスにCSV形式で書き出す、pandas の便利な機能です。インデックス(今回はタイムスタンプ)も一緒に保存されます。
5. メイン処理 (if __name__ == "__main__":):
    ◦ Pythonスクリプトが直接実行された場合にこのブロック内のコードが動きます。
    ◦ 定義した関数を read_fit_file → plot_ride_data → save_to_csv の順に呼び出し、処理を実行します。

実行結果のイメージ

このコードを実行すると、以下のようなグラフが表示され、指定した名前でCSVファイルが作成されます。(表示されるグラフの数は columns_to_plot で指定した存在する列の数によります)

例:
• 時間に対する走行距離の増加グラフ
• 時間に対する標高の変化グラフ
• 時間に対する速度の変化グラフ
...などが縦に並んで表示されます。X軸は時刻、Y軸はそれぞれのデータ値です。

まとめ

Pythonを使えば、ロードバイクの走行データを自分好みに分析・可視化することができます。今回紹介したコードは、そのための最初の一歩です。ぜひご自身のデータで試してみて、データ分析の楽しさを体験してください。皆さんの参考になれば幸いです。

Happy Cycling & Happy Coding!

Discussion