
Gráfico de superposición de respuesta escalada con ejes Y múltiples
¡ALTO! Si solo quieres el guion, lo tienes adjunto al final de la publicación.
EDITAR: ¡Alto! - Esto ahora está implementado en Graph Builder para v19. : )
Este es un gráfico especializado que escala todas las variables del eje y y las superpone y luego proporciona la escala del eje y en las unidades y el rango de la variable. Esto es útil cuando desea superponer 3 o 4 variables que están todas en diferentes rangos. En la figura anterior, los caudales están en el rango de 0 a 40, mientras que la potencia está en el rango de 200 a 1000 y de 100 a 500. Este tipo de gráfico se utiliza a menudo en aplicaciones científicas y de ingeniería donde varios sensores registran datos al mismo tiempo, pero el rango de cada sensor es bastante diferente. Por ejemplo, en un ejemplo de fermentación, la glucosa es de 1 a 5 g/dl, el pH es de 6,8 a 7,8, el ácido láctico es de 10 a 50 mmol y el recuento de células o la turbidez es un orden de magnitud mayor. Una forma sencilla de graficar la relación entre todas las variables a lo largo del tiempo es muy útil para comprender la cinética (o detectar patrones inusuales) del proceso.
Este gráfico en particular ha sido mi némesis durante bastante tiempo. Normalmente se requiere copiar y pegar de forma creativa, ya sea en JMP o en PowerPoint, para obtener algo que se vea razonablemente bien. Entonces, ¿por qué es tan difícil este gráfico? De alguna manera, hay que escalar y combinar los datos en un solo gráfico, y luego combinar todos los componentes sin escalar del eje Y con el gráfico de datos escalados. Todos los métodos que he visto y probado implican demasiados pasos de copiar y pegar, y los métodos que implican desagrupar y reformatear elementos del gráfico en PowerPoint a veces generan malos resultados debido al tedioso proceso de alinear todos los elementos correctamente en las diapositivas. Los procesos desordenados, manuales, tediosos y repetitivos requieren automatización con JSL.
Al pensar en una estrategia para crear una solución, hay un par de pasos a tener en cuenta
1. Haz un cuadro en el eje y para cada variable/columna.
2. Escala los valores de cada columna al mismo rango
3. Reforme los datos para que funcionen en un gráfico superpuesto.
4. También sería bueno que hubiera un cuadro de diálogo de inicio para seleccionar columnas para los roles y y el rol x (o tal vez solo usar números de fila).
5. Y por último, pero no menos importante, el gráfico debe incluir el cuadro del eje con cada variable en la escala original.
Afilé mi lápiz JSL y me animé a automatizar la creación de esta figura. Intentaré explicar lo que hice aquí y agradecería más información sobre cómo hacerla más funcional. Actualmente, es un script que puede generar un tipo de figura fija y no se molesta en limpiar el rastro de tablas intermedias que deja. (Adjuntaré el script completo a la publicación).
Primer paso: el diálogo de inicio
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
Cada vez que empiezo a escribir un diálogo de inicio, copio sin complejos el del índice de scripts, eliminando lo que no necesito o añadiendo más según sea necesario. El objetivo es capturar las Y y las X. Hay algunas reglas, como que las columnas deben ser numéricamente continuas y que debe haber al menos dos Y (la razón se explica más adelante, pero, si solo tienes una Y, ¿por qué la usas?). También hay un límite de 8 respuestas para Y; puedes editarlo; más de 8 me parecía demasiado confuso.
Las dos últimas líneas con eval(eval expr()) son cómo genero las variables ylist y xlist a partir de lo que se devuelve en "cd" desde el cuadro de diálogo de la columna.
Tabla de datos 1: La tabla de datos estandarizada
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 es la tabla que contendrá los datos después de su estandarización. Si no se selecciona ninguna X en el cuadro de diálogo, la primera columna es "Fila" y contiene los números de fila para usar como eje x en el gráfico. Si hay una X, se añade a la primera posición de la lista y. Así es, la variable lista y contiene tanto las X como las Y.
Rellenando dt1 y creando los "cuadros de eje"
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 siguiente parte recorre la lista de columnas seleccionadas para las Y. Si se especificó una X, entonces comienza con el segundo elemento de la lista.
La primera parte utiliza cierta lógica para configurar el bucle y crear una tabla con el resultado, así que continúe. La variante de la tabla depende de si se especificó o no una variable del eje x.
La parte central del script es muy importante, aunque corta. Obtiene los valores de cada columna de la tabla original (dt) y los coloca en dt1. No me siento muy orgulloso de esta línea: dt1 << Nueva columna ( Munger ( ylist[i], 1 , ":" , "" ), set values( yycol ) ); Pero la cuestión es que New column busca "colname" o colname, y :colname elimina este paso, así que simplemente omití los dos puntos. (Compartan una mejor manera de hacerlo en los comentarios).
La última parte examina cada conjunto de valores de columna y los utiliza para escalar un cuadro gráfico. El cuadro gráfico crea cuadros de eje con buena calidad, y si su ancho se establece en 0, solo se muestra el cuadro del eje Y. Este paso es importante para formatear el gráfico. La última línea inserta la expresión del cuadro gráfico en un cuadro de visualización que se evalúa al final. Al usar el nombre expr para referirse a obj1 (el cuadro gráfico), se ejecuta el script para crearlo y se evalúan todas las variables del script, pero este no se evalúa. Cada cuadro gráfico se inserta en un cuadro de lista horizontal (objhlb1). Posteriormente, este cuadro se inserta en otro cuadro.
Construyendo una tabla para hacer un gráfico a 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" );
Anteriormente mencioné una restricción: se debían seleccionar al menos dos variables y. Resulta que no se puede apilar solo una variable (porque ya está apilada) y el script se bloquea si solo hay una. Lo interesante aquí es la lógica que regula si se especifica una X o no. Si hay una X, no quería escalarla, y ahora no quiero incluirla en la pila porque quiero que sea la etiqueta de la X. Si no se especifica una X, quiero usar todas las variables de la lista y.
La última parte en rojo es difícil de explicar. En una sola línea, se construye, ejecuta y evalúa el script para la pila. "Ping" es el patrón que se sustituye; fue la primera palabra que me vino a la mente al pensar en rebotar estas expresiones.
¿Dónde está la expresión para dt2stackcols? Para mayor claridad y tranquilidad, mientras trabajaba en esto, moví todas las expresiones definidas al principio del script. Esto fue útil, ya que al cambiar el orden del script, todas las expresiones existían antes de ser necesarias. Si las dejaba dispersas en el script, a menudo intentaba insertarlas en una expresión definida más adelante. Echa un vistazo al script completo, adjunto, para ver cómo está diseñado. Puede que no sea la mejor práctica, pero estoy haciendo todo lo posible para mi grafo némesis.
Ensamblaje de todas las piezas
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
Una de las mejores cosas de JSL es que es un lenguaje basado en funciones. Esto logra dos cosas importantes: primero, facilita realizar tareas específicas sin tener que reinventar la rueda y, segundo, facilita la construcción de estructuras complejas (siempre que las consideres como una cebolla que construyes desde dentro hacia fuera). Por ejemplo, una forma típica de pensar sería algo como esto: primero compro un terreno, luego construyo un edificio en el terreno, luego acomodo habitaciones en el edificio, luego coloco cosas como mi silla de oficina en la habitación, me siento en la silla y, finalmente, empiezo a trabajar. El pensamiento funcional funciona así: primero había una silla y puse a Byron en ella. Luego puse la silla (que todavía tiene un Byron) en la oficina, que luego puse en el edificio, que puse en el terreno, que compré y, finalmente, Byron funciona.
El primer paso fue poner los cuadros del gráfico en objhlb1, luego hay un cuadro de lista vertical (objvlb) en el que se insertan un cuadro de contorno (objout) y objhlb1. Este es el lado izquierdo del gráfico. Como es el lado izquierdo, ovjvlb se inserta primero en un segundo cuadro de lista horizontal (objhlb2) seguido del gráfico del constructor de gráficos (obj). Finalmente, objhlb2 (que ahora contiene todo) se inserta en un nuevo cuadro de ventana para que pueda mostrarse. Y luego, para que todo funcione, se evalúa. Si solo usara Eval, entonces... No estoy seguro de qué sucede, pero no funciona, así que primero hice Eval Expr para que se muestre todo el script evaluado de todo (intente ejecutar solo Eval Expr( objwin ) en el script para ver a qué me refiero) y luego usé Eval para ejecutar esa parte.
Hay un pequeño formato de obj, el gráfico del Constructor de Gráficos. El título está desactivado, el tamaño del marco se reajusta y el eje Y está desactivado. Todo esto se hace para que coincida con la escala y la forma de los cuadros del eje Y. Es conveniente usar el gráfico del Constructor de Gráficos porque el eje X aún se puede editar, la leyenda se puede mover y todas las opciones de GB están disponibles para un formato adicional.
Punto final actual
Tal como está el script, en la séptima revisión importante, no está muy ordenado y deja un montón de tablas sin resolver. Esto y varias otras cosas deberían corregirse. Así que, si tienen ideas para añadir algo, por favor, envíenlas. Hay cosas que simplemente no funcionan; por ejemplo, los datos del gráfico no se vinculan dinámicamente con la tabla de datos original y el cuadro del eje Y no se vincula con el cuadro de contorno del Gráfico de Respuesta Escalada; tendrán que dimensionar cada uno por separado. Sería genial si eso funcionara. Por último, estoy casi 100% seguro de que hay maneras de hacer que el script falle o devuelva resultados desafortunados, así que estén atentos a nombres de columna especiales, valores atípicos extremos o muchos datos faltantes.
Me encantaría recibir comentarios sobre mejores formas de hacer que este script funcione. (Hablando de funcionamiento, lo probé en v13 y v14 en PC y Mac y funcionó bien para publicar).
Una nota final del autor:
Esto está escrito en el tono más informal, como si estuviéramos hablando de esto.
Esto es intencional porque, en mi humilde opinión, la jerga formal en la codificación dificulta la comunicación. En cambio, evito la confusión.