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.