有效地逐行分隔字符串

Separate string column by row efficiently

我试图根据字符串的分割将字符串列分成两部分。最好用下面的例子来说明。 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)

对于按行操作,我更喜欢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

您没有明确说明您的目标,但您似乎想从字符串中提取第一个数字。使用 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

另一种方法是去掉括号和空格,然后单独使用:

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,]" 匹配除数字和逗号之外的所有内容。

你快到了。这是我对两种方法的解释,一种与您的类似:

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

在第二个代码中,我使用了正则表达式(注意你也可以在这里使用str_replace)。这里我使用 map(因为 str_split 返回的项目是一个列表)来迭代返回的项目并通过 gsub 传递每个项目,它可以替换与反向引用项目匹配的正则表达式。另外为了 select 只有第一项,我在 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

一个选项是在从数据中删除 () 之后使用 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