Is there a way to use for loops within dplyr to reduce the number of str_detect terms needed?

妖精的绣舞 提交于 2021-01-27 21:11:52

问题


I'm currently working on a project, and I'm looking at classifying about a hundred thousand strings, based on their content.

The goal of this code is to identify if a string matches, classify them to a particular bucket, then to save the end result to a csv. No code contains more than one matching string.

I realise that after a certain point my code gets a little unreadable - mostly because if I have to change one of say, two hundred str_detect functions with the same format, I then have to find it in my case_when, etc.

I'm looking at a way to possibly integrate for loops and if conditionals into my function to improve readability and make modifying str_detect functions easier.

I've tried swapping out the case_when/str_detect combination by defining a tibble that includes all my string classes, string terms and classifications. Following that, I've swapped out the case_when for a for loop that integrates the tibble within str_detect, pulling out a specific string condition each turn.

# Working case_when version

library(dplyr)
library(stringr)

a.str <- "(?i)Apple"
b.str <- "(?i)Banana"
c.str <- "(?i)Corn"

food_set <- read_csv("Food.csv")

food_identified <- food_set %>% mutate(
     food.type = case_when( 
          str_detect(food_set, a.str ) = TRUE ~ "A",
          str_detect(food_set, b.str ) = TRUE ~ "B",
          str_detect(food_set, c.str ) = TRUE ~ "C"
     )
)

food_classified <- write_csv(food_identified,"Food_Classified.csv")
# Failing for loop version


library(dplyr)
library(stringr)

str_options <- tribble(
~variety.str,      ~String,   ~Classification,
#-----------/-------------/-------------------
"a.str"     , "(i?)Apple" ,               "A",
"b.str"     , "(i?)Banana",               "B",
"c.str"     , "(i?)Corn"  ,               "C"
)

food_set <- read_csv("Food.csv")

food_identified <- food_set %>% mutate(
     for (k in 1:3) {
          if(str_detect(food_set, str_options[k,2]) == TRUE) {
          food.type = str_options[k,3]
     }
     break
     }
)

food_classified <- write_csv(food_identified,"Food_Classified.csv")

The case_when code runs fine - it spits out a table with two columns (food, food_type).

The for loop doesn't work - it spits out an error saying 'no applicable method for 'type' applied to an object of class "c('tbl_df','tbl','data.frame')".

Does anyone have an idea as to how I might be able to get this working?


回答1:


Here's a way that just uses one call of str_detect. The problem here is that you can't use a normal join to match because the strings might contain other characters. Here what I do is join all the strings to match into one pattern to extract with, so we have a new column that can be joined on. Note that this is safe only because you said each row would only have one matching string, though you should check this (otherwise the order of the case_when would matter). We have to escape special characters before we join the strings to match, though.

You should also make sure that my interpretation of food_set matches your actual data, or include a dput of a sample.

library(tidyverse)

food_set <- tibble(
  food_set = c("sadgad(i?)Apple", "(i?)Bananaasdgas", "hgjdndg(i?)Cornadfba")
)

str_options <- tribble(
  ~variety.str,      ~String,   ~Classification,
  #-----------/-------------/-------------------
  "a.str"     , "(i?)Apple" ,               "A",
  "b.str"     , "(i?)Banana",               "B",
  "c.str"     , "(i?)Corn"  ,               "C"
)

str_regex <- str_options$String %>%
  str_replace_all("(\\W)", "\\\\\\1") %>%
  str_c(collapse = "|")

food_set %>%
  mutate(to_match = str_extract(food_set, str_regex)) %>%
  left_join(str_options, by = c("to_match" = "String"))
#> # A tibble: 3 x 4
#>   food_set             to_match   variety.str Classification
#>   <chr>                <chr>      <chr>       <chr>         
#> 1 sadgad(i?)Apple      (i?)Apple  a.str       A             
#> 2 (i?)Bananaasdgas     (i?)Banana b.str       B             
#> 3 hgjdndg(i?)Cornadfba (i?)Corn   c.str       C

Created on 2019-04-27 by the reprex package (v0.2.1)




回答2:


This could also be done with fuzzyjoin. One potential advantage / thing to watch out for is that it will join to all matching regexes.

library(tidyverse); library(fuzzyjoin)
food_set <- tibble(
  food_set = c("sadgad(i?)Apple", "(i?)Bananaasdgas", "hgjdndg(i?)Cornadfba")
)

food_set %>%
  regex_left_join(str_options, by = c("food_set" = "String"))


# A tibble: 3 x 4
  food_set             variety.str String     Classification
  <chr>                <chr>       <chr>      <chr>         
1 sadgad(i?)Apple      a.str       (i?)Apple  A             
2 (i?)Bananaasdgas     b.str       (i?)Banana B             
3 hgjdndg(i?)Cornadfba c.str       (i?)Corn   C  


来源:https://stackoverflow.com/questions/55886082/is-there-a-way-to-use-for-loops-within-dplyr-to-reduce-the-number-of-str-detect

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!