科学の箱

科学・IT・登山の話題

R

factorと数値型の変換-2

投稿日:

factorと数値型の変換でとりあえず区切り文字付き数字の変換方法についてめどは立ったが、そもそも区切り文字付き数字がなぜfactorになるのかがわからない。文字列にするにはパラメータを指定する必要がある。そもそも数値にただしく変換される文字形式もよくわかっていない。

そこで実際にread.csv()で何が起きているかを調べてみた。

read.csvの定義を調べてみると以下のように表示されている。

> read.csv
function (file, header = TRUE, sep = ",", quote = "\"", dec = ".", 
    fill = TRUE, comment.char = "", ...) 
read.table(file = file, header = header, sep = sep, quote = quote, 
    dec = dec, fill = fill, comment.char = comment.char, ...)
<bytecode: 0x0000000012be6438>
<environment: namespace:utils>

read.csvの本体はread.tableであり、read.csvはパラメータを付加してread.tableを呼び出していることがわかる。

次にread.tableについてみてみる。ソースは以下から参照できる。

https://svn.r-project.org/R/trunk/src/library/utils/R/readtable.R

ファイルを読み込んでカラムに振り分ける処理はおおむね以下の手順となる。

  • ファイルを開く
  • EOFチェック
  • 行の読み込み
  • トークン
  • 型設定
  • 出力
  • すべてのトークンについて処理
  • 次の行もしくはファイルの終わり
  • ファイルを閉じる

いま興味があるのは型設定だけなので、ソースからその処理だけを抜き出す。それっぽいのは以下の処理。

    for (i in (1L:cols)[do]) {
        data[[i]] <-
            if (is.na(colClasses[i]))
                type.convert(data[[i]], as.is = as.is[i], dec=dec,
			     numerals=numerals, na.strings = character(0L))
        ## as na.strings have already been converted to <NA>
            else if (colClasses[i] == "factor") as.factor(data[[i]])
            else if (colClasses[i] == "Date") as.Date(data[[i]])
            else if (colClasses[i] == "POSIXct") as.POSIXct(data[[i]])
            else methods::as(data[[i]], colClasses[i])
    }

どうやら列の型が指定されていない場合には、type.convert()という関数を使っているらしい。この関数について調べてみる。type.convert()のヘルプを見てみると以下のように記述されている。

Convert a character vector to logical, integer, numeric, complex or factor as appropriate.

文字列を論理型、整数型、数値型、コンプレックス、ファクタに変換するための関数である。ではtype.convert()について検証してみる。

まず数値でtype.convert()を実行してみる。

> type.convert(10)
 以下にエラー type.convert(10) :  最初の引数は文字モードでなくてはいけません

説明にもあるとおり、文字列のみ受け付けるようで、エラーとなった。次に区切り文字、小数点なしの数値文字列を変換してみる。

> type.convert("10")
[1] 10

数値変換された。クラスを確認してみる。

> class(type.convert("10") )
[1] "integer"

整数型となっている。

では、小数点付きにしてみる。

> type.convert("10.0")
[1] 10
> class(type.convert("10.0"))
[1] "numeric"

数値変換された。表示上は整数型に見えたが、型を調べてみたらnumericであった。小数点以下があると予測されるので、この変換は正しい。

では桁区切り文字付きにしてみた。

> type.convert("1,000.0")
[1] 1,000.0
Levels: 1,000.0

ファクタ型となってしまった。

この結果から、read.csv()ではtype.convert()によりデータ型変換が行われていると推測される。

type.convert()はRの内部コードとなりCで記述されている。コードは以下から参照できる。

http://svn.r-project.org/R/trunk/src/library/utils/src/io.c

コメントは以下のようになっている。

/* type.convert(char, na.strings, as.is, dec, numerals) */

/* This is a horrible hack which is used in read.table to take a
   character variable, if possible to convert it to a logical,
   integer, numeric or complex variable.  If this is not possible,
   the result is a character string if as.is == TRUE
   or a factor if as.is == FALSE. */

たしかにソースを読んでみたが短時間では解析が難しいがわかる範囲で追ってみる。まずこの関数は判別した型を以下の構造体に保管している。

    Typecvt_Info typeInfo;      /* keep track of possible types of cvec */
    typeInfo.islogical = TRUE;  /* we can't rule anything out initially */
    typeInfo.isinteger = TRUE;
    typeInfo.isreal = TRUE;
    typeInfo.iscomplex = TRUE;

またdoneローカル変数で終了チェックをしているようである。この情報を手掛かりとして処理を追ってみた。

まずislogical, isinteger, isrea, iscomplexについて処理をしている。これらの型であると判断できたらdone=TRUEにしている。

   if (typeInfo.islogical) {
    PROTECT(rval = allocVector(LGLSXP, len));
      |
      |
      |
    if (typeInfo.islogical) done = TRUE; else UNPROTECT(1);
    }

   if (!done && typeInfo.isinteger) {
    PROTECT(rval = allocVector(INTSXP, len));
      |
      |
      |
    }
    if(typeInfo.isinteger) done = TRUE; else UNPROTECT(1);
    }

    if (!done && typeInfo.isreal) {
    PROTECT(rval = allocVector(REALSXP, len));
      |
      |
      |
    if(typeInfo.isreal) done = TRUE; else UNPROTECT(1);
    }

    if (!done && typeInfo.iscomplex) {
    PROTECT(rval = allocVector(CPLXSXP, len));
      |
      |
      |
    if(typeInfo.iscomplex) done = TRUE; else UNPROTECT(1);
    }

このつぎに以下の条件分岐がある。

if (!done) {
	if (asIs) {
      |
        }
        else {

as.is=FALSEでファクタになるという説明がされていたのでelseについてみてみる。処理を追ってみると以下のロジックを見つけた。ここでファクタ型の設定をしている。

	    setAttrib(rval, R_LevelsSymbol, levs);
	    PROTECT(a = mkString("factor"));
	    setAttrib(rval, R_ClassSymbol, a);
	    UNPROTECT(3);

よってtype.convertでは以下のように処理が流れている。

  1. 論理型、整数型、数値型、コンプレックス型に変換する
  2. 変換ができなければasisにより処理を分ける
  3. asis=falseならファクタにする

ではなぜ桁区切り文字付き数字が数字に変換されないかを見てみる。まず整数型と浮動小数点型の変換はそれぞれ以下のようになっている。

		INTEGER(rval)[i] = Strtoi(tmp, 10);
		if (INTEGER(rval)[i] == NA_INTEGER) {
		    typeInfo.isinteger = FALSE;
		REAL(rval)[i] = Strtod(tmp, &endp, FALSE, &data, i_exact);
		if (!isBlankString(endp)) {
		    typeInfo.isreal = FALSE;

変換に用いている関数はStrtoiとStrtodである。Cで簡単にテストをしてみる。

#include <stdio.h>
#include <stdlib.h>
int main(){
        double x;
        char s[128], *e;
        char *target = "1,000";

        x = strtod(target, &e);
        
        printf("変換前数値=%s\n", target);
        printf("変換後数値=%.2f\n", x);
        printf("変換不可能部分=%s\n", e);

    }

結果は以下のようになった。

変換前数値=1,000
変換後数値=1.00
変換不可能部分=,000
続行するには何かキーを押してください . . .

まとめるとread.csvで桁区切りが受け付けられないのはc言語のstrtoiおよびstrtodの仕様によることがわかった。

type.convertはRの内部関数であるから手を入れるのは難しい。

read.table()で以下のように2か所ほど手を入れて”f_numeric”という指定で桁区切りを受け付けるようにしてみた。

    colClasses[colClasses %in% c("real", "double")] <- "numeric"
    known <- colClasses %in% c("logical", "integer", "numeric", "complex",
                               "character", "raw", "f_numeric")

            else if (colClasses[i] == "factor") as.factor(data[[i]])
            else if (colClasses[i] == "Date") as.Date(data[[i]])
            else if (colClasses[i] == "POSIXct") as.POSIXct(data[[i]])
            else if (colClasses[i] == "f_numeric") as.numeric(gsub(pattern = ",", replacement = "", x = data[[i]], fixed = TRUE))
            else methods::as(data[[i]], colClasses[i])

残念ながら、エラーが出てしまって、うまくいかない。

> revenue_01 <- read.csv(file="data.csv",head=TRUE, colClasses=c("character", "f_numeric"))
 以下にエラー methods::as(data[[i]], colClasses[i]) : 
  no method or default for coercing “character” to “f_numeric”

時間がないのでここまで。

メタ情報

inarticle



メタ情報

inarticle



-R
-

執筆者:


  1. foo-bar-baz より:

    いろいろ経緯はあるでしょうが,桁区切りのついた数値は,他の言語でも読めない(その言語のプログラムを記述しているC言語の制約による)ので,根本的には,数値は桁区切りしない,もしされているなら,「データを用意するときに,桁区切りを外して保存する」ということでしょう。Excelを使って作業するのなら,「書式」,「セル」,で「表示形式」を「標準」にするとか,普通のエディタならば「カンマを空文字列に全置換する」など。

comment

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

関連記事

no image

Rでグラフを作成するときに参考にするサイト

金子 邦彦 研究室 Web ページ バイオスタティスティクス 奥村 晴彦研究室 Related posts:回帰分析主成分分析oneway.test, aov, anovaの違い-2

no image

Wilcoxonの符号付順位決定

boneのspnbmdを使って年齢別に男女差があるかを確認してみる。検定ではWilcoxonの符号付順位決定を利用する。 boneは特定個人について骨密度を計測している。データにはIDと骨密度以外に性 …

no image

データの縦・横展開

stack()関数を用いて横長のデータを縦長にできることを教わった。このようなデータ形式の変換はよくあることなのでもう少し調べてみた。 scoreデータは以下のような形式となっている。 > he …

no image

一対比較法

一対比較法では複数の対象の順位を、個別の比較結果から明らかにすることができる。例えば今5種類の携帯電話があり、好ましさの順位を知りたいとする。一対比較法を利用しなければ、回答者は5について順番をつけて …

no image

Rによるやさしい統計学/5-統計的検定-2

練習問題-2 勉強時間と定期試験の点数についてデータを作成する。 study_time <- c(1, 3, 10, 12, 6, 3, 8, 4, 1, 5) point <- c(20 …

2014年5月
« 4月   6月 »
 1234
567891011
12131415161718
19202122232425
262728293031  

side bar top



アーカイブ

カテゴリー