波形の書式変換で困ってませんか?

「波形ファイルをCSVで入手したけれど、使用しているプログラムが固定書式にしか対応していなかった。」

こんな経験をしたことがある方、いらっしゃるのではないでしょうか。
(RESPシリーズではCSVファイル、固定書式ファイル、どちらの波形ファイルにも対応しております。)

そんな場合に自分でさっとプログラムを書くことができると、時間の節約になり便利です。
今回はCSV書式を固定書式に変換するプログラムの作成を通して、プログラムの書き方などを紹介して参りたいと思います。

実務などでご活用をお考えの方は、最後にサンプルコードをご用意しておりますのでダウンロードの上、お使いください。

 

開発言語:python3

実装難易度:初級~中級

目次

pythonについて

今回は人気言語pythonを使用します。pythonは世界中で人気な言語のひとつであり、今回のようなデータの変換、出力を簡単に書くことができます。
インストール方法は様々ですので、お使いの環境に合わせインストールください。pythonはユーザー数が多くWeb上に知見が多いのもメリットの一つですので、通常の環境でしたら簡単にインストールを行えます。
個人的にはAnacondaという一括インストールパッケージを利用するととても簡単なのでおすすめです。

そもそも固定書式とは

実装(プログラムを書くこと)の前に、csv書式と固定書式の違いを整理したいとおもいます。csv書式とは一般に、カンマ区切りで記述されているデータ形式のことです。エクセルなどで開くことができ、多くのソフトの出力ファイル形式として利用されています。一方で、固定書式とは”7F10.2″のように表現されるルールに従って記述されているデータ形式のことで、FORTRAN 言語による固定レコード読み込み時の書式指定を意味しています。例えば、”7F10.2″は「1行につき浮動小数点(FLOAT)型が小数点以下2桁で10桁分の幅が7個連続している」という意味になります。

CSV書式の例

EL-CENTRO_NS,
X,Y
0,-0.00409716125256073
0.02,-0.0316066725197542
0.04,-0.0295580918934738
0.06,-0.025753585016096
0.08,-0.0278021656423764
0.1,-0.0351185250219491
0.12,-0.0415569212759731
0.14,-0.0374597600234124

固定書式の例

-0.0      -0.0      -0.0      -0.0      -0.0      -0.0      -0.0
-0.0      -0.0      -0.0      -0.0      -0.0      -0.1      -0.1
-0.0      -0.0      -0.0      -0.0      -0.0      -0.0      -0.0
-0.1      -0.1      -0.0       0.0       0.0      -0.0      -0.0
-0.0      -0.1      -0.1      -0.1      -0.1      -0.1      -0.1
-0.0      -0.0      -0.0       0.0       0.0       0.1       0.1
0.1       0.1       0.1       0.1       0.1       0.1       0.1

プロトタイピング

では早速プログラミングしていきましょう。プログラムの作成する際は、いきなり完成形を作ろうせずに、機能の核となる部分を単純化して書いてみることがよいと思います。(プロトタイピングともいいます。)
今回の場合ですと、例えば「3.14151」を固定書式「7F10.1」で表される「#######3.1」(半角の空白を「#」で表しています)に変換する機能が核となるはずです。
実装すべき操作は以下の二つになります。

  • 「3.14151」を「3.1」に丸める。(四捨五入)
  • 「3.1」に指定の長さになるまで「 」(空白)を入れる。(一般にパディングと言います)

四捨五入 ~「3.14151」を「3.1」に丸める~

丸める(四捨五入)する方法はいくつかありますが、ここでは「フォーマット演算子」を使ってみたいと思います。
「フォーマット演算子」とは数字を決まった書式の文字列に変換する仕組みの一つです。以下に例を示します。

print("小数:%f"%3.1415)
# 小数:3.1415

print("小数:%.1f"%3.1415)
# 小数:3.1

print関数内に出てくる「%f」はFLOAT型(浮動小数点)が入ることを意図しています。
次に「%.1f」と書いた場合は小数点1桁目まで表示するという意味になります。

パディング ~「3.1」に指定の長さになるまで「 」(空白)を入れる~

パディングもフォーマット演算子により実現できます。
(pythonにはrjust関数というパディング用の関数が用意されていますので、それを用いる方法もあります)

以下のように記述します。「.」より前の数字が文字列の長さ、「.」より後の数字が小数点以下何桁まで表示するかを表しています。

print("%5.1f"%3.1415)
#  3.1
# ↑ 3.1の前に空白が2つ付いています

このコードにおいて”%5.1f”は「FLOAT型の数字を小数点以下1桁に丸め、文字数5個分になるよう半角の空白を挿入する」という意味になります。

CSVから固定書式へ変換ツール作成

核となる機能の実装方針が分かったら、全体としてどのような機能が必要になるのかを考えます。
具体的には以下のような項目が挙げられます。

  • csvから数値を読み込む。
  • 読み込んだ数値を固定書式に変換する。(先ほどプロトタイピングした機能)
  • 固定書式に変換された数値をテキストデータとして出力する。

まず完成例をご覧ください。

完成例

#
# -*- coding: utf-8 -*-
"""
Created on Tue Oct  2 10:04:51 2018

@author: sakamoto
"""

import pandas as pd
import csv

class FormatChanger(object):
def __init__(self,str_fixed_format:str):
num_1 = str_fixed_format.index("F")
num_2 = str_fixed_format.index(".")

self.number_of_items = int(str_fixed_format[:num_1])
self.number_of_one_section = int(str_fixed_format[num_1+1:num_2])
self.round_number = int(str_fixed_format[num_2+1:])

def convert_to_formatted_value(self,str_value:str):
# 固定書式に変換
str_change_format="%"+str(self.number_of_one_section)+"."+str(self.round_number)+"f"
str_value = str_change_format%float(str_value)
return str_value

def convert_to_formatted_values_text(self,str_value_lst:list):
# 固定書式に変換した文字列群を出力
text=""
for i in range(len(str_value_lst)):
# 変換された値を取得
value = self.convert_to_formatted_value(str_value_lst[i])

# 改行コードを入れるか
# 三項演算子を用いています
value += "\n" if int(i + 1) % self.number_of_items == 0 else ""

# 文字列群に追加
text+=value
return text

if __name__=="__main__":

# 固定書式の指定
str_fixed_format="7F10.1"

# 読み込みcsvファイル名
csv_file_name = "data.csv"

# 出力テキストファイル名
text_file_name = "sample.dat"

# csvの読み込み
DataFrame= pd.read_csv(csv_file_name)
acc_lst = list(DataFrame.iloc[:,1])

FormatChanger = FormatChanger(str_fixed_format)
acc_text=FormatChanger.convert_to_formatted_values_text(acc_lst[1:])

with open(text_file_name,"w") as fw:
writer = csv.writer(fw, lineterminator='\n') # 改行コード(\n)を指定しておく
fw.write(acc_text)

 

プログラム詳細の解説

if __name__=="__main__":とは

if __name__=="__main__":」とは「他のプログラムに呼び出された」場合に、処理(今回の場合ですと「固定書式のファイルを出力する」こと)を実行せずに、内部の関数を利用できるようにするテクニックになります。「if __name__=="__main__":」以下の処理は「他のプログラムに呼び出された」際には実行されず、直接呼び出された時(コマンドラインで実行された時)にのみ実行されることになります。
(参考:“python公式ドキュメント”

前提としまして、あるシステムが一つのプログラムファイルから構成されることはほとんどありません。しばしば他のプログラムで利用されたり、逆に他のプログラムを利用したりします。そのため、プログラムを書く際は他のプログラムでも使いやすい(「再利用しやすい」とも言います)ような作りにすると後々便利なことが多いです。本プログラムの上部に「import」が記述されている箇所がありますが、ここが「他のプログラムを呼び出している(利用する準備をしている)」部分になります。今回のような比較的簡易なプログラムでも他のプログラムの機能を利用していることが分かります。

import pandas as pd
import csv

またif __name__=="__main__":と記述することで、機能を記述する部分と動作する部分を分けて書くことができ、見やすいコードになります。

関数の引数型付け [発展]

普段からpythonを書かれている方は疑問に思われたかもしれませんが、pythonは動的型付け言語であり、関数の引数に型を宣言する必要がありません。しかし、今回ご紹介するコードでは以下のように引数に型を付けています。

def convert_to_formatted_value(self,str_value:str):
# 固定書式に変換
str_change_format="%"+str(self.number_of_one_section)+"."+str(self.round_number)+"f"
str_value = str_change_format%float(str_value)
return str_value

pythonは型付けがないため、非常に簡単に実装することができ、コードの量も比較的抑えることができます。また他の言語と比較して構文が簡単であり学習コストも低くできることもメリットの一つです。一方で、「動的型付け」であるということは、プログラム作成者以外がプログラムを見た際に、明示的に変数の型が分からないことを意図しています。そのため、レビューが十分に行えず、バグ(プログラムの不具合)を生む危険性があります。そのような弱点をカバーするために、私は「引数のみ型付けをする」ということを用いています。この方法を用いることで、pythonの書きやすさを活かしつつ、第三者によるレビューのハードルを下げることができるのではないかと思っています。

csvの読み込み

ファイルの読み込み方法は複数ありますが、ここでは人気のライブラリpandasを使ってみたいと思います。pandasはファイルの読み書きに優れた機能を持つライブラリの一つで、非常に簡単に記述することができます。pandasに関する情報は多く提示されておりますので、ここでは簡単な説明に留めたいと思います。

import pandas as pd
#
#中略
#
DataFrame= pd.read_csv(csv_file_name)
acc_lst = list(DataFrame.iloc[:,1])

「pd.read_csv(ファイル名)」でcsvを一気に読み込んでいます。読み込むとcsv内のデータはDataFrame型と呼ばれるものに変換されます。
「DataFrame.iloc[:,1]」はデータを取得しています。iloc[行、列]と入力することで該当のデータをリストとして取得できます。
「:」はすべての行という意味になりますので、「DataFrame.iloc[:,1]」は1列目の全データを意味しています。

テキストファイルへの書き込み

テキストファイルへの書き出しは以下のように行います。「acc_text」に固定書式に変換された文字が入ってきますので、それを書き出しています。

with open(text_file_name,"w") as fw:
writer = csv.writer(fw, lineterminator='\n') # 改行コード(\n)を指定しておく
fw.write(acc_text)

読み込んだ数値を固定書式に変換する

今回、csv書式を固定書式に変換する機能は「FormatChanger」クラスにすべてまとめています。クラスの詳細な説明は今回は割愛します。
クラスが持っている関数はプロトタイピングで実装したものがほとんどですが、プロトタイピングでは扱わなかった以下2つについて説明します。

  • __init__関数
  • 固定書式に変換した文字列のリストを出力用の文字列に変換する関数(convert_to_formatted_values_text関数)

初期化関数(__init__関数)

固定書式への変換方法は書式ごとに異なるはずですので、「FormatChanger」クラスを使用するにはどの固定書式に変換するのかを与える必要があります。
それを記述しているのが、以下の部分になります。「def __init__(self,str_fixed_format:str)」はFormatChangerクラスを初期化するための関数になります。

class FormatChanger(object):
def __init__(self,str_fixed_format:str):

「FormatChanger」を呼び出すときは以下のように書きます。

FormatChanger = FormatChanger(str_fixed_format)

固定書式に変換した文字列のリストを出力用の文字列に変換する関数

プロトタイピングでは各々の数値を固定書式に変換するところまで実装しました。しかし、実際にテキストファイルに出力するには指定されたデータごとに改行する必要があり、そのためには、改行する位置に改行コード(一般に「\n」)を末尾に付けます。
以下に抜粋しますが、行っていることは単純です。固定書式に変換された文字列が格納されたリスト(str_value_list)を受け取り、固定書式に従い指定の文字列に改行コードを加えています。

def convert_to_formatted_values_text(self,str_value_lst:list):
# 固定書式に変換した文字列群を出力
text=""
for i in range(len(str_value_lst)):
# 変換された値を取得
value = self.convert_to_formatted_value(str_value_lst[i])

# 改行コードを入れるか
# 三項演算子を用いています
value += "\n" if int(i + 1) % self.number_of_items == 0 else ""

# 文字列群に追加
text+=value
return text

改行コードを入れるかどうかを判断するのに、if文を用いていますが、ここでは三項演算子という仕組み(ifとelseを一行で書くこと)を使っています。三項演算子を用いることでコードを短くできますが、多用すると読みにくくなるのでご注意ください。
三項演算子を使わなければ、以下のようになります。どちらを使うかは好みです。

if int(i + 1) % self.number_of_items == 0:
value += "\n"

サンプルプログラムのダウンロードと注意点

今回のプログラムは以下からダウンロードください。

Converter_csv_to_fixed_format

本プログラムによるいかなるトラブル、損失、損害について弊社は一切責任を負いません。

ここまで読んでいただきまして、ありがとうございました。
ご意見、ご質問などございましたら、お気軽にお問い合わせください。

返信を残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です