SASでの文字コードの扱い方
概要
あんまりないと思うが, 文字コードが異なるOS間でデータのやりとりをするときの話.
SAS v9台を前提に (動作確認は SASonDemand でやっている)
文章量は PDF 換算で 5 ページ
テキストファイルの文字コードを特定する
Windows で作成したテキストファイルはたいていシフトJISになるが, SASonDemand は UTF-8 なのでそのまま読み込もうとすると文字化けする. 文字コードの変換については, 『Shift-JIS の固定長ファイルを UTF-8 環境に読み込む (kcvt関数) - SAS | data Memorandum ; set Memory ; run ;』 のように kcvt()
関数を使ったり, Linux に SAS が入っているなら pipe
エンジンを使って iconv
や nkf
コマンドを挟むという手もある. しかし, 単にテキスト全体が異なる文字コードで, csv のようにセパレータがある*1というのなら, もう少し楽な方法がある. ENCODING=オプション で紹介されているencoding=
オプションだ.
あ,イロハニ,123 あ,イロハニ,123
という内容のファイルをシフトJISの csv で作成し, SASonDemand で読み込ませてみる.
data test; infile "&homedir/sjis/test_sj_r.csv" dlm=',' encoding=sjis; length var1 $3 var2 $9 var3 8; input var1 var2 var3; run; proc contents data=test; run; proc print data=test; run;
なお, encoding=
オプションは infile
だけではなく filename
, でも使える. また 書き出す際の file
ステートメントでも同じである.
しかし, これを実行すると期待どおりの結果が帰ってこない.
原因のヒントはログにあって,
123 としか入力してないはずなのに, 末尾に . がある. 実は, この csv の改行コードは CR になっている*2. そのため, 改行コードを正しく認識できず, 不正な文字列として処理されてしまった. 改行コードは読み込み時だけでなく, 書き出しの際にも問題になる. 例えば CR を改行コードとして出力した場合, Windows のメモ帳などでは CR+LF を改行コードとして認識するから, すべての行が改行されず1行にすべて横並びになってしまう. 今は CR 単体を改行コードとして使う場面はほとんどなさそうだが, Linux 系は LF, Windows 系は CR+LF, が普通だから, 文字コードに気をつけるときは改行コードにも気をつけた方がいい.
この改行コードも, 実は簡単に指定できて, termstr=
オプションを使う(http://support.sas.com/kb/14/178.html). そしてこれも infile
, file
, filename
の全てのステートメントで使用できる. 今回の場合は
data test; infile "&homedir/sjis/test_sj_r.csv" dlm=',' encoding=sjis termstr=CR; length var1 $3 var2 $9 var3 8; input var1 var2 var3; proc contents; proc print;run;
で読み込める. SASonDemand だとオートコンプリート機能があるので, 自動で候補が現れるが, 指定する名称は CRLF
とか LF
とかそのまんまである (なお小文字でもいい).
文字コードの異なるデータセット
先の contents
プロシジャのスクリーンショットで見たように, SASのデータセット (sas7bdat
) は, 文字のエンコードが何かを記録している. よって, 基本的に SAS は文字コードが現在のセッションと異なるものでも自動で変換してくれる. そのとき, こういうメッセージが表示される.
ただし, 文字コードの異なるデータセットを読み込む際に, 問題が起こる場合が2通りある.
1つは, 何らかの理由で, sas7bdat から文字コード情報が欠落しているため文字化けが起こる場合, もう1つは, 変換後の文字列のバイト数が文字変数の長さを超えてしまうため文字列の切り捨てが起こる場合である.
1つ目の, 文字コード情報が欠落している場合は, 対処が簡単で, テキストファイルのときと同様に, 文字コードを指定するオプションを使う. これには2つ方法があり, 1つは libname
ステートメントに inencoding=
オプションを使うことでライブラリのデータセットに一括で指定する方法で, もう1つはデータセットオプションの encoding=
を使う方法がある (Technical Support, SAS(R) 9.2 National Language Support (NLS): Reference Guide).また, これらはどうやら v9 以降でしか使えないようなので注意. 具体的には, こういうようなコードになる.
libname sjis "&homedir/sjis" inencoding=sjis; proc print data=sjis.ds; run; /*出力の場合は outencoding*/ libname utf "&homedir/utf" outencoding='utf-8'; /*proc copy では文字コード変換されない*/ data utf.ds; set sjis.ds; run;
libname sjis "&homedir/sjis"; proc print data=sjis.ds(encoding=sjis); run;
もちろん, 指定する文字コードを間違えると文字化けしたりエラーが起こったりする*3.
なお, これまでは sjis
を指定してきたが, utf-8
のようにハイフンを含む名称を指定する場合, 引用符で囲まないとエラーが発生する.
次に, 変換後のバイト数が文字変数の長さを超えてしまう場合だが, これは sjis
から utf-8
へ変換するときによく起こる (UTF-8 のほうが1文字のバイト数が大きくなる場合が多いので ). 加えて言うと, 文字コードが指定されていても発生する. こういう場合, inencoding=’utf-8’
に加えて cvp
(character variable padding)エンジンを利用して, 読み込み時に文字変数の長さを拡張する.
libname sjis cvp "&homedir/sjis" inencoding=sjis; proc print data=sjis.ds; run;
(参考: CVPエンジンを使用した文字データ切り捨てへの対応 ).
cvp
エンジンについては, 公式サイトではないが 『SASでのトランスコーディングあれこれ。 - The Nameless City』 でいろいろ細かく解説されていて参考になる. cvp
によりデフォルトでは 1.5倍に拡張される (). sjis から utf-8 への変換ならだいたいこれでなんとかなるが, 半角カナ等が含まれると足りなくなる可能性がある. そういうときは cvpmultiplier=
オプション (省略形 cvpmult=
) で倍率を指定する.
また, cvp エンジン使用時の注意点として, cvp
を使ったライブラリは強制的に読み込み専用になる, ということがある. これはあくまで, 読み込み時に変換しているだけなので, 新しい文字コードに変換したデータセットを作成したい場合, 別のライブラリに出力する必要がある. よって, sjis から utf-8 へ変換するのに最も安全な方法は
libname sjis cvp "&homedir/sjis" inencoding=sjis cvpmult=3; libname utf "&homedir/utf"; data utf.ds; set sjis.ds; run;
のようにすることになる (データセットが文字コード情報を保持しているなら, inencoding=
は不要).
最後に, cvp
オプションの弱点を言っておくと, これは全文字変数の長さを一律に拡張するもので, 個別に長さを指定できない, ということがある. つまり, 例えば date=20150912
とか, sex=”F”
のように, 文字コードを変えてもバイト数が変わらないような文字列しか入っていない文字変数についても長さを拡張してしまう. よって,ファイルサイズの膨張が気になる場合, cvp はあまり使えないかもしれない.
不要な文字変数の長さの拡張をしたくない場合, 例えば データセットを変換する前にデータセットを読み込んで, 全オブザベーションを確認して, バイト数の変わるカナや漢字が含まれるかどうかを判定し, contents
プロシジャで各文字変数の長さを取得し, マクロ変数に取り込み, あらためてデータステップで カナ漢字を含む文字変数だけマクロを使って length
に倍数を掛けて..., という風に, プログラムを書くのも処理するのもそれなりに時間のかかることをしなければならない.