備忘ログ

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

Rでデータフレーム上で同じ値の列を探し出す方法のメモ

qiita.com

Qittaのこの記事を読んで、重複する列を削除したいと思うことってあるかなぁ、と思ったけどよく考えれば重複する列を削除したいという場合もありえそうだなぁと確かに思った。

上記のQittaの記事では同じ名前の列があるかどうか、ということに着目して操作している様子。

ただ、Rだと基本的に同じ名前の列はうまくできない。

参考までに

indenkun.hatenablog.com

そもそも、Qittaの例では値用のCSVファイルと列名用のCSVファイルで分けて運用しているようだが、ちょっと特殊な運用な気がする。少なくともこのような運用を自分ではしたことがないしする予定もない。

そこで、Rで重複する列を削除するにあたり、考えられうる方法としては重複する列を探すために列の値がすべて同じ列を探し出すことがまず必要なので必要なのではないかと思った。そして、抽出された同じ値の列通しを人間の目で目視してデータハンドリングしておくことが大事なんじゃないかと思った。

同じ値の列を勝手に消して出力することもそんなに難しくないが(挙動としてはunique()の列番)、同じ値を持つ列だからといって同じとは限らないんじゃないか、と思ったから。

というわけで同じ値の列を探し出すテキトー関数について考えてみたのでメモしておく。

same.value.col <- function(data, col.name = c("number", "name")){
  col.name = match.arg(col.name)
  
  same.value.col.list <- vector("list", ncol(data))
  if(col.name == "name") names(same.value.col.list) <- names(data)
  
  for(i in 1:ncol(data)){
    same.value.col.number <- NULL
    for(k in 1:ncol(data))
      if(all(data[i] == data[k]) && i != k){
        if(col.name == "number") same.value.col.list[i] <- c(same.value.col.number, k)
        else same.value.col.list[i] <- c(same.value.col.number, names(data)[k])
      }
  }
  
  same.value.col.list
}

使ってみる。サンプルデータを次のように作る。

d <- data.frame(ID = sample(1:10, 100, replace = TRUE),
                BIRTH = sample(month.name, 100, replace = TRUE),
                Number = 1:100,
                number = 1:100,
                value_1 = rep(10, 100),
                value_2 = rep(10, 100))

Numbernumberがすべて同じ値の列、value_1value_2がすべて同じ値の列となっている。

上記関数を使用し、同じ値の列を探す。

same.value.col(d)
## [[1]]
## NULL
## 
## [[2]]
## NULL
## 
## [[3]]
## [1] 4
## 
## [[4]]
## [1] 3
## 
## [[5]]
## [1] 6
## 
## [[6]]
## [1] 5

リストで答えを返し、同一値の列が存在しなければNULL、あればその列番号を返す。上記の例では1列目はすべてが同じ値の列は存在しないが、3列名は4列目とすべて同じ値の列だよ、ということがわかる(その他の列に関しても表示している通り同じ列が存在したりしなかったりする)。対(3列目にとって4列目が同じということは、4列目にとっては3列目が同じ値)になるものが必ず存在するのでそれを表示しないうにも手をかければできるが、逆に混乱しそうなので残しておいた。

列名で出力することもできる。

same.value.col(d, col.name = "name")
## $ID
## NULL
## 
## $BIRTH
## NULL
## 
## $Number
## [1] "number"
## 
## $number
## [1] "Number"
## 
## $value_1
## [1] "value_2"
## 
## $value_2
## [1] "value_1"

上記からはIDの列は同じ値の列が存在せず、Numberの列はnumberの列と同じ値だということがわかる(その他の列に関しても表示している通り同じ列が存在したりしなかったりする)。

前述したように同じ値の列だからといって同じものとは限らないと個人的には思っている。たまたまか、必然的にかわからないが同じ値になっていただけでちゃんと別のものを測った値である可能性もあるので、列番号や列名から値を確認してデータフレームをいじるべきだと個人的には思っている。

この例でいうと、Numbernumberは同じ通し番号だったが、value_1value_2はたまたかすべてが同じ値だったというだけだったということもあり得る。

こうやって目視で確認した上で、

# numberの列を消す、今回は結果は表示しない。
dplyr::select(d, !number)

などすればよいのではないかと思う。

forを列数×2回実行されるので巨大なデータフレームになると少しもっさりするかもしれないが、中身でやってる処理は同じ値か確認しているだけなのでそこまで遅くはならないと思う。

2021/04/07追記:パッケージ化した

今までブログ上で書き散らかした関数(以外もある)を少し関数名や細かい挙動を調整して、上記の関数もまとめたものをパッケージ化してGitHubからインストールできるようにした(GitHubソースコードを管理したいだけ)。

GitHub - indenkun/infun: This is a collection of R utilities functions for me, but maybe also for you.

install.packages("remotes")
remotes::install_github("indenkun/infun")

でインストールできる。詳しくはないが関数の紹介はREADMEに書いたつもり。

自分で使いたい関数をパッケージ化した感じで今後も適当に自分で使いたい関数があれば適宜入れていく。自作関数であっても汎用性があるものは適宜パッケージ化したほうが名前空間的にいいし、Rスクリプトファイルの紛失が防げる。

:追記終了