この記事で学べること#

  • JSBSimのCSV出力形式の理解
  • pandasを使ったCSVデータの読み込み
  • matplotlibによる時系列グラフの作成
  • 複数パラメータの同時表示(サブプロット)
  • グラフのカスタマイズ方法

対象読者#

  • JSBSimでシミュレーションを実行したことがある方
  • Pythonの基礎知識がある方(変数、関数、リストの理解)
  • シミュレーション結果を視覚的に分析したい方

JSBSimは、シミュレーション結果をCSV形式で出力します。本記事では、PythonでCSVデータを読み込み、グラフ化する基礎を学びます。


JSBSim CSV出力形式#

サンプルCSVファイル#

JSBSimのシミュレーション結果は、以下のようなCSV形式で保存されます。

time,phase,altitude_m,airspeed_m_s,beta_deg,phi_deg,psi_deg,p_deg_sec,r_deg_sec,rudder,throttle
0.008,initialization,150.000,11.983,0.0000,-0.00,0.00,0.0000,-0.0000,0.000000,0.5000
0.017,initialization,150.000,11.967,0.0001,-0.00,360.00,-0.0000,0.0000,0.000000,0.5000
0.025,initialization,150.000,11.951,0.0001,-0.00,0.00,-0.0001,0.0000,0.000000,0.5000

CSVの構造#

ヘッダー行(1行目):

  • 各列の変数名をカンマ区切りで記述

データ行(2行目以降):

  • 各時刻のシミュレーション結果を数値で記述

主要な列#

列名 説明 単位
time シミュレーション時刻
altitude_m 高度 m
airspeed_m_s 対気速度 m/s
phi_deg バンク角(ロール)
theta_deg ピッチ角
psi_deg ヨー角(機首方位)
beta_deg 横滑り角
rudder ラダー入力 正規化値
throttle スロットル 正規化値

環境準備#

必要なライブラリ#

pip install pandas matplotlib

パッケージ説明:

  • pandas: CSV読み込み・データ処理
  • matplotlib: グラフ作成

インポート#

import pandas as pd
import matplotlib.pyplot as plt

Step 1: CSVデータの読み込み#

基本的な読み込み#

import pandas as pd

# CSVファイルを読み込む
df = pd.read_csv('dutch_roll_demo.csv')

# データの先頭5行を表示
print(df.head())

出力例:

    time           phase  altitude_m  airspeed_m_s  beta_deg  phi_deg  psi_deg
0  0.008  initialization      150.00        11.983    0.0000    -0.00     0.00
1  0.017  initialization      150.00        11.967    0.0001    -0.00   360.00
2  0.025  initialization      150.00        11.951    0.0001    -0.00     0.00
3  0.033  initialization      150.00        11.937    0.0002    -0.00     0.00
4  0.042  initialization      150.00        11.923    0.0002    -0.00     0.00

データの基本情報確認#

# データ形状(行数・列数)
print(f"データ数: {len(df)}行, {len(df.columns)}列")

# 列名の確認
print(f"列名: {df.columns.tolist()}")

# 統計情報
print(df.describe())

出力例:

データ数: 8000行, 11列
列名: ['time', 'phase', 'altitude_m', 'airspeed_m_s', ...]

              time  altitude_m  airspeed_m_s
count  8000.000000  8000.000000    8000.000000
mean     20.000000   139.756250      12.345678
std      11.547005     8.234567       0.876543
min       0.008000   114.400000      11.500000
max      40.000000   150.000000      13.200000

Step 2: 単一パラメータの時系列グラフ#

高度の時系列プロット#

import matplotlib.pyplot as plt

# グラフ作成
plt.figure(figsize=(10, 6))
plt.plot(df['time'], df['altitude_m'], linewidth=2)

# ラベル・タイトル
plt.xlabel('Time [s]', fontsize=12)
plt.ylabel('Altitude [m]', fontsize=12)
plt.title('Altitude vs Time', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3)

# 保存・表示
plt.savefig('altitude.png', dpi=150, bbox_inches='tight')
plt.show()

解説:

  • figsize=(10, 6): 図のサイズ(幅10インチ、高さ6インチ)
  • linewidth=2: 線の太さ
  • grid(True, alpha=0.3): グリッド表示(透明度30%)
  • bbox_inches='tight': 余白を自動調整
  • dpi=150: 解像度(画面表示用)

Step 3: 複数パラメータの同時表示#

サブプロットを使った2段表示#

# 2行1列のサブプロット作成
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True)

# 上段: 高度
ax1.plot(df['time'], df['altitude_m'], 'b-', linewidth=2, label='Altitude')
ax1.set_ylabel('Altitude [m]', fontsize=12)
ax1.grid(True, alpha=0.3)
ax1.legend(loc='best')

# 下段: 対気速度
ax2.plot(df['time'], df['airspeed_m_s'], 'r-', linewidth=2, label='Airspeed')
ax2.set_xlabel('Time [s]', fontsize=12)
ax2.set_ylabel('Airspeed [m/s]', fontsize=12)
ax2.grid(True, alpha=0.3)
ax2.legend(loc='best')

# タイトル
fig.suptitle('Flight Data - Altitude & Speed', fontsize=14, fontweight='bold')

# レイアウト調整・保存
fig.tight_layout()
plt.savefig('altitude_speed.png', dpi=150, bbox_inches='tight')
plt.show()

解説:

  • subplots(2, 1): 2行1列のサブプロット作成
  • sharex=True: X軸を共有(時間軸が同期)
  • ax1, ax2: 各サブプロットの軸オブジェクト
  • 'b-', 'r-': 青線、赤線(色指定)
  • tight_layout(): サブプロット間の余白を自動調整

Step 4: 姿勢角の表示#

ロール・ピッチ・ヨー角の3段表示#

# 3行1列のサブプロット
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(10, 10), sharex=True)

# ロール角(φ)
ax1.plot(df['time'], df['phi_deg'], linewidth=2)
ax1.set_ylabel('Roll Angle φ [deg]', fontsize=12)
ax1.grid(True, alpha=0.3)

# ピッチ角(θ)
ax2.plot(df['time'], df['theta_deg'], linewidth=2)
ax2.set_ylabel('Pitch Angle θ [deg]', fontsize=12)
ax2.grid(True, alpha=0.3)

# ヨー角(ψ)
ax3.plot(df['time'], df['psi_deg'], linewidth=2)
ax3.set_xlabel('Time [s]', fontsize=12)
ax3.set_ylabel('Yaw Angle ψ [deg]', fontsize=12)
ax3.grid(True, alpha=0.3)

# タイトル
fig.suptitle('Attitude Angles (Roll, Pitch, Yaw)', fontsize=14, fontweight='bold')

# 保存
fig.tight_layout()
plt.savefig('attitude_angles.png', dpi=150, bbox_inches='tight')
plt.show()

Step 5: データの抽出と分析#

特定フェーズのデータ抽出#

JSBSimのシミュレーションでは、phase列でフェーズを管理します。

# フェーズ一覧を表示
print(df['phase'].unique())
# 出力例: ['initialization' 'excitation' 'observation']

# 観測フェーズのみを抽出
observation_df = df[df['phase'] == 'observation']

print(f"観測フェーズのデータ数: {len(observation_df)}行")

時刻範囲での抽出#

# 10秒~20秒のデータを抽出
time_range_df = df[(df['time'] >= 10) & (df['time'] <= 20)]

# 抽出したデータをプロット
plt.figure(figsize=(10, 6))
plt.plot(time_range_df['time'], time_range_df['altitude_m'], linewidth=2)
plt.xlabel('Time [s]', fontsize=12)
plt.ylabel('Altitude [m]', fontsize=12)
plt.title('Altitude (10-20s)', fontsize=14)
plt.grid(True, alpha=0.3)
plt.savefig('altitude_10_20s.png', dpi=150)
plt.show()

Step 6: グラフのカスタマイズ#

色とスタイルの変更#

# 線のスタイル・色・マーカーを指定
plt.figure(figsize=(10, 6))
plt.plot(df['time'], df['altitude_m'],
         color='green',           # 色指定
         linestyle='--',          # 破線
         linewidth=2.5,           # 線の太さ
         marker='o',              # マーカー(丸)
         markersize=3,            # マーカーサイズ
         markevery=50,            # 50点ごとにマーカー表示
         label='Altitude')

plt.xlabel('Time [s]', fontsize=12)
plt.ylabel('Altitude [m]', fontsize=12)
plt.title('Altitude with Custom Style', fontsize=14)
plt.legend(loc='upper right')
plt.grid(True, alpha=0.3)
plt.savefig('altitude_custom.png', dpi=150)
plt.show()

スタイルオプション:

  • linestyle: '-'(実線)、'--'(破線)、'-.'(一点鎖線)、':'(点線)
  • color: 'red', 'blue', 'green', '#FF5733'(16進カラーコード)
  • marker: 'o'(丸)、's'(四角)、'^'(三角)、'*'(星)

軸範囲の設定#

# 高度グラフの範囲を指定
plt.figure(figsize=(10, 6))
plt.plot(df['time'], df['altitude_m'], linewidth=2)

# X軸範囲: 0~40秒
plt.xlim(0, 40)

# Y軸範囲: 100~160m
plt.ylim(100, 160)

plt.xlabel('Time [s]', fontsize=12)
plt.ylabel('Altitude [m]', fontsize=12)
plt.title('Altitude (Fixed Range)', fontsize=14)
plt.grid(True, alpha=0.3)
plt.savefig('altitude_fixed_range.png', dpi=150)
plt.show()

Step 7: 複数データの比較#

2つのCSVファイルを比較#

# 2つのシミュレーション結果を読み込む
df1 = pd.read_csv('test1.csv')
df2 = pd.read_csv('test2.csv')

# 高度を比較プロット
plt.figure(figsize=(10, 6))
plt.plot(df1['time'], df1['altitude_m'], 'b-', linewidth=2, label='Test 1')
plt.plot(df2['time'], df2['altitude_m'], 'r--', linewidth=2, label='Test 2')

plt.xlabel('Time [s]', fontsize=12)
plt.ylabel('Altitude [m]', fontsize=12)
plt.title('Altitude Comparison', fontsize=14)
plt.legend(loc='best')
plt.grid(True, alpha=0.3)
plt.savefig('altitude_comparison.png', dpi=150)
plt.show()

Step 8: 完全な解析スクリプト例#

実用的な解析スクリプト#

"""
JSBSim CSV Data Visualization Script
"""
import pandas as pd
import matplotlib.pyplot as plt

# CSVファイル読み込み
csv_file = 'dutch_roll_demo.csv'
df = pd.read_csv(csv_file)

print(f"[OK] Loaded: {csv_file}")
print(f"     Data points: {len(df)}")
print(f"     Time range: {df['time'].min():.2f}s - {df['time'].max():.2f}s")

# 図1: 高度・速度(2段)
fig1, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True)

# 高度
ax1.plot(df['time'], df['altitude_m'], 'b-', linewidth=2, label='Altitude')
ax1.set_ylabel('Altitude [m]', fontsize=12)
ax1.grid(True, alpha=0.3)
ax1.legend(loc='best')

# 速度
ax2.plot(df['time'], df['airspeed_m_s'], 'r-', linewidth=2, label='Airspeed')
ax2.set_xlabel('Time [s]', fontsize=12)
ax2.set_ylabel('Airspeed [m/s]', fontsize=12)
ax2.grid(True, alpha=0.3)
ax2.legend(loc='best')

fig1.suptitle('Flight Data - Altitude & Speed', fontsize=14, fontweight='bold')
fig1.tight_layout()
fig1.savefig('output_altitude_speed.png', dpi=150, bbox_inches='tight')
print("[OK] Saved: output_altitude_speed.png")

# 図2: 姿勢角(3段)
fig2, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(10, 10), sharex=True)

# ロール
ax1.plot(df['time'], df['phi_deg'], linewidth=2)
ax1.set_ylabel('Roll φ [deg]', fontsize=12)
ax1.grid(True, alpha=0.3)

# ピッチ
ax2.plot(df['time'], df['theta_deg'], linewidth=2)
ax2.set_ylabel('Pitch θ [deg]', fontsize=12)
ax2.grid(True, alpha=0.3)

# ヨー
ax3.plot(df['time'], df['psi_deg'], linewidth=2)
ax3.set_xlabel('Time [s]', fontsize=12)
ax3.set_ylabel('Yaw ψ [deg]', fontsize=12)
ax3.grid(True, alpha=0.3)

fig2.suptitle('Attitude Angles', fontsize=14, fontweight='bold')
fig2.tight_layout()
fig2.savefig('output_attitude.png', dpi=150, bbox_inches='tight')
print("[OK] Saved: output_attitude.png")

print("[Done] Visualization complete")

実行方法#

python visualize_jsbsim_data.py

出力:

[OK] Loaded: dutch_roll_demo.csv
     Data points: 8000
     Time range: 0.01s - 40.00s
[OK] Saved: output_altitude_speed.png
[OK] Saved: output_attitude.png
[Done] Visualization complete

よくある問題と対処法#

問題1: 列名エラー(KeyError)#

# ❌ エラー例
plt.plot(df['time'], df['altitude'])  # KeyError: 'altitude'

原因: 列名が間違っている

対策: 列名を確認

# ✅ 正しい方法
print(df.columns)  # 列名リストを表示
plt.plot(df['time'], df['altitude_m'])  # 正しい列名

問題2: グラフが表示されない#

原因: plt.show()を実行していない

対策:

# グラフ作成
plt.plot(df['time'], df['altitude_m'])

# 表示(必須)
plt.show()

: Jupyter Notebookでは%matplotlib inlineを使用すると、plt.show()なしで表示されます。

問題3: 日本語が文字化けする#

原因: matplotlibのデフォルトフォントが日本語非対応

対策:

# 日本語フォント設定(Windows)
plt.rcParams['font.family'] = 'MS Gothic'

# 日本語フォント設定(Mac)
plt.rcParams['font.family'] = 'Hiragino Sans'

# 日本語フォント設定(Linux)
plt.rcParams['font.family'] = 'IPAexGothic'

まとめ#

本記事では、JSBSimのCSV出力データをPythonで可視化する基礎を解説しました。

重要なポイント:

  • pandas: pd.read_csv()でCSVデータを読み込み
  • matplotlib: plt.plot()で時系列グラフを作成
  • サブプロット: subplots()で複数パラメータを同時表示
  • データ抽出: df[条件]で特定範囲・フェーズのデータを抽出
  • カスタマイズ: 色・スタイル・軸範囲を自由に調整可能

次のステップとして、Plotly.jsによるインタラクティブな3D可視化(D-3「3D軌道プロット」)や、アニメーション表示(D-4「Frames API基礎」)に挑戦してみましょう。


参照資料#

本記事の執筆にあたり、以下の資料を参照しました [@pandas_docs_2025; @matplotlib_docs_2025; @matplotlib_tutorials_2025; @mckinney_python_data_analysis_2022]。