
Mehrere Y-Achsen mit einem skalierten Antwort-Overlay-Diagramm
STOP! Wenn Sie nur das Skript benötigen, finden Sie es unten im Beitrag.
BEARBEITEN: Stopp! – Dies ist jetzt im Graph Builder für v19 implementiert. : )
Dies ist ein spezielles Diagramm, das alle Variablen der y-Achse skaliert und überlagert und dann die Skala der y-Achse in den Einheiten und dem Bereich der Variablen bereitstellt. Dies ist nützlich, wenn Sie 3 oder 4 Variablen überlagern möchten, die alle in unterschiedlichen Bereichen liegen. In der Abbildung oben liegen die Durchflussraten im Bereich von 0–40, während die Leistung im Bereich von 200–1000 und 100 bis 500 liegt. Diese Art von Diagramm wird häufig in wissenschaftlichen und technischen Anwendungen verwendet, bei denen mehrere Sensoren gleichzeitig Daten aufzeichnen, aber der Bereich der einzelnen Sensoren sehr unterschiedlich ist. Bei einem Fermentationsbeispiel beispielsweise beträgt der Glukosewert 1–5 g/dl, der pH-Wert 6,8–7,8, der Milchsäurewert 10–50 mmol und die Zellzahl oder Trübung ist um ein Vielfaches höher. Eine einfache Möglichkeit, die Beziehung zwischen allen Variablen im Zeitverlauf grafisch darzustellen, ist sehr hilfreich, um die Kinetik des Prozesses zu verstehen (oder ungewöhnliche Muster zu erkennen).
Dieses spezielle Diagramm ist schon seit einiger Zeit mein größter Feind. Normalerweise erfordert es etwas kreatives Kopieren und Einfügen in JMP oder PowerPoint, um etwas halbwegs ansprechendes zu erhalten. Warum ist dieses Diagramm so schwierig? Irgendwie müssen die Daten skaliert und zu einem Diagramm kombiniert werden, und dann müssen alle unskalierten Y-Achsen-Komponenten mit dem skalierten Datendiagramm kombiniert werden. Alle Methoden, die ich gesehen und ausprobiert habe, erfordern viel zu viele Kopier- und Einfügeschritte, und die Methoden, die das Aufheben und Neuformatieren von Diagrammelementen in PowerPoint erfordern, führen manchmal zu schlechten Ergebnissen, da es mühsam ist, alle Elemente auf meinen Folien richtig auszurichten. Unordentliche, manuelle, mühsame und sich wiederholende Prozesse schreien nach JSL-Automatisierung.
Bei der Überlegung einer Strategie zum Skripten einer Lösung sind einige Schritte zu berücksichtigen
1. Erstellen Sie für jede Variable/Spalte ein Y-Achsenfeld.
2. Skalieren Sie die Werte jeder Spalte auf den gleichen Bereich
3. Passen Sie die Daten so an, dass sie in einem Overlay-Diagramm funktionieren.
4. Es wäre auch schön, wenn es einen Startdialog zum Auswählen von Spalten für Y-Rollen und die X-Rolle gäbe (oder vielleicht nur zum Verwenden von Zeilennummern).
5. Und zu guter Letzt muss das Diagramm die Achsenbox mit jeder Variablen in der Originalskala enthalten.
Ich habe meinen JSL-Bleistift gespitzt und versucht, die Erstellung dieser Figur zu automatisieren. Ich werde versuchen, hier zu erklären, was ich getan habe, und würde mich über weitere Diskussionen freuen, wie man es funktionaler gestalten kann. Der aktuelle Stand ist ein Skript, das eine Art feste Figur generieren kann und sich nicht um die Bereinigung der hinterlassenen Zwischentabellen kümmert. (Ich hänge das vollständige Skript an den Beitrag an.)
Erster Schritt: Der Startdialog
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
Jedes Mal, wenn ich anfange, einen Startdialog zu schreiben, kopiere ich schamlos den aus dem Skriptindex, lösche, was ich nicht brauche, oder füge bei Bedarf mehr hinzu. Ziel ist es, die Ys und die Xs zu erfassen. Hierfür gibt es einige Regeln, z. B. müssen die Spalten numerisch fortlaufend sein, und es müssen mindestens zwei Ys vorhanden sein (der Grund dafür wird später klarer, aber auch: Wenn Sie nur ein Y haben, warum verwenden Sie dann dieses?). Es gibt außerdem ein Limit von acht Y-Antworten. Sie können dies gerne bearbeiten, mehr als acht erschienen mir zu unübersichtlich.
Die letzten beiden Zeilen mit eval(eval expr()) geben an, wie ich die Variablen ylist und xlist aus dem erzeuge, was in „cd“ vom Spaltendialog zurückgegeben wird.
Datentabelle 1: Die standardisierte Datentabelle
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 ist die Tabelle, die die Daten nach der Standardisierung enthält. Wenn im Dialog kein X ausgewählt ist, ist die erste Spalte „Zeile“ und enthält Zeilennummern, die als X-Achse im Diagramm verwendet werden. Wenn ein X vorhanden ist, wird es an der ersten Position von ylist eingefügt. Ja, das stimmt, die Variable ylist enthält sowohl X- als auch Y-Zeichen.
Füllen von dt1 und Erstellen der „Achsenboxen“
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>
);
Der nächste Teil durchläuft die Liste der für die Y-Werte ausgewählten Spalten. Wenn ein X angegeben wurde, beginnt er mit dem zweiten Eintrag in der Liste.
Der erste Teil verwendet eine Logik zum Einrichten der Schleife und zum Erstellen einer Tabelle für das Ergebnis. Die Tabellenvariante hängt davon ab, ob eine X-Achsenvariable angegeben wurde oder nicht.
Der Mittelteil des Skripts ist trotz seiner Kürze wirklich wichtig. Er ruft die Werte aus jeder Spalte der Originaltabelle (dt) ab und fügt die skalierten Werte in dt1 ein. Ich bin nicht wirklich stolz auf diese Zeile: dt1 << Neue Spalte ( Munger ( ylist[i], 1 , ":" , "" ), set values( yycol ) ); Aber hier ist der Haken: New column benötigt "colname" oder colname, und :colname beendet diesen Schritt, deshalb habe ich den Doppelpunkt einfach weggelassen. (Schickt mir in den Kommentaren eine bessere Lösung.)
Der letzte Teil betrachtet die einzelnen Spaltenwerte und verwendet sie zum Skalieren eines Diagrammfelds. Das Diagrammfeld erzeugt ansprechende Achsenfelder. Wenn die Breite des Diagrammfelds auf 0 gesetzt ist, wird nur das Y-Achsenfeld angezeigt. Dieser Schritt ist wichtig für die Formatierung des Diagramms. Die letzte Zeile fügt den Ausdruck für das Diagrammfeld in ein Anzeigefeld ein, das am Ende ausgewertet wird. Indem ich mit name expr auf obj1 (das Diagrammfeld) verweise, erstelle ich das Skript, das alle Variablen im Skript auswertet, das Skript selbst jedoch nicht. Jedes Diagrammfeld wird in ein horizontales Listenfeld (objhlb1) eingefügt. Später wird dieses Feld in ein anderes Feld eingefügt.
Erstellen einer Tabelle zum Erstellen eines Diagramms aus:
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" );
Oben erwähnte ich eine Einschränkung: dass mindestens zwei y-Variablen ausgewählt werden mussten. Es stellte sich jedoch heraus, dass man nicht nur eine Variable stapeln kann (da diese bereits gestapelt ist), und das Skript stürzt ab, wenn nur eine Variable vorhanden ist. Interessant ist hier die Logik, die sich mit der Angabe eines X-Werts befasst. Wenn ein X-Wert vorhanden ist, wollte ich ihn bereits nicht skalieren und ihn nun nicht in den Stapel aufnehmen, da ich ihn als X-Beschriftung benötige. Wenn kein X-Wert angegeben ist, möchte ich alle Variablen in der y-Liste verwenden.
Der letzte rote Teil ist schwer zu erklären. In einer Zeile wird das Skript zum Erstellen des Stacks erstellt und ausgeführt/ausgewertet. „Ping“ ist das Muster, das ersetzt wird. Es war das erste Wort, das mir einfiel, als ich überlegte, diese Ausdrücke zu verwenden.
Wo ist der Ausdruck für dt2stackcols? Der Übersichtlichkeit halber und meiner geistigen Gesundheit wegen habe ich während der Arbeit alle definierten Ausdrücke an den Anfang des Skripts verschoben. Das war hilfreich, denn als ich die Reihenfolge des Skripts änderte, waren die Ausdrücke alle schon vorhanden, bevor sie benötigt wurden. Hätte ich sie einfach im Skript verstreut gelassen, hätte ich hin und wieder versucht, sie in einen später im Skript definierten Ausdruck einzufügen. Sehen Sie sich das vollständige Skript im Anhang an, um zu sehen, wie es aufgebaut ist. Es ist vielleicht keine bewährte Methode, aber ich setze alle Hebel für meinen Nemesis-Graph in Bewegung.
Zusammenbau aller Teile
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
Einer der größten Vorteile von JSL ist, dass es eine funktionsbasierte Sprache ist. Das ermöglicht zwei wichtige Dinge: Erstens vereinfacht es die Ausführung bestimmter Aufgaben, ohne das Rad neu erfinden zu müssen, und zweitens erleichtert es den Aufbau komplexer Strukturen (solange man sie als eine Zwiebel betrachtet, die man von innen nach außen aufbaut). Eine typische Denkweise wäre beispielsweise so: Zuerst kaufe ich ein Grundstück, dann baue ich ein Gebäude darauf, dann richte ich Räume im Gebäude ein, dann stelle ich Dinge wie meinen Bürostuhl in den Raum, dann setze ich mich in den Stuhl und beginne schließlich mit der Arbeit. Funktionsdenken funktioniert so: Zuerst gab es einen Stuhl, und ich setzte Byron hinein. Dann stellte ich den Stuhl (ich habe immer noch einen Byron) ins Büro, das ich dann in das Gebäude stellte, das ich auf das Grundstück stellte, das ich dann kaufte, und schließlich funktioniert Byron.
Der erste Schritt bestand darin, die Diagrammfelder in objhlb1 einzufügen. Dann gibt es ein vertikales Listenfeld (objvlb), in das ein Gliederungsfeld (objout) und objhlb1 eingefügt werden. Dies ist die linke Seite des Diagramms. Da es die linke Seite ist, wird ovjvlb zuerst in ein zweites horizontales Listenfeld (objhlb2) eingefügt, gefolgt vom Diagramm des Diagrammerstellers (obj). Schließlich wird objhlb2 (das jetzt alles enthält) in ein neues Fensterfeld eingefügt, sodass es angezeigt werden kann. Und damit das Ganze funktioniert, wird es ausgewertet. Wenn ich nur Eval verwende, dann … Ich bin nicht sicher, was passiert, aber es funktioniert nicht. Daher habe ich zuerst Eval Expr ausgeführt, sodass das gesamte ausgewertete Skript mit allem angezeigt wird (versuchen Sie, nur Eval Expr(objwin) im Skript auszuführen, um zu verstehen, was ich meine) und dann habe ich Eval verwendet, um diesen Teil auszuführen.
Es gibt eine kleine Formatierung von obj, dem Graph Builder-Diagramm. Der Titel wird deaktiviert, die Rahmengröße angepasst und die Y-Achse deaktiviert. Dies alles dient dazu, die Skalierung und Form der Y-Achsen-Boxen anzupassen. Das Graph Builder-Diagramm ist praktisch, da die X-Achse weiterhin bearbeitet, die Legende verschoben und alle GB-Optionen für weitere Formatierungen zur Verfügung stehen.
Aktueller Endpunkt
Das Skript ist in der aktuellen Version (7. Hauptrevision) nicht sehr übersichtlich und hinterlässt viele Tabellen. Dies und einige andere Punkte sollten behoben sein. Wenn Sie also Ideen für Ergänzungen haben, senden Sie sie uns bitte. Manche Dinge funktionieren einfach nicht, z. B. werden die Daten im Diagramm nicht dynamisch mit der ursprünglichen Datentabelle verknüpft, und auch das Y-Achsenfeld wird nicht mit dem Rahmenfeld des skalierten Antwortdiagramms verknüpft. Sie müssen jedes Feld einzeln skalieren. Es wäre großartig, wenn das funktionieren würde. Schließlich bin ich mir fast hundertprozentig sicher, dass es Möglichkeiten gibt, das Skript zum Absturz zu bringen oder ungünstige Ergebnisse zu liefern. Achten Sie daher auf spezielle Spaltennamen, extreme Ausreißer oder viele fehlende Daten.
Ich würde mich über Kommentare zu besseren Möglichkeiten freuen, dieses Skript zum Laufen zu bringen. (Apropos funktionieren: Ich habe es in V13 und V14 auf dem PC und Mac getestet und es lief problemlos.)
Eine Schlussbemerkung des Autors:
Dies ist in einem höchst informellen Ton geschrieben, als würden wir darüber sprechen.
Dies ist beabsichtigt, da formaler Fachjargon beim Programmieren meiner Meinung nach die Kommunikation behindert. Stattdessen verzichte ich auf Verschleierung.