備忘ログ

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

Rで値の置換用のリストをデータフレーム等で持っている場合の値の置換について考えてみる

Rでの値の置換を考えてみたのでメモ。

データフレーム(Excelファイルなどから読み込んだ)のリストで持っている条件に完全一致したものを同じリストで持っている値に置換するというのを考える。

最初に自作関数で実行する方法を提示し、その他の方法も後述(リストを使わない場合も含め)する。

例では文字列を出しているが数値等でも同じ感じで行けると思う。

set.seed(123)
kita_touhoku <- sample(c("秋田", "秋田県", "あきた",
                         "青森", "青森県", "あおもり",
                         "岩手", "岩手県", "いわて"),
                       size = 50, replace = TRUE)
kita_touhoku
##  [1] "あきた"   "あきた"   "秋田県"   "あおもり" "青森県"   "青森"    
##  [7] "あおもり" "いわて"   "青森県"   "あきた"   "いわて"   "いわて"  
## [13] "いわて"   "あきた"   "岩手県"   "岩手"     "いわて"   "あきた"  
## [19] "青森"     "秋田"     "岩手"     "青森県"   "岩手"     "いわて"  
## [25] "いわて"   "岩手"     "青森県"   "岩手"     "青森県"   "あおもり"
## [31] "いわて"   "秋田県"   "青森県"   "岩手県"   "秋田県"   "秋田"    
## [37] "いわて"   "いわて"   "あおもり" "青森県"   "いわて"   "青森"    
## [43] "あおもり" "岩手県"   "あおもり" "あおもり" "岩手"     "秋田"    
## [49] "あおもり" "秋田県"

これを、それぞれ漢字表記の「〇〇県」にしたいと思ったときに、これくらいの数ならコード上にパターンを書いてもいいけどもっと数が増えたりすると他に置換する値のリストと置換後の値のリストをExcelファイルなどで持っておいて、それを参照して置換したいと思う。

たとえば次のような置換用リストのデータフレームがある場合を想定する。(「この値はこの値に変換してね」的Excelのファイル)

replacement_list <- data.frame(pattern = c("秋田", "あきた", "青森", "あおもり", "岩手", "いわて"),
                               replacement = c("秋田県", "秋田県", "青森県", "青森県", "岩手県", "岩手県"))
knitr::kable(x = replacement_list)
pattern replacement
秋田 秋田県
あきた 秋田県
青森 青森県
あおもり 青森県
岩手 岩手県
いわて 岩手県

これを解決する手法はいくつか想定されるが、結構しがちな処理なのでサクッと関数で持っているのが楽なのでgithubに公開している{infun}replace_match()という関数を入れた。

remotes::install_github(indenkun/infun)
library(infun)
replace_match(kita_touhoku, pattern = replacement_list$pattern, replacement = replacement_list$replacement)
##  [1] "秋田県" "秋田県" "秋田県" "青森県" "青森県" "青森県" "青森県" "岩手県"
##  [9] "青森県" "秋田県" "岩手県" "岩手県" "岩手県" "秋田県" "岩手県" "岩手県"
## [17] "岩手県" "秋田県" "青森県" "秋田県" "岩手県" "青森県" "岩手県" "岩手県"
## [25] "岩手県" "岩手県" "青森県" "岩手県" "青森県" "青森県" "岩手県" "秋田県"
## [33] "青森県" "岩手県" "秋田県" "秋田県" "岩手県" "岩手県" "青森県" "青森県"
## [41] "岩手県" "青森県" "青森県" "岩手県" "青森県" "青森県" "岩手県" "秋田県"
## [49] "青森県" "秋田県"

このreplace_match()は次のように定義しているので次のコードをコピーしても実行できる。

replace_match <- function(x, pattern, replacement, nomatch){
  if(length(pattern) != length(replacement))
    stop("pattern length must be same length that replacement.")

  ans <- match(x, pattern)
  if(any(is.na(ans))){
    res <- length(ans)
    for(i in 1:res){
      if(is.na(ans[i])){
        if(missing(nomatch)) res[i] <- x[i]
        else res[i] <- nomatch
      }else{
        res[i] <- replacement[ans[i]]
      }
    }
  }else{
    res <- replacement[ans]
  }
  return(res)
}

その他の方法

その他の方法について考えてみる。

今回の場合、文字列なので{stringr}パッケージのstr_replace_all()を使ってやってみる。

str_replace_all()はベクトルで値を与えてやることで複数パターンを置換することができる。

library(stringr)
# 書くのがちょっと面倒なので秋田だけ変換する
str_replace_all(string = kita_touhoku, 
                pattern = c("秋田" = "秋田県", "あきた" = "秋田県"))
##  [1] "秋田県"   "秋田県"   "秋田県県" "あおもり" "青森県"   "青森"    
##  [7] "あおもり" "いわて"   "青森県"   "秋田県"   "いわて"   "いわて"  
## [13] "いわて"   "秋田県"   "岩手県"   "岩手"     "いわて"   "秋田県"  
## [19] "青森"     "秋田県"   "岩手"     "青森県"   "岩手"     "いわて"  
## [25] "いわて"   "岩手"     "青森県"   "岩手"     "青森県"   "あおもり"
## [31] "いわて"   "秋田県県" "青森県"   "岩手県"   "秋田県県" "秋田県"  
## [37] "いわて"   "いわて"   "あおもり" "青森県"   "いわて"   "青森"    
## [43] "あおもり" "岩手県"   "あおもり" "あおもり" "岩手"     "秋田県"  
## [49] "あおもり" "秋田県県"

これはコメントに書いたとおり書くのがめんどくさいが、要するに名前付きベクトルで置換パターンと置換後の値を指定してやると良いので次のようにしてパターンの名前付きベクトルを作ると手間が省かれる。

str_replace_list <- replacement_list$replacement
names(str_replace_list) <- replacement_list$pattern
str_replace_all(string = kita_touhoku,
                pattern = str_replace_list)
##  [1] "秋田県"   "秋田県"   "秋田県県" "青森県"   "青森県県" "青森県"  
##  [7] "青森県"   "岩手県"   "青森県県" "秋田県"   "岩手県"   "岩手県"  
## [13] "岩手県"   "秋田県"   "岩手県県" "岩手県"   "岩手県"   "秋田県"  
## [19] "青森県"   "秋田県"   "岩手県"   "青森県県" "岩手県"   "岩手県"  
## [25] "岩手県"   "岩手県"   "青森県県" "岩手県"   "青森県県" "青森県"  
## [31] "岩手県"   "秋田県県" "青森県県" "岩手県県" "秋田県県" "秋田県"  
## [37] "岩手県"   "岩手県"   "青森県"   "青森県県" "岩手県"   "青森県"  
## [43] "青森県"   "岩手県県" "青森県"   "青森県"   "岩手県"   "秋田県"  
## [49] "青森県"   "秋田県県"

ただこの場合、よく見てみるとわかるが、秋田県秋田県県になっている。これは秋田県の秋田の箇所がパターンにマッチして秋田県と置換され、秋田県県となっているので古のノウハウに基づき、先に秋田県を秋田に変換してくなどの手法を取ると解決するが少し手間になる。

# 今回の場合は県をすべて消しておくと解決する
kita_touhoku_ <- str_replace_all(kita_touhoku, "県", "")
str_replace_all(string = kita_touhoku_,
                pattern = str_replace_list)
##  [1] "秋田県" "秋田県" "秋田県" "青森県" "青森県" "青森県" "青森県" "岩手県"
##  [9] "青森県" "秋田県" "岩手県" "岩手県" "岩手県" "秋田県" "岩手県" "岩手県"
## [17] "岩手県" "秋田県" "青森県" "秋田県" "岩手県" "青森県" "岩手県" "岩手県"
## [25] "岩手県" "岩手県" "青森県" "岩手県" "青森県" "青森県" "岩手県" "秋田県"
## [33] "青森県" "岩手県" "秋田県" "秋田県" "岩手県" "岩手県" "青森県" "青森県"
## [41] "岩手県" "青森県" "青森県" "岩手県" "青森県" "青森県" "岩手県" "秋田県"
## [49] "青森県" "秋田県"

文字列の中の文字列を置換する関数なので目的外使用とも言える。

複数パターンの値の置換を考えると{dplyr}case_match()case_when()よりも比較的簡単かと思うので考えてみる。

library(dplyr, warn.conflicts = FALSE)
case_match(.x = kita_touhoku,
           c("あきた", "秋田") ~ "秋田県",
           c("あおもり", "青森") ~ "青森県",
           c("いわて", "岩手") ~ "岩手県",
           .default = kita_touhoku)
##  [1] "秋田県" "秋田県" "秋田県" "青森県" "青森県" "青森県" "青森県" "岩手県"
##  [9] "青森県" "秋田県" "岩手県" "岩手県" "岩手県" "秋田県" "岩手県" "岩手県"
## [17] "岩手県" "秋田県" "青森県" "秋田県" "岩手県" "青森県" "岩手県" "岩手県"
## [25] "岩手県" "岩手県" "青森県" "岩手県" "青森県" "青森県" "岩手県" "秋田県"
## [33] "青森県" "岩手県" "秋田県" "秋田県" "岩手県" "岩手県" "青森県" "青森県"
## [41] "岩手県" "青森県" "青森県" "岩手県" "青森県" "青森県" "岩手県" "秋田県"
## [49] "青森県" "秋田県"

これをcase_when()の場合を考えてみる。

case_when(kita_touhoku %in% c("あきた", "秋田") ~ "秋田県",
          kita_touhoku %in% c("あおもり", "青森") ~ "青森県",
          kita_touhoku %in% c("いわて", "岩手") ~ "岩手県",
          .default = kita_touhoku)
##  [1] "秋田県" "秋田県" "秋田県" "青森県" "青森県" "青森県" "青森県" "岩手県"
##  [9] "青森県" "秋田県" "岩手県" "岩手県" "岩手県" "秋田県" "岩手県" "岩手県"
## [17] "岩手県" "秋田県" "青森県" "秋田県" "岩手県" "青森県" "岩手県" "岩手県"
## [25] "岩手県" "岩手県" "青森県" "岩手県" "青森県" "青森県" "岩手県" "秋田県"
## [33] "青森県" "岩手県" "秋田県" "秋田県" "岩手県" "岩手県" "青森県" "青森県"
## [41] "岩手県" "青森県" "青森県" "岩手県" "青森県" "青森県" "岩手県" "秋田県"
## [49] "青森県" "秋田県"

となる。case_when()のほうが複数条件を複雑に組み合わせて値を作り出せるが、ただのTRUE / FALSEで置換条件を考える場合はcase_match()のほうが簡便だと思う。

ただ、いずれの場合も変換用のデータフレームを持っていてそれを参照してやるは少し手間なので膨大な量を繰り返し処理するのは少し大変そう。

あとはif_else()ifelse()もあり得るが大変。