Разбейте строки на более мелкие, чтобы создать новые строки во фрейме данных (в R) - PullRequest
0 голосов
/ 19 мая 2018

Я новичок в R, и в настоящее время я борюсь с тем, как разделить строки в каждой строке фрейма данных, а затем создать новую строку с измененной строкой (наряду с изменением оригинала).Это пример ниже, но фактический набор данных намного больше.

library(dplyr)
library(stringr)
library(tidyverse)
library(utils)

posts_sentences <- data.frame("element_id" = c(1, 1, 2, 2, 2), "sentence_id" = c(1, 2, 1, 2, 3), 
                "sentence" = c("You know, when I grew up, I grew up in a very religious family, I had the same sought of troubles people have, I was excelling in alot of ways, but because there was alot of trouble at home, we were always moving around", "Im at breaking point.I have no one to talk to about this and if I’m honest I think I’m too scared to tell anyone because if I do then it becomes real.I dont know what to do.", "I feel like I’m going to explode.", "I have so many thoughts and feelings inside and I don't know who to tell and I was going to tell my friend about it but I'm not sure.", "I keep saying omg!it's too much"), 
                "sentence_wc" = c(60, 30, 7, 20, 7), stringsAsFactors=FALSE)

Я хочу разбить предложения, которые превышают определенное количество слов (15 для этого набора данных), создать новые предложения изнутрив более длинных предложениях используется регулярное выражение, так что сначала я пытаюсь разбить его по периодам (или другим символам), затем, если количество слов все еще слишком велико, я пытаюсь запятые, за которыми следует I (или заглавная буква), а затем я пытаюсь 'и'сопровождаемый заглавной буквой и т. д. Каждый раз, когда я создаю новое предложение, ему необходимо изменить предложение со старой строки на первую часть предложения, а также изменить количество слов (у меня есть функция для этого) вместе сс созданием новой строки с тем же идентификатором элемента, идентификатором предложения, который следует за последовательностью (если значение_пункта равно 1, теперь новым предложением равно 2), счетчиком слов нового предложения, а затем заменой всех следующих предложений на следующее число предложений_ид,

Я работаю над этим несколько дней и не могу понять, как это сделать.Я пытался использовать нестандартные токены, str_split / extract и различные комбинации фильтров, мутаций и т. Д. Dplyr вместе с поиском в Google / SO.Кто-нибудь знает лучший способ сделать это?Dplyr предпочтительнее, но я открыт для всего, что работает.Не стесняйтесь задавать вопросы, если вам нужны какие-либо разъяснения!

Изменить, чтобы добавить ожидаемый кадр выходных данных:

expected_output <- data.frame("element_id" = c(1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2), "sentence_id" = c(1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6), 
                                   "sentence" = c("You know, when I grew up", "I grew up in a very religious family", "I had the same sought of troubles people have", "I was excelling in alot of ways, but because there was alot of trouble at home, we were always moving around", "Im at breaking point.", "I have no one to talk to about this and if I’m honest I think I’m too scared to tell anyone because if I do then it becomes real.", "I dont know what to do.", "I feel like I’m going to explode.", "I have so many thoughts and feelings inside and", "I don't know who to tell and", "I was going to tell my friend about it but I'm not sure.", "I keep saying omg!", "it's too much"), 
                                   "sentence_wc" = c(6, 8, 8, 21, 4, 27, 6, 7, 9, 7, 13, 4, 3), stringsAsFactors=FALSE)

Ответы [ 4 ]

0 голосов
/ 22 мая 2018

Это решение сначала разделяет предложение запятой или точкой перед заглавной буквой.Затем разделите предложение только запятой и точкой.Наконец, если предложение все еще выше предельных слов.предложения разделяются каждым прописным буквой.

posts_sentences <- data.frame("element_id" = c(1, 1, 2, 2, 2), "sentence_id" = c(1, 2, 1, 2, 3), 
                              "sentence" = c("You know, when I grew up, I grew up in a very religious family, I had the same sought of troubles people have, I was excelling in alot of ways, but because there was alot of trouble at home, we were always moving around", "Im at breaking point.I have no one to talk to about this and if I’m honest I think I’m too scared to tell anyone because if I do then it becomes real.I dont know what to do.", "I feel like I’m going to explode.", "I have so many thoughts and feelings inside and I don't know who to tell and I was going to tell my friend about it but I'm not sure.", "I keep saying omg!it's too much"), 
                              "sentence_wc" = c(60, 30, 7, 20, 7), stringsAsFactors=FALSE)

# To create an empty data frame to save the new elements

new_posts_sentences <- data.frame(element_id = as.numeric(),
                 sentence_id =as.numeric(), 
                 sentence = character(), 
                 sentence_wc = as.numeric(),  stringsAsFactors=FALSE) 

limit_words <- 15 # 15 for this data set

countSentences <- 0

for (sentence in posts_sentences[,3]) {

        vector <- character()

        Velement_id <- posts_sentences$element_id[countSentences + 1]

        vector <- c(vector, sentence) #To create a vector with the sentences
        vector <- vector[!vector %in% ''] #remove empty elements from vector

        ## First we will separate the sentences that start with a uppercase after of a capital letter
        if(lengths(gregexpr("[A-z]\\W+", sentence)) > limit_words ){

                vector <- vector[!vector %in% sentence]

                split_points <- unlist(gregexpr("[:,:]\\s[A-Z]", sentence)) # To get the character position

                ## If a sentences is still over the limit words value. Let's split it for each comma or period
                sentences_1 <- substring(sentence, c(1, split_points + 2), c(split_points -1, nchar(sentence)))

                for(sentence in sentences_1){

                        vector <- c(vector, sentence)
                        vector <- vector[!vector %in% '']

                        if(lengths(gregexpr("[A-z]\\W+", sentence)) > limit_words){

                                vector <- vector[!vector %in% sentence]

                                split_points <- unlist(gregexpr("[:,:]|[:.:]", sentence))

                                sentences_2 <- substring(sentence, c(1, split_points + 1), c(split_points -1, nchar(sentence)))

                                ## If a sentence is still s still over the limit words value. Let's split it for each capital letter

                                for(sentence in sentences_2){

                                        vector <- c(vector, sentence)
                                        vector <- vector[!vector %in% '']

                                        if(lengths(gregexpr("[A-z]\\W+", sentence)) > limit_words){

                                                vector <- vector[!vector %in% sentence]

                                                split_points <- unlist(gregexpr("[A-Z]", sentence))

                                                sentences_3 <- substring(sentence,c(1, split_points), c(split_points -1, nchar(sentence)))

                                                vector <- c(vector, sentences_3)
                                                vector <- vector[!vector %in% '']

                                        }

                                }

                        }

                }

        }

        ## To make a data frame o each original sentence
        element_id <- rep(Velement_id, length(vector))
        sentence_id <- 1:length(vector)
        sentence_wc <- character()
        for (element in vector){sentence_wc <- c(sentence_wc, (lengths(gregexpr("[A-z]\\W+", element)))) }
        sentenceDataFrame <- data.frame(element_id, sentence_id, vector, sentence_wc)       

        ## To join it with the final dataframe
        new_posts_sentences <- rbind(new_posts_sentences, sentenceDataFrame)

        countSentences <- countSentences + 1

}

Вы получаете этот фрейм данных

print(new_posts_sentences)

   element_id sentence_id                                           vector sentence_wc
1           1           1                         You know, when I grew up           5
2           1           2             I grew up in a very religious family           7
3           1           3    I had the same sought of troubles people have           8
4           1           4                  I was excelling in alot of ways           6
5           1           5    but because there was alot of trouble at home           8
6           1           6                     we were always moving around           4
7           1           1                             Im at breaking point           3
8           1           2      I have no one to talk to about this and if           11
9           1           3                                      I’m honest            3
10          1           4                                         I think            2
11          1           5        I’m too scared to tell anyone because if            9
12          1           6                        I do then it becomes real           5
13          1           7                           I dont know what to do           5
14          2           1                I feel like I’m going to explode.           8
15          2           1 I have so many thoughts and feelings inside and            9
16          2           2                    I don't know who to tell and            8
17          2           3      I was going to tell my friend about it but           10
18          2           4                                     I'm not sure           3
19          2           1                  I keep saying omg!it's too much           7

Надеюсь, это поможет.

0 голосов
/ 22 мая 2018

Вот подход tidyverse, который позволяет вам указать свою собственную эвристику, которая, я думаю, должна быть лучшей в вашей ситуации.Ключом является использование pmap для создания списков каждой строки, которые затем можно разделить при необходимости с помощью map_if.На мой взгляд, это сложная ситуация с одним dplyr, потому что мы добавляем строки в нашу операцию, и поэтому rowwise сложно использовать.

Структура split_too_long()в основном:

  1. Используйте dplyr::mutate и tokenizers::count_words, чтобы получить количество слов каждого предложения
  2. сделать каждую строку элементом списка с purrr::pmap, который принимает фрейм данныхв качестве списка столбцов в качестве входных данных
  3. используйте purrr::map_if, чтобы проверить, больше ли количество слов, чем наш желаемый предел
  4. используйте tidyr::separate_rows, чтобы разбить предложение на несколько строк, если указано выше условиевстречается,
  5. , затем замените счетчик слов на новый счетчик слов и удалите все пустые строки с filter (созданным разделителями с удвоением).

Затем мы можем применить этодля различных разделителей, поскольку мы понимаем, что элементы должны быть разделены дальше.Здесь я использую эти шаблоны, соответствующие упомянутой вами эвристике:

  • "[\\.\\?\\!] ?", которая соответствует любому из .!?, и необязательный пробел
  • ", ?(?=[:upper:])", который соответствует ,,необязательный пробел, предшествующий заглавной букве
  • "and ?(?=[:upper:])", что соответствует and необязательный пробел, предшествующий заглавной букве.

Он правильно возвращает те же разделенные предложения, что и в ожидаемом результате,sentence_id легко добавить обратно в конце с помощью row_number, а ошибочные начальные / конечные пробелы можно удалить с помощью stringr::str_trim.

Предостережения:

  • Iнаписал это для читабельности в исследовательском анализе, следовательно, разделение на списки и связывание обратно каждый раз.Если вы заранее решите, какие разделители вам нужны, вы можете поместить их в один шаг map, который, вероятно, сделает его быстрее, хотя я не описал это в большом наборе данных.
  • Согласно комментариям, естьеще предложения с более чем 15 словами после этих разделений.Вам нужно будет решить, какие дополнительные символы / регулярные выражения вы хотите разделить, чтобы уменьшить длину.
  • Имена столбцов в настоящее время жестко закодированы в split_too_long.Я рекомендую вам заглянуть в programming with dplyr виньетка, если для вас важна возможность указывать имена столбцов при вызове функции (для этого нужно всего несколько настроек)
posts_sentences <- data.frame(
  "element_id" = c(1, 1, 2, 2, 2), "sentence_id" = c(1, 2, 1, 2, 3),
  "sentence" = c("You know, when I grew up, I grew up in a very religious family, I had the same sought of troubles people have, I was excelling in alot of ways, but because there was alot of trouble at home, we were always moving around", "Im at breaking point.I have no one to talk to about this and if I’m honest I think I’m too scared to tell anyone because if I do then it becomes real.I dont know what to do.", "I feel like I’m going to explode.", "I have so many thoughts and feelings inside and I don't know who to tell and I was going to tell my friend about it but I'm not sure.", "I keep saying omg!it's too much"),
  "sentence_wc" = c(60, 30, 7, 20, 7), stringsAsFactors = FALSE
)

library(tidyverse)
library(tokenizers)
split_too_long <- function(df, regexp, max_length) {
  df %>%
    mutate(wc = count_words(sentence)) %>%
    pmap(function(...) tibble(...)) %>%
    map_if(
      .p = ~ .$wc > max_length,
      .f = ~ separate_rows(., sentence, sep = regexp)
      ) %>%
    bind_rows() %>%
    mutate(wc = count_words(sentence)) %>%
    filter(wc != 0)
}

posts_sentences %>%
  group_by(element_id) %>%
  summarise(sentence = str_c(sentence, collapse = ".")) %>%
  ungroup() %>%
  split_too_long("[\\.\\?\\!] ?", 15) %>%
  split_too_long(", ?(?=[:upper:])", 15) %>%
  split_too_long("and ?(?=[:upper:])", 15) %>%
  group_by(element_id) %>%
  mutate(
    sentence = str_trim(sentence),
    sentence_id = row_number()
  ) %>%
  select(element_id, sentence_id, sentence, wc)
#> # A tibble: 13 x 4
#> # Groups:   element_id [2]
#>    element_id sentence_id sentence                                      wc
#>         <dbl>       <int> <chr>                                      <int>
#>  1          1           1 You know, when I grew up                       6
#>  2          1           2 I grew up in a very religious family           8
#>  3          1           3 I had the same sought of troubles people ~     9
#>  4          1           4 I was excelling in alot of ways, but beca~    21
#>  5          1           5 Im at breaking point                           4
#>  6          1           6 I have no one to talk to about this and i~    29
#>  7          1           7 I dont know what to do                         6
#>  8          2           1 I feel like I’m going to explode               7
#>  9          2           2 I have so many thoughts and feelings insi~     8
#> 10          2           3 I don't know who to tell                       6
#> 11          2           4 I was going to tell my friend about it bu~    13
#> 12          2           5 I keep saying omg                              4
#> 13          2           6 it's too much                                  3

Создано в 2018-05-21 пакетом Представить (v0.2.0).

0 голосов
/ 22 мая 2018

Альтернатива tidyverse решение:

library(dplyr)
library(tidyr)
library(stringr)
library(tidyverse)
library(utils)

check_and_split <- function(element_id, sentence_id, sentence, sentence_wc,
                             word_count, attmpt){

  methods <- c("\\.", ",\\s?(?=[I])", "and\\s?(?=[A-Z])")
  df <- data.frame(element_id=element_id,
             sentence_id=sentence_id,
             sentence=sentence,
             sentence_wc=sentence_wc,
             word_count=word_count,
             attmpt=attmpt,
             stringsAsFactors = FALSE)

    if(word_count<=15 | attmpt>=3){
      return(df) #early return
    } else{
     df %>% 
        tidyr::separate_rows(sentence, sep=methods[attmpt+1]) %>% 
        mutate(word_count=str_count(sentence,'\\w+'),
               attmpt = attmpt+1)
    }
}

posts_sentences %>% 
  mutate(word_count=str_count(sentence,'\\w+'),
         attmpt=0) %>%
  pmap_dfr(check_and_split) %>% 
  pmap_dfr(check_and_split) %>% 
  pmap_dfr(check_and_split) 

Здесь мы создаем вспомогательную функцию, которая принимает строку (разлагается на элемент, обслуживаемый purrr::pmap()), мы собираем ее обратно в кадр данных,проверьте, не превышает ли число слов 15 и количество попыток в предложении.Затем мы используем tidyr::separate_rows() с токеном разделения, соответствующим следующей попытке, обновляем word_count и number of attempts и возвращаем фрейм данных.

Я применяю одну и ту же функцию три раза - ее, вероятно, можно обернуть вцикл (lapply / purrr :: map не будет работать, так как нам нужно обновить последовательно обновляемый фрейм данных).

С точки зрения токенов регулярного выражения, сначала мы используем литерал ., затем мы отслеживаем запятую и ноль или более пробелов, за которыми следует «I».Обратите внимание на позитивный синтаксис.Наконец, мы пытаемся «и» с, возможно, пробелом, заглядывая вперед, за которым следует заглавная буква.

Надеюсь, это имеет смысл

0 голосов
/ 21 мая 2018

РЕДАКТИРОВАТЬ: я отредактировал весь ответ, чтобы более подробно рассмотреть конкретную проблему.

Это не совсем общий характер, поскольку предполагается, что группы сделаны исключительно на основе element_id.

split_too_long <- function(str, max.words=15L, ...) {
  cuts <- stringi::stri_locate_all_words(str)[[1L]]

  # return one of these
  if (nrow(cuts) <= max.words) {
    c(str, NA_character_)
  }
  else {
    left <- substr(str, 1L, cuts[max.words, 2L])
    right <- substr(str, cuts[max.words + 1L, 1L], nchar(str))
    c(left, right)
  }
}

recursive_split <- function(not_done, done=NULL, ...) {
  left_right <- split_too_long(not_done, ...)

  # return one of these
  if (is.na(left_right[2L]))
    c(done, left_right[1L])
  else
    recursive_split(left_right[2L], done=c(done, left_right[1L]), ...)
}

collapse_split <- function(sentences, regex="[.;:] ?", ...) {
  sentences <- paste(sentences, collapse=". ")
  sentences <- unlist(strsplit(sentences, split=regex))
  # return
  unlist(lapply(sentences, recursive_split, done=NULL, ...))
}

group_fun <- function(grouped_df, ...) {
  # initialize new data frame with new number of rows
  new_df <- data.frame(sentence=collapse_split(grouped_df$sentence, ...),
                       stringsAsFactors=FALSE)
  # count words
  new_df$sentence_wc <- stringi::stri_count_words(new_df$sentence)
  # add sentence_id
  new_df$sentence_id <- 1L:nrow(new_df)
  # element_id must be equal because it is a grouping variable,
  # so take 1 to repeat it in output
  new_df$element_id <- grouped_df$element_id[1L]
  # return
  dplyr::filter(new_df, sentence_wc > 0L)
}

out <- posts_sentences %>%
  group_by(element_id) %>%
  do(group_fun(., max.words=5L, regex="[.;:!] ?"))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...