Pythonでデータを読む: 列幅固定のテキストファイル

(関連する話題がふえたら、ページを再編成する可能性があります。)

表構造

観測や研究から出てくるデータセットは、 概念的には、たてよこの表(ひょう)の形になっていることが多い。 表は、横にのびた (縦方向は1ますの) 「行」のあつまりと見ることも、 縦にのびた (横方向は1ますの)「列」のあつまりと見ることもできる。

表のうちでも代表的な構成は、データベースマネジメントシステムの主要概念のひとつである 関係データベース (relational database) であつかわれるような表だ。 そこでは、列はそれぞれ固有の性質をもっていてよいが、行はおたがいに同質である。

関係データベースだけでなく、たとえば Awk 言語であつかうことが想定されているデータは 概念的にはこのような表の構造をもったものである。

わたしは、このような構造のデータをあつかうことが多い。 そのことは、ブログ記事「わたしがよくつかうデータ形式 (1) 論理的構造 ... たてよこの表、不完全な正規化」に書いた。

Python では、Pandas というパッケージの データフレーム (DataFrame) という構造が、 このような表構造のデータをあつかうのに適している。 これは R のデータフレームともにている。 R のデータフレーム操作にはだいたい、それぞれに対応する Pandas のデータフレーム操作があるようだ。

表構造をあらわすファイルの形式

表構造のデータをたくわえるファイルの形式のうち、 人が読み書きできる文字からなるテキストファイルで構成されたものとしては、 コンマ区切り (CSV) と タブ区切り (TSV) がよくつかわれる。 ただし、わたしはタブ区切りのファイルを自分ではつくらない。 人がファイルを編集するとタブと空白がまぎれることがあるからだ。 Pandas では、コンマ区切りのファイルは、read_csv() で読むこと、to_csv() で書くことができる。 タブ区切りのファイルは read_table() で読める。

列幅固定のテキストファイル

かつて、科学技術計算ではほとんどFortranがつかわれていたころは、 データファイルの全部の行に共通に、各行の何文字めから何文字めまでが何の列をあらわす、と決めることが多かった。 文字の種類は1文字1バイトの文字コードであらわせるものにかぎられていたので、 「何文字め」と「何バイトめ」とは同じことだった。 空白もふくめてどの文字も幅が一定のフォントで印字すれば、列がたてにそろう。 列と列とのあいだに空白をいれるとはかぎらなかった。

わたしは、その形式をうけつぎながら、 列と列とのあいだに1つ以上の空白がはいるようにして、 空白区切り(ただし連続した空白は1つの空白と同じとみなす)としても読める形式をこのんでつかってきた。 そのことは、ブログ記事「わたしがよくつかうデータ形式 (2) テキストファイル、空白くぎり 兼 フィールド長固定」に書いた。

漢字などの日本語文字は、1文字1バイトでないし、 固定幅フォントでも文字幅が (ASCIIコードで表現されるような)アルファベットや数字とはちがう。 1980-90年代には、漢字は、1文字2バイト、文字幅もアルファベットの2倍、に 事実上きまっていたので、 バイト数をそろえれば見かけの列もそろったが、いまはそうでもないので、 「列幅固定」は、文字が1バイトのASCIIコードで表現できるばあいにかぎったほうがよさそうだ。

Python でテキストファイルを読む

Python では、readline() という関数で、テキストファイルを1行ずつ読むことができる。 readline() は、関数値として1行ぶんの文字列を返す。 ただし、ファイルのおわりにぶつかったときは、0にあたる値を返す ('0' という文字とはちがう)。 そこで、返り値が 0 でないかぎりくりかえす形のループで、ファイルのおわりまで読むことができる。

ほかのプログラム言語の同様な関数とちがって、 Pythonのreadline() で読みこまれた文字列は、行のおわりに改行コードがついたままである。 この改行コードはとりのぞいておきたいことが多い。 strip() をすすめる人もいる。これは改行コードのほかに、行のおわりやはじめの空白もとりのぞく。 改行コードをとりのぞくことにかぎりたいならば、replace('\n', '') がよい。 (今回のプログラム例ではやっていないが、 '\n' つまり LF のほかに、'\r' つまり CR もとりのぞく必要があるかもしれない。)

テキストファイルの行をフィールドにわけ、データフレームに格納する

Python では、たとえば str という文字列の j 文字めから k 文字めまでを str[j:k] のような形でとりだすことができる。 ただし、Python は 0 からかぞえる。 わたしのように 1 からかぞえる習慣の人にとっては、これは、j+1 文字めから k+1 文字めにあたる。

実例: NOAA NCEI (旧NCDC) の GHCN Daily の地点表を読む

地上気象観測データを、おそらく世界でいちばんたくさん集め、公共に提供している機関は、 アメリカ合衆国の NOAA (海洋大気庁) の National Centers for Environmental Information (NCEI)のうち、 2015年まで National Climatic Data Center (NCDC) とよばれていた部門である。 ここでは、アメリカ国内のデータセットのほかに、世界規模のデータセットを編集している。 そのうちに、Global Historical Climatology Network (GHCN) というデータセット群がある。 わたしは、GHCN の 月ごとの値のデータセットをつかってきたが、 日ごとの値のデータセットもあるので、これからそれをつかおうとしている。 そのデータセットの説明はつぎのウェブページと、それに続く [- Data Access] などのぺージにある。

そのページからリンクされた readme.txt というファイルに、 データセットのファイル構成やそれぞれのファイルの書式の説明がある。 このうち、地点表 ghcnd-stations.txt は、列幅固定のテキストファイルで、各列はつぎのようになっている。

------------------------------
Variable   Columns   Type
------------------------------
ID            1-11   Character
LATITUDE     13-20   Real
LONGITUDE    22-30   Real
ELEVATION    32-37   Real
STATE        39-40   Character
NAME         42-71   Character
GSN FLAG     73-75   Character
HCN/CRN FLAG 77-79   Character
WMO ID       81-85   Character
------------------------------

地点表自体のはじめの3行はつぎのようになっている。 (そのまえの2行はわたしが追加したもので、 行のあたまから何文字めかをしめす。ただし 0 はじまりにした。)

0000000000111111111122222222223333333333444444444455555555556666666666777777777788888
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234
ACW00011604  17.1167  -61.7833   10.1    ST JOHNS COOLIDGE FLD                       
ACW00011647  17.1333  -61.7833   19.2    ST JOHNS                                    
AE000041196  25.3330   55.5170   34.0    SHARJAH INTER. AIRP            GSN     41196

これを読んでデータフレームに入れる Python プログラムを書いてみた。 (このデータのばあいは、readme.txt に書かれた Columns の両側の数値をもってきて、 1 をひいてつかったほうが簡単だったのだが、 同様な別のデータでは、情報が「各列がしめる文字数」と「列間の空白の数」という形で あたえられることが多いので、それに対応できる形にした。)

#  read_stn_list.py
#  2021-05-07 masudako

# 列幅固定のテキストファイルから表データを読む
# 例、NOAA NCEI (旧 NCDC) の GHCN Daily の地点表を読む

# パッケージを予約する
import pandas as pd

# 入力データの構造
width = (11,  8,  9,  6,  2, 30,  3,  3,  5) # 各列の文字数
spaci = ( 1,  1,  1,  1,  1,  1,  1,  1,  0) # 読みとばす文字数
ncol = len(width) # 列数

# 空のデータフレームを列名だけ指定してつくる
columns_all = ['id', 'lat', 'lon', 'elev', 'state', 'stname', \
                'gsn_flag', 'hcn_flag', 'wmo_id' ]
df = pd.DataFrame(columns=columns_all)

# 作業用配列
record = ['' for k in range(ncol)]

# [入力データを1行ずつ読み、フィールドに分けて、データフレームに追加する。]
# 入力データファイル
filename = 'ghcnd-stations.txt'
f = open(filename, 'r')
# データを1行読み、
# 改行コードをとりのぞく。(空白には手をつけたくないので strip でなく replace による。)
line = f.readline().replace('\n', '')
while line:
    pos = 0 # 各行の実質データはPython でいう0、ふつうにいえば1文字めからはじまる
# 要素ごとに record に入れる
    for k in range(ncol):
        record[k] = line[pos:pos+width[k]]
        pos = pos + width[k] + spaci[k]
# 1行ずつ df に追加する
    df = df.append(pd.Series(record, index=df.columns), ignore_index=True)
# 次の行を読み、改行コードをとりのぞく
    line = f.readline().replace('\n', '')
f.close()

# 数値のはいった列は浮動小数点型にする
columns_num = ['lat', 'lon', 'elev']
df[columns_num] = df[columns_num].astype(float)

# データフレームを利用する。この例では、単純に print する。
print(df)

2021-05-08
増田 耕一 (MASUDA Kooiti)
[計算機を利用した気象分野の実習の教材] [教材目録ページ] [日本語入り口ページ] [ホームページ]
[卒論指導のページ] [3年生セミナーのページ]