cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
Check out the JMP® Marketplace featured Capability Explorer add-in
Choose Language Hide Translation Bar
CitizenNo3
Level II

JSL to fit model, maximize desirability, and extract results with user-selectable columns

Hello, 

 

I'm working on a script that will allow users to

  • Select columns to fit into a model
  • Identify the primary variable and specify the goal value
  • Identify what column to split the model by
  • Allow JMP to create the model, optimize desirability to hit the intended target, then output the model results
  • Copy the results from a convenient output table 

 

I have functional scripts that do what I want without allowing for user input or selection, but making the script interactive has been challenging. I think I'm mostly there, but I have a few specific things I'm struggling with

  • Question 1: in a previous version of this script, I used a different window for each model using a for loop with a While( x == "y[i]") line. I'd rather use By( :x ) because it is more flexible and gives one output window. However using By( :x ) breaks the later script to get a scriptable object from the report. Is it possible to create a different report for each sub-box in the model window? Or a better way to code lines 173-181? I build those lines based on these other two threads: thread 1, thread 2.
  • Question2: I need the option to be able to have a variable number effects that are modeled without having desirability functions (later the full script will also have effects that are minimized but this simplified demo script was getting too complicated. On lines 122-126 I am able to created the desired expression in ModExpr, but because it's a list it didn't work when I tried to directly substitute it into the profile. My workaround is having a number of placeholders greater than the number of variables I expect to model and the code seems to just ignore the nonsense parts if I have fewer than 6 modeled effects... but this seems super janky. Is there a way to fix the commented code in lines 130-134 such that the list is combined into as single expression that will work? I Couldn't get Concatenation to play nice either, but I bet there is something simple that I missed.

 

Here is a sample script using sample data. The model is a terrible fit, but that's not really important for the demo. When running, if you clear the selection for "by", things will run fine and output results. If anything is left in the "by" section, then line 174 will break.

 

Thank you for any help!

Names Default To Here( 1 );
//sample data for demo
dt = Open( "$SAMPLE_DATA/Football.jmp" );

xvar = {};
yvar = {};
byvar = {};
ytarg = 60;
dtout = {};

//let users select the columns to model
win = New Window( "Model Variable Selection",
	<<Modal,
	Text Box( "Available Columns" ),
	H List Box(
		// list box to present cols
		allcols = Col List Box( dt, all ),
		Lineup Box(
			2,
			//user to select input factors
			bbx = Button Box( "Factors", xcols << append( allcols << get selected ) ), 
			xcols = Col List Box( "Numeric", <<Modeling Type( {"Continuous"} ), min items( 1 ) ),
			xcols << set height (100),
			xcols << set items({"Height", "Weight"});
			
			//user to select one critical metric that has a specific target
			bby = Button Box( "Critical response", ycol << append( allcols << get selected ) ), 
			ycol = Col List Box( "Numeric", <<Modeling Type( {"Continuous"} ), min items( 1 ), max items( 1 ) ),
			ycol << set height (50),
			ycol << set items("Speed");
			
			//user to specify the target value
			Spacer Box( size( 1, 30 )),
			H List Box( Text Box( "Target value: " ), Number Edit Box( ytarg ) ), // data entry
			
			//user specify metrics to model
			bbmod = Button Box( "Model only", modcols << append( allcols << get selected ) ), 
			modcols = Col List Box( "Numeric", <<Modeling Type( {"Continuous"} ), max items (6) ),
			modcols << set height (100),
			modcols << set items({"Speed2", "Bench"});
			
			//user specify columns to separate by. Latter code works fine if this is blank, but breaks if it is populated
			bbby = Button Box( "By", bycols << append( allcols << get selected ) ), 
			bycols = Col List Box(  ),
			bycols << set height (100),
			bycols << set items( {"Position", "Position2"});
			
		),
		V List Box(
			bbOK = Button Box( "OK",
				varList = (xcols << Get Items);
				yList = (ycol << Get Items);
				ModList = (modcols << Get Items);
				byList = (bycols << Get Items);
				respList = yList || ModList;
			),
			bbCancel = Button Box( "Cancel" )
		)
	)
);
// stop the script if user chooses Cancel
If( win["Button"] == -1,
	Throw( "User Cancelled" )
);

//parse reponse column names into list
respExpr = expr(Y());
for(i=1, i<=nitems(respList), i++,
		insertinto(respExpr, parse(evalinsert(":name(\!"^respList[i]^\!")")))
);

// parse word '& RS' into effects list 	
varExpr = expr(Effects());
For( i = 1, i <= N Items( varList ), i++,
	Insert Into( varExpr, Parse( ":Name(\!"" || varList[i] || "\!")" || Char( "& RS" ) ) )
);

// parse two-way interactions into effects list
For( i = 1, i <= N Items( varList ), i++,
	For( k = i, k <= N Items( varList ), k++,
 		Insert Into( varExpr, Parse( ":Name(\!"" || varList[i] || "\!")" || Char( " * " ) || ":Name(\!"" || varList[k] || "\!")" ) )
));

//parse by column names into list
byExpr = expr(by());
for(i=1, i<=nitems(byList), i++,
		insertinto(byExpr, parse(evalinsert(":name(\!"^byList[i]^\!")")))
);

jslExprFit = expr(
	try(FitModelDialogWindow << closewindow());
	FitModelDialogWindow = Fit Model(
		_Y_,
		_EFFECTS_,
		//Keep dialog open( 1 ), //can turn on to see what the model is
		Personality( "Standard Least Squares" ),
		Emphasis( "Effect Screening" ),
		_BY_
		//Where( :Position == "qb" & :Position2 == "o")
		//in previous script versions, I used a where(:x == y) with a for loop to create a new window for each model
	);
);
substituteInto(jslExprFit, 
	expr(_Y_), nameexpr(respExpr),
	expr(_EFFECTS_), nameexpr(varExpr),
	expr(_BY_), nameexpr(byExpr)
);

fit = jslExprFit << run;

//create expressions to model responses with no goals
ModExpr = {};
for(i=1, i<=nitems(ModList), i++,
	ModExpr[i] = expr(
		_MOD_ << Response Limits(
				{Lower( 1, 1 ), Middle( 2, 1 ), Upper( 3, 1 ), Goal( "None" ),
				Importance( 1 )}
			),
		)
);

for(i=1, i<=nitems(ModList), i++,
	substituteInto(ModExpr[i], 
		expr(_MOD_), nameexpr(ModList[i])
	),
);

//this didn't work, added another << between items

//ModExpr2 = ModExpr[1];
//
//for(i=2, i<=nitems(ModList), i++,
//	insert into(ModExpr2, ModExpr[i])
//);


jslExprProfiler = expr(
	fit << Profiler( 1,
		Confidence Intervals( 1 ),
		Desirability Functions( 1 ),
		
		//sets desirability target of the crit response
		_Y2_ << Response Limits(
			{Lower( ytarg-1, 0.0183 ), Middle( ytarg, 1 ), Upper( ytarg+1, 0.0183 ),
			Goal( "Match Target" ), Importance( 1 )}
		),
		
		//add placeholders to replace with model only effects
		//this is super gross but I couldn't figure out a way to covert the list to a continuous function 
		//substituting in the ModExpr list directly didn't work (maybe due to the curly brackets?)
		_MOD1_,
		_MOD2_,
		_MOD4_,
		_MOD5_,
		_MOD6_,
	);
);

substituteInto(jslExprProfiler, 
	expr(_Y2_), nameexpr(yList[1]),
	expr(_MOD1_), nameexpr(ModExpr[1]),
	expr(_MOD2_), nameexpr(ModExpr[2]),
	expr(_MOD3_), nameexpr(ModExpr[3]),
	expr(_MOD4_), nameexpr(ModExpr[4]),
	expr(_MOD5_), nameexpr(ModExpr[5]),
	expr(_MOD6_), nameexpr(ModExpr[6]),
);

profile = jslExprProfiler << run;

//creates a report and maximizes desirability
//doesn't work if there is anything in the by() field
rfit = fit << Report;
pf = report(fit)["Prediction Profiler"] << get scriptable object;
pf << Maximize Desirability;

pf << Remember Settings; //this adds a new box with the parameters from the prediction profiler

//create a data table with the parameters from the prediction profile
//in my other version (with the for loop a Where( :x = y[i]) in the model, I than had some code to concatenate tables together and change some formatting
dtout[1] = (rfit["Prediction Profiler"]["Remembered Settings"][Table Box(1)] << make into data table); 
1 ACCEPTED SOLUTION

Accepted Solutions
jthi
Super User

Re: JSL to fit model, maximize desirability, and extract results with user-selectable columns

I quickly looked at this, do note that I'm using JMP16 and For Each() instead of For().

 

Question1:

I think you need to get separate references to the outline boxes, one option is too use XPath and then loop over the list of lists. Also

//creates a report and maximizes desirability
//doesn't work if there is anything in the by() field
pf_obs = fit << XPath("//OutlineBox[text()='Prediction Profiler']"); // list of lists so easiest to loop over them
For Each({pf_ob}, pf_obs,
	pf = pf_ob << Get Scriptable Object;
	pf << Maximize and Remember;
);

//create a data table with the parameters from the prediction profile
//in my other version (with the for loop a Where( :x = y[i]) in the model, I than had some code to concatenate tables together and change some formatting
dtout[1] = Report(fit[1])["Prediction Profiler"]["Remembered Settings"][Table Box(1)] << Make Combined Data Table

jthi_0-1672298361819.png

 

Question2:

I would do a bit larger refactor to this part of code to make it more clean, but you can do also "quick fix" one for this. Use Insert Into to add Response Limits to Profiler expression

jslExprProfiler = expr(
	Profiler( 1, 
		Confidence Intervals( 1 ),
		Desirability Functions( 1 ),
		
		//sets desirability target of the crit response
		_Y2_ << Response Limits(
			{Lower( ytarg-1, 0.0183 ), Middle( ytarg, 1 ), Upper( ytarg+1, 0.0183 ),
			Goal( "Match Target" ), Importance( 1 )}
		)
	);
);

For Each({mod}, ModList,
	Insert Into(
		jslExprProfiler, EvalExpr(
			Expr(mod) << Response Limits({Lower(1, 1), Middle(2, 1), Upper(3, 1), Goal("None"), Importance(1)})
		)
	)
);

jslExprProfiler = Substitute(
	Expr(fit << _prof_),
	Expr(_prof_), Name Expr(jslExprProfiler)
);

jslExprProfiler = Substitute(
	Name Expr(jslExprProfiler),
	expr(_Y2_), nameexpr(yList[1])
);

Full code:

View more...
Names Default To Here( 1 );
//sample data for demo
dt = Open( "$SAMPLE_DATA/Football.jmp" );

xvar = {};
yvar = {};
byvar = {};
ytarg = 60;
dtout = {};

//let users select the columns to model
win = New Window( "Model Variable Selection",
	<<Modal,
	Text Box( "Available Columns" ),
	H List Box(
		// list box to present cols
		allcols = Col List Box( dt, all ),
		Lineup Box(
			2,
			//user to select input factors
			bbx = Button Box( "Factors", xcols << append( allcols << get selected ) ), 
			xcols = Col List Box( "Numeric", <<Modeling Type( {"Continuous"} ), min items( 1 ) ),
			xcols << set height (100),
			xcols << set items({"Height", "Weight"});
			
			//user to select one critical metric that has a specific target
			bby = Button Box( "Critical response", ycol << append( allcols << get selected ) ), 
			ycol = Col List Box( "Numeric", <<Modeling Type( {"Continuous"} ), min items( 1 ), max items( 1 ) ),
			ycol << set height (50),
			ycol << set items("Speed");
			
			//user to specify the target value
			Spacer Box( size( 1, 30 )),
			H List Box( Text Box( "Target value: " ), Number Edit Box( ytarg ) ), // data entry
			
			//user specify metrics to model
			bbmod = Button Box( "Model only", modcols << append( allcols << get selected ) ), 
			modcols = Col List Box( "Numeric", <<Modeling Type( {"Continuous"} ), max items (6) ),
			modcols << set height (100),
			modcols << set items({"Speed2", "Bench"});
			
			//user specify columns to separate by. Latter code works fine if this is blank, but breaks if it is populated
			bbby = Button Box( "By", bycols << append( allcols << get selected ) ), 
			bycols = Col List Box(  ),
			bycols << set height (100),
			bycols << set items( {"Position", "Position2"});
			
		),
		V List Box(
			bbOK = Button Box( "OK",
				varList = (xcols << Get Items);
				yList = (ycol << Get Items);
				ModList = (modcols << Get Items);
				byList = (bycols << Get Items);
				respList = yList || ModList;
			),
			bbCancel = Button Box( "Cancel" )
		)
	)
);
// stop the script if user chooses Cancel
If( win["Button"] == -1,
	Throw( "User Cancelled" )
);

//parse reponse column names into list
respExpr = expr(Y());
for(i=1, i<=nitems(respList), i++,
		insertinto(respExpr, parse(evalinsert(":name(\!"^respList[i]^\!")")))
);

// parse word '& RS' into effects list 	
varExpr = expr(Effects());
For( i = 1, i <= N Items( varList ), i++,
	Insert Into( varExpr, Parse( ":Name(\!"" || varList[i] || "\!")" || Char( "& RS" ) ) )
);

// parse two-way interactions into effects list
For( i = 1, i <= N Items( varList ), i++,
	For( k = i, k <= N Items( varList ), k++,
 		Insert Into( varExpr, Parse( ":Name(\!"" || varList[i] || "\!")" || Char( " * " ) || ":Name(\!"" || varList[k] || "\!")" ) )
));

//parse by column names into list
byExpr = expr(by());
for(i=1, i<=nitems(byList), i++,
		insertinto(byExpr, parse(evalinsert(":name(\!"^byList[i]^\!")")))
);

jslExprFit = expr(
	try(FitModelDialogWindow << closewindow());
	FitModelDialogWindow = Fit Model(
		_Y_,
		_EFFECTS_,
		//Keep dialog open( 1 ), //can turn on to see what the model is
		Personality( "Standard Least Squares" ),
		Emphasis( "Effect Screening" ),
		_BY_
		//Where( :Position == "qb" & :Position2 == "o")
		//in previous script versions, I used a where(:x == y) with a for loop to create a new window for each model
	);
);
substituteInto(jslExprFit, 
	expr(_Y_), nameexpr(respExpr),
	expr(_EFFECTS_), nameexpr(varExpr),
	expr(_BY_), nameexpr(byExpr)
);

fit = jslExprFit << run;
//create expressions to model responses with no goals

jslExprProfiler = expr(
	Profiler( 1, 
		Confidence Intervals( 1 ),
		Desirability Functions( 1 ),
		
		//sets desirability target of the crit response
		_Y2_ << Response Limits(
			{Lower( ytarg-1, 0.0183 ), Middle( ytarg, 1 ), Upper( ytarg+1, 0.0183 ),
			Goal( "Match Target" ), Importance( 1 )}
		)
	);
);

For Each({mod}, ModList,
	Insert Into(
		jslExprProfiler, EvalExpr(
			Expr(mod) << Response Limits({Lower(1, 1), Middle(2, 1), Upper(3, 1), Goal("None"), Importance(1)})
		)
	)
);

jslExprProfiler = Substitute(
	Expr(fit << _prof_),
	Expr(_prof_), Name Expr(jslExprProfiler)
);

jslExprProfiler = Substitute(
	Name Expr(jslExprProfiler),
	expr(_Y2_), nameexpr(yList[1])
);

profile = jslExprProfiler << run;

//creates a report and maximizes desirability
//doesn't work if there is anything in the by() field
pf_obs = fit << XPath("//OutlineBox[text()='Prediction Profiler']"); // list of lists so easiest to loop over them
For Each({pf_ob}, pf_obs,
	pf = pf_ob << Get Scriptable Object;
	pf << Maximize and Remember;
);

//create a data table with the parameters from the prediction profile
//in my other version (with the for loop a Where( :x = y[i]) in the model, I than had some code to concatenate tables together and change some formatting
dtout[1] = Report(fit[1])["Prediction Profiler"]["Remembered Settings"][Table Box(1)] << Make Combined Data Table
-Jarmo

View solution in original post

1 REPLY 1
jthi
Super User

Re: JSL to fit model, maximize desirability, and extract results with user-selectable columns

I quickly looked at this, do note that I'm using JMP16 and For Each() instead of For().

 

Question1:

I think you need to get separate references to the outline boxes, one option is too use XPath and then loop over the list of lists. Also

//creates a report and maximizes desirability
//doesn't work if there is anything in the by() field
pf_obs = fit << XPath("//OutlineBox[text()='Prediction Profiler']"); // list of lists so easiest to loop over them
For Each({pf_ob}, pf_obs,
	pf = pf_ob << Get Scriptable Object;
	pf << Maximize and Remember;
);

//create a data table with the parameters from the prediction profile
//in my other version (with the for loop a Where( :x = y[i]) in the model, I than had some code to concatenate tables together and change some formatting
dtout[1] = Report(fit[1])["Prediction Profiler"]["Remembered Settings"][Table Box(1)] << Make Combined Data Table

jthi_0-1672298361819.png

 

Question2:

I would do a bit larger refactor to this part of code to make it more clean, but you can do also "quick fix" one for this. Use Insert Into to add Response Limits to Profiler expression

jslExprProfiler = expr(
	Profiler( 1, 
		Confidence Intervals( 1 ),
		Desirability Functions( 1 ),
		
		//sets desirability target of the crit response
		_Y2_ << Response Limits(
			{Lower( ytarg-1, 0.0183 ), Middle( ytarg, 1 ), Upper( ytarg+1, 0.0183 ),
			Goal( "Match Target" ), Importance( 1 )}
		)
	);
);

For Each({mod}, ModList,
	Insert Into(
		jslExprProfiler, EvalExpr(
			Expr(mod) << Response Limits({Lower(1, 1), Middle(2, 1), Upper(3, 1), Goal("None"), Importance(1)})
		)
	)
);

jslExprProfiler = Substitute(
	Expr(fit << _prof_),
	Expr(_prof_), Name Expr(jslExprProfiler)
);

jslExprProfiler = Substitute(
	Name Expr(jslExprProfiler),
	expr(_Y2_), nameexpr(yList[1])
);

Full code:

View more...
Names Default To Here( 1 );
//sample data for demo
dt = Open( "$SAMPLE_DATA/Football.jmp" );

xvar = {};
yvar = {};
byvar = {};
ytarg = 60;
dtout = {};

//let users select the columns to model
win = New Window( "Model Variable Selection",
	<<Modal,
	Text Box( "Available Columns" ),
	H List Box(
		// list box to present cols
		allcols = Col List Box( dt, all ),
		Lineup Box(
			2,
			//user to select input factors
			bbx = Button Box( "Factors", xcols << append( allcols << get selected ) ), 
			xcols = Col List Box( "Numeric", <<Modeling Type( {"Continuous"} ), min items( 1 ) ),
			xcols << set height (100),
			xcols << set items({"Height", "Weight"});
			
			//user to select one critical metric that has a specific target
			bby = Button Box( "Critical response", ycol << append( allcols << get selected ) ), 
			ycol = Col List Box( "Numeric", <<Modeling Type( {"Continuous"} ), min items( 1 ), max items( 1 ) ),
			ycol << set height (50),
			ycol << set items("Speed");
			
			//user to specify the target value
			Spacer Box( size( 1, 30 )),
			H List Box( Text Box( "Target value: " ), Number Edit Box( ytarg ) ), // data entry
			
			//user specify metrics to model
			bbmod = Button Box( "Model only", modcols << append( allcols << get selected ) ), 
			modcols = Col List Box( "Numeric", <<Modeling Type( {"Continuous"} ), max items (6) ),
			modcols << set height (100),
			modcols << set items({"Speed2", "Bench"});
			
			//user specify columns to separate by. Latter code works fine if this is blank, but breaks if it is populated
			bbby = Button Box( "By", bycols << append( allcols << get selected ) ), 
			bycols = Col List Box(  ),
			bycols << set height (100),
			bycols << set items( {"Position", "Position2"});
			
		),
		V List Box(
			bbOK = Button Box( "OK",
				varList = (xcols << Get Items);
				yList = (ycol << Get Items);
				ModList = (modcols << Get Items);
				byList = (bycols << Get Items);
				respList = yList || ModList;
			),
			bbCancel = Button Box( "Cancel" )
		)
	)
);
// stop the script if user chooses Cancel
If( win["Button"] == -1,
	Throw( "User Cancelled" )
);

//parse reponse column names into list
respExpr = expr(Y());
for(i=1, i<=nitems(respList), i++,
		insertinto(respExpr, parse(evalinsert(":name(\!"^respList[i]^\!")")))
);

// parse word '& RS' into effects list 	
varExpr = expr(Effects());
For( i = 1, i <= N Items( varList ), i++,
	Insert Into( varExpr, Parse( ":Name(\!"" || varList[i] || "\!")" || Char( "& RS" ) ) )
);

// parse two-way interactions into effects list
For( i = 1, i <= N Items( varList ), i++,
	For( k = i, k <= N Items( varList ), k++,
 		Insert Into( varExpr, Parse( ":Name(\!"" || varList[i] || "\!")" || Char( " * " ) || ":Name(\!"" || varList[k] || "\!")" ) )
));

//parse by column names into list
byExpr = expr(by());
for(i=1, i<=nitems(byList), i++,
		insertinto(byExpr, parse(evalinsert(":name(\!"^byList[i]^\!")")))
);

jslExprFit = expr(
	try(FitModelDialogWindow << closewindow());
	FitModelDialogWindow = Fit Model(
		_Y_,
		_EFFECTS_,
		//Keep dialog open( 1 ), //can turn on to see what the model is
		Personality( "Standard Least Squares" ),
		Emphasis( "Effect Screening" ),
		_BY_
		//Where( :Position == "qb" & :Position2 == "o")
		//in previous script versions, I used a where(:x == y) with a for loop to create a new window for each model
	);
);
substituteInto(jslExprFit, 
	expr(_Y_), nameexpr(respExpr),
	expr(_EFFECTS_), nameexpr(varExpr),
	expr(_BY_), nameexpr(byExpr)
);

fit = jslExprFit << run;
//create expressions to model responses with no goals

jslExprProfiler = expr(
	Profiler( 1, 
		Confidence Intervals( 1 ),
		Desirability Functions( 1 ),
		
		//sets desirability target of the crit response
		_Y2_ << Response Limits(
			{Lower( ytarg-1, 0.0183 ), Middle( ytarg, 1 ), Upper( ytarg+1, 0.0183 ),
			Goal( "Match Target" ), Importance( 1 )}
		)
	);
);

For Each({mod}, ModList,
	Insert Into(
		jslExprProfiler, EvalExpr(
			Expr(mod) << Response Limits({Lower(1, 1), Middle(2, 1), Upper(3, 1), Goal("None"), Importance(1)})
		)
	)
);

jslExprProfiler = Substitute(
	Expr(fit << _prof_),
	Expr(_prof_), Name Expr(jslExprProfiler)
);

jslExprProfiler = Substitute(
	Name Expr(jslExprProfiler),
	expr(_Y2_), nameexpr(yList[1])
);

profile = jslExprProfiler << run;

//creates a report and maximizes desirability
//doesn't work if there is anything in the by() field
pf_obs = fit << XPath("//OutlineBox[text()='Prediction Profiler']"); // list of lists so easiest to loop over them
For Each({pf_ob}, pf_obs,
	pf = pf_ob << Get Scriptable Object;
	pf << Maximize and Remember;
);

//create a data table with the parameters from the prediction profile
//in my other version (with the for loop a Where( :x = y[i]) in the model, I than had some code to concatenate tables together and change some formatting
dtout[1] = Report(fit[1])["Prediction Profiler"]["Remembered Settings"][Table Box(1)] << Make Combined Data Table
-Jarmo