備忘ログ

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

Rで三桁ごとにコンマが打たれている1,000,000みたいな数字を1000000に変換したい

qiita.com

この記事をみてふーんって思って、いろいろ考えてみたっていうやつ。

このコードの気になる点を上げると

  • 1,000,000の場合、「1」と「000」と「000」がそれぞれ別の引数として取り扱われている。
  • そのため、複数データの入力受け付けない。
  • 複数のデータを受け付けるようにpurrrなどで受け付けたデータの回数分計算させるのもちょっと手間(後述)。
  • コンマとコンマの間は必ず3桁(変更可能)を想定しており、過不足があればへんてこな結果を返す。

という、使いにくさがある(もともと記事では書きたいというだけでしかないので、変換用途としたいという意図はないのかもしれない)。

そこで解決策を考えてみた。

あと、最後にのせたもとの関数を活かした解決策以外はコンマを含む数字を文字列として扱うこととする(例:“1,234,456”)。

一番単純な解決策

コンマ消せばいい、という単純な発想にもとづけば、{stringr}パッケージのstr_replage_allでコンマを消していく処理をすればいい。

stringr::str_replace_all("1,000,000", pattern = ",", replacement = "")
## [1] "1000000"

これなら複数データも受け付ける。

df <- c("1,234,567", "234,567")
stringr::str_replace_all(df, pattern = ",", replacement = "")
## [1] "1234567" "234567"

文字列のまま答えを返しているが、数にして返したかったら結果をas.numericで数とするといい(パイプを使いたいので{dplyr}パッケージを読んでおく)。

library("dplyr")
## 
## Attaching package: 'dplyr'

## The following objects are masked from 'package:stats':
## 
##     filter, lag

## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
stringr::str_replace_all(df, pattern = ",", replacement = "") %>% as.numeric()
## [1] 1234567  234567

ただし、この方法だと不規則な箇所(例:1,23,456,7890)にコンマが打たれているへんてこなデータもコンマを消して返してくる。個人的には正しい形じゃないデータは変換しないでそのままの形で返すか、エラーを出して停止するかなどしてほしい。

一応関数化したものをおいておく。

Num <- function(str, convert = TRUE){
  str <- stringr::str_replace_all(str, pattern = ",", replacement = "")
  if(convert) str <- as.numeric(str)
  return(str)
}

これを使うと、

df <- c("1,234,567", "234,567")
Num(df)
## [1] 1234567  234567

となる。

文字列のままが良ければ、convertFALSEとするといける。

Num(df, convert = FALSE)
## [1] "1234567" "234567"

三桁ごとにコンマが打たれている数以外はそのままの文字列を返すようにする

三桁ごとにコンマで区切られた数字をそうではない数字に変換する、その逆の変換を行う関数を含むパッケージを作った。

indenkun/NumComma

挙動の詳細はREADMEの通り。

remotes::install_github("indenkun/NumComma")

でインストールできる。

x <- c("2,020", "1,234.56")
NumComma::NumComma_rm(x)
## [1] "2020"    "1234.56"

文字列中のコンマで区切られた数字も変換できる。

NumComma::NumComma_rm_str("日本の総人口は2020年1月1日時点で124,271,318人です。")
## [1] "日本の総人口は2020年1月1日時点で124271318人です。"

逆に数字をコンマで区切られた数字に変換することもできる(追記:{scales}パッケージのcomma`関数のほうがコンマつけるだけならいいと思う)。

NumComma::NumComma_add("123456")
## [1] "123,456"

文字列中の数字をコンマで区切られた数字に変更することもできる。

NumComma::NumComma_add_str("日本の総人口は2020年1月1日時点で124271318人です。")
## [1] "日本の総人口は2,020年1月1日時点で124,271,318人です。"

年数とかの数字はコンマで区切らない事が多いのでそういうことを擬似的に実現するための引数も備えている。

NumComma::NumComma_add_str("日本の総人口は2020年1月1日時点で124271318人です。", digit = 4)
## [1] "日本の総人口は2020年1月1日時点で124,271,318人です。"
NumComma::NumComma_add_str("日本の総人口は2020年1月1日時点で124271318人です。", small.num = 3000)
## [1] "日本の総人口は2020年1月1日時点で124,271,318人です。"

そのままの関数を生かして複数データを受け付けるようにする

三桁ごとに区切られた数字(一番上の桁だけが1~3個の可変長で、それ以降は3個か、数字がなくなった以降はNA)が一つづつ引数となるので、下記のようなデータが必要になる。

一行ごとに一つの数を意味している(1,234と2,345,678を意図している)。

x1 x2 x3
1 234 NA
2 345 678

これをそれぞれの行についてその行の列の数字を最後の数字またはNAの一つまえの数字まで一つずつ引数として渡せるようにしなければいけない。

具体的には下記のようなコードになる。

Num_kai <- function(num.data, digit = 3){
  # ループで回すときにdigitが最後だとこれも桁かと間違われるので、引数の順番を変える。
  Num <- function(digit=3, ...) {
    args <- unlist(list(...))
    result <- 0
    d <- 10 ^ digit
    for(arg in args) {
      result <- result * d + arg
    }
    result
  }
  
  ans <- NULL
  # 行数分だけループする必要がある
  for(i in 1:nrow(num.data)){
    # データが可変長もどきなので、行のNAの列を削除する
    dat <- num.data[i, ] %>% dplyr::select_if(~ !is.na(.))
    # 行ごとに三点ドット箇所にNAを削除した行を投入する
    ans[i] <- Num(digit, dat)
  }
  return(ans)
}

これで、

df <- data.frame(x1 = c(1  , 2),
                 x2 = c(234, 345),
                 x3 = c(NA, 678))
Num_kai(df)
## [1]    1234 2345678

とできる。

工夫すればもっとシンプルにかけるかもしれない。

x1 x2 x3
NA 1 234
2 345 678

こっちの形式のデータでも行ける。

df <- data.frame(x1 = c(NA , 2),
                 x2 = c(1  , 345),
                 x3 = c(234, 678))
Num_kai(df)
## [1]    1234 2345678

しかし、この形式のデータならNA列を消して数字のある列を結合したほうが素直な気がする。

雑感

R上でコンマを含む数字データをR上で取り扱うことがあるのかという件について。

とりあえず、Qiitaにあるコードやそのコードを活かした上の関数の受け付けるような文字列じゃないコンマを含む数(日本語が謎すぎて意味がわからない)は基本的に無いんじゃないかと思う。取り扱いにくいし。ただ、絶対ないとは言い切れなくて桁ごとに数を格納するような変わったデータもあり得るかもしれない。

ただしそいういう桁ごとに数字が格納されいてる変則的なデータフレームではない限りは、基本的に取り扱いえるのは“1,234,567”みたな文字列としてのコンマを含む文字列形式の数字になると思う。

これはどういう場合に存在し得るかというと、神エクセルデータのようなエクセルデータやCSVデータの場合にコンマを含む数字が存在するものを処理すると文字列として読み込まれた場合には存在し得ると思う。これは頻出だが、正直前処理次第というところもあるので一旦おいておく。

今回考えるのはエクセル上で次のような表示になっているデータの場合。

人口 売上
1,000 1,000
2,000 2,000
3,000 3,000
4,000 4,000
5,000 5,000
6,000 6,000
7,000 7,000
8,000 8,000
9,000 9,000
10,000 10,000
11,000 11,000
12,000 12,000
13,000 13,000
14,000 14,000
15,000 15,000
16,000 16,000
17,000 17,000
18,000 18,000
19,000 19,000
20,000 20,000

この形式のエクセルデータや、これをCSV化したCSVファイルを読み込むとき、{readxl}read_excel{readr}read_csvで読み込むなら何も指定しなくても賢くコンマを外した数として取り込んでくれる。

read.csvの場合のみ素直にコンマを含む文字列として読み込んでしまうので、そんなときには出会うかもしれない。ただし、今どきのRの解説書はほとんど{tidyverse}を使ってデータハンドリングするようになっているのでこういうこともそうないかもしれない。

ただこうやってみると逆に、{readxl}{readr}は意図せずデータが書き換わっている(あえてコンマ付きにしていたデータだったら)可能性もあるということに気づいた。

あとは文字列中のコンマを含む数字は結構見るので文字列スクレイピングして読み込んでると出会うかもしれない。そういうときに、表記を整えるために{NumComma}のようなパッケージが使えるかもしれない(文字列処理しないからよくわからないけど)。