J'ai essayé de résoudre ce problème avec mapply, mais je pense que je vais devoir utiliser plusieurs applications imbriquées pour que cela fonctionne, et cela est devenu vraiment déroutant.

Le problème est le suivant:

Dataframe one contient environ 400 mots-clés. Ceux-ci appartiennent à environ 15 catégories. Dataframe two contient un champ de description de chaîne et 15 colonnes supplémentaires, chacune nommée pour correspondre aux catégories mentionnées dans dataframe one. Cela a des millions de lignes.

Si un mot-clé de la trame de données 1 existe dans le champ de chaîne de la trame de données 2, la catégorie dans laquelle le mot-clé existe doit être marquée dans la trame de données 2.

Ce que je veux devrait ressembler à ceci:

    > #Dataframe1 df1
    >> keyword    category
    >> cat        A
    >> dog        A
    >> pig        A
    >> crow       B
    >> pigeon     B
    >> hawk       B
    >> catfish    C
    >> carp       C
    >> ...
    >>
    > #Dataframe2 df2
    >> description    A    B    C    ....
    >> false cat      1    0    0    ....
    >> smiling pig    1    0    0    ....
    >> shady pigeon   0    1    0    ....
    >> dogged dog     2    0    0    ....
    >> sad catfish    0    0    1    ....
    >> hawkward carp  0    1    1    ....
    >> ....

J'ai essayé d'utiliser mapply pour que cela fonctionne mais cela échoue, me donnant l'erreur "argument plus long pas un multiple de longueur plus court". Il calcule également cela uniquement pour la première chaîne de df2. Je n'ai pas dépassé cette étape, c'est-à-dire en essayant d'obtenir des indicateurs de catégorie.

    > mapply(grepl, pattern = df1$keyword, x = df2$description)

Quelqu'un pourrait-il être utile? Je te remercie beaucoup. Je suis nouveau sur R, donc cela aiderait également si quelqu'un pouvait mentionner quelques «règles du pouce» pour transformer les boucles en fonctions d'application. Je ne peux pas me permettre d'utiliser des boucles pour résoudre ce problème car cela prendrait beaucoup trop de temps.

1
dmrzl 20 avril 2017 à 08:12

3 réponses

Meilleure réponse

Quelle que soit l'implémentation, compter le nombre de correspondances par catégorie nécessite k x d comparaisons, où k est le nombre de mots clés et d le nombre de descriptions.

Il existe quelques astuces pour résoudre ce problème rapidement et sans beaucoup de mémoire:

  • Utilisez des opérations vectorisées. Celles-ci peuvent être effectuées beaucoup plus rapidement que l'utilisation pour les boucles. Notez que lapply, mapply ou vapply ne sont que des raccourcis pour les boucles for. Je parallélise (voir ci-après) sur les mots-clés de telle sorte que la vectorisation puisse être sur les descriptions qui est la plus grande dimension.
  • Utilisez la parallélisation. L'utilisation optimale de vos multiples cœurs accélère le processus au prix d'une augmentation de la mémoire (puisque chaque cœur a besoin de sa propre copie).

Exemple:

keywords            <- stringi::stri_rand_strings(400, 2)
categories          <- letters[1:15]
keyword_categories  <- sample(categories, 400, TRUE)
descriptions        <- stringi::stri_rand_strings(3e6, 20)

keyword_occurance <- function(word, list_of_descriptions) {
  description_keywords   <- str_detect(list_of_descriptions, word)
}

category_occurance <- function(category, mat) {
  rowSums(mat[,keyword_categories == category])
}

list_keywords <- mclapply(keywords, keyword_occurance, descriptions, mc.cores = 8)
df_keywords   <- do.call(cbind, list_keywords)
list_categories <- mclapply(categories, category_occurance, df_keywords, mc.cores = 8)
df_categories <- do.call(cbind, list_categories)

Avec mon ordinateur, cela prend 140 secondes et 14 Go de RAM pour faire correspondre 400 mots-clés dans 15 catégories à 3 millions de descriptions.

0
Pieter 20 avril 2017 à 21:05

Ce que vous recherchez, c'est ce que l'on appelle une matrice de termes de document (ou dtm en bref), qui provient du NLP (Natural Language Processing). Il existe de nombreuses options disponibles. Je préfère text2vec. Ce paquet est incroyablement rapide (je ne serais pas surpris s'il surpasserait les autres solutions ici de grande ampleur) surtout en combinaison avec tokenizers.

Dans votre cas, le code ressemblerait à ceci:

# Create the data
df1 <- structure(list(keyword = c("cat", "dog", "pig", "crow", "pigeon", "hawk", "catfish", "carp"), 
                      category = c("A", "A", "A", "B", "B", "B", "C", "C")), 
                 .Names = c("keyword", "category"), 
                 class = "data.frame", row.names = c(NA,-8L))
df2 <- structure(list(description = structure(c(2L, 6L, 5L, 1L, 4L,3L),
                                              .Label = c("dogged dog", "false cat", "hawkward carp", "sad catfish", "shady pigeon", "smiling pig"), class = "factor")), 
                 .Names = "description", row.names = c(NA, -6L), class = "data.frame")

# load the libraries
library(text2vec) # to create the dtm
library(tokenizers) # to help creating the dtm
library(reshape2) # to reshape the data from wide to long

# 1. create the vocabulary from the keywords
vocabulary <- vocab_vectorizer(create_vocabulary(itoken(df1$keyword)))

# 2. create the dtm
dtm <- create_dtm(itoken(as.character(df2$description)), vocabulary)

# 3. convert the sparse-matrix to a data.frame
dtm_df <- as.data.frame(as.matrix(dtm))
dtm_df$description <- df2$description

# 4. melt to long format
df_result <- melt(dtm_df, id.vars = "description", variable.name = "keyword")
df_result <- df_result[df_result$value == 1, ]

# 5. combine the data, i.e., add category
df_final <- merge(df_result, df1, by = "keyword")
# keyword   description value category
# 1    carp hawkward carp     1        C
# 2     cat     false cat     1        A
# 3 catfish   sad catfish     1        C
# 4     dog    dogged dog     1        A
# 5     pig   smiling pig     1        A
# 6  pigeon  shady pigeon     1        B
1
David 20 avril 2017 à 08:48

Il y a peut-être un moyen plus élégant de le faire, mais c'est ce que j'ai proposé:

## Your sample data:
df1 <- structure(list(keyword = c("cat", "dog", "pig", "crow", "pigeon", "hawk", "catfish", "carp"), 
    category = c("A", "A", "A", "B", "B", "B", "C", "C")), 
    .Names = c("keyword", "category"), 
    class = "data.frame", row.names = c(NA,-8L))
df2 <- structure(list(description = structure(c(2L, 6L, 5L, 1L, 4L,3L),
    .Label = c("dogged dog", "false cat", "hawkward carp", "sad catfish", "shady pigeon", "smiling pig"), class = "factor")), 
    .Names = "description", row.names = c(NA, -6L), class = "data.frame")

## Load packages:
library(stringr)
library(dplyr)
library(tidyr)

## For each entry in df2$description count how many times each keyword
## is contained in it:
outList <- lapply(df2$description, function(description){
        outDf <- data.frame(description = description,
                value = vapply(stringr::str_extract_all(description, df1$keyword), 
                        length, numeric(1)), category = df1$category) 
    })

## Combine to one long data frame and aggregate by category:
outLongDf<- do.call('rbind', outList) %>%
    group_by(description, category) %>%
    dplyr::summarise(value = sum(value))

## Reshape from long to wide format:
outWideDf <- tidyr::spread(data = outLongDf, key = category,
    value = value)

outWideDf
# Source: local data frame [6 x 4]
# Groups: description [6]
# 
#     description     A     B     C
# *        <fctr> <dbl> <dbl> <dbl>
# 1    dogged dog     2     0     0
# 2     false cat     1     0     0
# 3 hawkward carp     0     1     1
# 4   sad catfish     1     0     1
# 5  shady pigeon     1     1     0
# 6   smiling pig     1     0     0

Cette approche permet cependant de capturer également le "cochon" chez "pigeon" et le "chat" chez le "silure". Je ne sais pas si c'est ce que vous voulez, cependant.

0
ikop 20 avril 2017 à 05:48