この記事で学べること#
- 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.5000CSVの構造#
ヘッダー行(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 pltStep 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.200000Step 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]。