Новые столбцы, использующие str_extract_all в обратном направлении - PullRequest
1 голос
/ 10 марта 2019

Проблема:

Я хочу создать фрейм данных со строками, совпадающими со столбцом длинной строки.Во фрейме данных «d» столбец жанра представляет собой строковый столбец с разными жанрами.Проблема возникает из-за того, что в каждой строке не совпадает количество совпадающих элементов.

library(tidyverse)

d <- structure(list(genres = c("[{'id': 35, 'name': 'Comedy'}]", "[{'id': 35, 'name': 'Comedy'}, {'id': 18, 'name': 'Drama'}, {'id': 10751, 'name': 'Family'}, {'id': 10749, 'name': 'Romance'}]", 
                               "[{'id': 16, 'name': 'Animation'}, {'id': 12, 'name': 'Adventure'}, {'id': 10751, 'name': 'Family'}]"
), budget = c(1.4e+07, 4e+07, 8e+06)), row.names = c(NA, -3L), class = c("tbl_df", 
                                                                         "tbl", "data.frame"))
d
#> # A tibble: 3 x 2
#>   genres                                                             budget
#>   <chr>                                                               <dbl>
#> 1 [{'id': 35, 'name': 'Comedy'}]                                     1.40e7
#> 2 [{'id': 35, 'name': 'Comedy'}, {'id': 18, 'name': 'Drama'}, {'id…  4.00e7
#> 3 [{'id': 16, 'name': 'Animation'}, {'id': 12, 'name': 'Adventure'…  8.00e6

Мой не элегантный способ

Я нашел рабочий процесс, который можно обойти с этим, который немного не изящен.Сначала извлеките все совпадения с помощью str_extract_all с помощью simplify = T, который возвращает фрейм данных.Затем создайте векторную строку с именами столбцов, присвойте извлеченный фрейм данных и, наконец, используйте bind_cols:


foo <- str_extract_all(d$genres, '(?<=\'name\':\\s\')([^\']*)', simplify = T)
colnames(foo) <- paste0("genre_", 1:ncol(foo), "_extract")
foo <- foo %>% as_tibble()
foo_final <- bind_cols(d, foo)

foo_final 
#> # A tibble: 3 x 6
#>   genres budget genre_1_extract genre_2_extract genre_3_extract
#>   <chr>   <dbl> <chr>           <chr>           <chr>          
#> 1 [{'id… 1.40e7 Comedy          ""              ""             
#> 2 [{'id… 4.00e7 Comedy          Drama           Family         
#> 3 [{'id… 8.00e6 Animation       Adventure       Family         
#> # … with 1 more variable: genre_4_extract <chr>

Создано в 2019-03-10 пакетом Представить (v0.2.1)

Я хотел бы знать, есть ли способ сделать это в обратном направлении с помощью операторов pipe, mutate или map_df ... Я уверен, что естьлучший способ сделать это.

1 Ответ

2 голосов
/ 11 марта 2019

Ответ на оригинальный вопрос

Я не уверен, что это более элегантно, чем ваше решение, но это чистые трубы:

# Use `str_count` to determine the maximum number of genres associated with any
# single row, and create one column for each genre.  Stored here for
# convenience, since it's needed twice below.
genre.col.names = paste("genre",
                        1:(max(str_count(d$genres, "\\}, \\{")) + 1),
                        sep = "_")
# Use `separate` to split the `genres` column into one column for each genre.
# Then use `mutate` and some regular expressions to populate each column with
# just the genre name and no other material (quotes, brackets, etc.).
d %>%
  separate(genres,
           into = genre.col.names,
           sep = "\\}, \\{") %>%
  mutate_(.dots = setNames(paste("gsub(\".*'name': '([A-Za-z]+)'.*\", \"\\\\1\", ",
                                 genre.col.names,
                                 ")",
                                 sep = ""),
                           genre.col.names))

Перевод данных в длинный формат

Мне кажется, что более аккуратный формат будет длинным: по одной строке на фильм на жанр. (Я предполагаю, что каждая запись является фильмом ...?) Если этот формат работает для вас, вот способ получить его (и я уверен, что есть еще более простые способы):

library(fuzzyjoin)
# Get all the genres appearing in any record and put them in a dataframe.
all.genres = data.frame(
  genre.name = unique(unlist(str_extract_all(d$genres, '(?<=\'name\':\\s\')([^\']*)')))
)
# Left join the main data frame to the genre list using a regular expression.
d %>%
  rownames_to_column() %>%
  regex_left_join(all.genres, by = c("genres" = "genre.name")) %>%
  select(rowname, genre.name)

Флаг столбцов

Если вам действительно нужны данные в широком формате, как насчет одного столбца для каждого возможного жанра, и значение равно TRUE/FALSE? Это становится громоздким, если есть много возможных жанров, конечно. Но преимущество в том, что информация о том, является ли этот фильм комедией, например, всегда находится в одном столбце. (С помощью genre_1, genre_2 и т. Д. Вы должны проверить каждый столбец, чтобы выяснить, является ли фильм комедией или нет.) Вот способ получить это:

# Using the list of unique genres that we created earlier, add one column for
# each genre that contains a flag for whether the record is an example of that
# genre.
d %>%
  mutate_(.dots = setNames(paste("grepl(\"", all.genres$genre.name, "\", genres)",
                                 sep = ""),
                           all.genres$genre.name))
...