
具有缩放响应叠加图的多个 Y 轴
停!如果你只想要脚本,它附在文章底部。
编辑:停止! - 现在已在 Graph Builder v19 中实现。:)
这是一种特殊的图表,它缩放并叠加所有 y 轴变量,然后以变量的单位和范围提供 y 轴刻度。当您想要叠加 3 或 4 个不同范围的变量时,这种方法很有用。在上图中,流速在 0-40 范围内,而功率在 200-1000 和 100 到 500 范围内。这种类型的图表通常用于科学和工程应用中,其中多个传感器同时记录数据,但每个传感器的范围差异很大。例如,在发酵示例中,葡萄糖为 1-5 g/dl,pH 为 6.8 - 7.8,乳酸为 10-50 mmol,细胞计数或浊度要大一个数量级。一种绘制所有变量随时间变化的关系的简单方法对于理解过程的动力学(或检测异常模式)非常有帮助。
这张图表一直是我的心头大患。通常需要在 JMP 或 PowerPoint 中进行一些创造性的复制粘贴才能获得看起来不错的效果。那么,为什么这张图表这么难制作呢?因为你得想办法将数据缩放并合并到一张图表中,然后再将所有未缩放的 y 轴分量与缩放后的数据图表合并。我见过和尝试过的所有方法都涉及太多太多的复制粘贴步骤,而那些需要在 PowerPoint 中取消分组和重新格式化图表元素的方法有时会产生糟糕的结果,因为需要费力地将所有元素在幻灯片上对齐。这种混乱、手动、繁琐和重复的流程迫切需要 JSL 自动化。
在思考编写解决方案的策略时,需要考虑以下几个步骤
1. 为每个变量/列制作一个 y 轴框。
2. 将每列的值缩放到相同的范围
3. 重塑数据,以便它能够在叠加图中发挥作用。
4. 如果有一个启动对话框用于选择 y 角色和 x 角色的列(或者可能只使用行号)那就太好了。
5. 最后但同样重要的一点是,图表需要包含原始比例中每个变量的轴框。
我削尖了我的 JSL 铅笔,费了好大劲才尝试自动生成这个图形。我会尽量解释一下我的做法,也希望大家能进一步讨论如何让它更实用。目前的状态是一个脚本,可以生成一种固定类型的图形,而且不会清理它留下的中间表痕迹。(我会把完整的脚本附在帖子里)
第一步:启动对话框
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
每当我开始编写启动对话框时,我都会毫不犹豫地从脚本索引中复制一个,删除不需要的部分,或根据需要添加更多内容。目标是捕获 Y 值和 X 值。这里有一些规则,例如列必须是数字连续的,并且必须至少有 2 个 Y 值(这样做的原因稍后会更清楚,但是,如果你只有一个 Y 值,那你为什么要用这个呢?)此外,Y 值响应数限制为 8 个,请随意编辑,超过 8 个似乎太乱了。
最后两行带有 eval(eval expr()) 的内容是我如何从列对话框的“cd”中返回的内容生成 ylist 和 xlist 变量。
数据表1:标准化数据表
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 是包含标准化后数据的表格。如果对话框中未选择 X,则第一列为“行”,其中包含行号,用作图表的 X 轴。如果存在 X,则将其添加到 ylist 的第一个位置。没错,变量 ylist 同时包含 X 和 Y。
填充 dt1 并制作“轴框”
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>
);
下一部分循环遍历为 Y 选择的列列表。如果指定了 X,则从列表中的第二项开始
第一部分使用一些逻辑来设置循环,并创建一个表格来记录结果。表格变量取决于是否指定了 x 轴变量。
脚本的中间部分虽然很短,但非常重要。它从原始表 (dt) 的每一列获取值,并将缩放后的值放入 dt1 中。我对这行代码不太满意:dt1 << 新专栏 ( 芒格 ( ylist[i], 1 , “:” , “” ), set values( yycol ) ); 但问题是,新列需要的是“colname”或 colname,而 :colname 会取消这一步,所以我干脆把冒号去掉了。(请在评论区留言,分享更好的实现方法。)
最后一部分查看每组列值,并用它们来缩放图形框。图形框可以生成漂亮的坐标轴框,如果图形框的宽度设置为 0,则仅显示 y 轴框。这一步对于图形的格式化至关重要。最后一行将图形框的表达式插入到显示框中,该显示框最终会被求值。通过使用 name expr 引用 obj1(图形框),我得到了创建它的脚本,并且脚本中的所有变量都会被求值,但脚本本身不会被求值。每个图形框都会插入到一个水平列表框 (objhlb1) 中。之后,这个框会被插入到另一个框中。
建立一个表格来制作图表:
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" );
上面我提到了一个限制:至少要选择两个 y 变量。但实际上,你不能只堆叠一个变量(因为它已经堆叠了),如果只有一个变量,脚本就会崩溃。这里有趣的地方在于处理是否指定 X 的逻辑。如果指定了 X,我本来就不想缩放它,现在我不想把它包含在堆叠中,因为我想把它作为 X 的标签。如果没有指定 X,那么我想使用 ylist 中的所有变量。
最后红色部分很难解释。一行代码就构建并运行/评估了执行堆栈的脚本。“Ping”是被替换掉的模式,当我考虑如何将这些表达式反复使用时,它首先浮现在我的脑海中。
dt2stackcols 的表达式在哪里?为了清晰起见,也为了我在处理这个问题时保持理智,我把所有定义的表达式都移到了脚本的开头。这很有帮助,因为当我改变脚本的顺序时,这些表达式在需要之前就已经存在了。如果我把它们散落在脚本中,我就会时不时地试图插入到脚本中稍后定义的表达式中。请查看附件中的完整脚本,了解它的布局。这可能不是最佳实践,但我正在为我的复仇女神图竭尽全力。
组装所有部件
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
JSL 的一大优点在于它是一种基于函数的语言。这实现了两个重要的功能:首先,它使得特定任务的执行变得简单,无需重新发明轮子;其次,它使得构建复杂结构变得简单(只要你把它们想象成一个从内到外逐层构建的洋葱即可)。例如,一种典型的思维方式是这样的:首先,我买一块地,然后在地上盖一栋楼,然后在楼里安排房间,然后把办公椅之类的东西放进房间里,最后我坐在椅子上,开始工作。函数思维的工作原理是这样的:首先有一把椅子,我把 Byron 放进去。然后,我把椅子(仍然有 Byron)放进办公室,办公室放进楼里,楼里又放进地块,最后我买下地块,Byron 终于开始工作了。
第一步是将图形框放入 objhlb1,然后有一个垂直列表框(objvlb),其中插入一个轮廓框(objout)和 objhlb1。这是图表的左侧。由于它是左侧,因此首先将 ovjvlb 插入第二个水平列表框(objhlb2),然后插入图形生成器图表(obj)。最后将 objhlb2(现在包含所有内容)插入到一个新的窗口框中,以便可以显示它。然后,为了使一切正常工作,它会被评估。如果我只是使用 Eval,那么......我不确定会发生什么但它不起作用,所以我首先执行了 Eval Expr,以便显示所有内容的整个评估脚本(尝试在脚本中运行 Eval Expr(objwin)来了解我的意思)然后我使用 Eval 来运行该部分。
图形生成器绘图 obj 有一些格式化操作。标题已关闭,框架大小已重新调整,y 轴也已关闭。这一切都是为了使其与 y 轴框的比例和形状相匹配。使用图形生成器绘图非常方便,因为 x 轴仍然可以编辑,图例可以移动,并且所有 GB 选项都可用于进一步的格式化。
当前终点
就目前脚本而言,在第七次重大修订中,它显得不太整洁,并且留下了一堆表格。这个问题以及其他一些问题应该得到修复。所以,如果您有任何补充建议,请随时提出。有些功能就是无法实现,例如,图表中的数据无法动态链接到原始数据表,而且 Y 轴框无法链接到“缩放响应图”轮廓框,您必须分别调整每个框的大小。如果这些功能有效就太好了。最后,我几乎 100% 确定有一些方法会导致脚本中断或返回不理想的结果,因此请留意特殊的列名、极端异常值或大量缺失数据。
我很乐意听到关于如何让这个脚本更好地运行的评论。(说到运行,我在 PC 和 Mac 上的 v13 和 v14 中测试了它,并且运行良好。)
作者最后补充:
这是用最非正式的语气写的,就像我们在谈论这件事一样。
这是有意为之,因为在我看来,编码中的正式术语会阻碍沟通。因此,我尽量避免使用晦涩难懂的术语。