
Grafico a sovrapposizione di risposta su più assi Y con scala
STOP! Se vuoi solo lo script, lo trovi allegato in fondo al post.
MODIFICA: Stop! - Questa funzionalità è ora implementata in Graph Builder per la versione 19. : )
Questo è un grafico specializzato che scala tutte le variabili dell'asse y e le sovrappone, fornendo poi la scala dell'asse y nelle unità e nell'intervallo della variabile. Questo è utile quando si desidera sovrapporre 3 o 4 variabili che rientrano in intervalli diversi. Nella figura sopra, le portate sono comprese tra 0 e 40, mentre la potenza è compresa tra 200 e 1000 e tra 100 e 500. Questo tipo di grafico viene spesso utilizzato in applicazioni scientifiche e ingegneristiche in cui più sensori registrano dati contemporaneamente, ma l'intervallo di ciascun sensore è piuttosto diverso. Ad esempio, in un esempio di fermentazione, il glucosio è compreso tra 1 e 5 g/dl, il pH tra 6,8 e 7,8, l'acido lattico tra 10 e 50 mmol e la conta cellulare o torbidità è di un ordine di grandezza maggiore. Un modo semplice per rappresentare graficamente la relazione tra tutte le variabili nel tempo è molto utile per comprendere la cinetica (o per rilevare andamenti insoliti) del processo.
Questo grafico specifico è stato la mia nemesi per un bel po' di tempo. Di solito ci vuole un po' di creatività nel copiare e incollare, sia in JMP che in PowerPoint, per ottenere qualcosa di sufficientemente bello. Perché questo grafico è così difficile? In qualche modo bisogna ridimensionare e combinare i dati in un unico grafico, e poi combinare tutti i componenti non ridimensionati dell'asse y con il grafico dei dati ridimensionati. Tutti i metodi che ho visto e provato comportano troppi passaggi di copia e incolla e i metodi che prevedono la separazione e la riformattazione degli elementi del grafico in PowerPoint a volte possono generare risultati scadenti a causa della noia di dover allineare correttamente tutti gli elementi nelle diapositive. Processi disordinati, manuali, tediosi e ripetitivi richiedono a gran voce l'automazione JSL.
Nel pensare a una strategia per scrivere una soluzione ci sono un paio di passaggi da considerare
1. Crea un riquadro sull'asse y per ogni variabile/colonna.
2. Ridimensionare i valori di ogni colonna allo stesso intervallo
3. Rimodellare i dati in modo che funzionino in un grafico sovrapposto.
4. Sarebbe anche bello se ci fosse una finestra di dialogo di avvio per selezionare le colonne per i ruoli y e il ruolo x (o magari usando solo i numeri di riga)
5. E ultimo ma non meno importante, il grafico deve includere la casella dell'asse con ciascuna variabile nella scala originale.
Ho temperato la mia matita JSL e ho provato ad automatizzare la creazione di questa figura. Cercherò di spiegare cosa ho fatto qui e apprezzerei ulteriori discussioni su come renderlo più funzionale. Lo stato attuale è uno script in grado di generare un solo tipo di figura fissa e non si preoccupa di ripulire la scia di tabelle intermedie che lascia dietro di sé. (Allego lo script completo al post)
Primo passaggio: la finestra di dialogo di avvio
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
Ogni volta che inizio a scrivere una finestra di dialogo di avvio, copio spudoratamente quella dall'indice di scripting, eliminando ciò che non mi serve o aggiungendone altro se necessario. L'obiettivo è catturare le Y e le X. Ci sono alcune regole qui, come le colonne che devono essere numeriche continue e, inoltre, che ci devono essere almeno 2 Y (il motivo sarà più chiaro in seguito, ma anche, se hai una sola Y, perché la usi?). C'è anche un limite di 8 risposte Y, sentiti libero di modificarlo, più di 8 mi sembravano troppo disordinate.
Le ultime due righe con eval(eval expr()) sono il modo in cui genero le variabili ylist e xlist da ciò che viene restituito in "cd" dalla finestra di dialogo della colonna.
Tabella dati 1: La tabella dati standardizzata
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 è la tabella che conterrà i dati dopo la standardizzazione. Se non si seleziona alcuna X nella finestra di dialogo, la prima colonna è "Riga" e contiene i numeri di riga da utilizzare come asse x nel grafico. Se è presente una X, questa viene aggiunta alla prima posizione di ylist. Sì, esatto, la variabile ylist contiene sia le X che le Y.
Riempimento di dt1 e creazione delle "caselle degli assi"
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 parte successiva scorre l'elenco delle colonne selezionate per le Y. Se è stata specificata una X, inizia con il secondo elemento nell'elenco
La prima parte utilizza un po' di logica per impostare il ciclo e creare una tabella per il risultato, quindi andiamo avanti. La variante della tabella dipende dal fatto che sia stata specificata o meno una variabile sull'asse x.
La parte centrale dello script è davvero importante, anche se breve. Ottiene i valori da ogni colonna della tabella originale (dt) e inserisce i valori scalati in dt1. Non sono molto orgoglioso di questa riga: dt1 << Nuova colonna ( Munger ( ylist[i], 1 , ":" , "" ), set values( yycol ) ); Ma ecco il punto: New column vuole "colname" o colname, e :colname annulla questo passaggio, quindi ho semplicemente eliminato i due punti. (Inviate un modo migliore per farlo nei commenti.)
L'ultima parte esamina ciascuno degli insiemi di valori di colonna e li usa per ridimensionare un riquadro grafico. Il riquadro grafico crea dei riquadri con assi ben definiti e, se la larghezza del riquadro grafico è impostata a 0, viene visualizzato solo il riquadro dell'asse y. Questo passaggio è importante per la formattazione del grafico. L'ultima riga inserisce l'espressione per il riquadro grafico in un riquadro di visualizzazione che viene valutato alla fine. Usando il nome expr per riferirsi a obj1 (il riquadro grafico), faccio in modo che lo script lo crei e tutte le variabili nello script vengano valutate, ma lo script non viene valutato. Ogni riquadro grafico viene inserito in una casella di riepilogo orizzontale (objhlb1). Successivamente, questo riquadro viene inserito in un altro riquadro.
Costruire una tabella per creare un grafico da:
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" );
Sopra ho menzionato un vincolo: che dovessero essere selezionate almeno due variabili y. Ebbene, a quanto pare non è possibile impilare una sola variabile (perché è già impilata) e lo script si blocca se ce n'è una sola. La cosa interessante qui è la logica che gestisce la presenza o meno di una X specificata. Se c'è una X, non volevo scalarla, e ora non voglio includerla nello stack perché la voglio come etichetta X. Se non c'è una X specificata, allora voglio usare tutte le variabili in ylist.
L'ultima parte in rosso è difficile da spiegare. In una sola riga, lo script per creare lo stack viene compilato ed eseguito/valutato. "Ping" è il pattern che viene sostituito, è stata la prima parola che mi è venuta in mente quando ho pensato di rimbalzare queste espressioni.
Dov'è l'espressione per dt2stackcols? Per motivi di chiarezza e per la mia sanità mentale, mentre lavoravo a questo progetto, ho spostato tutte le espressioni definite all'inizio dello script. Questo è stato utile, perché man mano che cambiavo l'ordine dello script, tutte le espressioni esistevano già prima di essere necessarie. Se le avessi lasciate sparse nello script, ogni tanto avrei provato a inserirle in un'espressione definita più avanti nello script. Date un'occhiata allo script completo, allegato, per vedere come è strutturato. Potrebbe non essere una buona pratica, ma sto dando il massimo per il mio grafico nemisis.
Assemblaggio di tutte le parti
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
Uno dei vantaggi principali di JSL è che è un linguaggio basato su funzioni. Questo permette di raggiungere due obiettivi importanti: innanzitutto, semplifica la realizzazione di attività specifiche senza dover reinventare la ruota, e in secondo luogo, semplifica la progettazione di strutture complesse (a patto che si considerino come una cipolla che si costruisce dall'interno verso l'esterno). Ad esempio, un tipico schema di pensiero sarebbe questo: prima compro un terreno, poi costruisco un edificio sul terreno, poi ci metto delle stanze, poi ci metto dentro oggetti come la mia sedia da ufficio, poi mi siedo sulla sedia e infine inizio a lavorare. Il pensiero funzionale funziona così: prima c'era una sedia, e ci ho messo Byron. Poi ho messo la sedia (che ha ancora un Byron) nell'ufficio, che poi ho messo nell'edificio, che poi ho messo nel lotto, che poi ho comprato e infine Byron lavora.
Il primo passo è stato quello di inserire i box del grafico in objhlb1, poi c'è una casella di riepilogo verticale (objvlb) in cui sono inseriti un box di contorno (objout) e objhlb1. Questo è il lato sinistro del grafico. Dato che si trova sul lato sinistro, ovjvlb viene inserito in una seconda casella di riepilogo orizzontale (objhlb2), seguita prima dal grafico del generatore di grafici (obj). Infine, objhlb2 (che ora contiene tutto) viene inserito in una nuova finestra in modo che possa essere visualizzato. E poi, per far funzionare il tutto, viene valutato. Se usassi solo Eval, allora... Non sono sicuro di cosa succeda, ma non funziona, quindi ho eseguito prima Eval Expr in modo che venga visualizzato l'intero script valutato di tutti gli elementi (prova a eseguire solo Eval Expr(objwin) nello script per capire cosa intendo) e poi ho usato Eval per eseguire quella parte.
C'è una piccola formattazione di obj, il grafico del generatore di grafici. Il titolo è disattivato, le dimensioni della cornice sono regolate e l'asse y è disattivato. Tutto questo per adattarlo alla scala e alla forma dei riquadri dell'asse y. È comodo usare il grafico del generatore di grafici perché l'asse x può ancora essere modificato, la legenda può essere spostata e tutte le opzioni di GB sono disponibili per ulteriori formattazioni.
Punto finale corrente
Allo stato attuale, alla settima revisione principale, lo script non è molto ordinato e lascia un sacco di tabelle incomprensibili. Questo e molti altri problemi dovrebbero essere risolti. Quindi, se avete idee per aggiunte, inviatemele. Alcune cose non funzioneranno, ad esempio i dati nel grafico non saranno collegati dinamicamente alla tabella dati originale e anche il riquadro dell'asse Y non sarà collegato al riquadro del grafico di risposta scalato, dovrete ridimensionare ognuno in modo indipendente. Sarebbe fantastico se funzionasse. Infine, sono quasi sicuro al 100% che esistano modi per far sì che lo script si interrompa o restituisca risultati insoddisfacenti, quindi fate attenzione ai nomi di colonna speciali e ai valori anomali estremi, o a molti dati mancanti.
Mi piacerebbe ricevere commenti su modi migliori per far funzionare questo script. (A proposito di funzionamento, l'ho testato nelle versioni 13 e 14 su PC e Mac e ha funzionato bene, al momento della pubblicazione.)
Nota finale dell'autore:
Lo scritto è molto informale, come se stessimo parlando di questo argomento.
Questo è intenzionale perché, a mio modesto parere, il gergo formale nella programmazione ostacola la comunicazione. Invece, evito l'offuscamento.