Chapter 4: JSL Language Foundations

Overview

Fundamental Concepts

JSL Statement Form

Functions

Operators

Data Structures

Data Tables

Lists

Matrices

Associative Arrays

Namespaces

Name Resolution

Column Name Resolution

Conditional Logic

If()

Match()

Choose()

Looping

For()

For Each Row()

While()

Break() and Continue()

Summary

Overview

Now that your mind has started racing through all the possibilities, let’s go over some of the foundational basics of the JMP scripting language.  As we have said before, JSL is quite distinctive.  In this section, we discuss JSL concepts from the perspective of someone who might not consider themselves a programmer. 

Fundamental Concepts

JSL is a powerful language built upon the concepts of objects, operators, messages and arguments.  Let’s discuss each of these in detail.

JSL Statement Form

The basic premise of a JSL statement is that you send messages to JMP objects in order to tell them what to do.

The following is the basic form of a JSL statement:

object << Message( Argument );

Object

An object is the thing that you want to do something with.  For now, let’s think of objects as things that you can see, such as data tables, columns, or an analysis.  But keep in mind that there are less visible objects that can accept messages, as well.

In the preceding statement form, the object can be specified by its name or a reference.  For example, suppose the Big Class data table object reference was assigned to dt earlier in your script, such as when the table was opened.  You could use either dt or Data Table( "Big Class" ) to specify the desired data table as the object to receive the message.

Send Operator

The two less than signs (<<) together make up the Send operator.  Essentially, these two symbols point to the object that will accept the message specified on the right.  Keep in mind that there cannot be any space between these two symbols in order for JMP to correctly interpret them as the Send operator.

Message

A message is a command; it is the instruction that you want JMP to execute.  In other words, it is what you want JMP to do to, or for, the specified object.  Messages can also return a value.  For example, you might want to add a new column to a data table.  In this case, you would use the Data Table() function or reference as the object.  Next would be the Send operator, followed by the New Column() message instructing JMP to add the new column to the specified data table.  Because the New Column() message returns a reference to the column, you can also assign the reference when the column is created. Your JSL code might look something like the following, which instructs JMP to add a new column named “My New Col” to the Big Class data table:

col = Data Table( "Big Class" ) << New Column( "My New Col" );

Argument

An argument is supporting information for the message.  Arguments provide any detail necessary for JMP to complete the task described by the message.  In the preceding example, the name of the new column was provided as the argument.  Sometimes the argument requires the use of a specific option name, known as a named argument, which can have arguments of its own.  Expanding upon the New Column() code from the preceding example, the following includes a named argument called Format() to specify the format of the column:

col = Data Table( "Big Class" ) << New Column( 
   "My New Col" //argument
   "Numeric" //argument
   "Continuous" //argument
   Format( "Percent", 12, 1 ) //named argument
);

Messages can have multiple arguments. In some cases, all are required, or sometimes only some are required, while others are optional. Typically, options are separated by commas within the message and appear in a specific order.  But there are also messages that do not require any argument at all.  An example of a command that needs no argument is Clear Select

     dt << Clear Select;

JMP does not need any additional information in order to deselect the currently selected rows.

Semicolon

Finally, we separate JSL statements with the all-important semicolon (;). Remember from chapter 1 that the semicolon is actually known as the Glue operator.  This is a signal to JMP that indicates that the end of a JSL statement has been reached and that more might follow. Without an ending semicolon, you are likely to encounter interactive errors in JMP Error Alert windows, as well as in your log when JMP sees the next JSL statement.  Plus, your script will come to an abrupt halt. 

Fortunately, JMP does try to help you determine your problem.  In the case of a missing semicolon, the error message might suggest there was a missing semicolon, as in the following example: 

Data Table( "Big Class" ) << New Column( "My New Col" )
Data Table( "Big Class" ) << Add Rows( 1 );

When the preceding code is run, the following is returned in the log:

/*:

 

Unexpected "Data Table". Perhaps there is a missing ";" or ",".

Line 2 Column 1: Data Table( "Big Class" ) << Add Rows( 1 );

The remaining text that was ignored was

Data Table"("Big Class)<<Add Rows(1);

But there is so much more to discover about JSL. 

Functions

JSL functions are commands that perform a specific task.  Like messages, they can accept arguments and return a result.  However, they are not sent as a message to a specific object, but rather can be executed independently. There are both built-in JSL functions and functions that you can define yourself. 

Built-In Functions

To see a list of built-in functions, click the Help menu and select Scripting Index.  Change the drop-down selection to Functions, as shown in the following figure.

Figure 4.1 Scripting Index

Figure 4.1 Scripting Index

In the left column, select Programming.  As you select function entries in the middle column, notice the syntax for each is listed at the top of the window, on the right.  The symbols < > around an argument mean that it is an optional argument.  Functions that return a value appear with a variable followed by an equal sign before the function name.

Select All Functions in the left column of the Scripting Index.  Review each of the following functions and notice the types of arguments and return values.

Table 4.1 Examples of Built-In Functions

Function

Arguments

Return Value

Clear Log()

None

None

N Table()

None

The number of currently open data tables

Loc Max()

Matrix (Required)

The first position in the matrix of the maximum value found in the matrix

Concat Items()

List of Strings (Required)
String Separator (Optional)

A single string made of all the list elements separated by a space or the specified separator

Close()

Data Table Reference (Optional)
NoSave | Save(“path”) (Optional)

None

 

image shown here

Pay close attention to the return values from both messages and functions. Knowing what is returned is key to knowing how to proceed to the next step in your scripting project!

You can use functions within functions.  The return value for the inner function is the argument to the outer function.

In the following code, we have altered the example from the Scripting Index entry for the Abbrev Date() function.  Because the function returns a value, we can assign the result to a JSL variable. Notice that the argument for Abbrev Date() is another function called Today().  The Today() function returns the current date and time in a number of seconds since midnight, January 1, 1904.  Since that is not an easily recognizable value, we use it with the Abbrev Date() function to ensure that the value returned is easily understood as a date.

todayDate = Abbrev Date( Today() );
Show( todayDate );

After executing this code, the todayDate JSL variable now represents a string representation of the date the function was executed, which was returned by the Today() function.

//:*/

todayDate = Abbrev Date( Today() );

Show( todayDate );

 

/*:

 

todayDate = "11/4/2017";

Custom Functions

You can also create your own custom functions by using the Function() function.  This is very handy for a section of JSL script that you want to be able to reuse again and again.  The basic syntax is as follows:

functionName = Function(
     {argument list}, 
     <{local variables}>,
     Script
);

 

Suppose you want to present informational or warning messages to the user during the script execution.  The message displayed will change based upon specific conditions.  For example, your small section of JSL code to create a window with specific settings might look like the following: 

New Window( "Notice:",
   <<Modal,
   Lineup Box( N Col( 2 ), Spacing( 10 ),
          Icon Box( "Excluded" ),
          Text Box( 
                 "Selected column has all missing values.", 
                    <<Set Font Size( 12 ) 
 
          )
   )
);

Using a custom function enables you to define the function once, and then use it again and again simply by calling it by name and supplying any necessary arguments.  You don’t have to rewrite or copy and paste the same code in various places in your script.  This helps keep your script more readable, as well.

In the following code, we have placed the preceding script into a custom function.  The first argument indicates that there is one required argument, the message to be displayed.  Notice that the text in the TextBox() function is replaced by the name of the custom function’s argument, message.

alertMsg = Function( {message},
   New Window( "Notice:",
          <<Modal,
          Lineup Box( N Col( 2 ), Spacing( 10 ),
                 Icon Box( "Excluded" ),
                 Text Box( message, <<Set Font Size( 12 ) )
          )
   )
);

To call the function, simply use the function name and supply the text to be displayed. 

//Call the function later in the script
alertMsg( "Selected column has all missing values." );

Figure 4.2  Warning Message

Figure 4.2 Warning Message

A JSL string variable could also be used as the function argument.

//Use a JSL string variable to call the function later in the script
msg = "Column must be numeric.";
alertMsg( msg );

Figure 4.3  Warning Message as JSL Variable

Figure 4.3 Warning Message as JSL Variable

In the preceding example, the message argument is required.  We could alter the function definition slightly to include a default value for message, which makes the argument optional.

alertMsg = Function( {message = "Operation cancelled."},
   New Window( "Notice:",
          <<Modal,
          Lineup Box( N Col( 2 ), Spacing( 10 ),
                 Icon Box( "Excluded" ),
                 Text Box( message, <<Set Font Size( 12 ) )
          )
   )
);

Now when the function is called, you can specify your desired text for the message argument or accept the default text.

alertMsg();

Figure 4.4 Default Warning Message

Figure 4.4 Default Warning Message

image shown here

For the preceding example custom functions, in addition to the benefits already described, there is the added benefit of having a clean and consistent format for the messages that are presented to the user throughout your script.

Custom functions also enable you to specify that JSL variables should be local to the function.  What does it mean to be local to the function?  It means that the JSL variables and their values are not known to any other part of the script except for the function itself.  Why would you want to keep a JSL variable local to the function?  Well, you might not care–but you might.  Variables that are local to the function are like temporary variables in other languages.  The lifespan of the variable is limited to the execution of the function, as they do not persist after the function completes its script and returns. Using local variables is an easy way to keep your namespace cleared of any temporary JSL variable definitions. 

 

Local variables can be specified in one of two ways:

        {local variables} – Specifies a list of variable names.

        {Default Local} – Using {Default Local} makes all unscoped JSL variables local to the function. See the Namespaces section in this chapter for further details about scoping and working with namespaces.

The default return value of a custom function is the value returned by the last evaluated expression in the function’s script argument.  However, you can specify the value to be returned by the function upon completion by using the Return() function in your script argument. 

The Return() function is useful in a custom function for a couple of reasons:

        The Return() function can be used as a way to break out of a function.  For example, suppose you needed to ensure that the argument supplied to the function was a number.  You could use Return() to specify that a numeric missing value is returned instead of continuing with the script argument.

myFunction = Function( {x},

   If( !Is Number( x ),

          Return( . )

   );

   y = x * 5;

);

When the argument supplied to myFunction is a number, the value returned by the function is the value of y:

var1 = myFunction( 2 );

Show( var1 );

/*:

 

var1 = 10;

But when the argument supplied to myFunction is not a number, the value returned by the function is missing:

a = "abc";

var2 = myFunction( a );

Show( var2 );

/*:

 

var2 = .;

        The Return() function can be used to simply specify what is returned by the function.  For example, if your function generates a data table, it would be a good idea to return a reference to that data table.  In the following example, the function does the following:

   accepts a data table reference

   anonymizes the data

   closes the original table

   returns a data table reference 

Notice that the function’s argument is for a data table reference. By defining a default value, the function can be run with a specified data table reference, or the function will use the current data table, whatever table that might be at run time.

 

TestFn = Function( {dt = Current Data Table()},

   {Default Local},

   //If no table open, prompt to open

   If( N Table() == 0,

          dt = Open()

   );

   //Generate an anonymized data table

   anondt = dt << Anonymize();

   //Close the original table

   Close( dt, No Save );

   //Return a reference to the new table

   Return( anondt );

);

 

//Use default table reference to call the function

dtAnonymize = TestFn();

 

//OR

 

//Use a data table reference to call the function

fit_dt = Open( "$SAMPLE_DATAFitness.jmp" );

dtAnonymize = TestFn( fit_dt );

When you run a Show() function on the dtAnonymize JSL variable, you will see that it represents the new data table.  Without using the Return() option, the value stored in dtAnonymize would be a missing value.

//:*/

Show( dtAnonymize );

/*:

 

dtAnonymize = DataTable("Fitness Anonymized");

 

image shown here

Having trouble remembering when to use a comma versus a semicolon?  A general rule of thumb is to use a comma to separate items, and to use a semicolon to glue JSL statements together.  In the preceding examples, commas are used to separate arguments, and semicolons are used at the end of the JSL statements that make up the script argument within the function definition.

 

Operators

Earlier in this chapter we introduced the Send operator.  In general, operators are the symbolic representation of built-in JSL functions.  So just as the two less than signs (<<) make up the Send operator, there is also a corresponding Send() function that performs the same action. 

// New column created using the Send operator
dt << New Column( "Test", Numeric, Continuous );
 
// Equivalent action using the Send() function
Send( dt, New Column( "Test", Numeric, Continuous ) );

You might also recall that the semicolon used at the end of JSL statements is known as the Glue operator. The following demonstrates the equivalent use of the Glue operator and the Glue() function:

// Assignment of values to the var1 and var2 variables
var1 = 1;
var2 = 2;
 
// Equivalent action using the Glue() function
Glue( var1 = 1, var2 = 2 );

Operators are quite useful for comparing values, performing arithmetic, defining lists and matrices, and scoping JSL names, among other actions.  The following table organizes some of the more common operators by type with the corresponding built-in function. 

Table 4.2 Common Operators by Type

Operator Type

Operator

Function

Syntax

Arithmetic

+

Add()

x + y

Add( x, y )

 

-

Subtract()

x - y

Subtract( x, y )

 

*

Multiply()

x * y

Multiply( x, y )

 

/

Divide()

x / y

Divide( x, y )

Assignment

=

Assign()

x = 5

Assign( x, 5 )

 

+=

Add To()

x += 5

Add To( x, 5 )

 

++

Post Increment()

x++

Post Increment( x )

 

--

Post Decrement()

x--

Post Decrement( x )

Character

||

Concat()

a || b

Concat( a, b )

 

||=

Concat To()

a ||= b

Concat To( a, b )

Comparison

==

Equal()

a == b

Equal( a, b )

 

!=

Not Equal()

x != y

Not Equal( x, y )

 

Greater()

x > y

Greater( x, y )

 

>=

Greater or Equal()

x >= y

Greater or Equal( x, y )

 

Less()

x < y

Less( x, y )

 

<=

Less or Equal()

x <= y

Less or Equal( x, y )

Logical

&

And()

a & b

And( a, b )

 

|

Or()

a | b

Or( a, b )

 

!

Not()

!a

Not( a )

Programming

{}

List()

{a, b}

List( a, b )

 

[]

Matrix()

[x, y]

Matrix( {x, y} )

 

::

Index()

x::y

Index( x, y )

Scoping

:

As Column()

:columnName

As Column( "columnName" )

 

::

As Global()

::jslVariable

As Global( "jslVariable" )

 

Further details about each of these functions can be found in the Scripting Index. 

Data Structures

Maybe you’ve heard the term “data structure” before.  But if you haven’t, it might seem confusing.  Data structures are simply objects that organize and store data for easy retrieval.  You use data structures of all types in your everyday life all the time. 

Think about it.  When you need items from the grocery store, you write a list.  Some of you might even organize the list by the layout of the store so that you don’t forget anything or have to retrace your steps when you get there. (You know who you are!)  Or maybe you have several stores to go to.  You probably organize your list by store.  You wouldn’t put items found in the grocery store in the same list as items found the hardware store, right?

In JMP, data structures refer to containers of data that are held in memory.  According to the Scripting Guide, the basic data structures offered in JSL are lists, matrices, and associative arrays.  But data tables can be considered data structures, too.  Let’s review each of these.

Data Tables

When you import data into JMP, you can see all of your data organized by rows and columns.  In fact, tables are a common way to arrange, organize, and display data. 

In JMP, you can easily reference your data by the column name and the row number. Here is an example of referencing a cell using the column name and row number:

/* Open the Big Class sample data table
*/
dt = Open( "$SAMPLE_DATA/Big Class.jmp" );
 
/* :columnName[row] */
student = :name[1];

Data tables make it easy to summarize, sort, subset, and transform the data, as well.  Each of these actions can be performed by selecting the desired action from the Tables menu.  After the desired table is generated, you can copy the JSL script from the Source table scripts, just as we have done in the early chapters. 

Lists

Much like your shopping list, a JSL list is a container for storing items.  A list is rather flexible in the items it can hold.  Some common things stored in lists are strings, numbers, platform objects, and even other lists.  The items stored do not have to be of a single type.  Your list could contain any combination of these items.

Although a list is a JSL object, it does not have the same visual display that you see with a data table.  You can define a list by using either the List() function or the corresponding list operator, {}

mySchoolList = {"pencils", "paper", "notebook", "crayons"};
myWorkList = List( "travel mug", "coffee" );

But there are also times where JMP creates lists for you.  

Suppose you wanted to extract the names of all the students in the Big Class sample data table.  You can send the Get Values message to the column containing the names of the children. 

/* Open the Big Class sample data table */
dt = Open( "$SAMPLE_DATABig Class.jmp" );
 
/* Store the children's names in a list */
classNames = :name << Get Values;

 

Because the Get Values message returns a list, the result is a list of every value in the column.

//:*/

Show( classNames );

/*:

classNames = {"KATIE", "LOUISE", "JANE", "JACLYN", "LILLIE", "TIM", "JAMES", "ROBERT", "BARBARA", "ALICE", "SUSAN", "JOHN", "JOE", "MICHAEL", "DAVID", "JUDY", "ELIZABETH", "LESLIE", "CAROL", "PATTY", "FREDERICK", "ALFRED", "HENRY", "LEWIS", "EDWARD", "CHRIS", "JEFFREY", "MARY", "AMY", "ROBERT", "WILLIAM", "CLAY", "MARK", "DANNY", "MARTHA", "MARION", "PHILLIP", "LINDA", "KIRK", "LAWRENCE"};

Functions can also return lists.  An example of this is the Files In Directory() function.

fileList = Files In Directory( "$SAMPLE_DATAVariability Data" );

Notice that the result is a list of all the file names in the directory. 

//:*/

Show( fileList );

/*:

fileList = {"2 Factors Crossed.jmp", "2 Factors Nested.jmp", "3 Factors Crossed & Nested.jmp", "3 Factors Crossed.jmp", "3 Factors Nested & Crossed.jmp", "3 Factors Nested.jmp", "Gasket.jmp", "MSALinearity.jmp", "One Main Effect.jmp", "Wafer.jmp"};

 

image shown here

If there were any folders in the Variability Data directory, the folder names would also be included in the returned list.

When you run a platform, such as Bivariate, what is returned is a reference to the platform object.  When you use the By() option, the return value is a list of Bivariate[] objects. 

/* Open a sample data table */
dt = Open( "$SAMPLE_DATABig Class.jmp" );
 
/* Run a Bivariate of height by weight
*/
biv1 = dt << Bivariate( Y( :height ), X( :weight ) );
 
/* Run the same Bivariate using age as the By variable */
biv2 = dt << Bivariate( Y( :height ), X( :weight ),
   By( :age )
);

When you compare the biv1 and biv2 variables, you will see that using the By() option returned one Bivariate[] object for each level of the By variable.  In this case, that is one for each age represented in the data.

//:*/
Show( biv1, biv2 );
/*:
biv1 = Bivariate[];
biv2 = {Bivariate[], Bivariate[], Bivariate[], Bivariate[], Bivariate[], Bivariate[]};

There are lots of useful things you can do with lists, such as accessing specific values.  For example, suppose we wanted to fit a line on only the first Bivariate[] represented by biv2.  We would send the Fit Line message as follows:

biv2[1] << Fit Line;

An important thing to keep in mind is that it is possible, and often desirable, to send a message to all the items in a list. 

Continuing with the preceding example, suppose you wanted to add a horizontal line to show the mean of the Y for each Bivariate.  The Fit Mean message can be sent to a Bivariate[] object to add the desired horizontal line.  When you send that message to the list of Bivariate[] objects, the message is actually sent to every Bivariate[] object in the list.  So all you have to do is send the Fit Mean message to biv2, as shown here.

biv2 << Fit Mean;

Matrices

A matrix is a data structure used for storing numbers in rows and columns. It is useful for performing calculations efficiently.  Unlike lists, matrices are strictly made of numeric values.  You can make a matrix from a list, but all the values must be numeric.  A matrix containing only one row or only one column is also referred to as a vector.

m1 = [1, 2, 4, 8, 16];
m2 = Matrix( {2, 3, 5, 7, 11} );

JMP can create a matrix for you, as well.  For example, you can extract a data table into a matrix.  The Get as Matrix message can be sent to the data table to obtain a matrix of just the numeric values from the data table.

/* Open the Big Class sample data table */
dt = Open( "$SAMPLE_DATA/Big Class.jmp" );
 
/* Extract numeric values to a matrix */
m = dt << Get As Matrix;

Notice that the results contain the values for only the age, height, and weight columns.

/*:

 

 

[12 59 95,

12 61 123,

12 55 74,

12 66 145,

12 52 64,

...

The Get All Columns As Matrix message returns a matrix of all the data, including character columns. 

/* Extract all data values to a matrix */
m = dt << Get All Columns As Matrix;

The character data are represented as numbers that are incremented according to the unique values in the column.

/*:

 

Matrix( 40, 5 ) assigned.

You can also use matrices as the argument for certain functions and to perform calculations. 

Sqrt( m );
m3 = m1 + m2;
m4 = m*5;

There’s an awful lot to know about using matrices in JMP.  Therefore, if you are interested in learning more, we recommend that you carefully review the “Matrices” section of the “Data Structures” chapter in the Scripting Guide (Help Books Scripting Guide).

Associative Arrays

Associative arrays look a bit like lists but they are much more than that.  An associative array consists of keys and their values.  In a very general sense, you can think of the keys in an associative array as the column headings. The keys must all be unique, just as all column names in a data table must be unique.

A key’s values are similar to the rows of data in a column.  But with an associative array, the number of values for each key does not have to be the same.  This means that each key and its values are not necessarily related to another key’s values in the same way that a row of data is related.

Let’s look at an example.  Suppose we built an associative array containing data about a house.

homes = Associative Array();
homes["neighborhood"] = "Lakewood";
homes["city"] = "Riverside";
homes["type"] = "single family";
homes["bedrooms"] = {"master", "br2", "br3", "br4"};
homes["bathrooms"] = {"ba1", "ba2", "half"};
homes["sqft"] = 2250;
homes["garage"] = "Y";
homes["lotsize"] = 0.5;
homes["fireplace"] = "Y";
homes["basement"] = "N";
 
homes << Get Contents;

 

Notice that the keys are returned in alphabetic order, while the key values appear as assigned.

/*:

{

   {"basement", "N"},

   {"bathrooms", {"ba1", "ba2", "half"}},

   {"bedrooms", {"master", "br2", "br3", "br4"}},

   {"city", "Riverside"},

   {"fireplace", "Y"},

   {"garage", "Y"},

   {"lotsize", 0.5},

   {"neighborhood", "Lakewood"},

   {"sqft", 2250},

   {"type", "single family"}

}

Another way to visualize this would be as shown in Figure 4.5.  The outer container is the associative array.  Each key is represented with a darker background.  And the values for each key are listed below the key name with a white background.

Figure 4.5 Visualization of Associative Array

Figure 4.5 Visualization of Associative Array

Namespaces

The concept of namespaces is somewhat abstract to those of us who weren’t trained as programmers.  A quick internet search returns lots of analogies to convey the concept of namespaces.  For JMP, we think of namespaces in terms of a whiteboard.  The whiteboard itself is representative of a single instance of JMP.  Imagine that the whiteboard is divided into panes, like a window.  Each pane is a namespace where all the JSL variables you have defined, along with their current values, are listed.  When JMP is closed, all of the defined JSL variables are destroyed, regardless of the namespace where  they are defined. The white board is wiped clean.

image shown here

What does scope mean?  A scope gives context so that JMP knows where to find the definition of a name.  You can specify that a JSL variable is defined in a specific namespace.  You can also specify a data table as a scope so that JMP looks in the proper table for a column.

In JMP, there are a few types of namespaces. Here are the two most common:

        Global – Up to this point, our example scripts have created JSL variables in what’s known as the global namespace. The global namespace is the default namespace and spans the current instance of JMP. This means that when you define variable x in one script, that variable and its value is known to any other script that you run in that instance of JMP. You know you are using the global namespace when you have done nothing to specify that the JSL variables are limited in scope, or you have used the As Global() function or double colon ( :: ) scoping operator in front of your JSL variable names.  JSL variables that are defined in the global namespace are referred to as global variables.

        Here – The Here namespace spans the current Script window. You turn on the Here namespace by placing Names Default To Here( 1 ); at the beginning of your script.  You’ve probably noticed this at the beginning of the examples in the Scripting Index. JSL variables defined in the Here namespace are not available to other Script windows in the same instance of JMP. This means that variable x in one Script window can have a value of 5, while the variable x defined in a different script with the Here namespace turned on can have a value of 10. The two variables do not affect one another. With the Here namespace, there is no special scoping required. When you turn on the option at the top of the script, JMP looks for the value in the Here namespace only–unless you have specified a different scope. For example, if you use double colons in front of a variable, JMP looks in the global namespace for that variable definition, even though you have the Here namespace specified at the top of the script. This means that you can use JSL variables defined in either namespace in the same script, as long as you properly scope the global variables.

Let’s look at an example. In the following script, var1 is defined in the global namespace. Even without looking at the results in the embedded log, you know this because no scoping operators are used and the Here namespace is not specified at the top of the script. 

Figure 4.6 Global Namespace

Figure 4.5 Global Namespace

image shown here

Show Symbols() presents the current value of all JSL variables known to the Script window in the log, separated by the namespace in which the variables are defined.

Now, in a second Script window, we specify to use the Here namespace and define a variable with the same name as our var1 global variable, but with a different value. 

On line 4, var2 is defined in the global namespace using the double colon scoping operator with a string value. 

Finally, the variable var3 is defined by adding the values of the var1 global variable to the var1 variable defined in the Here namespace.

Figure 4.7 Here and Global Namespaces

Figure 4.6 Here and Global Namespaces

As you review the log, notice that there are now two variables defined in the Here namespace and two variables defined in the global namespace.

Now, let’s run the Show Symbols(); in the first script again.  Notice that the new global variable is shown, but the variables that were defined in the Here namespace of the second script are not shown.  This is because the variables defined in the Here namespace of the second script are known only by the second script.

Figure 4.8 Show Symbols() in the First Script Window

Figure 4.7 Show Symbols() in the First Script Window

Just to mix things up a bit, let’s open a third Script window and use the Here namespace.  Again, we define a variable named var1, which is the same name as a global variable and one that was defined in the second script’s Here namespace. 

As you review the results in the log, notice that the script only knows about the var1 variable defined in the current Here namespace and the variables defined in the global namespace.  None of the JSL variables defined in the Here namespace of the second script are known to any other Script window.  The JSL variables defined in the global namespace are known by all of the Script windows.

Figure 4.9 Show Symbols() in a Third Script Window

Figure 4.8 Show Symbols() in a Third Script Window

Name Resolution

What is meant by the phrase name resolution?  As you might guess, in the context of programming, it is the association or linking of a name to what it represents.  What is a name?  In JMP, a name is simply what you call something, whether it be a column, data table reference, report, string, number, function, expression, and so on. 

There are some rules that must be followed when assigning a name.  The complete list is included in the “Names” section of the “JSL Building Blocks” chapter in the Scripting Guide.  However, the main thing to know is that names start with an alphabetic character or underscore.  After the initial character, the name can include most characters that you would expect, such as alphabetic characters, numbers, spaces, and some special characters. 

image shown here

Though you can use many different types of characters in a name, we recommend using only alphanumeric characters and underscores.  Most importantly, names should make sense and work for your programming style.

After you have a name assigned, JMP needs to be able to resolve that name to what it represents.  This is where proper scoping comes in.  Scoping is simply a way to tell JMP where to find a name with its defined value.  For example, you might want to tell JMP to look for the name in the global or Here namespace or as a data table column. 

As we discussed previously, if the script has Names Default To Here( 1 ); at the top, JMP tries to resolve any unscoped names in the Here namespace.  If the script does not have Names Default to Here() turned on, or if the name is preceded by the double colon scoping operator, then JMP looks up the name in the global namespace.  If the name is not found, then an error is returned. 

There is a hierarchy that JMP uses to resolve names. We recommend that you review the “Rules for Resolving Names” section in the “JSL Building Blocks” chapter of the Scripting Guide for a full explanation.

Column Name Resolution

Proper scoping of column names is quite important so that JMP knows that the name is a column and also in which data table to find that column.  We have found that this is one area that many scripters struggle with.

The following bullets identify two methods for ensuring that a name is resolved to the correct column.  These methods have different return values, which is an important consideration when writing your script.

        As Column():  A scoping function that returns a reference to a column in the specified data table on the current row.  If the current row is not set, then a missing value is returned. This is because the current row in JMP is always zero unless specified otherwise.

As noted in the JSL Syntax Reference, the following are equivalent syntax:

dt:name
As Column(dt,“name”)

Uses:  As Column() and its operator equivalent are best used in messages and functions that control the current row.  It is useful when a value from the column should be referenced and you have a string variable containing the column name. Some examples are Select Where(), formulas, and For Each Row(), because each evaluate on every row of the data table.

        Column():  A function that returns a reference to a column in the specified data table as a whole unit, in its entirety.  The Column() function is quite flexible because it can be used to reference the column as a unit or be subscripted to reference a value in a column.  Because of this flexibility, you might find that this is your preferred function for referencing data table columns.  The syntax for each is as follows:

//Returns a reference to the entire column
Column( dt, "name" )
//Returns a reference to the value in the column on row i
Column( dt, "name" )[i]

Uses:  The Column() function is commonly used where entire columns are referenced or consumed, such as when sending messages to the column or in platform calls.  However, it is useful with a row subscript in order to return a single value from the specified row.  For example, suppose you are using a For() loop to loop through all the rows of a data table.  Because a For() loop does not control the current row, you could use the Column() function with the increment variable specified for the row number.  If using it in Select Where(), formulas, or For Each Row(), you can specify a row subscript using the Row() function, which references the current row, whatever that might be at that time.

//Returns a reference to the value in the column on the current row
 Column( dt, "name" )[Row()]

More information about For() loops can be found in the “Looping” section of this chapter.

 

image shown here

The data table reference is technically optional for both the As Column() and Column() functions.  However, it is recommended as a good scripting habit to prevent unexpected results.

 

Both the As Column() and Column() functions are flexible in the arguments that they accept:

        The actual column name as a string (in double quotation marks):

As Column( dt, "age" );
Column( dt, "age" );  

        The column name stored as a string in a JSL variable:

var = "age";
As Column( dt, var );
Column( dt, var );

        The column by index:

As Column( dt, 2 );
Column( dt, 2 );

        The column by index stored as a JSL numeric variable:

n = 2;
As Column( dt, n );
Column( dt, n );

Consider the example in Figure 4.9. As described earlier in this section, notice that the As Column() scoping methods return missing values because JMP is looking for the value in the Age column on the current row, which is zero.  Also notice that the Column() function returns a reference to the column as a whole, except when it is subscripted. 

Figure 4.10 Result of As Column() and Column()

Figure 4.9 Result of As Column() and Column()

Now let’s look at what happens when we change the context by sending column messages.  Either method for referencing columns can be used for sending messages to the column. For example, suppose you wanted to change the modeling type of a column or add a column property.

/* Change the modeling type to ordinal using a scoping operator */
dt:age << Set Modeling Type( "Ordinal" );
/* Add a column property using the Column() function to reference the column */
Column(dt,"age") << Set Property( "Row Order Levels",1 );

Figure 4.11 Before

Figure 4.10 Before

Figure 4.12 After

Figure 4.11 After

Notice in Figure 4.12 that the modeling type of the Age column is changed to Ordinal, and the Row Order Levels column property has been added. In this context, JMP is able to accept either method for referencing the column, because the message is valid for the column.

Name()

When saving a script from JMP, you might have seen a column name wrapped in a Name() function, also known as the Name parser.  This happens when the column name contains characters that do not follow JMP rules for column names.  For example, a column name that has a forward slash in the name would be Name( "Height/Weight" ).  See the Name() function in the Scripting Index for details about valid characters for use in column names. 

The Name() function is quite inflexible because the only acceptable argument is a string containing the actual column name.  For this reason, we recommend that you use either As Column() or Column() for referencing columns.

Conditional Logic

The ability to control your script based upon specific conditions is powerful.  You might want certain steps to happen based upon an expected value, but different steps to happen if a different value is encountered. 

JMP offers several built-in functions to help you write a script that conditionally directs the next action.  Here we discuss the If(), Match(), and Choose() functions, which we think you will find quite useful.

If()

The If() function is probably the most commonly used conditional function.  It is easy to understand and read in a script.  In addition, it enables you to make a variety of comparisons. 

The generic syntax is as follows.  Notice that each argument is separated by a comma.

If( condition, then, else );

Here is a simple equality example:

If(
   //Condition
   :Response == "Y",
   //Then - executes if the condition is true
   string = "Customer wants dessert.",
   //Else - executes if the condition is false 
   string = "Customer does not want dessert."
);

The preceding example specified only one condition: Response is equal to “Y”. But you can specify multiple conditions, each with its own individual actions.  In addition, the If() function’s flexibility enables you to specify expressions that make any comparison that would return a zero or nonzero result. In this case, zero is considered not true and any nonzero result would be considered true.  This means that you can do things such as check for a value in a list, make numeric comparisons, and even specify multiple criteria.  The following example checks multiple conditions and uses multiple comparisons:

    If(
        //Condition 1
         :Response == "Y" & :Age < 5
    ,
        //Then 1
         :Promo = "Free"
    , 
        //Condition 2
         :Response == "Y" & (5 <= :Age <= 12 | :Age >= 60)
    ,
        //Then 2
         :Promo = "$1 off"
    );

But what if you want more than one thing to happen when a condition is met?  No problem! Simply use semicolons between the then statements. To make the example more clear, we have placed the commas separating each of the arguments for the If() function on their own lines.

           If(
               Is Missing( :Topping ), //Condition 1
                    :Order = "Customer does not want ice cream."//Then
                      ,
                :Topping == 0, //Condition 2
                    :Order = "Plain ice cream."; //Then Stmt 1
                    :Allergen = "M"; //Then Stmt 2
                      ,
                :Topping == 1, //Condition 3
                    :Order = "Ice cream with hot fudge sauce.";//Then Stmt 1
                    :Allergen = "M"; //Stmt 2
                      ,
                :Topping == 2, //Condition 4
                    :Order = "Ice cream with caramel sauce.";//Then Stmt 1
                    :Allergen = "M/S"; //Then Stmt 2
                      ,
                :Topping == 3, //Condition 5
                    :Order = "Ice cream with fresh strawberries.";//Then Stmt 1
                    :Allergen = "M"; //Then Stmt 2
                      ,
               //Else
                :Order = "Ice cream with EVERYTHING!";//Else Stmt 1
                :Allergen = "M/S/P"; //Else Stmt 2
           );

Match()

You might be familiar with Match() as a handy interactive function used in a formula to capture all the unique values in the column automatically.  An example of how to do this can be found in the “Examples and Tutorials” section of the “Formula Editor” chapter in the Using JMP book.

But Match() can also be used in a script.  Though Match() is only useful for equality comparisons, a benefit is that the condition is not written for each comparison. This means that the code is a little more succinct than for an If() when used outside the context of a formula.

The following is a simple example that demonstrates recoding values in a column using Match()

     /* Open a sample data table */
     dt = Open( "$SAMPLE_DATAPopcorn Trials.jmp" );
     /* Recode oil amt values */
     For Each Row(
         Match( :oil amt,
             "little" //Value 1
         ,
      :oil amt = "1 tsp"//Then 1
         ,
             "lots" //Value 2
         ,
      :oil amt = "4 Tbsp"//Then 2
         )
     );

The following example is an adaptation of the last If() example from the previous section using Match() instead.  When you only have equality comparisons, you might find that you prefer using Match() because you only have to enter the value being compared instead of the entire equality condition.

       Match( :Topping,
           ., //Value 1
               :Order = "Customer does not want ice cream." //Then 
       ,
           0, //Value 2
               :Order = "Plain ice cream.";//Then Stmt 1
               :Allergen = "M";//Then Stmt 2
       ,
           1, //Value 3
               :Order = "Ice cream with hot fudge sauce."; //Then Stmt 1
               :Allergen = "M";//Stmt 2
       ,
           2, //Value 4
               :Order = "Ice cream with caramel sauce."; //Then Stmt 1
               :Allergen = "M/S";//Then Stmt 2
       ,
           3, //Value 5
               :Order = "Ice cream with fresh strawberries."; //Then Stmt 1
               :Allergen = "M";//Then Stmt 2
       ,
           //Else
           :Order = "Ice cream with EVERYTHING!";//Else Stmt 1
           :Allergen = "M/S/P";//Else Stmt 2
       );

Choose()

The Choose() function might be a little less commonly used, but it is particularly convenient when you are unloading values from a dialog.  With Choose(), the first expression should evaluate to an index.  It is this index that is used to determine which supplied result value is returned.

To see what we mean, let’s go through an example.  Suppose you wanted to query your users to select their preferred size of vehicles.  You could build a dialog that presents a question, as shown in the following code:

nw = New Window( "Car Preferences",
   <<Modal,
   <<Return Result,
   Outline Box( "What size vehicle do you prefer?",
       cat = Radio Box( {, "SUV","Sporty"} )
   )
);

When users make their selections, the result returned to nw will be something like the following:

nw = {cat = 4, Button(1)};

In this case, the value selected in the Radio Box was "Sporty".  Now you can use the return value, which represents an index, in your Choose() function.

Print(
   "The customer might like a " || 
   Choose( nw["cat"], //Resolves to 4
          "Honda Civic",      //1st result
          "Toyota Camry",     //2nd result
          "Chevrolet Tahoe"//3rd result
          "Ford Mustang"      //4th result 
   )
);

That means that the value provided to the Choose() function by nw["cat"] was a 4, which indicates that the fourth result value was returned by Choose(). Thus, the following was printed in the log:

"The customer might like a Ford Mustang"

Further details and examples of If(), Match(), and Choose() can be found in the “Conditional Functions” section of the “JSL Building Blocks” chapter in the Scripting Guide.

Looping

Looping in JMP refers to repeatedly executing a sequence of JSL code until some condition is met.  There are several types of loops, but each share this same overall purpose.  You can use loops in JMP to loop through lists, columns, and rows; to repeat processing a specific number of times; or to repeat processing until a condition is encountered. 

image shown here

A bit of warning at the start: Before you run any type of loop, we suggest that you save your work.  It is pretty easy to make a mistake and find that JMP is in an infinite loop.  Trust us.  Save your work first.

 

For()

The For() loop is flexible and probably the easier to understand of the methods in JMP for looping. For() loops enable you to continually process a set of JSL code until a specified condition is met.  The generic syntax for a For() loop is as follows:

For( start, whileExpr, increment, script );

start – The starting value of the increment variable.  This is evaluated only at the time the For() loop begins.

whileExpr The condition that specifies how long to continue the processing.  This condition is checked at the beginning (top) of each loop (iteration).

increment The amount to increase or decrease the increment variable for each iteration of the loop.  This occurs at the bottom of each iteration of the loop after the script has been executed.

script – The JSL code that should be processed repeatedly.

There are any number of reasons why you might need to use a For() loop.  The following is an example that demonstrates how to loop through a list of continuous columns to analyze. 

/* Open a sample data table */
dt = Open( "$SAMPLE_DATABig Class.jmp" );
/* Extract a list of the continuous columns in the table */
colList = dt << Get Column Names( Continuous, String );
/* Loop through the list and create a Oneway analysis of each */
For( i = 1, i <= N Items( colList ), i++,
   dt << Oneway(
          Y(Column( dt, colList[i] ) ),
          X( :age ),
          Means(1 ),
          Mean Diamonds( 1 )
   )
);

image shown here

There are many more examples of looping in Section 2 of this book.

For Each Row()

For Each Row() loops through every row of a data table.  The current data table is used, unless the optional data table reference is specified, which we do recommend doing.  And as we mentioned earlier, For Each Row() controls the current row during execution.  So there is no need for row subscripting.

The following example demonstrates how to use For Each Row() to assign a value to a column without using a formula.

/* Open a sample data table */
dt = Open( "$SAMPLE_DATATravel Costs.jmp" );
/* Create a new column to store the Month */
dt << New Column( "Month", Numeric, Nominal );
/* Loop though each row and set the value to the month
   of the Departure Date */
For Each Row( dt,
   dt:Month = Month( :Departure Date )
);

While()

The While() loop allows the loop to continue until the condition returns 0, or false.  Especially for the new scripter, we find that While() loops can be a little more challenging to grasp because you can easily end up in an infinite loop. 

Suppose you wanted to know the corresponding degrees in Fahrenheit for every degree Celsius from 0 to 100. The following example uses the While() loop to calculate the degrees in Fahrenheit and print the values to the log.

/* Initial value in Celsius */
c = 0;
/* Continue until c equals 100 */
While( c <= 100,
   f = c * 9 / 5 + 32;
   Print(
Char( c ) || " degrees Celsius = " || 
Char( f ) || " degrees Fahrenheit" );
   c++;
);

image shown here

Always save everything you’ve worked on before trying your loop the first time.  This will save you from some really bad headaches!  Wait… did we say that before? Yes, but we feel that strongly about it!

 

Break() and Continue()

At times, you might need to escape a loop iteration or get out of the loop entirely.  The Break() and Continue() functions are just for these situations. 

Break() – Breaks out of the loop and continues with the next JSL statement after the closing parenthesis of the current loop.

Continue() – Breaks out of the current iteration of the loop and returns control back to the top of the loop for the next iteration.

Both Break() and Continue() work in For(), For Each Row() and While() loops.

Summary

This chapter provides basic programming fundamentals.  Familiarizing yourself with these concepts will help you to better understand the solutions demonstrated in Section 2 of this book. 

Next, let’s look at some helpful resources and how to handle unexpected situations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset