有效地逐行分隔字符串

时间:2018-04-19 16:13:20

标签: r dplyr stringr rowwise

我试图根据切断字符串将字符串列分成两部分。以下示例最佳说明。 rowwise确实有效,但考虑到data.frame的大小,我想使用更有效的方法。如何避免使用rowwise

library(dplyr)
library(stringr)
library(tidyr)

#make data
a <- "(1, 10)"
b <- "(10, 20)"
c <- "(20, 30)"

df <- data.frame(size = c(a,b,c))

# Goal is to separate the 'size' column into 'lower' and 'upper' by
# extracting the value contained in the parens and split by a comma.
# Once the column is split into 'upper' and 'lower' I will perform 
# additional operations.

# DESIRED RESULT
  size     lower upper 
  <fct>    <chr> <chr> 
1 (1, 10)  1     10
2 (10, 20) 10    20
3 (20, 30) 20    30

# WHAT I HAVE TRIED

> #This works... but too inefficient
> df %>%
+   rowwise() %>%
+   mutate(lower = str_split(size, ",") %>% .[[1]] %>% .[1] %>%
+            str_split("\\(") %>% .[[1]] %>% .[2])
  size     lower
  <fct>    <chr>
1 (1, 10)  1    
2 (10, 20) 10   
3 (20, 30) 20   

> # I'm not sure why this doesn't work
> df %>%
+   mutate(lower = str_split(size, ",") %>% .[[1]] %>% .[1] %>%
+            str_split("\\(") %>% .[[1]] %>% .[2])
      size lower
1  (1, 10)     1
2 (10, 20)     1
3 (20, 30)     1

> #Not obivous how to use separate (tidyr)
> df %>%
+   separate(size, sep=",", c("lower", "upper"))
  lower upper
1    (1   10)
2   (10   20)
3   (20   30)

4 个答案:

答案 0 :(得分:1)

对于rowwise操作,我更喜欢data.table。

试试这个

library(data.table)
library(stringi)

#make data
a <- "(1, 10)"
b <- "(10, 20)"
c <- "(20, 30)"

dt <- data.table(c(a,b,c))
dt[, lower := tstrsplit(V1, ",")[1]]
dt[, lower:= stri_replace_all_regex(lower, '\\(', '')]

dt

答案 1 :(得分:1)

您没有明确说明您的目标,但似乎您想要从字符串中提取第一个数字。使用stringi::str_extract_first_regex

可以轻松完成
library(stringi)
stri_extract_first_regex(df$size, "[0-9]+")
# [1] "1"  "10" "20"

所以在你的情况下,

df %>% mutate(lower = as.numeric(stri_extract_first_regex, size, "[0-9]+"))

您可以使用stri_extract_all_regex提取所有数字。

根据您的修改:

df$nums = str_extract_all(df$size, "[0-9]+")
df$lower = as.numeric(sapply(df$nums, `[[`, 1))
df$upper = as.numeric(sapply(df$nums, `[[`, 2))
df
#       size   nums lower upper
# 1  (1, 10)  1, 10     1    10
# 2 (10, 20) 10, 20    10    20
# 3 (20, 30) 20, 30    20    30

另一种方法是摆脱parens和whitespace然后单独使用:

df %>%
    mutate(just_nums = str_replace_all(size, "[^0-9,]", "")) %>%
    separate(just_nums, into = c("lower", "upper"))
#       size lower upper
# 1  (1, 10)     1    10
# 2 (10, 20)    10    20
# 3 (20, 30)    20    30

正则表达式模式"[^0-9,]"匹配除数字和逗号之外的所有内容。

答案 2 :(得分:1)

选项是从数据中删除tidyr::separate(后使用)

library(tidyverse)
df %>% mutate(size = gsub("\\(|)","",size)) %>%  # Both ( and ) has been removed.
  separate(size, c("Min", "Max"), sep = ",")
#   Min Max
# 1   1  10
# 2  10  20
# 3  20  30

答案 3 :(得分:0)

你快到了。以下是我对两种方法的解释,一种类似于你的方法:

在第一个代码中,我使用了tidytext包中的unnest_tokens,它可以在不同的行上拆分单词,因为你想在逗号之前提取第一个项目(我假设它是基于你的例子,尽管你应该提到它)。我通过使用filter命令选择了第一行。

在第二个代码中,我使用了正则表达式(请注意,您也可以使用此处str_replace)。这里我使用map(因为str_split返回的项是一个列表)来迭代返回的项并通过gsub传递每个,这可以替换与后引用项匹配的正则表达式。另外,为了只选择第一项,我在gsub的末尾使用了[[1]]。

library(tidyverse)
library(stringr)
library(tidytext)
df %>% 
    unnest_tokens(lower,size, token="words",drop=F) %>% 
    filter(row_number()%%2==T)

df %>% 
    mutate(lower = map(str_split(df$size, ","), function(x)gsub("\\((\\w+)","\\1",x)[[1]]))

<强>输出

   #       size lower
   # 1  (1, 10)     1
   # 2 (10, 20)    10
   # 3 (20, 30)    20

如果您想要在逗号之前和之后提取两个术语,您也可以使用extract函数。

tidyr::extract(df, size, c("lower", "upper"), regex= "\\((\\w+),\\s+(\\w+)\\)")

<强>输出

  #   lower upper
   # 1     1    10
   # 2    10    20
   # 3    20    30