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);