備忘ログ

チラシの裏的備忘録&メモ

Rのzipangu::kansuji2arabicで期待した結果を返さない場合の個人的暫定解決策(追記あり)

R上で漢数字をアラビア数字に置き換えたいときにはzipanguパッケージのzipangu::kansuji2arabicを使うのが早いが、条件によっては期待する結果を返さないので少し考えてみた。

追記:千百十一を1001に変換してしまうバグがあったので修正し、10万以上も対応したものを作ってみた

indenkun.hatenablog.com

zipangu::kansuji2arabicについて

uribo.hatenablog.com

zipanguはCRANに安定版、開発版がGitHubにある。 現状開発版と安定版に差異は実用上ないので、CARNからインストールする。

install.packages("zipangu")

漢数字を一文字を変換したいなら

zipangu::kansuji2arabic("百")
## [1] "100"

で変換できる。

漢数字を複数変換する場合には

zipangu::kansuji2arabic_all("五丁目五番地")
## [1] "5丁目5番地"

で複数の漢数字を変換できる上に元の漢数字以外の文字列は保持される。

zipangu::kansuji2arabicで期待した結果を返さない場合

とても便利なのだけれど、例えば二千二十という漢数字は2020をいう解を期待するのだけれど、

zipangu::kansuji2arabic("二千二十")
## [1] "二千二十"

zipangu::kansuji2arabicは漢数字複数文字を与えた場合(c()で与えれば複数文字も可)は変換できなくなる。

zipangu::kansuji2arabic_all("二千二十")
## [1] "21000210"

となり、期待する解とは異なる。

これはソースコードを読むとすぐわかるが、zipangu::kansuji2arabic_allの挙動は

  • 文字列を一文字単位で分割(stringr
  • その後一文字ごとに漢数字ならアラビア数字( 文字列)に置換する(zipangu::kansuji2arabic
  • 文字列を再度結合(paset0)

という流れのためで二千二十の件で言えば、それぞれ2、1000、2、10と置換されたものが結合されて21000210となっている。

住所の五丁目五番地を変換するのならこの挙動でも特に不都合を感じない(Github上のREADMEも住所データを例に上げているし)が、二千二十が2020になるのを期待しているとちょっと困る。

全然今回の件と関係はないがREADMEで例に挙げられている

zipangu::kansuji2arabic_all("北海道札幌市中央区北一条西二丁目")
## [1] "北海道札幌市中央区北1条西2丁目"

は北一条の一まで1に書き換わっていて、漢数字置換の難しさを感じる。 使われている漢数字が固有名詞の一部なのか、数字情報としてのものなのかは人の目ならなんとなくわかるが、一文字ごとの置換だとそんなことお構いなしになってしまうが、禁則処理は無限大で場合によっては数字に置換したい場合もあるだろうし単純な処理だと難しそう。

個人的暫定解決策

漢数字だけを与える場合、ループ処理を含む処理になっていしまうが、0~99999(零から九万九千九百九十九)までちゃんと期待する漢数字をアラビア数字に変える方法を拙いなりに考えた。

zipangu::kansuji2arabic_allを少し書き足しただけ。

依存関係はzipanguがインストールされていれば解決される。

library(magrittr)
kansuji2arabic_kai <- function(str, ...) {

  # n <-と%>% as.numeric()を追加して、整数型に変換する
    n <- stringr::str_split(str,
                     pattern = stringr::boundary("character")) %>%
       purrr::map(zipangu::kansuji2arabic, ...) %>%
       purrr::reduce(c) %>% as.numeric()

    # nをループ処理しながら桁を示す十百千万と、零から九までで分けて処理する
  if(length(n) == 1){
    res <- n
    return(res)
  }else{
    res <- NULL
    for(i in 1:length(n)){
      if(i == length(n) && n[i - 1] >= 10)
        res[i] <- n[i]
      else if(length(n[i - 1]) == 0 && n[i] >= 10)
        res[i] <- n[i]
      else if(n[i] <= 9 && n[i + 1] >= 10 )
        res[i] <- n[i] * n[i + 1]
    }
  }
  res <- purrr::reduce(na.omit(res), `+`)
  return(res)
}

人間が漢数字読むときと同じように桁を表す漢字とその桁の数字を表す漢字を判別しながら、二千なら2*1000と解釈して最後にそれらを全部足し合わせるというゴリゴリとした処理で回している。

(人間の脳内処理に関しては多分こんな風に読んでる?というレベルの認識で、習慣化しすぎてよくわからない。 正直漢数字はアラビア数字化の必要がなければ漢数字のママ脳内処理しているし、千の位まではIMEみたいに文字列と数字が直結している気がする)

kansuji2arabic_kai("二千二十")
## [1] 2020

となるので期待する数字が得られる。

一方で処理の途中で整数型にしているので整数型にならない文字列は処理できなくなってしまうので、住所の処理などはできなくなってしまう。

純粋に漢数字のみの文字列しか受け付けない。ただし期せずして5千や2万などの数字一文字+桁数の漢数字は受け付けるようになった。30万とか、30千とかは受け付けない。

またたまにある、23を意図した二三や、203を意図した二〇三は処理できない。

要するにzipangu::kansuji2arabic_allでできたことができなくなてしまう。

あとは、10万以上に対応していないのは万以上ではその上の桁に移行するときに十から千までの文字を使うけど、これは百万なら100*10000なのだけれど、そこに数字が絡んでくると条件分岐が多くてよくわからないことになってしまうと思ったから。

個人的に二千とか十八とか日付で出てくる範囲がでてくれば暫定OKだった。

ただ、二千二十年七月十二日みたいなのを2020年7月12日みたいに変換できたらいいと思ってるけど、そこまでは至っていない、力不足(今後の課題……?)。

あとforループ以外でかけたほうがRっぽいのでそっちのほうが良かった(書けなかった)。