使用case_when()时出现多条件评估问题。

3

我想检测字符串中是否存在特定的关键词和短语,如果有,我想在一个新列中发布一个特定的数字。我的问题是,有些字符串有多个关键字,但case_when只返回第一个匹配项。是否有方法解决这个问题,或者我应该使用替代方案来代替case_when?

ID<-c(1,2,3,4,5)
fruits<-c("banana apple orange", "apple orange", "orange", "orange apple", "nothing")
df<-data_frame(ID,fruits)
#I need to assign a random number to each fruit type

df %>% 
  mutate("Fruit Type"=case_when(
    grepl("banana",fruits)~34,
    grepl("apple",fruits)~45,
    grepl("orange",fruits)~88,
))

ID       fruits                  Fruit Type
1      banana apple orange           34
2      apple orange                  45
3      orange                        88
4      orange apple                  45
5      nothing                       NA

我希望你能够理解我的意思。
ID        fruits       fruit_type         
1   banana apple orange    34       
1   banana apple orange    45       
1   banana apple orange    88       
2   apple orange           45       
2   apple orange           88       
3   orange                 88       
4   orange apple           88       
4   orange apple           45       
5   nothing                NA

此外,有没有一种方法可以将其转换为长格式,以使其更像这样呈现?
ID        fruits       fruit_type  fruit_type2  fruit_type3     
1   banana apple orange    34        45              88                                     
2   apple orange           45        88              NA                                     
3   orange                 88        NA              NA                             
4   orange apple           88        45              NA                                                     
5   nothing                NA        NA              NA         
5个回答

3

以下是使用separatestr_detectacross的另一种解决方案:

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

df %>% 
  separate(fruits, c("fruit_type1", "fruit_type2", "fruit_type3"), remove = FALSE) %>% 
  mutate(across(contains("fruit_type"), ~case_when(
    str_detect(., "banana") ~ 34,
    str_detect(., "apple") ~ 45,
    str_detect(., "orange") ~ 88)
  ))

输出:

     ID fruits              fruit_type1 fruit_type2 fruit_type3
  <dbl> <chr>                     <dbl>       <dbl>       <dbl>
1     1 banana apple orange          34          45          88
2     2 apple orange                 45          88          NA
3     3 orange                       88          NA          NA
4     4 orange apple                 88          45          NA
5     5 nothing                      NA          NA          NA

2

我们使用str_replace_all将字符串值更改为命名向量,然后在空格处拆分 'fruit_type' 列以扩展数据 (separate_rows),并将 'fruit_type' 的类型更改为 numeric 类。

library(dplyr)
library(tidyr)
library(stringr)
out <- df %>% 
    mutate(fruit_type = str_replace_all(fruits, 
     setNames(as.character(c(34, 45, 88)), c("banana", "apple", "orange")))) %>% 
    separate_rows(fruit_type) %>%
    mutate(fruit_type = as.numeric(fruit_type))

-输出

out
# A tibble: 9 x 3
     ID fruits              fruit_type
  <dbl> <chr>                    <dbl>
1     1 banana apple orange         34
2     1 banana apple orange         45
3     1 banana apple orange         88
4     2 apple orange                45
5     2 apple orange                88
6     3 orange                      88
7     4 orange apple                88
8     4 orange apple                45
9     5 nothing                     NA

使用这个输出,我们可以使用 pivot_wider 将其转换为“宽”格式。
library(data.table)
out %>% 
    mutate(rn = str_c('fruit_type', rowid(ID))) %>% 
    pivot_wider(names_from = rn, values_from = fruit_type)

-输出

# A tibble: 5 x 5
     ID fruits              fruit_type1 fruit_type2 fruit_type3
  <dbl> <chr>                     <dbl>       <dbl>       <dbl>
1     1 banana apple orange          34          45          88
2     2 apple orange                 45          88          NA
3     3 orange                       88          NA          NA
4     4 orange apple                 88          45          NA
5     5 nothing                      NA          NA          NA

2
你可以先将不同的水果分别放在不同的行中,然后使用 case_when -
library(dplyr)
library(tidyr)

res <- df %>%
  separate_rows(fruits, sep = '\\s+') %>%
  mutate(Fruit_Type =case_when(
    grepl("banana",fruits)~34,
    grepl("apple",fruits)~45,
    grepl("orange",fruits)~88,
  ))
  
res

#     ID fruits  Fruit_Type
#  <dbl> <chr>        <dbl>
#1     1 banana          34
#2     1 apple           45
#3     1 orange          88
#4     2 apple           45
#5     2 orange          88
#6     3 orange          88
#7     4 orange          88
#8     4 apple           45
#9     5 nothing         NA

为了获得宽格式的数据,您可以执行以下操作 -
res %>%
  group_by(ID) %>%
  mutate(row = paste0('Fruit', row_number()), 
         fruits = paste0(fruits, collapse = ' ')) %>%
  ungroup %>%
  pivot_wider(names_from = row, values_from = Fruit_Type)

#    ID fruits              Fruit1 Fruit2 Fruit3
#  <dbl> <chr>                <dbl>  <dbl>  <dbl>
#1     1 banana apple orange     34     45     88
#2     2 apple orange            45     88     NA
#3     3 orange                  88     NA     NA
#4     4 orange apple            88     45     NA
#5     5 nothing                 NA     NA     NA

这非常接近我想做的事情,但在我的数据集中有更多不仅仅是水果名称的单词,所以我无法按每个单词分开。例如,我的数据集中每行都会说类似于“有一个香蕉,一个苹果和一个橙子”的内容。使用separate_rows会为每个单词创建一行,但我只想要与相关水果单词有关的行。有没有办法使用separate_rows仅针对特定的单词? - emv7

1
这里是一种完全不同的、类似于数据库的方法,它使用了一个水果和果实类型的查找表。这种方法可以处理任意数量的水果和果实类型。
# create or read lookup table
lut <- readr::read_table(
"fruit    fruit_type
banana           34
apple            45
orange           88")

library(dplyr)
library(tidyr)
df %>% 
  mutate(fruit = fruits) %>% 
  separate_rows(fruit, sep = "\\s+") %>% 
  left_join(lut, by = "fruit") %>% 
  group_by(ID) %>% 
  mutate(rowid = row_number(ID)) %>% 
  pivot_wider(id_cols = c(ID, fruits), values_from = fruit_type, 
              names_prefix = "fruit_type", names_from = rowid)
     ID fruits              fruit_type1 fruit_type2 fruit_type3
  <dbl> <chr>                     <dbl>       <dbl>       <dbl>
1     1 banana apple orange          34          45          88
2     2 apple orange                 45          88          NA
3     3 orange                       88          NA          NA
4     4 orange apple                 88          45          NA
5     5 nothing                      NA          NA          NA

fruits列被复制并拆分。现在,fruit列包含单个水果,分别位于不同的行中。这些与查找表lut相结合,以获取匹配的fruit_type值。在将此结果重塑为宽格式之前,新列需要编号。这是通过对每个ID内的行进行编号来实现的。

编辑:

根据OP的评论,生产数据集包含段落,其中关键字不仅由空格分隔,还由逗号等标点符号分隔,或者以其复数形式出现,并带有尾随的s。此外,关键字可能以大写字母书写,或者在段落中出现多次。

我们可以尝试从段落中提取关键字,而不是将所有单词分开。这可以通过将所有关键字组合成一个带有交替项的正则表达式|来实现。因此,正则表达式banana|apple|orange将匹配任何一种水果。

为了测试,我们需要一个更复杂的用例:

df <- tibble(fruits = readr::read_lines(
"There are bananas, oranges, and also apples here
One Orange and another orange make two Oranges 
apples and pineapples go together
But pineapples alone must not be counted
banana apple orange
apple orange
orange
orange apple
nothing")
) %>% 
  mutate(ID = row_number())

使用修改后的代码。
df %>% 
  mutate(fruit = fruits %>% 
           tolower() %>% 
           stringr::str_extract_all(paste(lut$fruit, collapse = "|")) %>% 
           lapply(unique)) %>% 
  unnest(fruit, keep_empty = TRUE) %>% 
  left_join(lut, by = "fruit") %>% 
  group_by(ID) %>% 
  mutate(rowid = row_number(ID)) %>% 
  pivot_wider(id_cols = c(ID, fruits), values_from = fruit_type, 
              names_prefix = "fruit_type", names_from = rowid)

我们得到
     ID fruits                                             fruit_type1 fruit_type2 fruit_type3
  <int> <chr>                                                    <dbl>       <dbl>       <dbl>
1     1 "There are bananas, oranges, and also apples here"          34          88          45
2     2 "One Orange and another Orange make two Oranges "           88          NA          NA
3     3 "apples and pineapples go together"                         45          NA          NA
4     4 "But pineapples alone must not be counted"                  45          NA          NA
5     5 "banana apple orange"                                       34          45          88
6     6 "apple orange"                                              45          88          NA
7     7 "orange"                                                    88          NA          NA
8     8 "orange apple"                                              88          45          NA
9     9 "nothing"                                                   NA          NA          NA

这种方法可以检测复数形式的关键字,而且不受大小写影响。

请注意,我故意选择使用 lapply(unique) 只统计段落中关键字的多次出现一次。如果要单独计算每个出现次数,则只需删除该行代码。

但是,该方法存在一个(至少一个)缺点:单词 pineapple 被视为 apple,因为它包含了子字符串 apple


这非常接近我想要做的事情,但是我在 separate_rows 函数上遇到了问题。在我的数据集中,每个“水果”关键字都是段落的一部分,因此我无法按每个单词分开。例如,“这里有香蕉、橙子和苹果”,所以我不想为每个单词创建一个新行;只为那些特定的水果关键字创建新行。是否有任何方法可以使用 separate_rows 实现这一点? - emv7
@emv7 我明白了。问题不仅在于逗号和其他标点符号,还有复数形式中的尾随_s_。 - Uwe
谢谢,当我将这个粘贴到Rstudio中时,我得到了一个不同的结果。它创建了fruit_type1到fruit_type15,而不仅仅是fruit_type1、2和3。我很难发布完整的结果,但它只在每一列中粘贴了一个水果类型编号。 - emv7
通过注释掉 group_by(ID) %>% 这一行,我能够创建出类似的结果(每列只有一个数字,共15列)。 - Uwe

0

如果你有一个宽数据框,你可以利用{tidyr}中的一些有用函数,比如pivot_longer()。(还有一个函数pivot_wider()可以做相反的操作。)

下面的解决方案首先创建一个更宽的数据框,然后将其缩小为更长的数据框。因此,它按照与你列出的顺序相反的顺序生成数据框。

library(dplyr)
library(tidyr)

ID <- c(1, 2, 3, 4, 5)
fruits <- c("banana apple orange",
            "apple orange",
            "orange",
            "orange apple",
            "nothing")
df <- tibble(ID, fruits)

new_df <- 
  df %>%
  mutate(fruit_type = if_else(grepl("banana", fruits), 34, NA_real_),
         fruit_type2 = if_else(grepl("apple", fruits), 45, NA_real_),
         fruit_type3 = if_else(grepl("orange", fruits), 88, NA_real_))
new_df
#> # A tibble: 5 x 5
#>      ID fruits              fruit_type fruit_type2 fruit_type3
#>   <dbl> <chr>                    <dbl>       <dbl>       <dbl>
#> 1     1 banana apple orange         34          45          88
#> 2     2 apple orange                NA          45          88
#> 3     3 orange                      NA          NA          88
#> 4     4 orange apple                NA          45          88
#> 5     5 nothing                     NA          NA          NA

long_df <-
  new_df %>%
  pivot_longer(cols = starts_with("fruit_type"), names_to = "fruit_type") %>%
  select(-fruit_type) %>%
  rename(fruit_type = value) %>%
  distinct() %>%  # Remove duplicates
  group_by(ID, fruits) %>%
  mutate(n = n()) %>%
  filter(!is.na(fruit_type) | n == 1) %>%
  select(-n)
long_df
#> # A tibble: 9 x 3
#> # Groups:   ID, fruits [5]
#>      ID fruits              fruit_type
#>   <dbl> <chr>                    <dbl>
#> 1     1 banana apple orange         34
#> 2     1 banana apple orange         45
#> 3     1 banana apple orange         88
#> 4     2 apple orange                45
#> 5     2 apple orange                88
#> 6     3 orange                      88
#> 7     4 orange apple                45
#> 8     4 orange apple                88
#> 9     5 nothing                     NA

reprex package (v2.0.0)于2021年07月23日创建


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