Subscript-Slice Operator
The subscript operation, in square brackets after an expression «X»,
X[I = v]
returns the element of array «X» for which index «I» equals value «v». It's like subscripting in many other computer languages, but a lot more powerful because of Analytica's Intelligent Arrays features.
If «X» has more indexes than «I», this expression returns the slice (or subarray) of «X» for which I = v. The resulting slice is indexed by the other Index(es) of «X».
Subscript also works if v is an array, returning an array of all the slices of «X» for which I = v. The result is indexed by the Index(es) of «v», as well as any Index(es) of «X» other than «I». You can use this feature to subset, sort, or reindex an Array, as described below.
The slice operator X[@I = n] returns the slice of array «X» for the nth element of index «I». Slice is the same as Subscript, except that you put '@', the positional operator, before the Index «I». So it treats Index «I» as a list of positions -- i.e. integers 1 to m, where m = Size(I).
You can subscript or slice over multiple indexes together, for example:
X[I = v, J = u]
which returns the element (or slice) of «X» over indexes «I» and «J».
Subscript operator can work over an expression as well as a variable name, for example:
Sum(Y, J)[K = 2]
gives the slice of the sum of Y
over index J
for which K = 2
. It is equivalent to:
Sum(Y[K = 2], J)
If no element of «I» matches the value of «v» for subscript, it returns Null and a warning message (unless turned you turn off that warning).
Naming Indexes
In Analytica, you must name the Index(es) over which you are subscripting, e.g.
X[I = 1, J = 'B']
Other computer languages don't expect names in subscripts. They require you to list the indexes in the sequence used in their internal array representation:
X[1, 'B']
The advantages of identifying indexes by name, as in Analytica, include:
- you don't need to know or care about the sequence of the indexes in the array representation -- unlike spreadsheets, and most general languages, which need to you to know which index refers to rows, columns, or higher dimensions. In Analytica, it is your choice when displaying an array which index to show over the rows and columns; not something inherent in the underlying representation.
- you can list Index(es) in any sequence, ignoring any other Index(es) that the array may have. As you develop an Analytica model, or perform scenario or sensitivity analysis, it is common to add further indexes. Expressions using subscripts with named indexes are robust to such changes in the model, unlike conventional languages in which they break.
Naming indexes in subscripts is similar to the named parameter syntax available in Analytica for calling functions: You can specify names for each parameter and don't need to worry about their sequence or omitting unneeded optional parameters -- e.g.
FindInText(', ', text: X, caseInsensitive: True, repeat: True)
The difference is that naming indexes in subscripts is required, where naming parameters in function calls is optional as long as you insert all parameters in the standard sequence.
Implicit Indexing -- subscripting over an irrelevant index
If you Subscript (or Slice) an array «X» over an Index «I» that is not actually an index of «X», it simply returns the value of «X» unchanged. This is with Analytica's fundamental principle of of implicit indexing: If «X» is not explicitly indexed by Index «I», it is assumed to have the same value for each value of «I».
For example, suppose variable Markup
is the percentage mark-up applied by a retailer to each product line it sells. If it applies the same mark-up to all products, it can set Markup
to a single percentage. If it wants to apply different Markup
's to different product Categories or at stores in different Locations, it can define Markup
as an Array indexed by Categories
and Locations
. If Markup
does not vary over time, then it need not be indexed by Time
(or Month
or Year
) -- i.e. Markup[Year = 2014]
would have the same value for any value of Year because it isn't indexed by Year.
Positional and Associational indexing
The standard subscript operation, without the '@', uses associational indexing: It identifies the value of index «I» by association with the value or label of each element of the Index. The slice operator, with the '@', uses positional indexing. It identifies each slice by its position in index «I», an integer from 1 to m, where m = Size(I).
Usually, it is wisest to make sure that the values of an Index are unique, but occasionally you must use an Index that contains repeated values. If Index Name
contains the last name of all employees, some employees may have the same last name. In that case, subscripting by Name
may be ambiguous:
Salary[Name = 'Smith']
will return the salary of the first person named Smith
. In such cases, you can use positional indexing to select a slice for the person you want.
Salary[@Name = 42]
This assumes you know the ID (number in the Name
index) for each person.
For more, see Associative vs. Positional Indexing.
Subscript(x, i, v) and Slice(x, i, v) functions
The functions Subscript(x, i, v) and Slice(x, i, v) are exactly equivalent to x[i = v] and x[@i = v] respectively. They exist mostly for forward compatibility from the earliest releases of Analytica that did not include the operator syntax.
Subscripts and Array Abstraction
When «x» or «n» is atomic, the slice/subscript operations usually reduce the dimensionality of the array by one dimension (the exception being when the index is not a dimension of the original value). However, in general, «x» or n can be arbitrary expressions, and the result of «x» or «n» may be array-valued. In general, dimensions appearing in «x» or «n» will appear in the result, so the dimensionality may actually increase as a result of applying the slice or subscript operators.
As array abstraction operates over the «x» and «n» parameters, the slice and subscript operators offer extremely general and powerful lookup operators. Fairly complex operations equivalent to re-indexing, VLookup operations in spreadsheets, outer-joins in relational databases, and others are achieved quite simply and directly using subscript or slice operations. These operators are also used for sorting or re-ordering arrays, filtering rows, and other operations. This flexibility relieves Analytica from having to have a plethora of lookup functions often found in many other languages. However, mastering the full power of Slice and Subscript operators may take some time.
Re-indexing
Re-indexing is a common operation -- replace one index of an array by another index. If indexes I
and J
have the same elements in the same order with no duplicates, which might arise if J
is defined as:
J := CopyIndex(I)
This simple expression re-indexes as you want, if array A
is indexed by I
:
A[I = J]
The result is identical to A
, except that it is indexed by J
instead of I
.
You can do the same using the positional operator:
A[@I = @J]
The advantage of this is that it works if I
and J
are the same length but have different elements, or if the indexes contain duplicate elements.
Here is an example, to compute the outer-product of a vector, V
indexed by I
with its transpose:
Index J := CopyIndex(I)
-- createsJ
with identical length and values toI
.V * V[I = J]
The result is the outer product, with dimensions I
x J
.
Re-ordering
The slice and subscript operators can be used to re-order an array in various scenarios. Often, you will have an index J
, which is a permutation of index I
. In this case, a re-indexing re-orders the elements of A
, using just A[I = J]
.
A common example of this is sorting. Suppose Row
and Col
are both indexes of array A
, and you want to sort on the column A[Col = 'ROI']
. Here you would create a new index, SortedRow
, defined as
SortIndex(A[Col = 'ROI'])
and then compute the sorted array using A[Row = SortedRow]
.
Another example of re-ordering is the reversing of elements. In Analytica, the Dynamic function computes starting from the beginning of Time. In many dynamic programming applications, we would like to start from the final time point and work our way back. Often the way this is done is by reversing the the array so that we can use Dynamic, and then reversing the result once computed. This is one example where reversing an array is useful. To reverse an array, the Slice operator is the most convenient:
A[@I=size(I) - @I + 1]
It's often useful to shift an array left or right along a given index. The basic form of a shift-left or shift-right is A[@I = @I - 1]
or A[@I = @I + 1]
respectively. See the "out of range conditions" section below for how to handle the rightmost or leftmost element of the result, which otherwise end us as Null.
Filtering
Filtering extracts a subset of "rows" from an array along a given dimension. If we want a subset of slices along dimension I
, we need to define a new index, say I2
, containing a subset of I
. For example, to obtain the subset of people younger than 30, a subset of the People
index, we define the new index, YoungPeople
, and use it in the Subscript:
Index YoungPeople := Subset(PersonData[Trait = 'Age'] < 30)
PersonData[People = YoungPeople]
Multi-step lookup, outer-join, or VLookup
We often need to combine two arrays with different indexes, where one array contains values associated with elements of the other array. This combination is called an "outer join" for databases or VLookup (or HLookup) in Excel. For example, suppose we want to obtain the salary of each Person
, given the array, Salary_by_profession
, with the salary for each Profession
and another array, Profession_by_person
, with the Profession
for each Person
:
Profession Salary_by_profession 'Dock loader' $45,000 'Crane operator' $75,000 'Forklift driver' $32,000 Person Profession_by_person 'Joe Smith' 'Crane operator' 'Mark Jones' 'Forklift driver' 'Greg Johnson' 'Forklift driver' In Analytica, we can compute the salary by person with this simple Subscript expression:
Salary_by_profession[Profession = Profession_by_person]
The result is:
Person Salary_by_person 'Joe Smith' $75,000 'Mark Jones' $32,000 'Greg Johnson $32,000
When a subscript is Out of Range
If you evaluate
A[I = x]
when x is not an element of I, or
A[@I = n]
when n is not a valid position of I (e.g., n < 0 or n > size(I)), the result is "out of range" and usually returns Null. If the Null will appear in the result, it usually gives an "Out of range" warning. You can switch off this warning in two ways: You can unset the default preference in "Show Result Warnings" preference, or you can enclose the expression in the IgnoreWarnings() function
IgnoreWarnings(A[@I = @I - 1])
It is generally better to wrap the expression with the IgnoreWarnings function: It runs faster as a result of relieving Analytica from having to track whether the out-of-range condition impacts the final result.
Subscript Assignment
You may assign a value to a Subscripted element of a local variable using either the subscript (or slice) operator on the left-hand side of the := operator. For example:
v[Time = 5] := 0
When using Subscript assignment, the variable being assigned to MUST be local, even if used from an OnChange expression (or Button script). (See Assignment Operator :: :: for why you can't usually assign to a Global variable.)
Subscript assignment does array abstraction in the usual ways. In expressions
V[I = x] := y
V[@I = n] := y
any of the parameters «V», «x», «n», and «y» may be arrays with one or multiple Indexes. If local variable «V» is not indexed by «I» before the assignment, it will be after the assignment. Any slices for which I <> x (or @I <> n) will contain the same value(s) as «V» did before the assignment -- consistent with the Principle of Implicit Indexing. Only the assigned slice V[I = x] gets the new value, «y». If «y» has an Index (or Indexes) not already in «V», «V» will gain that Index (those Indexes) during the assignment. Again, each slice of «V» for which I <> x will have the original values of «V» repeated over the new Index(es) of «y».
For example, suppose «V» is indexed only by «I», but «y» contains the index «J». Then after evaluating
V[I = x] := y
«V» will be indexed by both «I» and «J». The slice corresponding to I = x will have a potentially different value for each element of «J», but the other slices along «I» will be constant across «J» (having the original value of «v»).
Subscripting and Meta-Inference
Meta-Inference is an advanced Analytica programming topic concerning computation about the objects and structure of the model itself. For example, it may compute the number of input variables, or find the common inputs of two variables. Meta-inference must handle the situation when an index contains object identifiers or expressions as elements. The default behavior of Subscript requires these variables and expressions to be evaluated, and for those that evaluate to scalars, both the scalar value and the original varTerm or expression are recognized by Subscript. This is demonstrated by the following example:
Variable A := 5
Variable B := 10
Index I := [ A, B ] { Global index, defined as List, with identifier A and B in each cell }
Variable C := Table(I)(3, 4)
C[I = 5] → 3
C[I = A] → 3
C[I = VarTerm(A)] → 3
IndexValue(I) → [A, B] { These are VarTerms -- handles to objects }
There is a distinct difference between the first two subscript examples and the third.
C[I = 5]
andC[I = A]
are performing associative lookup based on the evaluated result of the index elements, whileC[I = VarTerm(A)]
is using the raw un-evaluated IndexValue.When your intention is to reason about the structure of the objects in the model, the default functionality of subscript has a couple undesirable aspects. First, it forces the elements of the index to be evaluated. If those computations are expensive, they must be carried out before your meta-inference can proceed, and if there are errors or warnings while evaluating, those errors appear. Second, if some elements evaluate to varTerms, there may be ambiguities.
The following example demonstrates these concerns. The example collects the definition (e.g., for a report) of all the objects with definitions in module
Report_A
.Index allObjects := Contains of Report_A;
var v := allObjects;
Var allTitles := Title of v;
Var allDefns := Definition of v;
Index Objects_With_Defs := Subset(not IsUndef(allTitles));
allDefns[allObjects = Objects_with_Defs]
When the subscript operation in the last line is evaluated, all the objects listed in the
Objects
index will need to be fully evaluated. If a variable inModule Report_A
contains an error, the error will appear when the last line is evaluated. If a variable takes 5 hours to compute, this expression will trigger and wait for that computation, even though the result is not really needed here.To avoid these problems, you must tell Analytica to treat the
allObjects
index as a meta-index, i.e., as an index containing literal expressions or identifiers, whose raw unevaluated values are to be used in associative lookup, but whose evaluated element values are not to be used. This is done by changing the first line to:MetaIndex allObjects := Contains of Report_A;
- ...
For a global index object, to achieve this treatment, you must set the MetaOnly attribute to 1 (true), see MetaOnly for instructions. In the previous example, when the MetaOnly attribute set to true for Index
I
, the previous expressions evaluate asC[I = 5] → Null { not found }
C[I = A] → Null { not found }
C[I = VarTerm(A)] → 3
With MetaOnly set, index elements containing system variables such as INF, Null, True, False, Pi, etc., will not match to the underlying value, since these are actually system variable objects. See MetaOnly for more details.
See Also
Enable comment auto-refresher