2016-07-28 2 views
2

私は、以下の問題に対して、より短くてきれいな解決法(たぶんひっそりとしたもの)を探しています。 「」「」私がやりたいことR - パターンに一致するすべての文字列を抽出し、リレーショナルテーブルを作成します。

id   string 
1 A 1.001 xxx 123.123 
2 B 23,45 lorem ipsum 
3 C  donald trump 
4 D ssss 134, 1,45 

は、区切り文字がある場合には(関係なく、すべての数字を抽出することであるか - >この場合には、私はその文字列を想定しています。私はdata.frame「データ」を持っています「134は、1.45」は、2つの数字に抽出することができます。

が、私はこの(下記のコードを実行するために管理さ)が、:134および1.45)とdata.frame「出力」を作成これに似て探してソリューションは私にとってもとても効率的ではありません(2つのfor-loops)。誰かが(liststringrから)str_extract_allと数字を抽出し、

# data 
data <- data.frame(id = c("A", "B", "C", "D"), 
        string = c("1.001 xxx 123.123", 
          "23,45 lorem ipsum", 
          "donald trump", 
          "ssss 134, 1,45"), 
        stringsAsFactors = FALSE) 

# creating empty data.frame      
len <- length(unlist(sapply(data$string, function(x) gregexpr("[0-9]+[,|.]?[0-9]*", x)))) 
output <- data.frame(id = rep(NA, len), string = rep(NA, len)) 

# main solution 
start = 0 

for(i in 1:dim(data)[1]){ 
    tmp_len <- length(unlist(gregexpr("[0-9]+[,|.]?[0-9]*", data$string[i]))) 
    for(j in (start+1):(start+tmp_len)){ 
    output[j,1] <- data$id[i] 
    output[j,2] <- regmatches(data$string[i], gregexpr("[0-9]+[,|.]?[0-9]*", data$string[i]))[[1]][j-start] 
    } 
    start = start + tmp_len 
} 

# further modifications 
output$string <- gsub(",", ".", output$string) 
output$string <- as.numeric(ifelse(substring(output$string, nchar(output$string), nchar(output$string)) == ".", 
            substring(output$string, 1, nchar(output$string) - 1), 
            output$string)) 

output 

答えて

5

1)基本Rこれは、比較的単純な正規表現なしのパッケージを使用しています。

最初の2行のコードでは、コンマの後ろに のスペースを置き換え、残りのすべてのカンマをドットで置き換えます。この2行の後sは次のようになります。コードの次の4行でc("1.001 xxx 123.123", "23.45 lorem ipsum", "donald trump", "ssss 134 1.45")

が始まり、各文字列フィールドの末尾から空白をトリムし、空白が リストを生成するには、文字列フィールドを分割します。 grepは、数字とドットのみからなる要素を出力します。 (正規表現^[0-9.]*$は、単語の先頭に0文字以上の数字またはドットの後に単語の終わりが続くので、それらの文字だけを含む単語だけが一致します)。最後にdata$idを名前として追加します。これらの4行が実行された後、リストLlist(A = c("1.001", "123.123"), B = "23.45", C = NA, D = c("134", "1.45"))になります。

最後のコード行では、リストを適切な名前のデータフレームに変換します。

s <- gsub(", ", " ", data$string) 
s <- gsub(",", ".", s) 

L <- strsplit(trimws(s), "\\s+") 
L <- lapply(L, grep, pattern = "^[0-9.]*$", value = TRUE) 
L <- ifelse(lengths(L), L, NA) 
names(L) <- data$id 

with(stack(L), data.frame(id = ind, string = values)) 

与える:

2)magrittr(1)magrittrパイプラインとして書き込むのこの変化。

library(magrittr) 

data %>% 
    transform(string = gsub(", ", " ", string)) %>% 
    transform(string = gsub(",", ".", string)) %>% 
    transform(string = trimws(string)) %>% 
    with(setNames(strsplit(string, "\\s+"), id)) %>% 
    lapply(grep, pattern = "^[0-9.]*$", value = TRUE) %>% 
    replace(lengths(.) == 0, NA) %>% 
    stack() %>% 
    with(data.frame(id = ind, string = values)) 

3)dplyr/tidyrこれはdplyrとtidyrを使用して、代替パイプラインソリューションです。 unnestは長い形式に変換されます。idは、後でcompleteを使用して、後続のフィルタリングによって削除されるIDを回復できるようになります。フィルタは迷惑行を削除し、completeは、それぞれidのNA行を挿入します。

library(data.table) 

DT <- as.data.table(data) 
DT[, string := gsub(", ", " ", string)][, 
    string := gsub(",", ".", string)][, 
    string := trimws(string)][, 
    string := setNames(strsplit(string, "\\s+"), id)][, 
    list(string = list(grep("^[0-9.]*$", unlist(string), value = TRUE))), by = id][, 
    list(string = if (length(unlist(string))) unlist(string) else NA_character_), by = id] 
DT 

data.table

library(dplyr) 
library(tidyr) 

data %>% 
    mutate(string = gsub(", ", " ", string)) %>% 
    mutate(string = gsub(",", ".", string)) %>% 
    mutate(string = trimws(string)) %>% 
    mutate(string = strsplit(string, "\\s+")) %>% 
    unnest() %>% 
    mutate(id = factor(id)) 
    filter(grepl("^[0-9.]*$", string)) %>% 
    complete(id) 

4)アップデートはジャンク言葉は数字やドットを持っていないという仮定を削除しました。また、(2)、(3)、(4)といくつかの改良が加えられました。

+0

Unnest - これは私が探していた機能です。素晴らしい投稿 - ありがとう! 1つの質問:L user2280549

+0

"\\ s"は常に空白文字が1つあればOKです - 確かではありませんでした。 –

+0

(2)と(3)を改善しました。 –

2

我々は(gsubを使用して).と数字の間に,を置き換えることができ、この(好ましく使用dplyr)を行う行うには良い方法を提案することができが0の等しいlist要素をNAに置き換え、listの名前を 'id'列のに設定してlistdata.frameに変換し、列の名前を変更します。

library(stringr) 
setNames(stack(setNames(lapply(str_extract_all(gsub("(?<=[0-9]),(?=[0-9])", ".", 
     data$string, perl = TRUE), "[0-9.]+"), function(x) 
    if(length(x)==0) NA else as.numeric(x)), data$id))[2:1], c("id", "string")) 
# id string 
#1 A 1.001 
#2 A 123.123 
#3 B 23.45 
#4 C  NA 
#5 D  134 
#6 D 1.45 
+0

ありがとうございます。あなたは非常に親切で、より詳しく( "?<= [0-9])、(?= [0-9])"と書いてください。私はPerlのような表記に慣れていません – user2280549

+0

@ user2280549それは2つの数字の間に '、'があることを意味するルックアライメントです – akrun

+0

ええ、私はそれを理解しました。私はPerl表記で[0-9]の前に "<="または "="が何を意味するのだろうかと疑問に思っていました。 – user2280549

1

Gabor'sと同じ考えです。

sp = setNames(strsplit(data$string, " "), data$id) 

spc = lapply(sp, function(x) { 
    x = x[grep("[^0-9.,]$", x, invert=TRUE)] 

    if (!length(x)) 
    NA_real_ 
    else 
    mapply(type.convert, x, dec=gsub("[^.,]", "", x), USE.NAMES=FALSE) 
}) 

setNames(rev(stack(spc)), names(data)) 

    id string 
1 A 1.001 
2 A 123.123 
3 B 23.45 
4 C <NA> 
5 D  134 
6 D 1.45 

は残念ながら、type.convertが一度に小数点区切り文字の両方を考慮するのに十分な堅牢ではありませんので、我々:私はむしろカスタム正規表現置換を書くよりも、内蔵された(read.tableで使用type.convert、)文字列の構文解析Rのを使用することを期待していましたtype.convert(x, dec = "[.,]")ではなく、mapplyマラキーが必要です。

関連する問題