About Me

My photo
Just wasting time sharing knowledge about, Big Data and Analytics

Feb 18, 2014

Sochi 2014, R, D3.js

Ramnath design rCharts wich combine the powerful of open source R and D3.js.
It give very pretty plots. Here is some code, using package XML to collect the data from http://www.sochi2014.com/fr/classement-des-medailles and plot a kind of sankey diagram with rCharts

@Ramnath wrote this :
https://gist.github.com/ramnathv/4b810adae24f168d4330 and the result look like :

Plot with Open source R and rCharts library




















I wrote a piece of code to create a sankey diagram and the result look like we see below

> mm = subset(meltP,select=c(Country, Medal, Count))
> colnames(mm) <- c("target","source","value")
> sankeyPlot <- rCharts$new()
> #We need to tell R where the Sankey library is.
> #I  put it as a subdirectory to my current working directory (.)
> sankeyPlot$setLib('rCharts_d3_sankey-gh-pages')
> #We also need to point to an HTML template page
> sankeyPlot$setTemplate(script = "rCharts_d3_sankey-gh-pages/layouts/chart.html")
> sankeyPlot$set(
+   data = mm,
+   nodeWidth = 15,
+   nodePadding = 10,
+   layout = 32,
+   width = 900,
+   height = 600
+ )
 

Dec 30, 2013

Hadoop for R's Data scientist

I don’t exactly know where to start. But, after a real pleasant discussion with one of my ex colleague, it seems that there are many thongs around Hadoop ecosystem and R for analyst that should be said by a data scientist, means that, someone who don’t know much more about big data architecture, but who should know the essentials about the simple architecture that can allow to run better analysis in the best conditions.

For sure, good knowledge about how R can use hadoop platform to run better analysis is very important.

A small plan :

- Hadoop  ecosystem

- R and Hadoop

- Launch big data job on R using Hadoop



Hadoop ecosystem


We use Hadoop when :

- you need agility

- you need to perform analysis with diversity of data sources

- your  architecture require to move on tim

- you need to reduce your costsI

Hadoop is :

An ecosystem

Designed for storage and analytical computation

Design for running analysis in parallel

When we talk about Hadoop, we deal with

HDFS (Hadoop Distributed File System : The core of solution)

Map Reduce :
Use data from HDFS and execute algorithm based on mar-reduce paradigm

High Level languages: Pig, Hive : Query language with embedded the map reduce paradigm to solve complex problems

HBase : the couch on the top of HDFS storage to build and manipulate data when random access memory is needed.

Pig or Hive : I don’t know. As you feel

You can get your own opinion reading this http://stackoverflow.com/questions/3356259/difference-between-pig-and-hive-why-have-both, but, it’s important to remind that as data scientist, it is always better to get structured data. When we have to do this, we should think about the best languages wich can suit for this quickly. Both languages run for sure map reduce paradigm and every operation is reduce to map and reduce.

Another thing to remind is:

–      Hive: HQL is like SQL

–      Pig: Pig Latin is a bit like Perl

In my next post, I will focus on Hive and Pig. For now, I just want to point that, Hive & Pig are components of Hadoop and Data scientist should know how to deal about

There’s a good post on Internet which explain how to install Hadoop and how to make it connected with R. In my opinion, the best is : Tutorial R and Hadoop except the part wich explain that we need homebrew to set Hadoop environment

Interact with Hadoop








In the terminal, we can build basic operations. For example, pick up data from cluster and load it into memory for analysis with R;




We can then load this data with R command

We can also do this in R terminal without dealing with the Mac's terminal
> system("~/hadoop/bin/hadoop fs -copyToLocal ~/hadoop/data/mag.csv ~/Documents/Recherches/test2.csv")

The main hadoop command can be find out there : http://hadoop.apache.org/docs/r0.18.3/hdfs_shell.html

A small map reduce job

Map reduce is a programming pattern witch aid in the parallel analysis of data.
The name is because of two parts of the algorithm :

map = to identify the subject of the data by key
reduce = group by key identified and run analysis

There is many packages to run map reduce job in R :

* HadoopStreaming
* Hive
* Rhipe
* RHadoop (with rmr2,Hbase, Rhdfs) are  maintain by Revolution Analytics and provide some of good functions to interact with hadoop environment
etc... For sure, I overlook many others good packages.

Let us introduce how to run a simple map reduce job using RHadoop
suppose that we have this data :
> x=sample(rep(c("F","V","R"),10000),size=1000000,replace=T)
> df=data.frame(value=x, note=abs(9*rnorm(1000000)))
> head(df)
  value      note
1     F  4.209874
2     V  9.587087
3     F  6.323354
4     V  9.274668
5     R 13.886767
6     V  5.273159
> dim(df)
[1] 1000000       2

And we want to determine witch value has "note" greater than the mean of all value
If we have to do this using R, we should run this :
> meanAll = mean(df$note)
> meanAll
[1] 7.18068
> meanGroup<-aggregate(x=df$note,by=list(df$value),FUN=mean)
> meanGroup
  Group.1        x
1       F 7.170956
2       R 7.189213
3       V 7.181848
> index =meanGroup$x>=meanAll
> index
[1] FALSE  TRUE  TRUE
> meanGroup$Group.1[index]
[1] R V

If we want to do this with map reduce, we will do something like this
demo = to.dfs(demo)
monMap = function(k,v)
{
  w <- v[2]
  keyval(w,v[1])
}
monReduce<-function(k,val)
{
  keyval(k, mean(val))
}
job<-mapreduce(input=demo, map =monMap,reduce = monReduce)
from.dfs(job)

Some helpful litterature around Map Reduce
http://www.asterdata.com/wp_mapreduce_and_the_data_scientist/
https://class.coursera.org/datasci-001/lecture/71
http://www.information-management.com/ad_includes/welcome_imt.html

In my next post, I will talk about Pig and Hive for preparing dataset beforme machine learning,





Nov 2, 2013

Les k-plus prcoches voisins :Vite, il faut se la réapproprier...

K-plus proches voisins

K-plus proches voisins

Fondamentaux

La notion de voisinage d'un point est assez intuitive. Une définition simple serait : une zone de l'espace qui comprend ce point. C'est une notion centrale en mathématique, particulièrement en analyse lorsqu'on souhaite unifier la notion de finitide (calcul de limites) d'un espace métrique.

Dans les espaces métriques, une autre notion centrale est la notion de distance qui est de fait très imbriquée à la notion de voisinage.

Définissons donc le voisinage d'un point comme étant l'ensemble des autres points qui sont à une certaine distance de lui.

La notion de distance même dépasse largement le cadre de cet exposé. L'avantage de cette définition provisoire nous permet au moins une incursion dans la statistique qui se contente en général des définitions sommaires, non rigoureuses et qui par l'extension des propriétés permet un raisonnement asymptotique : c'est à dire, s'appuyant sur la loi des grands nombres.

Garder à l'esprit les fondements mathématiques de la notion de distance et de la notion de voisinage permet d'être très critique sur les résultats que les algorithmes que nous verrons plus loin donnent.

Définition

les K-nn (K-nearest neighbor) : Méthode d'apprentissage supervisée qui raisonnent avec le principe sous-jacent : “dis moi qui sont tes amis, je te dirais qui tu es”. Elle diffère des méthodes d'apprentissages traditionnelles car aucun modèle n'est induit à partir d'exemple. A chaque fois que l'on veut classer un nouvel individu, on refait tourner l'algorithme et on cherche de nouveaux amis. Exemple : Si l'on veut prédire la probabilité de survenue d'un cancer chez un nouveau patient on procède en deux étapes : 1. On recherche selon les caractéristiques de ce patient les patients qui lui ressemble 2. Si parmi ces “voisins”, il y a eu plus de cancer, alors le patient a une forte probabilité d'avoir le cancer.

Pourquoi j'aime cette méthode

Les limites d'utilisation de cette méthode étaient liées aux architectures et à la capacité des ordinateurs. Le déploiement de cette méthodologie dans un environnement de production se faisait généralement en créant des grosses classes induites par l'apprentissage et ensuite des nouveaux individus étaient affectés en fonction de la distance à ces classes, ce qui évitait de refaire tourner l'algorithme à chaque fois et de reconsidérer les classes.

Aujourd'hui, il n'est plus nécessaire de le faire. Un modèle peut être reconsidéré à chaque mis à jour et on peut recalculer un voisinage en quelques secondes. Cette méthode reviendra donc bientôt, j'en suis persuadé, au goût du jour lorsqu'un problème de classification sera évoqué.

A quoi cela sert?

Les domaines de prédilection sont variés

Un qui a le vent en poupe est la classification des documents indexés dans un moteur de recherche

Plus ancien, dans le geomarketing, lorsqu'on a des points de vente en réseau dispersés sur un territoire donnés, il peut être très efficace lorsqu'on souhaite en ouvrir un d'imaginer à quel autre point de vente il pourrait ressembler pour anticiper son chiffre d'affaire

Si l'on veut construire un moteur de recommandation sur un site internet, les achats des autres visiteurs du site permettent de définir les suggestions à d'autres potentiels acheteurs sous forme de recommandation d'achats

Si l'on veut déterminer quel client d'une enseigne est appétent à une carte de fidélité/un crédit etc, on peut simplement regarder qui dans la base de données à disposition lui ressemble au mieux.

Toutes ces choses dites, on se rend bien compte qu'il s'agit en réalité du bon sens dans le commerce. Une amie n'ayant aucune connaissance statistique, travaillant dans un magasin de vêtements me faisait remarquer que lorsqu'une client entre, elle détermine quel type d'articles serait susceptibles de l'intéresser en regardant simplement la façon dont elle est habillée, le sac à main à son bras, en estimant son âge.

il me semble que c'est ce que cette méthode fait aussi.

Les principales difficultés / Exemples Introductifs

Par définition même, la mise en oeuvre de cette méthodologie se heurte à deux problèmes fondamentaux

  • Le choix du voisinage (Combien de voisins retenir?)
  • Quelle distance considérée?

Les résultats obtenus par la méthode dépendent de ces deux critères. On peut arriver à des résultats totalement différent selon les méthodes. (Insérer schéma)

Exemple:

Nous sommes en pleine période d'espionnage généralisé alors considérons une site internet qui souhaite alerter la direction des impôts sur les potentiels “fraudeurs à l'impôt”. On dispose des informations suivantes que le site a réussi à collecter :

  • l'achat d'un livre : Placer son argent dans paradis fiscaux
  • L'âge
  • le revenu
  • le nombre de cartes de crédits
  • Le statut marital

On suppose aussi que nous avons une direction des impôts qui emploie de bons data scientists et qui a donc réussi à partir des adresses Ip des clients du site internet à retrouver lesdits contribuables simplement parce qu'ils ont payés leur impôt à partir de leur ordinateur.

Sur ces clients là, les impôts ont la possibilité de savoir si les individus ont fraudé à l'impôt ou non et se pose la question de construire un modèle d'apprentissage pour détecter les potentiels fraudeurs à l'impôt.

Outre le côté “big brother”, cette approche n'est pas si éloignée de ce que pourrait être la réalité.

getwd()
## [1] "/Users/guibert/Desktop/knn"
setwd("Users/Guibert/Desktop/knn")
## Error: impossible de changer de répertoire de travail
rm(list = ls())
impots <- read.csv(file = "impots.csv", header = T, sep = ";")[, -1]
row.names(impots) = paste0("Contr", seq(1:dim(impots)[1]))
head(impots)
##        revenu nbCartes age statutM aFraude
## Contr1    150        3  30       1       1
## Contr2     90        2  40       1       0
## Contr3    100        1  55       0       1
## Contr4     80        2  35       1       0
## Contr5     15        1  20       0       0
## Contr6    250        3  28       0       1

Considérons monsieur Perrier agé de 25 ans, ayant un revenu de 35keur, 1 carte de crédit, Célibataire et l'on se demande s'il faudrerait l'impot :

  • On va calculer la distance entre monsieur Perrier et les autres menmbres de la liste
  • On va décider que ces voisins sont les gens qui sont le plus proches de lui
  • On va regarder parmi ces voisins combien ont fraudé à l'impôt
  • On décidera si monsieur Perrier est un potentiel fraudeur

Quel distance choisir? Il n y a pas de réponse définitive. La distance euclidienne est en général la plus utilisée? mais signalons qu'il en existe d'autres (la distance L1, la distance de Manhatan, de Minlowski, maximum, binaire, etc) On peut d'ailleurs soit même construire sa distance, tant qu'elle respecte les propriétés de base d'une distance :

  • d(A,A)=0
  • d(A,B)=d(B,A)
  • d(A,B)<=d(A,C)+d(C,B)

J'aime bien la distance d = |x-y| Elle est simple et traduit bien l'idée que l'on se fait de la distance

Mesurons la distance de Mr Perrier aux autres contribuables

attach(impots)
infoPerrier <- data.frame(revenu = 25, nbCartes = 1, age = 25, sttutM = 0)
maDist <- function(x, y) {
    d = sqrt(sum((x - y)^2))
    return(d)
}
df.dist = vector()
for (k in 1:nrow(impots)) {
    df.dist[k] <- maDist(impots[k, -5], infoPerrier)
}
(impotsDist = cbind(impots, df.dist))
##         revenu nbCartes age statutM aFraude df.dist
## Contr1     150        3  30       1       1  125.12
## Contr2      90        2  40       1       0   66.72
## Contr3     100        1  55       0       1   80.78
## Contr4      80        2  35       1       0   55.92
## Contr5      15        1  20       0       0   11.18
## Contr6     250        3  28       0       1  225.03
## Contr7     350        2  65       0       1  327.45
## Contr8     185        1  65       1       0  164.93
## Contr9     100        2  60       1       1   82.78
## Contr10     80        3  35       0       0   55.94
## Contr11     85        4  26       1       1   60.09
## Contr12     35        1  25       1       0   10.05
## Contr13     45        1  23       0       0   20.10
## Contr14     70        1  70       1       1   63.65
## Contr15     65        3  29       1       1   40.26

Si l'on trie le tableau par distance croissante, on s'aperçoit que les plus proches de Mr Perrier sont :

library(plyr)
arrange(impotsDist, df.dist)
##    revenu nbCartes age statutM aFraude df.dist
## 1      35        1  25       1       0   10.05
## 2      15        1  20       0       0   11.18
## 3      45        1  23       0       0   20.10
## 4      65        3  29       1       1   40.26
## 5      80        2  35       1       0   55.92
## 6      80        3  35       0       0   55.94
## 7      85        4  26       1       1   60.09
## 8      70        1  70       1       1   63.65
## 9      90        2  40       1       0   66.72
## 10    100        1  55       0       1   80.78
## 11    100        2  60       1       1   82.78
## 12    150        3  30       1       1  125.12
## 13    185        1  65       1       0  164.93
## 14    250        3  28       0       1  225.03
## 15    350        2  65       0       1  327.45

Combien de voisins choisir? On verra par la suite que cette question est moins simple. Mais ici, que l'on choisisse 1,2,3,4,5,6 voisins la majorité des voisins de Mr Perrier ne sont pas des fraudeurs. On peut donc sortir Mr Perrier de la liste des potentiels fraudeurs. Cela se traduit en général par un calcul naif de probabilité. Entendre naïf au sens sans à priori. Si on choisit 5 voisins, combien de voisins parmi les 5 ont froudé : 1/5. Soit 4/5 n'ont pas fraudé. On peut aussi faire des prédictions plus complexes en associant au calcul des probabilité une notion de poids. Par exemple en utilisant la fonction inverse. Si Poids = 1/distance, ce qui se traduirait par… plus on est proche dans le voisinage plus on compte dans la prédiction. Pour aller plus loin, s'il s'agissait non pas de prédire l'appartenance ou non à une classe, mais plutôt d'évaluer le degré d'appétence à la fraude, on pourrait imaginer toute fonction de poids qui donnerait de l'importance à des observations proches. Cette fonction de pondération peut prendre plusieurs formes. Voici deux que j'aime particulièrement:

d = seq(0, 2, 0.1)
par(mfrow = c(2, 1))
plot(d, 1/d, type = "b", main = "Fonction inverse")
plot(d, exp(-d^2), type = "b", main = "Sigmoîde")

plot of chunk unnamed-chunk-4

par(mfrow = c(1, 1))

Une remarque importante à faire ici est la suivante : Si l'on choisit un très grand nombre de voisins, proche du nombre d'observations, il ne sert plus à rien de calculer les distances pour prédire. En réalité, faire ce choix revient à prédire la classe dont les effectifs sont les plus important dans l'ensemble d'apprentissage. Le choix raisonnable de k est donc un art à part entière. La technique la plus répandue(celle qui donne de meilleurs résultats pratiques) est sans doute la validation croisée.

Et le sur-apprentissage? Il est presqu'inexistant avec cette technique. C'est un autre de ces avantages non négligeable. Le choix de grandes valeurs de k a plutôt pour fonction de lisser le bruit qui peut exister dans l'échantillon d'étude.

Le big data : Les ensembles étant de plus en plus volumineux, le temps de calcul des distances peut être un vrai frein à l'utilisation de cette méthode. Il est recommandé de souvent faire précéder cette technique par une technique de réduction de dimension. Je ne suis pas partisan de ces techniques lorsque les machines ont les capacités de réaliser ces opérations. pour une raison évidente : l'information contenu dans les marges est souvent très utile dans un grand ensemble de données.

Mise en oeuvre avec R

Si on reprend l'exemple de Monsieur Perrier, avec R, la mise en oeuvre d'un classifieur basé sur les plus voisins est simple. On peut directement appliquer le modèle à l'aide de la fonction knn du package FNN.

De la manière la plus simple qui soit en utilisant la validation croisée,

library(FNN)
train = impots[, 1:4]
fraude = as.factor(impots$aFraude)
labels = row.names(impots)
model.knn <- knn(train = train, test = train, k = 3, cl = fraude, prob = TRUE)
model.knn
##  [1] 1 0 1 0 0 1 1 1 1 0 0 0 0 1 0
## attr(,"prob")
##  [1] 0.6667 1.0000 0.6667 0.6667 1.0000 0.6667 0.6667 0.6667 0.6667 0.6667
## [11] 0.6667 1.0000 0.6667 1.0000 0.6667
## attr(,"nn.index")
##       [,1] [,2] [,3]
##  [1,]    1    8    3
##  [2,]    2    4   10
##  [3,]    3    9    2
##  [4,]    4   10   11
##  [5,]    5   12   13
##  [6,]    6    8    1
##  [7,]    7    6    8
##  [8,]    8    1    6
##  [9,]    9    3    2
## [10,]   10    4   11
## [11,]   11   10    4
## [12,]   12   13    5
## [13,]   13   12   15
## [14,]   14    9    3
## [15,]   15    4   10
## attr(,"nn.dist")
##       [,1]    [,2]   [,3]
##  [1,]    0  49.538  55.95
##  [2,]    0  11.180  11.27
##  [3,]    0   5.196  18.08
##  [4,]    0   1.414  10.49
##  [5,]    0  20.640  30.15
##  [6,]    0  74.826 100.02
##  [7,]    0 106.630 165.01
##  [8,]    0  49.538  74.83
##  [9,]    0   5.196  22.36
## [10,]    0   1.414  10.39
## [11,]    0  10.392  10.49
## [12,]    0  10.247  20.64
## [13,]    0  10.247  21.00
## [14,]    0  31.639  33.56
## [15,]    0  16.186  16.19
## Levels: 0 1

Le modèle affecte la valeur 0 ou 1 en fonction du fait que l'individu est potentiellement fraudé ou non et fournit aussi des informations comme les voisins utilisés pour le classement et les distances associées à chaque voisin.

voisins <- attr(model.knn, "nn.index")
labels.voisins = as.data.frame(apply(voisins, 2, function(col) labels[col]))
# Les probabilités
prob <- attr(model.knn, "prob")
prob <- ifelse(model.knn == "1", prob, 1 - prob)
knn.impots <- cbind(impots, labels.voisins, prob)
knn.impots
##         revenu nbCartes age statutM aFraude      V1      V2      V3   prob
## Contr1     150        3  30       1       1  Contr1  Contr8  Contr3 0.6667
## Contr2      90        2  40       1       0  Contr2  Contr4 Contr10 0.0000
## Contr3     100        1  55       0       1  Contr3  Contr9  Contr2 0.6667
## Contr4      80        2  35       1       0  Contr4 Contr10 Contr11 0.3333
## Contr5      15        1  20       0       0  Contr5 Contr12 Contr13 0.0000
## Contr6     250        3  28       0       1  Contr6  Contr8  Contr1 0.6667
## Contr7     350        2  65       0       1  Contr7  Contr6  Contr8 0.6667
## Contr8     185        1  65       1       0  Contr8  Contr1  Contr6 0.6667
## Contr9     100        2  60       1       1  Contr9  Contr3  Contr2 0.6667
## Contr10     80        3  35       0       0 Contr10  Contr4 Contr11 0.3333
## Contr11     85        4  26       1       1 Contr11 Contr10  Contr4 0.3333
## Contr12     35        1  25       1       0 Contr12 Contr13  Contr5 0.0000
## Contr13     45        1  23       0       0 Contr13 Contr12 Contr15 0.3333
## Contr14     70        1  70       1       1 Contr14  Contr9  Contr3 1.0000
## Contr15     65        3  29       1       1 Contr15  Contr4 Contr10 0.3333

La prédiction devient alors aisée. en regardant les probabilités.

Dans la modélisation ci dessus on a considéré que l'échantillon d'apprentissage et de test était identique. Dans le cas d'un nouvel échantillon, on remplacera l'échantillon de test par l'entrée correspondante. Exemple pour monsieur Perrier tout à l'heure, il suffit de :

train = impots[, 1:4]
test = infoPerrier
fraude = as.factor(impots$aFraude)
labels = row.names(impots)
model.knn <- knn(train = train, test = infoPerrier, k = 3, cl = fraude, prob = TRUE)
probP <- attr(model.knn, "prob")
(probP <- ifelse(model.knn == "1", prob, 1 - prob))
## [1] 0.3333
model.knn
## [1] 0
## attr(,"prob")
## [1] 1
## attr(,"nn.index")
##      [,1] [,2] [,3]
## [1,]   12    5   13
## attr(,"nn.dist")
##       [,1]  [,2] [,3]
## [1,] 10.05 11.18 20.1
## Levels: 0

On retrouve les mêmes résultats comme obtenus manuellement tout à l'heure.

L'exercice que l'on vient de faire, qui consiste à affecter la bonne classe est généralisable. Si l'on souhaite par exemple estimer l'endettement (variable aléatoire réelle positive) et non plus la fraude(binaire 0/1), alors on peut employer les mêmes méthodes : 1 - Rechercher les voisins les plus proches 2 - Estimer que l'endettement pour un nouveau client est la moyenne(pondérée) des endettements de ces voisins.

Ceci est une thématique très utilisée en geomarketing, dès qu'il y a une notion de réseau. Les points de ventes statistiquement proches ont potentiellement des chiffres d'affaire pas très éloignés. reprenons notre exemple et considérons la variable dette :

endet <- impots[, c(-2, -4:-5)]
dette = c(100, 200, 100, 70, 50, 10, 30, 40, 65, 25, 150, 50, 43, 35, 70)
endet$dette <- dette
train = endet[1:10, ]
test = endet[11:15, ]
# Récupérer les voisins statistiques pour chacun des individus dont on veut
# evaluer l'endettement
voisins = FNN::get.knnx(data = train[, -3], query = test[, -3], k = 3)
v = voisins$nn.index
labels = row.names(train)
(labels.voisins = as.data.frame(apply(v, 2, function(col) labels[col])))
##        V1      V2      V3
## 1 Contr10  Contr4  Contr2
## 2  Contr5  Contr4 Contr10
## 3  Contr5  Contr4 Contr10
## 4  Contr9  Contr3  Contr2
## 5  Contr4 Contr10  Contr2
test.knn = cbind(test, labels.voisins)
test.knn
##         revenu age dette      V1      V2      V3
## Contr11     85  26   150 Contr10  Contr4  Contr2
## Contr12     35  25    50  Contr5  Contr4 Contr10
## Contr13     45  23    43  Contr5  Contr4 Contr10
## Contr14     70  70    35  Contr9  Contr3  Contr2
## Contr15     65  29    70  Contr4 Contr10  Contr2

Si l'on considère le Contr11, l'estimation de sa dette se fera par une moyenne de la dette de Contr2, Contr10 et Contr4.

Soit donc :

mean(c(200, 25, 70))
## [1] 98.33

Le package FNN fournit une fonction qui fonctionne sur ce principe :

knn.reg(train = train[, -3], test = test[, -3], y = train$dette, k = 3, algorithm = c("kd_tree"))
## Prediction:
## [1]  98.33  48.33  48.33 121.67  98.33

Je précise -chose importante - qu'il ne s'agit que d'une moyenne et une meilleure approche consiste à pondérer cette moyenne par une fonction de la distance.

Je n'ai pas encore trouvé de moyen agréable de visualiser les knn. Appel à contribution :)

Il existe tout de même un moyen (pas très joli).

d = dist(impots[, -5])
fit <- cmdscale(d, eig = TRUE, k = 2)
x <- fit$points[, 1]
y <- fit$points[, 2]
plot(x, y, xlab = "Coordinate 1", ylab = "Coordinate 2", main = "En utilisant le MDS", 
    type = "n", )
text(x, y, labels = row.names(impots), cex = 0.7, col = "blue2")

plot of chunk unnamed-chunk-11

On viot bien que les proximités que l'on retrouve çà et là sont celles que l'on apercevait dans le modèle précédemment.