Ma motivation pour le défi de ce mois-ci était de vous faire penser au-delà de la définition traditionnelle des valeurs aberrantes comme étant uniquement des observations tombant loin du centre des données (par rapport à la quantité de variabilité des données). En général, nous pouvons considérer une valeur aberrante comme un point de données non représentatif, quelque chose qui n'appartient pas au reste des données. C'est là que réside le vrai défi : comment définissons-nous le représentant ? L'approche que je propose ci-dessous est mon point de vue sur le problème.
L'algorithme ci-dessous regroupe les observations en ensembles similaires et dissemblables en fonction des distances entre les points. Contrairement à l'approche agglomérative standard du clustering, commencez par un groupe. Définir un point de coupure comme la valeur à mi-chemin entre les observations les plus éloignées, à condition que leur distance dépasse trois fois la différence entre les 75 e quantile et médiane de toutes les distances non nulles (l'écart de distance). S'il n'y a pas de points de coupe, arrêtez. Sinon, remplacez le groupe existant par les deux nouveaux groupes. Répétez la procédure ci-dessus avec chaque nouveau groupe, en utilisant la distance à partir du nouveau groupe ou de l'ensemble de données complet, selon la plus grande des deux. Continuez jusqu'à ce qu'aucun groupe ne contienne de point de coupure.
findOutliers = Function({vals},{Default Local},
q1 = 3; //Spread Multiplier
q2 = 0.75; //Quantile to define upper bound of spread
distSpread = 0;
nMissing = 0;
tmpTbl = New Table("tmp",New Column("C1",Values(vals)),Private);
sumTbl = tmpTbl << Summary(Group(:C1),Private);
vals = Column(sumTbl,1) << Get Values;
freqs = Column(sumTbl,2) << Get Values;
If(N Row(Loc(vals)) == 1, Return([]));
Close(tmpTbl,No Save);
If(Is Missing(vals[1]),
nMissing = freqs[1];
freqs = freqs[2::N Row(vals)];
vals = vals[2::N Row(vals)];
);
vals = Sort Ascending(vals[Loc Nonmissing(vals)]);
groups = Eval List({vals});
cutPoints = [];
While(N Items(groups) > 0,
newGroups = {};
For Each({nextVals},groups,
distances = nextVals[2::N Row(nextVals)] - nextVals[1::(N Row(nextVals)-1)];
distSpread = Max(distSpread,Median(distances[Loc(distances>0)]) + q1*(Quantile(q2,distances[Loc(distances>0)]) - Median(distances[Loc(distances>0)])));
maxDist = Maximum(distances);
If(maxDist>distSpread,
cutID = Loc(distances==maxDist)[1];
cutPoints |/= (nextVals[cutID+1]+nextVals[cutID])/2;
If(cutID == 1,
Insert Into(newGroups,nextVals[2::N Row(nextVals)]),//ELSE
If(cutID == N Row(distances),
Insert Into(newGroups,nextVals[1::cutID]),
//ELSE
Insert Into(newGroups,nextVals[1::cutID]);
Insert Into(newGroups,nextVals[(cutID+1)::N Row(nextVals)]);
)
)
);
);
groups = newGroups;
);
cutPoints = Sort Ascending(cutPoints);
cutPoints;
);
Pour commencer, un peu de ménage est nécessaire pour contourner les données avec une seule valeur. Sur la base des résultats de Défi 9 , j'utilise Résumé pour rechercher simultanément les valeurs uniques dans les données et les trier. Les distances sont trouvées en décalant le vecteur de un et en prenant leur différence. L'algorithme procède en collectant des points de coupure et en rejetant des groupes sans points de coupure. Cela continue jusqu'à ce qu'il ne reste plus de groupes.
L'algorithme est assez rapide, nécessitant environ 1,6 seconde pour traiter toutes les colonnes de données dans le Sonde exemple de données Table de données sur ma machine. En haut de l'algorithme, j'ai ajouté deux variables pour vous permettre de modifier le multiplicateur 3x et le 75 e quantile. Dans le script ci-joint, Défi 11 Dépistage des valeurs aberrantes , j'ai inclus un habillage de fenêtre supplémentaire pour fournir un peu d'interface, permettant la sélection de colonnes et la génération d'une fenêtre de rapport contenant des informations récapitulatives et des histogrammes avec les points de coupe dessinés comme lignes de référence.
Je vais prendre une pause du JMP Challenge pour travailler sur un autre projet de script. Je vais mettre en place des instructions écrites et des vidéos pour de petits projets afin d'aider les utilisateurs débutants et intermédiaires à améliorer leurs compétences JSL. Je me concentrerai sur les opérations courantes telles que l'assemblage de tableaux, l'accès aux données de plusieurs sources, la création de rapports personnalisés, etc. J'essaierai également de travailler sur des choses plus avancées, comme la gestion des expressions et la correspondance de motifs. Si vous pensez que quelque chose pourrait être utile, veuillez me le faire savoir en le publiant ici, ou en m'envoyant un message ou un e-mail à la communauté ( don.mccormack@jmp.com ).