Python 入門講座 第8回: 未来のために、過去を振り返る。

今日の目標

Archive Appliance データのweb Viewerからデータを入手してグラフを作成します。

前回に続き、pandas, matplotlib モジュールなどを使います。

logging, os, subprocess などのモジュールも使います。

「Archiver Appliance WEB閲覧」からデータを入手する。

J-PARC制御システムWebの「Archiver Appliance WEB閲覧」ページでは、 J-PARC加速器制御システムでアーカイブされたデータをグラフとして表示できます。表示されたグラフを再現するgnuplot用スクリプトは、簡単な操作でローカルファイルとしてダウンロード可能です。 今日の講座では、このグラフをPythonを使って、書き直してみようというのが目的です。

gnuplot スクリプト ファイルには、グラフ作成に使われたデータが、グラフ作成のgnyplotスクリプトと共に含まれています。これらのデータを、このスクリプトに次の一行のgnuplotスクリプト を追加することで、csvファイルとして取り出します。

set table 'data.csv';replot ;unset table

gnuplotコマンド

gnuplotコマンドは-eオプションを使って、実行するgnuplotコマンドを引数にわたすことができます。

ここでは、

  1. loadコマンドを使って「Archiver Appliance WEB閲覧」からダウンロードした20211123T110243.036.pltを読み込んだ後、
  2. set table \"data.csv\";replot ;unset tableコマンドを実行する。

によって、csv形式のデータファイル"data.csv"を作成しています。

gnuplot -e "load \"20211123T110243.036.plt\";set table \"data.csv\";replot ;unset table"

gnuplotスクリプトファイルからcsvファイルを生成する。

ダウンロードしたgnuplot スクリプトファイルからcsvファイルを生成する手続きを、 pythonから実行してみます。

subprocessモジュールのrun関数に実行するプログラム名とその引数をリストとしてあたえます。

pythonプログラム中からシェルプログラムなどを実行する。

python中から、シェルコマンドなど他のプログラムを実行することができます。

  1. subprocessモジュールの runコマンド
  2. osモジュールのsystemコマンド(subprocess.run( <cmd string>, shell=True)で置き換え可能)

例外(Exception)に備える。

このプログラムでは、gnuplotスクリプトが画像データを保存するための./tmpディレクトリをosモジュールの.mkdir()関数を使って作成しています。

./tmpディレクトリが既に存在する場合には、os.mkdir("./tmp")の呼び出しは失敗して、FileExistsError例外が発生します。 しかし、このFileExistsError./tmpが既に存在していることを示しているだけですから、プログラムの実行を続けても問題ありません。 これをpythonのtry文を使って記述したのが以下のプログラムです。

try:
    os.mkdir("./tmp") # これを試して、... 成功なら"./tmp"が作成される。
except FileExistsError as errmsg:
    logging.ifo(errmsg)
    pass #FileExistsErrorの時はなにもしないで、プログラムの実行を続ける。

Python 用語集より

Python: EAFP「認可をとるより許しを請う方が容易 (easier to ask for forgiveness than permission、マーフィーの法則)」

C/C++: LBYL「ころばぬ先の杖 (look before you leap)」

例外を使わない方法(LBYL)も。

1) ディレクトリが存在するかどうか確認して(os.path.exists), 2) 存在すればメッセージを出力, 3) さもなければ、ディレクトリを作成する(os.mkdir)

というアプローチも, もちろん可能です。

loggingモジュール を使って、必要な時に実行ログを残す。

プログラム開発中には、プログラムの実行状態を示すメッセージを残したいことがあります。

この際にprint()関数を使ってしまうと、「必要がなくなった場合には、このprint()関数を取り除き、必要となったら再度かきたす。」といった手間が発生してしまいます。

loggingモジュールのdebug(),info(),warning(), fatal()(あるいはcritical())などの 関数を使って、実行時メッセージを出力するようにしておくと、logging levelの設定に応じてメッセージの出力を制御できます。

上記の例では、loglevelをINFOに設定しておくと, FileExistsError例外が発生した時、端末にエラーメッセージが表示されます。

loggingモジュールの設定を変えることで、メッセージを端末に出力するだけでなく、ファイル、データベース、ログサーバーなどに記録するようにもできます。

環境変数を使ってロギングレベルを変える。

さらに、プログラムを書き換えることなくロギングレベルを変更することができれば、 必要に応じてプログラムからのメッセージ出力の有無を切り替えることができて便利です。

プログラム実行時にロギングレベルを指定する方法にはいろいろな可能性がありますが、 ここでは環境変数を使う方法を紹介します。

pythonプログラムでは実行中のプロセスがもつ環境変数は、osモジュールのos.environ変数を使って、 読み書きすることができます。

環境変数の設定

pythonを実行する際に、sh/bashなどでは、

PYTHON_LOG_LEVEL=INFO python3

あるいは cshなどでは、

env PYTHON_LOG_LEVEL=INFO python3

としてpythonプロセスを起動すると、実行中のpythonプログラムでは、環境変数PYTHON_LOG_LEVELが"INFO"に設定されています。

CSVファイルの中身をpythonプログラムで読み込む。

出来上がったdata.csvファイルの中身をエディタで覗いてみます。


# Curve 0 of 2, 981 points
# Curve title: "MRMON:DCCT_073_1:VAL:MRPWR"
# x y ylow yhigh type
"2020 Jan.22 00:00:00"  504.467  504.126  504.673  i
"2020 Jan.22 00:00:32"  504.287  503.774  504.579  i
"2020 Jan.22 00:01:04"  504.212  503.507  504.593  i
...
"2020 Jan.22 08:59:44"  504.45  504.004  505.006  i


# Curve 1 of 2, 981 points
# Curve title: "MRMON:DCCT_073_2:VAL:MRPWR"
# x y ylow yhigh type
"2020 Jan.22 00:00:00"  511.526  511.123  511.714  i
"2020 Jan.22 00:00:32"  511.312  510.748  511.641  i
"2020 Jan.22 00:01:04"  511.245  510.463  511.704  i
....
"2020 Jan.22 08:59:44"  511.646  511.173  512.269  i

という様に、チャンネル毎のデータが空白行とコメント行(#)で区切られて記録されていることがわかります。

pandas.dataframeに読み込む

また各チャンネルのデータは一行毎にx, y, yhigh, ylow, typeの値が空白(\s)で区切られて記録されています。 データ数が981であることもわかります。このファイルをpandas.read_csv()関数で読み込んでみます。

各行の最初の項はデータの時刻ですから、日付/時刻として読み取ってやることが必要です。 最初のチャンネルはコメントを四行読み飛ばした後から、981行続いています。 次のチャンネルのデータは、その先さらに空行とコメントをあわせて五行読みとばした後から、981行続いています。

各列のラベルはx, y, ylow, yhigh, typeと設定します。(pandas.csv_readは各列のラベルをファイルから読み込むことも できますが、この例の場合行頭の'#'がじゃまになるので、手動で設定しています。)

この例の場合行頭の'#'がじゃまになる

#が最初のラベルとして認識されてしまうため、ラベルがひとつずれてしまいます。

読み込んだDataframeの中身を確認してみます。

Dataframeからグラフをプロットして見よう

得られた二つのデータフレームを一つのグラフに表示してみます。

最初のplotの戻り値(axes)を,次のplotaxにわたすことで、一つ以上データを同一のグラフに表示します。

こんどはmatplotlibを使ってグラフを作成

matplotlib.pyplot を使って、データをグラフ化してみます。

軸ラベルの調整

軸のラベルとTicksの調整を行います。

軸ラベルの調整(その2)

エラーバーの表示

処理を関数にまとめる。

これまで述べたことで、一つの.pltファイルに対して何をなすべきかはわかりました。 しかし、異なる日付のデータを取得する毎にプログラムを書き換えるのは面倒です。 ということで、.pltファイル名が与えられた時に,

を作ってみます。

pltファイルからcsvファイルを作成する関数

csvファイルからグラフとpngファイルを作成する関数

二つの関数を組み合わせて、グラフと.pngファイルを作成する関数

このplt2png()関数に与えるgnuplotスクリプトファイルの名前を変えるだけで、 グラフが.pngファイルに作成されます。

図:本日作成したpl2png()関数の働き

本日作成した`pl2png()`関数の働き

今回紹介したPythonの文法要素

dict.getメソッド

辞書型データのdのキーkの値は、

v=d[k]

で取り出すことができます。しかし、辞書型データdがこのキーkを含まない場合には、エラー(KeyError)となってしまいます。 このような時に、.getメソッドを使い、

v=d.get(k, "undefined")

とすることで、dがキーkをもっている時にはd[k]そうでないときには"undefined"が変数vに割り当てられます。

logging モジュール

loggingモジュールではいくつかのクラスが定義され、それらが組み合わされてうごいています。

ロガー(Logger)

loggingモジュールを使ったメッセージの出力先は、 端末だけではなく、ファイル、syslogシステムなどにも出力可能です。 また、これらの出力先を同時に組み合わせることも可能です。 この機能は、loggingモジュールのロガー(Logger)、ハンドラー(handler), フォーマッタ(Formatter)クラスを使って実現されています。

デフォルトのロガーで記録するログのレベルを変更するには、次の慣用句を使います。

logging.get_Logger().setLevel(logging.INFO)

loglevelとlogging 関数

ロガーのログレベルは、loggingで定義されている定数を使って設定します。

loggingモジュールのメッセージ関数は、呼び出し時のログレベルが自分自身のレベル と同じかそれ以下である時に、実際にメッセージを出力します。

レベル 定数 メッセージ関数 用途
NOTSET - 全てのメッセージ
DEBUG debug() デバッグのためにメッセージを出力
INFO info() プログラム実行に影響しない
WARNING warning() プログラム実行は継続するが、異常につながる可能性がある
ERROR error() プログラム実行中にエラーが発生。
CRITICAL critical() / fatal() 重大なエラー。プログラム実行を継続できない.

try

プログラム実行中に発生した例外(実行時エラーなど)が発生した場合、 適切な処理をおこなうことで、プログラムの実行を継続が可能な場合があります。 また、プログラムの実行継続が不能な場合には、エラーの原因などの情報と、それにより 「実行が不能になった」というメッセージをユーザに通知することは有用です。

このようなエラー(例外)発生時の対処法をプログラム中に書いておく時、try文が使われます。

try文の 概観は次のようになっています。

try:
    ...
 except <Exception> [as <id>]:
    ...
 except:
    ...
 else: # 例外が発生しなかった場合の処理
    ...
 finally: #例外発生 or not に関わらない、後始末処理
    ...

try文の正確な定義は:

try_stmt  ::=  try1_stmt | try2_stmt
try1_stmt ::=  "try" ":" suite
               ("except" [expression ["as" identifier]] ":" suite)+
               ["else" ":" suite]
               ["finally" ":" suite]
try2_stmt ::=  "try" ":" suite
               "finally" ":" suite

try-except-...構文とtry-finally構文。try:の後にはexcept:節かfinally:節のどちらかが続く必要があるということ。

例外

Pythonが発生する標準的な例外(組み込み例外)には次のようなものがあります (一部のみを示します、全ての組み込み例外は help(__builtins__) を実行して表示させます。) 一つの例外BaseExceptionを祖先にもつ、クラスです。

BaseException
            Exception
                ArithmeticError
                    FloatingPointError
                    OverflowError
                    ZeroDivisionError
                AttributeError
                EOFError
                MemoryError
                RuntimeError
                SyntaxError
                SystemError
                TypeError
                ValueError
                Warning
            KeyboardInterrupt

ユーザー独自の例外 を定義することも可能です( 例外を継承したクラスを定義します。)

今日のまとめ

Archive Appliance からデータを入手して、Pythonで処理する方法

  • os, logging, os.path
  • pandas, numpy, matplotlib
  • matplotlib.pyplot
  • matplotlib.dates

dict型データの.get()メソッドの使い方

例外処理の基本(try文)

次世代の(?) グラフ作成ライブラリ(plotly)

matplot/gnuplot以外にもpythonから利用可能なグラフ作成パッケージ/モジュールがあります。plotlyはその一つです。Javascriptをベースにしていて、HTML/Web ブラウザにグラフが表示されます。

python3 -m pip install -U plotly

でインストールできます。