Der Engineering Mailbag
Folge 5: I. TUN. NICHT. MÖGEN. SPINNEN !!! (Und Spinnenparzellen können auch wandern.)
Ich kann Spinnenpläne nicht für immer ignorieren, also los geht's ...
Hin und wieder stoßen wir Systemingenieure auf interessante Fragen, die etwas außerhalb des typischen Bereichs von liegen JMP Verwendungszweck. Diese Anwendungen sind im Allgemeinen clever und zeigen oft, dass die Verwendung von Daten nicht nur für geschäftliche oder technische Probleme gilt. In anderen Fällen sind die Fragen nur unerwartete, herausfordernde Probleme. Diese „Kurvenbälle“ (wie ich sie gerne nenne) gibt es in vielen verschiedenen Formen: Codierungsprobleme, interessante Analysen, Möglichkeiten zur Visualisierung von Daten ... Sie haben die Idee.
Gelegentlich bekomme ich Fragen zu Problemen, die ich wirklich nicht beantworten möchte. Die heutige Abfrage befindet sich in diesem kleinen Stapel, und ich habe diese für vermieden Jahre ! Das Thema dieses Eintrags in The Mail Bag wird von der Datengemeinschaft im Allgemeinen als beängstigende, unangenehme Handlung angesehen, daher denke ich, dass es für einen Halloween-Beitrag geeignet ist.
Da diese Art von Handlung in meiner täglichen Arbeit regelmäßig für Kunden aus der Kosmetik- und Verbraucheranalyse auftaucht, kann ich sie nicht für immer ignorieren. Ich nutze dies zum Anlass, um einer Reihe von Benutzergruppen in New York und New Jersey zu helfen. Aber ich muss sagen ... Ich mag keine Spinnenplots.
Die Frage
Wie gesagt, ich habe diese Frage öfter erhalten, als ich erwähnen möchte. Also, ich werde die eigentliche Frage-E-Mail überspringen, du musst mir nur vertrauen, dass es mehrere gegeben hat.
Es gab auch einige persönliche Anfragen.
Und ein paar Beiträge zur JMP Community.
Ja.
Unten finden Sie ein Beispiel aus einem von Experten begutachteten Artikel, den mir jemand als Referenz für das gegeben hat, was er gerne sehen würde:
(J. Inst. Brauen. 2012; 118: 325–333) Anmerkung des Autors an jeden Datenvisualisierungsspezialisten, der dies liest: Bitte verzeihen Sie mir. Ich weiß, dass dies eine schreckliche Grafik ist.
Die Antwort
Meine allgemeine Antwort auf diese Anfragen entspricht in der Vergangenheit dem, was die meisten Datenvisualisierungsexperten gesagt haben, nämlich Menschen davon abzuhalten, dieses Diagramm zu verwenden, wenn dies überhaupt möglich ist. Vor kurzem habe ich jedoch angefangen, über diese anhaltende Frage etwas anders nachzudenken. Ich habe das Problem seitdem anders angegangen.
Meine derzeitige Philosophie in Bezug auf Spinnenplots ist, dass ich offen dafür bin, Menschen zu helfen, zu sehen, wie man einen erstellt, wenn ich ihnen auch bessere Möglichkeiten zeigen kann, die gleichen Informationen auf dem Weg zu präsentieren. Wie es bei der Fall war Die andere Visualisierungsfrage habe ich beantwortet Für mich war der Prozess der Erstellung dieses Diagramms viel lehrreicher als das tatsächliche Ergebnis.
ICH HASSE. SPINNEN.
OK, ich habe (oder hatte) seit vielen Jahren eine starke Abneigung gegen Spinnen. Ich würde es nicht als einen ausgewachsenen Fall von Arachnophobie bezeichnen, aber viele Jahre lang würde ich alles mit acht Beinen (und gelegentlich sechs; Sie können nie zu sicher sein!) Mit extremen Vorurteilen versenden. Ich gab keine Gnade. Ich habe kein Viertel gegeben. ICH. Dauerte. Gefangene. Wie auch immer, ich bin irgendwann darüber hinweggekommen. Und mit Ausnahme von Zecken (Vampirspinnen!), Ich habe mit meinen achtbeinigen Freunden Frieden geschlossen. Wir halten uns jetzt im Allgemeinen nur aus dem Weg. Ich würde gerne denken, dass ein Teil meiner Abneigung gegen Spinnenplots von meinem Problem mit Spinnen herrührt, aber es könnte auch sein, dass sie ein königlicher Schmerz sind und ich instinktiv wusste, dass es ein bisschen beängstigend sein würde, einen zu machen. Das heißt nicht, dass es keine interessanten Codierungsprobleme gibt, die mit ihrer Erstellung verbunden sind, insbesondere wenn Sie möchten, dass sie die Interaktivität haben, die Benutzer von JMP erwarten (nur dass ich wusste, dass der Code selbst ein bisschen einschüchternd sein würde). Schauen wir uns das Problem an.
Scoping das Problem
Übe, was ich predige Ich begann mit einer Problemstellung:
Ich möchte ein JMP-Add-In erstellen, das ein Spider-Plot und andere Möglichkeiten zur Visualisierung eines Datensatzes in einem Framework anzeigt, das ich später hinzufügen kann.
Der nächste Schritt besteht darin, eine Schlagliste der verschiedenen Dinge zu erstellen, die ich tun muss:
- Holen Sie sich die Abmessungen für die grafische Darstellung vom Benutzer.
- Rufen Sie vom Benutzer eine Beschriftungsspalte für jede Datenuntermenge ab.
- Erstellen Sie für jede Teilmenge ein interaktives Spinnendiagramm für die bereitgestellten Abmessungen.
- Erstellen Sie für jede Teilmenge ein interaktives Radialdiagramm für die bereitgestellten Abmessungen.
- Erstellen Sie für jede Teilmenge ein interaktives paralleles Diagramm für die bereitgestellten Dimensionen.
- Erstellen Sie eine interaktive Tabelle mit paarweisen Korrelationen für die angegebenen Dimensionen.
Das Scoping des Problems ergab einige gute Neuigkeiten, insbesondere, dass ich viele dieser Dinge an JMP auslagern kann, wenn ich Application Builder verwende, was es auch sehr einfach macht, später bei Bedarf neue Visualisierungen hinzuzufügen. Die herausfordernden Teile befanden sich hauptsächlich in den ersten beiden Diagrammen und machten sie interaktiv. Die Grafiken von JMP funktionieren in kartesischen Koordinatensystemen ( x und y Koordinaten). Diese Graphen befinden sich in jeder Hinsicht in einem Polarkoordinatensystem ( r und φ Koordinaten). Infolgedessen benötige ich Code, um zwischen kartesischen und polaren Koordinatensystemen hin und her zu konvertieren. Fangen wir dort an.
Koordinatentransformationsfunktionen
Hier ist die Funktion, die ich zum Arbeiten zwischen den beiden Koordinatensystemen verwendet habe:
polarToRect = Function( {r, t},
Eval List( {r Cosine( t ), r Sine( t )} )
);
Es ist ziemlich einfach. Genau das, was Sie aus einem Geometrie-Lehrbuch finden würden. Es ist ein ziemlich nettes kleines Stück Code, da es Radius (r) und Winkel (t) nimmt und ein Koordinatenpaar (x, y) zurückgibt. Übrigens ist diese Funktion auch in einer Anwendung enthalten, auf die ich später verweise und die in den JMP-Beispielanwendungen enthalten ist.
Da ich in Application Builder arbeite, ist fast alles, was ich sonst noch tun werde, entweder eine Funktion oder ein Ausdruck. Alles an diesem Projekt wiederholt sich auch spektakulär, sodass die iterativen Teile meines Codes leichter zu lesen sind, wenn ich nur Funktionen aufrufe.
Die einfachen Teile
Wenn Sie sich das Finale ansehen App-Quelle Sie können sehen, dass ich einige vorhandene Teile von JMP: Graph Builder für ein paralleles Diagramm und Multivariate für die paarweisen Korrelationen nutze. Sie wurden fast ausschließlich mit JMP-generiertem Code oder Funktionen im Application Builder erstellt. Also werden wir sie zuerst aus dem Weg räumen.
Aufgrund der Art und Weise, wie Graph Builder in diesem Fall Spalten behandelt, und der Tatsache, dass ich in der Lage sein muss, eine beliebige Anzahl von Variablen zu verarbeiten, ist es für mich einfacher, das Graph Builder-Skript als Ausdruck zu erstellen und es dann an das Haupt-Skript anzuhängen Baum an der richtigen Stelle anzeigen. Hier ist der Code dafür:
drawParallel = Function( {pCol},
{default Local},
// build the list of variables with the format "X( :col1), X( :col2)" as a string
// also building a list of plot elements
colStr = "";
gElemStr = "";
For( i = 1, i <= N Items( pCol ), i++,
If( i == 1,
colStr = colStr || "X(" || Char( pCol[i] ) || ")";
gElemStr = gElemStr || "X(" || Char( i ) || ")";
,
colStr = colStr || ", X(" || Char( pCol[i] ) || ", Position( 1))";
gElemStr = gElemStr || ", X(" || Char( i ) || ")";
)
);
// Get the grouping variable as a string
xVarStr = Char( gCol );
// Insert the strings into the main string
Eval(
Parse(
Eval Insert(
"parallelVLB << Append(Graph Builder(
Size( 766, 256 ),
Show Control Panel( 0 ),
Show Legend( 0 ),
Variables( ^colStr^, Color( ^xVarStr^ ) ),
Elements( Parallel( ^gElemStr^, Legend( 3 ) ) ),
SendToReport(
Dispatch(
{},
\!"Graph Builder\!",
OutlineBox,
{Set Title( \!"Parallel Plot\!" ), Image Export Display( Normal )}
),
Dispatch( {}, \!"X title\!", TextEditBox, {Set Text( \!"Variables\!" )} ),
Dispatch( {}, \!"graph title\!", TextEditBox, {Set Text( \!"\!" )} )
)
))"
)
)
);
);
Beachten Sie, dass ich die Variablen nur als Zeichenfolgen formatiere und an den richtigen Stellen im Code einfüge und dann ausführe. Das Endergebnis ist eine vollständig interaktive parallele Darstellung in meiner App mit sehr wenig Codierung von meiner Seite. Ich habe versucht, eine parallele Darstellung manuell zu erstellen. es hat funktioniert, aber gerade noch. Ich empfehle es nicht. Verwenden Sie einfach Graph Builder und Anhängen () . Ich benutze auch die Eval Insert () konstruieren.Es ist etwas einfacher zu lesen als die Zeichenfolge mithilfe von Verkettungen zu erstellen.
Das multivariate Diagramm ist nur eine einfache Parametrisierung der multivariaten Plattform in JMP. Ich musste nicht einmal Code schreiben, um dieses Bit zu bekommen! Dort ist ein Advanced Mastering JMP-Webinar Das zeigt, wie man das mit nur Mausklicks macht. Auch hier mache ich diese Dinge nur aus Bequemlichkeitsgründen und weil ich wirklich nicht viel Zeit mit diesem Teil der App verbringen wollte. Es ist nicht faul - es ist ein effizienter Programmierer. JMP gibt Ihnen den Code, so dass Sie sich genauso gut Zeit sparen können.
Die radiale Darstellung
Das radiale Diagramm ist tatsächlich ein Port (mit einigen Änderungen) einer Beispiel-App, die im Beispielverzeichnis von JMP enthalten ist. Die große Modifikation ist, dass ich benutze Marker Seg () Anstatt von Marker() . Und das ist eigentlich ein wichtiger Punkt. Es ist möglich zu machen Marker() interaktiv mit einigen Mauserfassungsbefehlen und etwas Logik, aber Marker Seg () kümmert sich um all das automatisch . Sie müssen nur angeben, welche Datentabelle Sie zur Überwachung benötigen.
drawFlies = Function( {nP, pCol, obj},
{default Local},
// Create a list of angles for the spider plot
th = ((1 :: nP) - 1);
th = Shape( th, nP, 1 );
th = 2 * Pi() * th / nP;
//Get the data from the table as a matrix.
sel_dat = J( N Row( DataTable1 ), N Items( pCol ), 0 );
For( i = 1, i <= N Items( pCol ), i++,
sel_dat[0, i] = pCol[i] << get as matrix
);
// now get a matrix with two columns and nvars rows to keep track of the min and max of each variable
min_max = J( nP, 2, 0 ); // column1: minimum, column2: maximum. row for each variable chosen
For( i = 1, i <= nP, i++,
min_max[i, 1] = Min( sel_dat[0, i] );
min_max[i, 2] = Max( sel_dat[0, i] );
);
// Create some empty matrices for the converted values
xMat = [];
yMat = [];
// Convert the data matrix
For( i = 1, i <= nR, i++,
current = sel_dat[i, 0];
// for each variable, subtract the minimum and then divide by the range.
adj = J( N Row( current ), 1, 0.0 ) + 1.0 * ((current - min_max[0, 1]`) (min_max[0, 2] - min_max[0, 1])`);
adj = adj`;
total = Sum( adj );
{x, y} = polarToRect( adj, th );
x = Sum( x ) / total;
y = Sum( y ) / total;
xMat = xMat |/ x;
yMat = yMat |/ y;
);
// Draw markers
annotateRad = Expr(
// Matrices and book-keeping variables
xCoord = xxx;
yCoord = yyy;
obj[FrameBox( 1 )] << Append Seg( Marker Seg( xxx, yyy, Row States( DataTable1 ) ) );
);
// Substitute values into the annotation expression
Substitute Into( annotateRad, Expr( xxx ), xMat, Expr( yyy ), yMat );
annotateRad;
);
Wie Sie sehen können, befasst sich ein Großteil des Codes mit den radialen Transformationen usw.Das Interessante für diese Diskussion passiert erst, wenn Sie fast am Ende der Funktion angelangt sind. (Hinweis: Suchen Sie nach dem Markierungen zeichnen Kommentar.) Ich füge ein Marker Seg () zum Graph Elements-Stapel. Wenn Sie im fertigen Bericht mit der rechten Maustaste auf das radiale Diagramm klicken und auswählen Anpassen , Sie können es dort sehen:

Der Grund dass Marker Seg () ist so mächtig ist, dass es das hat Zeilenzustände () Referenz darin. Denken Sie daran, dass alles in JMP über Zeilenzustände über die Datentabelle verknüpft ist. Indem ich JMP mitteile, mit welcher Datentabelle der Bericht kommunizieren muss, kann ich alle Diagramme im Bericht synchronisieren und die Interaktivität nutzen, die ich von JMP gewohnt bin. Und es geht nur darum, von Markern zu Markersegmenten zu wechseln (was mich umgehauen hat, als ich es zum Laufen gebracht habe).
Die Spinnenhandlung
OK, wir haben nichts anderes, worüber ich sprechen kann, außer diesen verdammten Spinnenplot. Also, lasst uns loslegen.
Da es sich bei einem Spinnenplot im Grunde genommen um ein paralleles Plot handelt, das um die Mittelachse gewickelt ist, müssen wir mehr oder weniger ein paralleles Plot erstellen - wobei alle Punkte auf das Polarkoordinatensystem projiziert werden. Und wenn das schmerzhaft klingt, haben Sie Recht. Es ist. Es ist auch wirklich so Ja wirklich Es fällt Ihrem Gehirn schwer, radiale Informationen zu verarbeiten. Dieser eine Wechsel - von kartesisch zu polar - macht das Lesen von Spinnenkarten erheblich schwieriger. Wie auch immer, ich werde den Diskurs über die Vorzüge dieses Diagramms für ein anderes Mal speichern. Auf zum Code!
Die gute Nachricht ist, dass ich, da ich wirklich nur mit einer Dimension pro Variable zu tun habe, die volle Kontrolle über die habe eckig Teil des Koordinatensystems. Es gibt dort also einige Transformationsarbeiten, aber es ist nicht so schlimm. Es wird chaotisch, wenn Sie berücksichtigen müssen, dass der Benutzer möglicherweise einen breiteren Maßstab anzeigen möchte, als die Daten tatsächlich abdecken, z. B. liegen die Werte zwischen 3 und 5, der mögliche Maßstab liegt jedoch zwischen 1 und 7. Ich konnte damit umgehen, indem ich nach einer Achsenspalteneigenschaft suchte und dann die Max- und Min-Werte von dort abrief. Da dies kein kritischer Punkt in der Erzählung ist, können Sie im Quellcode, den ich dem Add-In beigefügt habe, sehen, wie dies geschehen ist.
Die radialen Speichen und Referenzlinien sind nur Linien, die mit der gezeichnet werden Linie() Befehl. Das Schwierigste an dieser ganzen Sache war, die Netze für jede Reihe interaktiv zu gestalten und die Legende zum Laufen zu bringen. Ich habe die Linien interaktiv gemacht, indem ich a verwendet habe Linie Seg () und ein Marker Seg () . Die Legende war eine Umnutzung eines wirklich alten Stücks JMP, das ich kürzlich als Row Legend bezeichnet habe. Schauen wir uns jeden an.
Hier ist der Code zum Erstellen der Linien im Spinnendiagramm. Wie beim Radialdiagramm sind viele Datenmanipulationen erforderlich, um es in ein Format zu bringen, das für die grafische Darstellung sinnvoll ist:
drawTrails = Function( {nP, pCol, obj},
{default Local},
// Create a list of angles for the spider plot
th = ((1 :: nP) - 1);
th = Shape( th, nP, 1 );
th = 2 * Pi() * th / nP;
//Get the data from the table as a matrix.
sel_dat = J( N Row( DataTable1 ), N Items( pCol ), 0 );
For( i = 1, i <= N Items( pCol ), i++,
sel_dat[0, i] = pCol[i] << get as matrix
);
// now get a matrix with two columns and nvars rows to keep track of the min and max of each variable
min_max = J( nP, 2, 0 ); // column1: minimum, column2: maximum. row for each variable chosen
// Check if the user wants to use axis column property values for max and min.
If( axisProp == 0,
// Directly calculate the values from the data table.
For( i = 1, i <= nP, i++,
min_max[i, 1] = Min( sel_dat[0, i] );
min_max[i, 2] = Max( sel_dat[0, i] );
)
,
// If the user wants to use axis column property values
For( i = 1, i <= nP, i++,
// Pull the column name and check if the axis property is defined
col = pCol[i];
axisFlag = Contains( col << Get Properties List, Expr( Axis ) );
//If the axis property is present pull the values from the property
If( axisFlag == 1,
axisPresent = col << Get Property( "Axis" );
min_max[i, 1] = Eval( Extract Expr( axisPresent, Min( Wild() ) ) );
min_max[i, 2] = Eval( Extract Expr( axisPresent, Max( Wild() ) ) );
,
// if the property is not present, calculate the values directly from the data
min_max[i, 1] = Min( sel_dat[0, i] );
min_max[i, 2] = Max( sel_dat[0, i] );
);
)
);
// Create some empty matices for the converted values
xMat = [];
yMat = [];
// Convert the data matrix
For( i = 1, i <= nR, i++,
current = sel_dat[i, 0];
// for each variable, subtract the minimum and then divide by the range.
adj = J( N Row( current ), 1, 0.0 ) + 1.0 * ((current - min_max[0, 1]`) (min_max[0, 2] - min_max[0, 1])`);
adj = adj`;
// an error check form missing values in the vector
For( k = 1, k <= N Rows( adj ), k++,
If( Is Missing( adj[k] ),
adj[k] = 0
)
);
{x, y} = polarToRect( adj, th );
xMat = xMat |/ x;
yMat = yMat |/ y;
);
// Draw Lines and Markers for spider plot
annotateSpider = Expr(
// coordinate matrices
xMat = xxx;
yMat = yyy;
nParam = nnn;
nPoints = N Rows( xMat );
nGroups = nPoints / nParam;
// Reshape the matrices to make them easier to work with
xSMat = Shape( xMat, nGroups );
ySMat = Shape( yMat, nGroups );
// Create an empty string to hold a list (as a string)
pathList = "";
// Draw the lines as polygons using the reshaped matrix
For( i = 1, i <= nGroups, i++,
// Get the coordinates for a given path and add the first value to the end to close the path
xCoord = xSMat[i, 0];
xCoord = xCoord || xCoord[1];
yCoord = ySMat[i, 0];
yCoord = yCoord || yCoord[1];
//
obj[FrameBox( 1 )] << Append Seg( Line Seg( xCoord, yCoord, Row States( DataTable1, {i} ) ) );
);
// Draw the markers
For( i = 1, i <= nP, i++,
// Get the coordinates for a given path
xCoord = xSMat[0, i];
yCoord = ySMat[0, i];
// Run Markers Seg (need to move and reformat the matrix)
obj[FrameBox( 1 )] << Append Seg( Marker Seg( xCoord, yCoord, Row States( DataTable1 ) ) );
);
);
// Substitute values into the annotation expression
Substitute Into( annotateSpider, Expr( xxx ), xMat, Expr( yyy ), yMat, Expr( nnn ), nP );
// Run the annotation expression
annotateSpider;
);
Die wichtigen Bits sind die Linien- und Markierungssegmente gegen Ende. Ich musste jede Verbindungslinie einzeln zeichnen. Da ist ein Pfad Seg () das hätte das wirklich einfach gemacht, aber es erzeugt ein gefülltes Polygon, wenn Sie versuchen, es zu färben. Also, Linien und Marker war es! Durch die Verwendung von Grafiksegmenten ist es wirklich einfach, über den Zeilenzustandsoperator eine Verbindung zur Datentabelle herzustellen. Da nun alle Diagramme die Zeilenzustände aus der Hauptdatentabelle betrachten, können sie alle gleichzeitig eingefärbt werden, indem jedem Zeilenzustand in der Datentabelle Farben zugewiesen werden.
Wenn Sie mit der rechten Maustaste auf einige der Diagramme in JMP klicken, wird die Option Zeilenlegende angezeigt. Es ist schon eine Weile in JMP und macht zwei Dinge. Zunächst werden die Zeilen in der Datentabelle nach Spalten gefärbt (wie dies bei der Option "Menü" Rotes Dreieck "in der Datentabelle der Fall ist). Zweitens wird neben der Visualisierung eine kleine Legende erstellt. Das war alles, was ich tun musste, um die Farben in vier Diagrammen miteinander zu synchronisieren. Hier ist der Code:
drawLegend = Function( {gVar, obj},
{default Local},
// extract the column name with the grouping variable
colName = gVar[1];
// pass the variables into the expression
annotateLegend = Expr(
obj[FrameBox( 1 )] << Row Legend( ggg, Color( 1 ), Continuous Scale( 0 ) )
);
// substitute into the expression
Substitute Into( annotateLegend, Expr( ggg ), colName );
// run the expression
annotateLegend;
);
Da alle meine Diagramme dieselbe Datentabelle für Zeilenzustände betrachten, erben sie alle automatisch die Farbe, die die Zeilenlegende zuweist! Super schick.
Dinge einpacken
Und das ist es. Für Scripter ist die Hauptsache, die Sie daraus machen möchten, die Leistungsfähigkeit der Grafiksegmente. Viele der Bits, die JMP so besonders machen, hängen davon ab, was diese Funktionen leisten können. Für alle anderen gibt es jetzt ein Spider-Plot-Add-In . Ich bin ziemlich stolz darauf, wie es ausgegangen ist. Aber bitte benutze es nicht. Es gibt viel bessere Möglichkeiten, diese Art von Daten zu visualisieren.;)
Anmerkung des Verfassers
Beim Schreiben dieses Blogs wurden keine Spinnen verletzt, obwohl einige vielleicht Anstoß genommen haben, mit Insekten (sechs Beinen) oder Zecken (auch Spinnentiere) zusammengewürfelt zu werden, aber sie sind Vampirspinnen, also würde ich das gerne sogar denken Spinnen hassen sie).