データファイルの読み書き

データファイルの種類

多くの実験データは、基本的に数値として得られます。もっというと、たくさんの数値の集まりであることが多いと思います。このような、たくさんの数値を一度に解析するのに適しているのが、numpy array(配列)です。そのため、まず実験データをnumpy arrayとして読み込む必要があります。ここでは、実験データファイル(以下ではtestdata.txtという実験データファイルがあると仮定します)を、適切なフォルダの中に置きます。例えば、デスクトップに実験データ解析用のフォルダを作っておき、さらに、その中に20210716のような日付のフォルダを作るのが良いかもしれません。そして、そのデータファイルと同じ場所にjupyter notebookをつくって、開いてください。

実験データファイルの形式に応じて、場合を分けて以下で説明します。自分の扱っているファイルがどういう形式なのかは、実験装置制御のソフトウェアのマニュアルを確認したり、指導教員・先輩に聞いてみましょう。実際には、ファイルの拡張子で、おおよそ予想ができます。

  • .csvファイル:comma separated valuesの略で、delmiter(詳細は、以下の2次元ファイルの説明を見てください)がコンマである2次元的なasciiファイル(asciiファイルは、要するに半角英数字だけから作られたテキストファイルのことだと思ってください。詳細は、下記の「バイナリファイル」の説明をみてください)です。

  • .txtファイル:おそらくasciiファイルです。1次元なのか2次元なのか、delimiterは何なのかなど、詳細な形式はファイルの中身を見るのが手っ取り早いでしょう。メモ帳やwordなどでファイルを開いて中身を確認してください

  • .datファイル:バイナリファイル(説明は下を読んでください)の可能性が高いですが、ひょっとしてasciiデータかもしれません。メモ帳などで中身を確認しましょう。

  • その他のファイル:ケースバイケース。聞いたことのない拡張子の場合、念の為メモ帳などで開いてみてascii形式かどうか確認しましょう。

最も単純な1次元的なascii形式の実験データの場合

testdata.txtの中身が

1
1.5
2
101
0
2

のように数字が一列に並んだテキストファイルだと仮定します。この場合は

import numpy as np # numpyを読み込む
filepath = "testdata.txt" # ファイル名を文字列として、変数filepathに代入
mydata = np.loadtxt(filepath) # ファイルを読み込んで、mydataを言う名前のnumpy arrayを作る

これで、mydataという名前のnumpy arrayに実験データが読み込まれました。

ヘッダーがあるascii形式の実験データの場合

testdata.txtの最初に

intensity is recored
1992/1/2 10:45
average of 10 trials
1
1.5
2
101
0
2

のように、数行に渡って、「このデータについての説明(実験日や実験条件など)」が記されている場合もあります。このようなファイル冒頭にある部分をヘッダーと呼びます。このヘッダー部分はnumpy arrayに読み込む必要がないため

import numpy as np
filepath = "testdata.txt"
mydata = np.loadtxt(filepath,skiprows = 3)

と、skiprowsというオプションを使うことで、3行飛ばしてからデータを読み込むことができます。

あるいは、

# intensity is recored
# 1992/1/2 10:45
# average of 10 trials
1
1.5
2
101
0
2

のようにヘッダー部分が特定の記号で始まっている場合は

import numpy as np
filepath = "testdata.txt"
mydata = np.loadtxt(filepath,comments = "#")

のようにcommentsオプションを使うことで、#からはじまる行を全部飛ばして読み込むことができます。

2次元的なascii形式の実験データの場合

csvファイル(comma separated valuesの略で、delimiterはコンマ)などの、2次元的に数値が並んだasciiファイルの場合を考えます。ここでは

1,130,200,400
2,900,100,140
3,200,100,140
4,120,100,500
5,220,100,900
6,120,110,100

のようにdelimiter(数値を区切るために使われる文字のこと)がカンマである場合を考えます。 この場合、

import numpy as np
filepath = "testdata.txt"
mydata = np.loadtxt(filepath,delimiter=',')

とすれば、2次元のnumpy arrayにデータが読み込まれます。データは2次元のnumpy arrayに読み込まれることに注意してください。 先ほどとの違いは、delimiter =","のオプションでdelimiter(区切り文字)を指定している部分だけです。

また、データに一部欠落がある場合を考えましょう。つまり、

1,130,200,400,
2,,100,140,
3,,100,140,
4,120,100,500,
5,220,100,900,
6,120,110,100,

のようにdelimierの間に値がない場合や、行の最後がdelimiterで終わっている場合にはgenfromtxtを使えば読むことができます。先程のloadtxtをgenfromtxtに置き換えるだけです。

import numpy as np
filepath = "testdata.txt"
mydata = np.genfromtxt(filepath,delimiter=',')

とすると、mydataにデータが読み込まれますが、データ値がなかった場所はNaN(「数値ではない」を意味する記号)に置き換えられています。NaNではなく0に置き換えたい場合は

import numpy as np
filepath = "testdata.txt"
mydata = np.genfromtxt(filepath,delimiter=',',filling_values=0)

としましょう。

2次元的なascii形式の実験データを、一列ずつ別のarrayに読み込みたい場合

上と同じデータを一列ずつ別の1次元numpy arrayに代入したい場合もあるでしょう。例えば、温度や電圧の時間変化などを測定している場合、各列は別の物理量なので別々に管理したいかもしれません。その場合、

import numpy as np
filepath = "testdata.txt"
mydata1,mydata2,mydata3,mydata4,mydata5 = np.genfromtxt(filepath,delimiter=',',unpack=True)

のようにunpack = Trueというオプションを使うことで、データを分けて(unpackして)、各 numpy arrayに代入できます。

バイナリファイルの場合

バイナリファイルとは一般には、テキストファイル以外のファイルすべてを指す専門用語です。一方で、実験データや数値解析に関連する文脈では、連続する1次元的な数値データを2進数化して「とあるルールのもとで」保存しているファイルのことを指します(つまり前者の意味のバイナリのほうが広い概念です)。ここでは両者を区別するために、前者を単に「バイナリ」、後者を「バイナリ数値データ」と呼びます(あくまでも、ここでの呼び方であって、一般的な呼び方では有りません)。

(まず、「バイナリ」の意味合いについて説明して、その後に読み込み方法を説明しますが、興味がなければ読み込み方法のところまで飛んでください) 「バイナリ」あるいは「バイナリ数値データ」を使うことの意味は、高速化・省容量化にあります。そもそも、コンピュータ上では、全てのファイルは2進数つまり0と1の組み合わせで保存されています。例外はありません。これはコンピュータが、電気回路として2進数をベースに動作しているためです。ただし、このままでは人間には理解ができず、意味がないので、何らかの「ルール」を決めて、ソフトウェア上でファイルを開いたときには、人間が読み書きできるように表示しています。WordファイルでもExcelファイルでも、独自のルールによってファイルを保存したり読み込んだりしています。一方で、このままでは、とあるソフトウェアで作られたファイルを、別のソフトウェアで読み書きするときに困った事態が生じます。お互いに「お互いのソフトウェアのルール」を全部知り尽くして、「ルール」に変更があるたびにソフトウェアの動作を変更し直す必要があります。これでは、面倒くさいので、広く使われそうなファイルの種類は、世界中で共通のルールを決めようということになります。これが標準化です。標準化されたファイル形式として最も有名なのがasciiファイルです。このルールは、7桁(あるいは8桁)の2進数と、アルファベットなど西洋の文字との間を対応させるルールです。あまりにも広く使われているので、asciiのルールで作られたファイルは、どんな文書用ソフトウェア(例えばメモ帳, word, textedit)でも読み書きできます。人間がテキストエディタで簡単に読み書きできるので、いろいろな用途で使われます。例えば実験によって得られた数値データもascii形式で保存される場合が多いでしょう。

ascii形式で実験データで保存しておくと、人間が読み書きがしやすいかわりに、ファイル容量を無駄に使っています。例えば、

10000
31000
49000

というデータをasciiで保存するには 1 0 0 0 0 改行 3 1 0 0 0 改行 4 9 0 0 0 という、合計17個の文字(改行も1つの文字として扱われます)が必要になります。asciiでは通常8桁(場合によって7桁)の2進数を組として1文字を表すことになっています(例えばaという文字は01100001という2進数に対応するルール)。つまり17個の文字に対応して、8×17 = 112桁の2進数を使ってデータを表すことになります。

一方で、このファイルに含まれる数字が10000から49000までしか無いことに着目して、「0から65535(=2^16-1)までの数字を16桁の2進数で表す」と決めておけば、この同じデータを2進数のままで保存しておくことができます。この場合、必要な桁数は16×3=48で、asciiを使う場合の半分以下ですみます。これが「バイナリ数値データ」に対応します。このようなファイルサイズの違いが重要となる場合(例えば1つのファイルが100MB以上の大きさで、たくさんのデータを扱うような場合、3倍は大きな差になるかもしれません)には、バイナリ数値データを使う意味があるかもしれません。また、一般に実験装置からやってくるデータを高速にPCに記録する必要がある場合もバイナリ数値データのほうがasciiよりも有利になります。

バイナリ数値データを扱う場合には、どのようなルールのもとにそのファイルが作られているかを知っている必要があります。「0から65535(=2^16-1)までの数字を16桁の2進数で表す」というルールのもとで作られた、100個の数値が保存されたバイナリ数値データがあったとしましょう。ところが、このファイルを間違って「0から255(=2^8-1)までの数字を8桁の2進数で表す」というルールだと解釈して読み込んでしまうと、200個の意味のない数値に変換されてしまいます。それ以外にも、2進数の数字をどの順番に保存しておくか(一番大きい桁から並べるか、小さい桁から並べるか)といったルールは、OSに依存しており、windowsで作られたバイナリ数値データをmacで読み込むと、うまくいかないなど、ややこしい面もあります。従って、解析になれないうちは、可能な限り「バイナリ数値データ」は使わないほうが良いでしょう。「バイナリ数値データ」を使うのは

  • ファイルサイズを、どうしても小さくしたい

  • numpy以外でデータを読み書きする必要があるかもしれない

  • 他人にはデータを渡さない。あるいは渡すとしても、上記のルールを伝えた上で渡すことが確実

のすべての条件が満たされた場合のみにしましょう。

  • ファイルサイズを可能な限り小さくしたい

  • numpy以外ではファイルの読み書きをしない

のであれば、バイナリ数値データではなく、numpy推奨のNPYフォーマットを使うようにしましょう(np.save()やnp.loadで読み書きが可能)。 それ以外の場合には、なるべくasciiファイルを用いましょう。

一般的な「バイナリ数値データ」は

myarray=np.fromfile('mydata.dat', dtype=np.uint16)

のようにしてfromfile関数を使うことで、numpy arrayとして読み込むことができます。ここでdtype=np.uint16はデータをUnsigned INTeger 16 bit(符号のない整数で、2進数16桁)読み込むことに対応します。その他の数値の形式については https://docs.scipy.org/doc/numpy/user/basics.types.html を参照してください。

また、numpy推奨のNPYフォーマットによるバイナリデータは

myarray = np.load('mydata.dat')

だけで読み込むことができます。

一般的な「バイナリ」ファイルの場合

ここでいう「バイナリ」の意味は、上記の「バイナリファイルの場合」を参照してください。一般的なバイナリファイルの場合、普通はお手上げです。何らかのプログラムを使って、asciiファイルに変換しましょう。ただし、あなたが使ってる実験装置や実験装置を制御するソフトウェアが、そこそこ知名度の高いものであれば、世界の誰かが、その特定のバイナリファイルを読み込むためのモジュールを提供している場合があります。例えばwiinspecというソフトウェアで保存されるSPEファイルは、https://github.com/antonl/pyWinSpecを使って読むことができます。

画像ファイルの場合

画像ファイルもnumpy arrayとして読み込むことができます。 予め、pillowというパッケージをインストールしておきましょう。pillowは画像をpythonで扱うための有用なパッケージです。 https://anaconda.org/anaconda/pillow によると

 conda install -c anaconda pillow 

がインストールコマンドです。test.pngという画像ファイルがある場合

from PIL import Image # pillowパッケージからimage モジュールを読み込む
import numpy as np

imfilepath="test.png" #ファイル名を指定して、文字列変数imfilepathに代入
im = np.array(Image.open(imfilepath)) # 画像を読み込んで、numpy arrayに代入

によってimというnumpy arrayに画像データが格納されます。もし画像データがカラーの場合、numpy arrayは3次元になります。ピクセルは2次元に並んでいますが、各ピクセルについて色情報(例えばRGBを表す3つの数字など)が必要なためです。自分が読み込んだ画像がどのようなnumpy arrayに変換されるか、気にするようにしましょう。numpy arrayの次元数はim.shapeによって確認できます。

マルチページtiffファイルの場合

マルチページtiffファイルは動画の一種として使われる場合もあります(連続した画像と考えることができます。)。ここでは、例としてtest.tifというファイルを読み込むことを考えます。

前述の手順に従って、pillowパッケージを予めインストールしておきます。その上で、

from PIL import Image,ImageSequence
import numpy as np

framenum = 10 #みたいフレーム数を入れる。最初のフレームは0番目、次が1番目という数え方。
imfilepath="test.tif"

im = Image.open(imfilepath) # マルチページtiffファイルを、「抽象的な意味で」変数imに代入。
im.seek(framenum)#マルチページtiffの中の何番目のフレームを見たいか指定する

myimgarray = np.array(im)

こうすることで、10番目のフレーム情報をmyimagearrayに読み込むことができます。

すべてのフレームに何らかの解析を行いたい場合には

from PIL import Image,ImageSequence
import numpy as np

imfilepath="test.tif"

with Image.open(imfilepath) as im:
    try:
        while 1:
            myframearray=np.array(im)
            #ここで現在のフレームに対して、なにか解析を行う。
            im.seek(im.tell()+1)            
    except EOFError:
        pass # end of sequence

のようにすれば動きます。

動画の場合(書きかけ)

aviファイルなど、一般的な動画ファイルの場合について説明を加えます。 予めopencvというパッケージをインストールしておきます。anacondaを使っている場合は

conda install opencv

でインストールが可能です。さらにopencvの動作にはffmpegというソフトウェアが必要です。ffmpegを直接インストールしてもよいのですが、ここでは以下のようにimageio-ffmpegというパッケージをインストールします(自動的にffmpegもインストールされます)。

$ conda install imageio-ffmpeg -c conda-forge

解析結果をファイルとして保存したい

グラフを画像として保存したい場合は、ipymplの保存ボタンを使って保存するのが一番楽です。解像度の高いグラフが欲しい場合、前述のようにdpiの値を400程度にすればよいでしょう。ただし、png形式しか対応していないので、pdfやepsなど他のファイル形式で保存したい場合には

plt.savefig("myfigure.pdf", dpi=150)

のようにすると直近のグラフを画像ファイルとして保存できます。

数値データを保存したい場合には、特別な理由がない限りasciiファイル形式で保存することを勧めます。

1次元のnumpy arrayならば(ここではmyarrayというデータを保存するとします)

np.savetxt("mydata.txt", myarray)

で保存が可能です。 この場合、例えば1982.51123という数字は、 1.982511230000000000×1031.982511230000000000\times10^3 のように小数点以下18桁を使って表し直した上で

1.982511230000000000e+03

というようなテキストで保存されます。 別の形式で保存したい場合は

np.savetxt('mydata.txt', myarray, fmt='%1.4e')

のようにfmtオプションを使いましょう。詳細はhttps://numpy.org/doc/stable/reference/generated/numpy.savetxt.htmlを参照してください。

2次元のnumpy arrayも同様に

np.savetxt("mydata.txt", myarray)

で保存が可能です。この場合、数値の間に半角スペースが入ります。半角スペースではなくコンマを使って数値を区切りたい場合には

np.savetxt("mydata.txt", myarray,delimiter=',')

とします。

最終更新