
Graphique de superposition de réponses à plusieurs axes Y avec échelle
STOP ! Si vous voulez juste le script, il est joint en bas de l'article.
EDIT : Stop ! - Ceci est désormais implémenté dans Graph Builder pour la version 19. 
Il s'agit d'un graphique spécialisé qui met à l'échelle toutes les variables de l'axe des Y et les superpose, puis fournit l'échelle de l'axe des Y dans les unités et la plage de la variable. Ceci est utile pour superposer 3 ou 4 variables de plages différentes. Dans la figure ci-dessus, les débits sont compris entre 0 et 40, tandis que la puissance est comprise entre 200 et 1 000 et entre 100 et 500. Ce type de graphique est souvent utilisé dans les applications scientifiques et d'ingénierie où plusieurs capteurs enregistrent des données simultanément, mais la plage de chaque capteur est très différente. Par exemple, dans un exemple de fermentation, la glycémie est de 1 à 5 g/dl, le pH de 6,8 à 7,8, l'acide lactique de 10 à 50 mmol, et la numération cellulaire ou la turbidité est d'un ordre de grandeur supérieur. Une méthode simple pour représenter graphiquement la relation entre toutes les variables au fil du temps est très utile pour comprendre la cinétique (ou détecter des tendances inhabituelles) du processus.
Ce graphique est mon ennemi depuis un certain temps. Il faut généralement faire des copier-coller créatifs, que ce soit dans JMP ou PowerPoint, pour obtenir un résultat satisfaisant. Alors, pourquoi ce graphique est-il si difficile ? Il faut d'une manière ou d'une autre mettre les données à l'échelle et les combiner en un seul graphique, puis combiner tous les composants non mis à l'échelle de l'axe des Y avec le graphique de données mis à l'échelle. Toutes les méthodes que j'ai vues et essayées impliquent beaucoup trop d'étapes de copier-coller, et les méthodes de dégroupage et de reformatage des éléments graphiques dans PowerPoint peuvent parfois produire des résultats médiocres en raison de la difficulté d'aligner correctement tous les éléments sur mes diapositives. Les processus manuels, fastidieux et répétitifs nécessitent une automatisation JSL.
Lorsque vous réfléchissez à une stratégie pour scénariser une solution, il y a quelques étapes à prendre en compte.
1. Créez une case sur l’axe des Y pour chaque variable/colonne.
2. Mettez à l'échelle les valeurs de chaque colonne dans la même plage
3. Remodelez les données afin qu'elles fonctionnent dans un tracé de superposition.
4. Il serait également intéressant qu'il y ait une boîte de dialogue de lancement pour sélectionner les colonnes pour les rôles y et le rôle x (ou peut-être simplement en utilisant les numéros de ligne)
5. Et enfin, le graphique doit inclure la boîte d’axe avec chaque variable dans l’échelle d’origine.
J'ai aiguisé mon crayon JSL et j'ai essayé d'automatiser la création de cette figure. Je vais essayer d'expliquer ce que j'ai fait ici et j'apprécierais une discussion plus approfondie sur la façon de la rendre plus fonctionnelle. L'état actuel est un script capable de générer un type de figure fixe et qui ne se préoccupe pas de nettoyer le sillage des tables intermédiaires qu'il laisse derrière lui. (Je joindrai le script complet à l'article.)
Première étape : la boîte de dialogue de lancement
cd = Column Dialog(
ylist = ColList( "Y", Min Col( 2 ), Max Col( 8 ), Data Type( "Numeric" ), Modeling Type( {"Continuous"} ) ),
xlist = ColList( "X", Max Col( 1 ), Modeling Type( {"Continuous", "Multiple Response"} ) )
);
Eval( Eval Expr( cd[1] ) ); //eval expr, returns what is in cd[1], and the eval makes it execute
Eval( Eval Expr( cd[2] ) ); //separately run just cd[1], then run evalexpr)cd[1], this is how I get the ylist and xlist variables
À chaque fois que je commence à écrire une boîte de dialogue de lancement, je copie sans vergogne celle de l'index de scripts, supprimant ce qui est inutile ou en ajoutant si nécessaire. L'objectif est de capturer les Y et les X. Il y a quelques règles ici, comme l'utilisation de colonnes numériques continues, et aussi, la présence d'au moins deux Y (la raison sera expliquée plus loin, mais aussi, si vous n'avez qu'un seul Y, pourquoi l'utilisez-vous ?). Il y a aussi une limite de huit réponses Y ; n'hésitez pas à modifier ceci ; plus de huit me semble trop compliqué.
Les deux dernières lignes avec eval(eval expr()) sont la façon dont je génère les variables ylist et xlist à partir de ce qui est renvoyé dans « cd » à partir de la boîte de dialogue de colonne.
Tableau de données 1 : Le tableau de données standardisé
dt1 = New Table( "multiple y-axis table" ); //this is the table where the standardized values go
If( N Items( xlist ) == 1, //note that if there is a column selected for xlist, then it doesn't get scaled
Insert Into( ylist, xlist, 1 ), //call me crazy, but the first value in ylist is the "X", if it exists
dt1 << New Column( "Row", formula( Row() ) )
);
DT1 est le tableau qui contiendra les données après leur standardisation. Si aucun X n'est sélectionné dans la boîte de dialogue, la première colonne est « Ligne » et contient les numéros de ligne à utiliser comme axe des abscisses dans le graphique. Si un X est présent, il est ajouté à la première position de la variable ylist. Oui, c'est exact, la variable ylist contient à la fois les X et les Y.
Remplissage de dt1 et création des « boîtes d'axes »
For(
<FONT color="#FF6600"> If( N Items( xlist ) == 1, //yep, that's an "if" argument inside a "for" loop
i = 2; //if an "x" is specified, then start with the second thing in the list
ycol = As Column( dt, ylist[1] ) << get values;
dt1 << New Column( Munger( ylist[1], 1, ":", "" ), set values( ycol ) );//this is soooo ugly
,i = 1), i <= N Items( ylist ), i++, //end of setup for the For loop</FONT>
<FONT color="#008000"> ycol = As Column( dt, ylist[i] ) << get values;
yycol = ((ycol - Min( ycol )) / (Max( ycol ) - Min( ycol )));
dt1 << New Column( Munger( ylist[i], 1, ":", "" ), set values( yycol ) ); //this is soooo ugly, very sorry</FONT>
<FONT color="#0000FF"> amin = Min( ycol ); //this second part of this script is getting values to make the y-axis boxes
amax = Max( ycol ); //I need the min and max values from each of the unscaled columns, along with their names
ymin = amin - ((amax - amin) * .1); //The y-axis box axis range is 10% higher and lower than the min and max.
ymax = amax + ((amax - amin) * .1); //It looks better and is more closely aligned with the scaled data in gb
obj1 = Graph Box( Framesize( 0, 230 ), yName( Char( ylist[i] ) ), Y Scale( ymin, ymax ) ); </FONT><BR /><FONT color="#0000FF"> //its not a true axis box, just a squished graph box. the axis box was free
Insert Into( objhlb1, Name Expr( obj1 ) ); </FONT><BR /><FONT color="#0000FF"> //there is a lot of inserting of name expr() here. its part of building expressions from the inside out.</FONT>
);
La partie suivante parcourt la liste des colonnes sélectionnées pour les Y. Si un X est spécifié, elle commence par le deuxième élément de la liste.
La première partie utilise une logique pour configurer la boucle et créer un tableau pour les résultats. La variante du tableau dépend de la spécification ou non d'une variable d'axe des abscisses.
La partie centrale du script est très importante, même si elle est courte. Elle récupère les valeurs de chaque colonne de la table d'origine (dt) et place les valeurs mises à l'échelle dans dt1. Je ne suis pas vraiment fier de cette ligne : dt1 << Nouvelle colonne ( Munger ( ylist[i], 1 , ":" , "" ), set values( yycol ) ); Mais voilà, la nouvelle colonne requiert « colname » ou « colname », et : « colname » supprime cette étape ; j'ai donc simplement supprimé les deux points. (N'hésitez pas à partager une meilleure méthode dans les commentaires.)
La dernière partie examine chaque ensemble de valeurs de colonne et les utilise pour mettre à l'échelle une zone graphique. La zone graphique crée de belles zones d'axes, et si sa largeur est définie sur 0, seule la zone de l'axe des Y apparaît. Cette étape est importante pour la mise en forme du graphique. La dernière ligne insère l'expression de la zone graphique dans une zone d'affichage qui est évaluée à la fin. En utilisant le nom expr pour référencer obj1 (la zone graphique), je lance le script pour la créer et toutes les variables du script sont évaluées, mais le script n'est pas évalué. Chaque zone graphique est insérée dans une liste déroulante horizontale (objhlb1). Cette liste est ensuite insérée dans une autre liste.
Construire un tableau pour réaliser un graphique à partir de :
For(
If( N Items( xlist ) == 1,
i = 2,
i = 1
), i <= N Items( ylist ), i++,
Insert Into( dt2stackcols, ylist[i] )
);
Insert Into( dt2stack, Name Expr( dt2stackcols ), 1 );
Eval(
Substitute( Expr( dt2 = dt1 << ping ),
Expr( ping ), Eval Expr( dt2stack )
)
); //This is tricky!
dt2 << set name( "multiple y-axis stack" );
J'ai mentionné plus haut une contrainte : au moins deux variables y doivent être sélectionnées. Or, il s'avère qu'il est impossible d'empiler une seule variable (car elle est déjà empilée) et que le script plante s'il n'y en a qu'une. Ce qui est intéressant ici, c'est la logique qui gère la spécification d'un X. S'il y a un X, je ne voulais déjà pas le mettre à l'échelle, et maintenant je ne veux pas l'inclure dans la pile, car je le veux comme étiquette X. Si aucun X n'est spécifié, je veux utiliser toutes les variables de la liste y.
La dernière partie en rouge est difficile à expliquer. En une seule ligne, le script de gestion de la pile est construit, exécuté et évalué. « Ping » est le motif qui est remplacé ; c'est le premier mot qui m'est venu à l'esprit lorsque j'ai réfléchi à la façon de faire rebondir ces expressions.
Où se trouve l'expression pour dt2stackcols ? Par souci de clarté et pour ma tranquillité d'esprit, j'ai déplacé toutes les expressions définies au début du script. C'était utile, car en modifiant l'ordre du script, les expressions existaient déjà avant d'être nécessaires. Si je les laissais dispersées dans le script, j'essayais de temps en temps de les insérer dans une expression définie plus loin. Consultez le script complet ci-joint pour voir comment cela fonctionne. Ce n'est peut-être pas une bonne pratique, mais je mets tout en œuvre pour mon graphe de némésis.
Assemblage de toutes les pièces
Insert Into( objout, Name Expr( objhlb1 ) );
Insert Into( objvlb, Name Expr( objout ) );
Insert Into( objhlb2, Name Expr( objvlb ) );
Insert Into( objhlb2, Name Expr( obj ) );
Insert Into( objwin, Name Expr( objhlb2 ) );
Eval( Eval Expr( objwin ) ); //Evaluating all the assembled parts after all the expressions are evaluated
gb << size( 600, 285 ); //if you want to format the graph, send messages to gb
L'un des avantages de JSL est qu'il s'agit d'un langage basé sur les fonctions. Cela accomplit deux choses importantes : premièrement, il facilite la réalisation de tâches spécifiques sans avoir à réinventer la roue ; deuxièmement, il facilite la réflexion sur la construction de structures complexes (à condition de les considérer comme un oignon que l'on construit de l'intérieur vers l'extérieur). Par exemple, une façon typique de penser serait la suivante : j'achète d'abord un terrain, puis je construis un bâtiment sur le terrain, puis j'y aménage des pièces, puis j'y installe des éléments comme ma chaise de bureau, puis je m'assois sur la chaise et enfin, je commence à travailler. La pensée fonctionnelle fonctionne ainsi : il y avait d'abord une chaise, et j'y ai installé Byron. Ensuite, j'ai installé la chaise (qui a toujours un Byron) dans le bureau, que j'ai ensuite installé dans le bâtiment, que j'ai ensuite installé sur le terrain, que j'ai ensuite acheté, et enfin, Byron travaille.
La première étape consistait à placer les zones du graphique dans objhlb1, puis une liste verticale (objvlb) dans laquelle sont insérés une zone de contour (objout) et objhlb1. Il s'agit du côté gauche du graphique. Comme c'est le côté gauche, ovjvlb est inséré dans une deuxième liste horizontale (objhlb2), suivi du graphique du générateur de graphiques (obj). Enfin, objhlb2 (qui contient désormais tout) est inséré dans une nouvelle fenêtre afin de pouvoir l'afficher. Pour que tout fonctionne, il est évalué. Si j'avais simplement utilisé Eval, alors… Je ne sais pas ce qui se passe, mais cela ne fonctionne pas. J'ai donc d'abord exécuté Eval Expr pour que le script évalué soit affiché (essayez d'exécuter simplement Eval Expr(objwin) dans le script pour comprendre), puis j'ai utilisé Eval pour exécuter cette partie.
Le tracé du générateur de graphiques obj a été légèrement mis en forme. Le titre, la taille du cadre et l'axe des Y sont désactivés. Tout cela permet de l'adapter à l'échelle et à la forme des cases de l'axe des Y. L'utilisation du générateur de graphiques est pratique : l'axe des X reste modifiable, la légende peut être déplacée et toutes les options GB sont disponibles pour un formatage ultérieur.
Point final actuel
Dans sa 7e révision majeure, le script actuel n'est pas très clair et laisse traîner de nombreuses tables. Ce problème, ainsi que plusieurs autres, devrait être corrigé. Si vous avez des suggestions d'ajouts, n'hésitez pas à nous les faire part. Certains problèmes ne se produiront pas : par exemple, les données du graphique ne seront pas liées dynamiquement à la table de données d'origine, et l'axe des Y ne sera pas lié à la zone de contour du graphique de réponse graduée ; vous devrez dimensionner chaque élément indépendamment. Ce serait formidable si cela fonctionnait. Enfin, je suis presque sûr à 100 % qu'il existe des moyens de faire échouer le script ou de renvoyer des résultats indésirables. Soyez donc attentif aux noms de colonnes spéciaux, aux valeurs aberrantes extrêmes ou aux données manquantes.
J'aimerais des commentaires sur de meilleures façons de faire fonctionner ce script. (En parlant de travail, je l'ai testé dans les versions v13 et v14 sur PC et Mac et il a bien fonctionné pour la publication.)
Note finale de l'auteur :
Ceci est écrit sur le ton le plus informel, comme si nous parlions de cela.
C'est intentionnel, car le jargon formel du codage, à mon avis, entrave la communication. J'évite donc toute obscurcissement.