Clustering textuel avec R
Dans l’article précédent, Oracle Text a été utilisé pour assurer le partitionnement d’un ensemble de recettes de cuisines. Dans ce post, la même opération va être réalisée à l’aide de R. Les données utilisées seront identiques (on les rapatrie avec ROracle depuis la base Oracle de l’article précédent):
> library(ROracle) Loading required package: DBI > ora = Oracle() > cnx = dbConnect(ora, username="c##rafa", password="Password1#", dbname="//clorai2-scan:1521/pdb_hodba08") > data_set <- dbGetQuery(cnx, "select * from RECETTES_CLEAN") >
On va se servir des fonctions de la librairie tm pour réaliser les opérations de manipulation de texte. On commence par créer un Corpus de termes à partir des listes d’ingrédients:
> library(tm) > IngredientsCorpus <- Corpus(VectorSource(data_set$INGREDIENTS), readerControl = list(language = "fr")) >
Ce Corpus va ensuite être re-travaillé en réalisant les opérations suivantes:
- Homogénéisation de la casse
- Suppression des symboles de ponctuation
- Suppression des chiffres
- Conversion des caractères accentués en caractères non-accentués
- Suppression des mots vides (on récupère depuis CTX_STOPWORDS la liste des mots vides générée dans le billet précédent)
- Suppression des espaces
- Racinisation des termes
> IngredientsCorpus <- tm_map(IngredientsCorpus, content_transformer(tolower)) > > replacePunctuation <- function(x) { + gsub("[[:punct:]]+", " ", x) + } > > IngredientsCorpus <- tm_map(IngredientsCorpus, content_transformer(replacePunctuation)) > > IngredientsCorpus <- tm_map(IngredientsCorpus, removeNumbers) > > replaceAccent <- function(x) { + iconv(x, to="ASCII//TRANSLIT//IGNORE") + } > > IngredientsCorpus <- tm_map(IngredientsCorpus, replaceAccent) > > mots_vides <- dbGetQuery(cnx, "select upper(spw_word) mot from CTX_STOPWORDS where spw_stoplist='RECETTE_STOPLIST'") > IngredientsCorpus <- tm_map(IngredientsCorpus, removeWords, tolower(mots_vides$MOT)) > > IngredientsCorpus <- tm_map(IngredientsCorpus, stripWhitespace) > > IngredientsCorpus <- tm_map(IngredientsCorpus, stemDocument, "fr") >
A partir du Corpus ainsi obtenu, on produit une matrice Documents/Termes (on extrait au passage les mots de moins de 3 lettres). On lui applique ensuite la fonction wigthTfIdf pour déterminer les poids Td-Idf de chaque terme:
> IngredientsDTM <- DocumentTermMatrix(IngredientsCorpus, control=list(minWordLength=3)) > > IngredientsDTM_TfIdf <- weightTfIdf(IngredientsDTM) >
Pour chaque document, on normalise les poids par la norme euclidienne/L2 du document:
> normalisation_L2 <- function(x) { + x / apply(x, MARGIN=1, + FUN=function(y) + { + norm(y, type="2") + }) + } > > IngredientsDTM_TfIdf_norm <- normalisation_L2(as.matrix(IngredientsDTM_TfIdf)) >
La matrice peut alors être utilisée par la fonction kmeans. On indique que l’on souhaite obtenir 2 clusters:
> recettes_cluster <- kmeans(IngredientsDTM_TfIdf_norm, 2) > table(recettes_cluster$cluster, data_set$CATEGORIE_PLAT) Salé Sucré 1 2 169 2 354 19 >
On peut voir à l’aide de la table de contingence que le résultat du partitionnement est très similaire à celui obtenu dans le billet précédent.