使用 R 从字符串列中提取单个字符。

3

背景

以下是我的gamedata数据集以dput形式呈现——它包括一些MLB比赛的得分记录。

structure(list(team = c("NYM", "NYM", "BOS", "NYM", "BOS"), linescore = c("010000000", 
"(10)1140006x", "002200010", "00000(11)01x", "311200"), ondate = structure(c(18475, 
18476, 18487, 18489, 18494), class = "Date")), class = "data.frame", row.names = c(NA, 
-5L))

例如,这里有一行比分:"002200010"。
一些行比分以'x'结尾,有些行比分括在括号中表示两位数字,例如"00000(11)01x"。每个不在括号中的数字表示该队在回合中获得了多少分。如果一个队在一局中得分超过九分,则将数字放在括号中。因此,在行比分“00000(11)01x”中,该队在第六局得到11分,并且没有在第九局下半局上场(由'x'表示)。
并非每个行比分都有九局。有些比赛有更多比赛局数,而有些只有六局。 我需要做什么 首先,我需要获取每个局中一个团队获得的得分,例如第一局、第二局、第三局等,并将每次得分放入新的列中。我希望解决方案使用dplyr。
我已经查看了stackoverflow推荐的解决方案,但没有找到符合我需求的。如果有,请分享其URL链接。
我尝试使用以下代码来实现:
gamedata %>%
  select(ondate, team, linescore) %>%
  mutate(inng1 = str_extract(linescore, "\\d|\\(\\d{2}\\)"))

这是输出结果:

ondate      team linescore    inng1
2020-08-01  NYM 010000000       0   
2020-08-02  NYM (10)1140006x  (10)  
2020-08-13  BOS 002200010       0   
2020-08-15  NYM 00000(11)01x    0   
2020-08-20  BOS 311200          3

其次,我该如何去掉“inng1”列中数字“10”周围的括号?

下面的代码导致了下面的错误:

gamedata %>%
  select(ondate, team, linescore) %>%
  mutate(inng1 = str_extract(linescore, "\\d|\\(\\d{2}\\)"))
 str_remove_all(inng1,"[()]")

我收到的错误信息如下:

"在stri_replace_all_regex(string,pattern,fix_replacement(replacement)中出现错误:对象'inng1'未找到"

第三步, 我需要知道如何提取每个额外局的得分,并从第二局开始,将每个值放入自己的列中,例如inng2、inng3等。

最终,我应该有上面显示的输出结果(没有括号表示每个两位数的局),每个局应该有一列,因此会有一个名为"inng1"、"inng2"、"inng3"、"inng4"等的列。局列中的数据需要是数字,因为稍后我将对它们进行求和。


2
inng1列包含每个观测值'linescore'列中的第一个值。inng2列包含每个观测值'linescore'列中的第二个值,以此类推。括号中的数字算作一个值。 - Metsfan
1
AnilGoyal,如果你所指的是第二个观测值中得分行末尾的最后一个括号,那是一个错误。在“x”之后永远不应该有括号。 - Metsfan
是的,我指的就是那个。请看我的回答。请检查一下。如果可以工作,我会添加适当的解释。 - AnilGoyal
@Metsfan 我应该如何处理 x 值? - Anoushiravan R
1
我认为这个应该被删除,因为OP已经说明他想要数字输出。 - AnilGoyal
显示剩余2条评论
2个回答

5

解决方案02

下面是另一种解决此问题的方法,比第一种方法更有效,并主要基于 purrr 函数族:

library(dplyr)
library(purrr)

df %>%
  bind_cols(
    map(df %>% select(linescore), ~ strsplit(.x, "\\(|\\)")) %>%
      flatten() %>%
      map_dfr(~ map(.x, ~ if(nchar(.x) > 2) strsplit(.x, "")[[1]] else .x) %>%
                reduce(~ c(.x, .y)) %>%
                keep(~ nchar(.x) != 0) %>% t() %>%
                as_tibble() %>% 
                set_names(~ paste0("inng", 1:length(.x)))) %>%
      mutate(across(everything(), ~ replace(.x, .x == "x", NA_character_)), 
             count_inng = pmap_dbl(cur_data(), ~ sum(!is.na(c(...)))), 
             sums_inng = pmap_dbl(select(cur_data(), starts_with("inng")), 
                                  ~ sum(as.numeric(c(...)), na.rm = TRUE)))
  )

  team    linescore     ondate inng1 inng2 inng3 inng4 inng5 inng6 inng7 inng8 inng9 count_inng
1  NYM    010000000 2020-08-01     0     1     0     0     0     0     0     0     0          9
2  NYM (10)1140006x 2020-08-02    10     1     1     4     0     0     0     6  <NA>          8
3  BOS    002200010 2020-08-13     0     0     2     2     0     0     0     1     0          9
4  NYM 00000(11)01x 2020-08-15     0     0     0     0     0    11     0     1  <NA>          8
5  BOS       311200 2020-08-20     3     1     1     2     0     0  <NA>  <NA>  <NA>          6
  sums_inng
1         1
2        22
3         5
4        12
5         7

解决方案01

我对我的解决方案进行了一些修改,因为它错误地替换了输出向量中的两位数,我认为已经修复了这个问题。 我认为这个解决方案可能会对你有所帮助。为此,我决定编写一个自定义函数来检测两位数,并修剪您的分数输出:

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

fn <- function(x) {
  out <- c()
  if(str_detect(x, "\\((\\d){2}\\)")) {
    double <- str_replace_all(str_extract(x, "\\((\\d){2}\\)"), "[)()]", "")
    ind <- str_locate(x, "\\(")
    x <- str_remove(x, "\\((\\d){2}\\)")
    out <- c(out, str_split(x, "")[[1]])
    out[(ind[1, 1]+1):(length(out)+1)] <- out[(ind[1, 1]):length(out)]
    out[ind] <- double
  } else {
    out <- c(out, str_split(x, "")[[1]])
  }
  if(any(grepl(")", out))) {
    out <- out[-which(out == ")")]
  }
  out
}

# Test
fn("(10)1140006x)")
[1] "10" "1"  "1"  "4"  "0"  "0"  "0"  "6"  "x" 

然后我们将其应用于我们的数据集,进行逐行操作:

df %>%
  mutate(linescore = map(linescore, fn)) %>% 
  unnest_wider(linescore) %>%
  rename_with(~ gsub("(\\.\\.\\.)(\\d)", paste0("inng", "\\2"), .), starts_with("...")) %>%
  mutate(across(starts_with("inng"), ~ {replace(.x, .x == "x", NA)
    as.numeric(.x)}), 
    inns_count = pmap_dbl(select(cur_data(), starts_with("inng")), 
                          ~ sum(!is.na(c(...)))), 
    inns_sums = pmap_dbl(select(cur_data(), starts_with("inng")), 
                         ~ sum(c(...), na.rm = TRUE)))

# A tibble: 5 x 13
  team  inng1 inng2 inng3 inng4 inng5 inng6 inng7 inng8 inng9 ondate     inns_count inns_sums
  <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <date>          <dbl>     <dbl>
1 NYM       0     1     0     0     0     0     0     0     0 2020-08-01          9         1
2 NYM      10     1     1     4     0     0     0     6    NA 2020-08-02          8        22
3 BOS       0     0     2     2     0     0     0     1     0 2020-08-13          9         5
4 NYM       0     0     0     0     0    11     0     1    NA 2020-08-15          8        12
5 BOS       3     1     1     2     0     0    NA    NA    NA 2020-08-20          6         7

1
我需要先对它进行轻微的修改。好的,那么我会这样做。 - Anoushiravan R
2
太棒了,伙计们。我正在尝试进一步操作,如何将正则表达式(, 1, 0, )替换为10 - TarJae
1
@TarJae 尝试使用反转函数。请参考此问题 - AnilGoyal
1
Anoushiravan,'x' 值只在棒球比赛中用于表示主队没有在最后一局的下半场击球。当出现 'x' 时,它只会出现在行分数的末尾。 - Metsfan
1
@TarJae 谢谢兄弟,我们在等你 :) - Anoushiravan R
显示剩余13条评论

4

大致如下所述-

  • 使用base R的gsub进行一些regex转换
  • 使用stringr::str_trimstringr::str_count()(可选)
  • 使用tidyr::separate
  • 还要使用dplyr::mutate

步骤如下-

  • 从字符串linescore中删除x(我将其变异为新列,您也可以变异现有列)
  • 使用regex再次使用gsub将括号外的每个字符替换为该字符加上一个空格
  • 然后删除括号字符串
  • 使用tidyr::separate将字符串分隔成不同的列。
  • 使用convert = TRUE将每个字符串转换为数字。

对于regex转换说明,请查看此处

library(tidyverse)
df <- structure(list(team = c("NYM", "NYM", "BOS", "NYM", "BOS"), linescore = c("010000000", 
                                                                                "(10)1140006x", "002200010", "00000(11)01x", "311200"), ondate = structure(c(18475, 
                                                                                                                                                             18476, 18487, 18489, 18494), class = "Date")), class = "data.frame", row.names = c(NA, 
                                                                                                                                                                                                                                                -5L))

df %>%
  mutate(inn = gsub('x', '', linescore),
         inn = str_trim(gsub("(.)(?![^(]*\\))", "\\1 ", inn, perl=TRUE)),
         inn = gsub('\\(|\\)', '', inn),
         innings_count = 1 + str_count(inn, ' ')) %>%
  separate(inn, into = paste0('innings_', seq(max(.$innings_count))), sep = ' ', fill = 'right', convert = TRUE)
#>   team    linescore     ondate innings_1 innings_2 innings_3 innings_4
#> 1  NYM    010000000 2020-08-01         0         1         0         0
#> 2  NYM (10)1140006x 2020-08-02        10         1         1         4
#> 3  BOS    002200010 2020-08-13         0         0         2         2
#> 4  NYM 00000(11)01x 2020-08-15         0         0         0         0
#> 5  BOS       311200 2020-08-20         3         1         1         2
#>   innings_5 innings_6 innings_7 innings_8 innings_9 innings_count
#> 1         0         0         0         0         0             9
#> 2         0         0         0         6        NA             8
#> 3         0         0         0         1         0             9
#> 4         0        11         0         1        NA             8
#> 5         0         0        NA        NA        NA             6

1
当我运行这段代码时,我得到了第二场比赛中“innings_1”列的整个得分记录,并填充了NA的其余行。 - thefringthing
2
太棒了,伙计们。我正在尝试进一步操作,如何将正则表达式(, 1, 0, )替换为10 - TarJae
1
@TarJae 使用这个 gsub("[(,) ]", "", "(, 1, 0, )") - Anoushiravan R
1
AnilGoyal,当我测试你的代码时,输出包含“x”值;然而,在你的类型转换代码中,当我运行它时会生成错误,所有的x都被替换为NA,这是我更喜欢的。 - Metsfan
1
'innings_count' 应该是 9、8、9、8、6,而不是 9、9、9、9、6。 - Metsfan
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接