3

次のような文字列があるとします。

> x <- c("16^TG40")

c(16 2 40)がで2ある結果を取得しようとしていlength(^TG)-1ます。このパターンは、たとえば次のようにして見つけることができます。

> gsub("(\\^[ACGT]+)", " \\1 ", x)
[1] "16 ^TG 40"

length-1ただし、この文字列を直接置き換えることはできません。一致したパターンを長さで置き換える簡単な方法はありますか?

かなりの検索(ここではSOとグーグル検索)の後、私はstringrパッケージに行き着きました、それは素晴らしいと思います。しかし、それでも、それはすべて、このパターンの場所を見つけて(を使用してstr_locate_all)、サブトリングを必要な値に置き換える(を使用してstr_sub)ことになります。私は100,000を超える文字列を持っていますが、非常に時間がかかります(パターンが文字列内で複数回発生する可能性があるため)。

現在、速度の低下を補うために並行して実行していますが、これが直接(または迅速に)可能かどうかを知りたいと思います。

何か案は?

4

3 に答える 3

8

(1) gsubfnこのgsubfnステートメントは、^... の部分をその長さをスペースで囲んだものに置き換え、strapplyその文字列から数字を抜き出して数値に変換します。strapply文字出力で十分な場合は省略します。

> library(gsubfn)
> xx <- gsubfn("\\^[ACGT]*", ~ sprintf(" %s ", nchar(x) - 1), x)
> strapply(xx, "\\d+", as.numeric)
[[1]]
[1] 16  2 40

(2) 長さのセットをループ

これは、各 ACGT シーケンスの文字数が mn から mx の間であると仮定し、ループ内で処理を進める gsub を使用して、i long の ACGT シーケンスを i に置き換えるだけです。可能な長さがわずかしかない場合は、数回の反復しかないため高速ですが、文字列がさまざまな長さを持つ可能性がある場合は、ループの反復がさらに必要になるため遅くなります。以下では、ACGT シーケンスの長さは 2、4、または 6 であると想定していますが、これらは調整する必要がある場合があります。このソリューションの考えられる欠点は、一連の可能なシーケンス長を想定する必要があることです。

x <- "4^CG5^CAGT656"

mn <- 2
mx <- 6
y <- x
for(i in seq(mn, mx, 2)) {
   pat <- sprintf("\\^[ACGT]{%d}(\\d)", i)
   replacement <- sprintf(" %d \\1", i)
   y <- gsub(pat, replacement, y)
}

(3) ACGT シーケンスのループ

この 1 つは、ACGT シーケンスをループし、1 つをその長さに置き換えて、何も残らなくなるまで繰り返します。ACGT シーケンスの数が少ない場合は反復回数が少ないため高速になりますが、ACGT シーケンスの数が多い場合は反復回数が多くなるため遅くなります。

x <- "4^CG5^CAGT656"
y <- x
while(regexpr("^", y, fixed = TRUE) > 0) {
    y <- sprintf("%s %d %s", sub("\\^.*", "", y),
        nchar(sub("^[0-9 ]+\\^([ACGT]+).*", "\\1", y)),
        sub("^[0-9 ]+\\^[ACGT]+", "", y))
}

基準

これがベンチマークです。上記のいくつかのソリューションでは、文字列を数値に変換しましたが (もちろん、これには余分な時間がかかります)、ベンチマークを比較できるようにするために、数値変換なしで文字列を作成する速度を比較しました。

x <- "4^CGT5^CCA656"
library(rbenchmark)
benchmark(order = "relative", replications = 10000,
   columns = c("test", "replications", "relative", "elapsed"),
   regmatch = {
      pat <- "(\\^[ACGT]+)"
      x2 <- x
      m <- gregexpr(pat, x2)
      regmatches(x2, m) <- sapply(regmatches(x2, m), modFun)
      x2
   },
   gsubfn = gsubfn("\\^[ACGT]*", ~ sprintf(" %s ", length(x) - 1), x),
   loop.on.len = {
    mn <- 2
    mx <- 6
    y <- x
    for(i in seq(mn, mx, 2)) {
       pat <- sprintf("\\^[ACGT]{%d}(\\d)", i)
       replacement <- sprintf(" %d \\1", i)
       y <- gsub(pat, replacement, y)
    }
   },
   loop.on.seq = {
    y <- x
    while(regexpr("^", y, fixed = TRUE) > 0) {
        y <- sprintf("%s %d %s", sub("\\^.*", "", y),
            nchar(sub("^[0-9 ]+\\^([ACGT]+).*", "\\1", y)),
            sub("^[0-9 ]+\\^[ACGT]+", "", y))
    }
  }
)

結果を以下に示します。2 つのループ ソリューションは、示された入力で最も高速でしたが、必要な反復回数によってパフォーマンスが異なるため、実際のデータによって違いが生じる可能性があります。loop.on.len ソリューションには、ACGT の長さが想定されたセットの中になければならないという欠点があります。Josh の regmatch ソリューションは、ループがなく、高速です。gsubfn ソリューションには、コードが 1 行しかなく、特に直接的であるという利点があります。

         test replications relative elapsed
4 loop.on.seq        10000    1.000    1.93
3 loop.on.len        10000    1.140    2.20
1    regmatch        10000    1.803    3.48
2      gsubfn        10000    7.145   13.79

更新 2 つのループ ソリューションを追加し、複数の ACGT シーケンスを処理しない以前の投稿の一部であったソリューションを削除しました (質問を明確にするコメントに基づく)。また、複数の ACGT シーケンスを処理するソリューションのみを含むベンチマークをやり直しました。

更新複数の ^... シーケンスで機能しない 1 つのソリューションを削除しました。以前にベンチマークから削除されていましたが、コードは削除されていませんでした。(1)の説明を改善。

于 2013-01-24T23:54:43.183 に答える
8

これが base-R アプローチです。

構文は直感的とは言えませんが、このテンプレートに厳密に従うことで、一致した部分文字列のあらゆる操作と置換を実行できます。(?gregexprより複雑な例については、 を参照してください。)

x2 <- x <- c("16^TG40", "16^TGCT40", "16^TG40^GATTACA40")

pat <- "(\\^[ACGT]+)"              ## A pattern matching substrings of interest
modFun <- function(ss) {           ## A function to modify them
    paste0(" ", nchar(ss) - 1, " ")
}

## Use regmatches() <- regmatches(gregexpr()) to search, modify, and replace.
m <- gregexpr(pat, x2)
regmatches(x2, m) <- sapply(regmatches(x2, m), modFun)
x2
## [1] "16 2 40"      "16 4 40"      "16 2 40 7 40"
于 2013-01-24T23:56:57.850 に答える
2

私は信じられないほど洗練されたgsubfn答えに投票していますが、私はすでにこの不格好なコードを持っているので:

mod <- gsub("(\\^[ACGT]+)", " \\1 ", x)
locs <- gregexpr(" ", mod , fixed=TRUE)[[1]]
paste( substr( x, 1, locs[1]-1), 
       diff(locs)-2, 
       substr(mod, locs[2]+1, nchar(mod) ) , sep=" ")
#[1] "16 2 40"
于 2013-01-25T00:02:14.987 に答える