<< Click to Display Table of Contents >> SERIES |
![]() ![]() ![]() |
The SERIES (or SER) statement alters timeseries variables (often just called 'series'). Series variables have no starting symbol like % (scalars) or # (collections), but they may include a frequency indicator !, for instance x!q for x in a quarterly version. When Gekko starts up, the default frequency is annual, so x will be understood as x!a. A Gekko series can have annual (a), quarterly (q), monthly (m), weekly (w), daily (d) or undated (u) frequency.
Guide: timeseries For an easier introductory guide on Gekko timeseries, see this page. For an introductory guide on array-timeseries, see this page.
Data tracing Gekko timeseries statements support data tracing, making it possible to trace a series values backwards in time to its origins, see this page. |
Compatibility note regarding lags: Since series statements are calculated in a vector-like fashion in Gekko 3.0/3.1.x (in contrast to the Gekko 2.x versions), lags no longer automatically accumulate period-for-period, if the left-hand side variable is present with lags on the right-hand side (lagged dependent/endogenous variable). See more on the help page on dynamic statements. In Gekko versions >= 3.1.7, a statement like for instance x = x[-1] + 1 will need to be decorated with an indication regarding whether it should accumulate (<dyn>) or not (<dyn = no>), cf. examples on the linked help page. Note that for absolute accumulations like x <dyn> = x[-1] + 1, you may use x <d>= 1, x ^= 1 or dif(x) = 1 instead (and there are also variants regarding relative accumulations, see below). Note that setting <dyn> (or block series dyn = yes) unnecessarily entails a speed penalty and should therefore not be used unless needed. |
Gekko has to kinds of timeseries: normal series and array-series. Array-series allow the use of multidimensional indexes, for instance x['a', 'b'], picking out a sub-series with 'a' in the first dimension and 'b' in the second dimension. This could be for instance input-output cells, indicating the providing ('a') and receiving ('b') sector of intermediate goods. Array-series are quite similar to the map collection, but with special capabilities convenient for series data. The sub-series x['a', 'b'], or the shorter notation x[a, b], is internally a normal series. Therefore, an array-series can be thought of as a container that contains a collection of normal series that can be accessed via the indexes.
Normal series (including array-subseries) will use the global time setting regarding the time period they are calculated over (cf. TIME), unless a local time period is indicated in the <>-option field.
If []-brackets are used to the right of a series variable, for a normal series it may either indicate a date (x[2025] or x[2020q3]) or a lag/lead (x[-2] or x[+1]). For an array-series, the []-index is used to pick out elements, for instance x[a, b]. These may be combined, like x[a, b][2025]. Beware that lags and leads must start with the symbol - or +, respectively, otherwise they are not interpreted as leads (so if %i = 1, you must use x[+%i], not just x[%i]). For integer lags like x[-1] or x[-2], you may use the shorter form x.1 and x.2, too.
To put values into a series, you can use a list of values on the right-hand side, for instance x = 1, -2, 3; (the list of values could also be put inside a parenthesis). If you need to use blank-separated numbers, you can use the data() function, for instance x = data('1 -2 3');.
If you need to use alias names for series, you can use an #alias list to assign one name to another. Cf. the last part of this help page.
Note that a bank-less variable like for instance x on the right-hand side of a SERIES expression may be searched for in other databanks than the first-position databank, cf. the databank search page. Beware that the keys [Tab] or [Ctrl+Space] offer autocompletion on timeseries names (cf. here).
variable <period operator KEEP=... LABEL=... SOURCE=... UNITS=... STAMP=... DYN MISSING=...> = expression;
variable[date] = expression; //updating for one period
variable[indexes] = expression; //array-series
f(variable) = expression; //left-side function: dif(), pch(), dlog(), log()
series ?; //show an overview series count from all open databanks
period |
(Optional). Local period, for instance 2010 2020, 2010q1 2020q4 or %per1 %per2+1. |
operator |
The operator can be d, p, m, q, mp, l or dl. See the 'Operators' section below. |
KEEP= |
If <keep=p> is used, Gekko will keep the growth rate of the left-hand series intact after the period over which the series is updated. For instance, x <2020 2025 m keep=p> = 100; will add 100 to x over the period 2020-25. The keep=p setting makes sure that the growth rate of x regarding 2026 and later already existing observations in x is the same as before the update.
You may alternatively use <keep=d> in the same manner, to keep absolute changes rather than relative changes. Other keep operators than d or p are not implemented yet. |
LABEL= |
(Optional). Label (string) for the series, cf. DOC. |
SOURCE= |
(Optional). Source (string) for the series, cf. DOC. |
UNITS= |
(Optional). Units (string) for the series, cf. DOC. |
STAMP= |
(Optional). Stamp (string) for the series, cf. DOC. |
DYN |
(Optional). With this option, lagged dependent/endogenous variables like x[-1] in the expression x = x[-1] + 1; accumulate over time. Using <dyn> entails a speed penalty, so please do not use if not needed (in this particular case, x ^= 1;, x <d>= 1; or dif(x) = 1; could be used instead). Using the <dyn> local option, or equivalently putting the expression inside a block series dyn = yes; ... ; end; basically means that the expression is run n times successively, for each observation in the time period. Therefore, the following two are equivalent:
Using <dyn> x <2021 2023 dyn> = x[-1] + 1; //the <dyn> option implicitly runs the expression three times
Explicit x <2021 2021> = x[-1] + 1; //these three lines are equivalent to the use of <dyn> above
Starting from Gekko 3.1.7, statements like x = x[-1] + 1; must have an indication of whether they are to be run dynamically or not, cf. the page on dynamic statements for much more information on such 'dynamics errors' and how to handle them.
If the endogenous variable does not appear lagged on the right-hand side (like x[-1] or some other lag or lag function), running the expression in one go over 2021-23 yields the same result as running it in three tempi. Therefore, <dyn> is a waste of effort in such cases. It is not disallowed to combine <dyn> with time-operators like for instance ^ or <d>, or left-hand side functions like dif(), but such combinations can be rather hard to understand. |
MISSING= |
(Optional). With <missing = ignore>, SERIES will deal with missing array sub-series and missing data values like GAMS, treating them as zero for sums and mathematical expressions. The following options are set locally and reverted afterwards: option series array calc missing = zero; option series data missing = zero. See also the appendix page on missings. |
variable |
Left-side name |
expression |
Any expression |
In addition to = (assignment), the following variants can also be used (see the 'Operators' section below):
• += add to existing • -= subtract from existing • *= multiply to existing • /= divide from existing • ^= set absolute time change • %= set percent time change • #= add to percent time change |
•If no period is given inside the <...> angle brackets, the global period is used (cf. TIME).
•If a variable on the right-hand side of = is stated without databank, Gekko may look for it in the list of open databanks (if databank search is active, cf. MODE).
•Looping: with a list like for instance #m = a, b;, you may use y{#m} = 1/(1 + x{#m}); to calculate ya from xa, and yb from xb.
Normal series
Normal series look like the following example:
x = 100; |
In that case, 100 is assigned to each observation in the global time period (cf. TIME). Different values for each observation can be assigned like this:
time 2021 2023; |
The right-hand side of the x series in this example is a comma-separated sequence of simple numbers. Beware that if you want to use a sequence of expressions, you must use a list definition (and lists are enclosed in parentheses, cf. the y series above).
You may indicate a local time period in the <>-option field:
time 2020 2030; |
The local time period overrules the global period. If the three values corresponded to quarters for a quarterly series x, the statement x!q = 100, 110, 90; could be used. Alternatively, one could change the global frequency first like this: option freq q; x = 100, 110, 90;. In that case, you do not need to use the frequency indicator x!q explicitly, since !q is added implicitly to x in all places where the frequency is not stated. Note that the example above sets the label of x to 'Gekko-variable' (cf. also DOC).
The right-hand side of a series variable can be any legal Gekko expression that evaluates to a series, or anything that evaluates to a list of values of a suitable length. For list values, you may repeat them using rep, for instance y = 1, 2 rep 2, 3; is equal to y = 1, 2, 2, 3;. The last value in a list may be indicated with rep * which will repeat the item a suitable number of times, if the left-hand side is a series. For instance: y <2021 2025> = 1, 2, 3 rep *;, where the series will get values 1, 2, 3, 3, 3 over the period 2021-25.
Series names may be composed with {}-curlies, representing characters. For instance:
time 2010 2012; |
Result (the four PRT arguments are shown in different colors):
b a b xb xa xb 2010 200.0000 100.0000 200.0000 2.0000 1.0000 2.0000 2011 200.0000 100.0000 200.0000 2.0000 1.0000 2.0000 2012 200.0000 100.0000 200.0000 2.0000 1.0000 2.0000 |
Array-series
For an easier introductory guide on array-timeseries, see this page. The dimensions of an array-series need to be stated when it is constructed. Afterwards, indexes are used to refer to its elements:
x = series(2); //two dimensions
x[a, b] = 100; //or: x['a', 'b'] = 100;
x[a, o] = 200; //or: x['a', 'o'] = 200;
As seen, you may use the shorter x[a, b] instead of the more strict x['a', 'b'], when the elements are simple names, for instance not containing blanks or special symbols.
When dealing with timeseries given in some logical structure apart from time (for instance input-output cells), name composition is often used, for instance using the name convention xab and xao instead of x[a, b] and x[a, o]. Using array-series, there are convenient summing functions like sum(#j, x[a, #j]), summing up the second dimension of the array-series x (for instance, with #j = ('b', 'o'), the index x[a, #j] will correspond to x[a, b], x[a, o]). The same kind of logic can also be implemented with name-conventions, for instance sum(#i, xa{#j}), where xa{#j} will correspond to xab, xao. Still, array-series can be very practical in order to organize timeseries in some non-time structure/dimensions, and an array-subseries like for instance x[a, b] can be used in the same way as a normal timeseries xab. Also, with array-series there is no risk of name-collisions. For instance, x[ab, c] is clearly different from x[a, bc], whereas a simple naming convention will produce the same name, xabc. This can be remedied with, for instance, underscores (x_ab_c vs. x_a_bc), but in that case why not just use array-series?
Elements that are simple numbers represented as strings may have values added or subtracted, for instance x[#a+1], where #a could be a list of strings representing ages, like ('18', '19', ..., '80').
You may perform simple mathematical operations on array-series without indexes, for instance p * x in the above example, being equivalent to p[#i, #j] * x[#i, #j]. Such possibilities (array-series algebra) will be augmented.
The following tables presents the different operators:
Type |
Operator |
Example |
Result |
Note |
Absolute |
^= |
x ^= 1200; |
x = x[-1] + 1200 |
Same as <d> or dif(x) = 1200;. See also the <dyn> option. |
Relative |
%= |
x %= 3.5; |
x = x[-1]*(1+3.5/100) |
Same as <p> or pch(x) = 3.5;. See also the <dyn> option. |
Absolute |
+= |
x += 1200; |
x = x + 1200 |
Same as <m>. You can also use -= to subtract values. |
Relative |
*= |
x *= 1.03; |
x = x*1.03 |
Similar to <q>. You can also use /= to divide with values. |
Change in relative |
#= |
x #= 2.1; |
x = x[-1]*(x0/x0[-1] + 2.1/100) |
Same as <mp>. See also the <dyn> option. |
In the formula regarding the # operator, x0 is the original timeseries, and x is the new one. Alternatively, the so-called 'short' operators may be used:
Type |
Option |
Example |
Result |
Note |
Absolute |
<d> |
x <d>= 1200; |
x <dyn> = x[-1] + 1200 |
Same as ^=. or dif(x) = 1200;. See also the <dyn> option. |
Relative |
<p> |
x <p>= 3.5; |
x <dyn> = x[-1]*(1+3.5/100) |
Same as %= or pch(x) = 3.5;. See also the <dyn> option. |
Absolute |
<m> |
x <m>= 1200; |
x = x + 1200 |
Same as +=. You can also use -= to subtract values. |
Relative |
<q> |
x <q>= 3; |
x = x*(1+3/100) |
Similar to *=. You can also use /= to divide with values.
|
Change in relative |
<mp> |
x <mp>= 2.1; |
x <dyn> = x[-1]*(x0/x0[-1] + 2.1/100) |
Same as #=. See also the <dyn> option. |
Log |
<l> |
x <l>= 5; |
x = exp(5) |
Same as log(x) = 5;. |
Relative |
<dl> |
x <dl>= 0.035; |
x <dyn> = x[-1]*exp(0.035) |
Same as dlog(x) = 0.035;. |
Left-side functions:
Type |
Option |
Example |
Result |
Note |
Absolute |
dif() |
dif(x) = 1200; |
x <dyn> = x[-1] + 1200 |
Same as ^= or <d>=. See also the <dyn> option. You may use diff() as synonym. |
Relative |
pch() |
pch(x) = 3.5; |
x <dyn> = x[-1]*(1+3.5/100) |
Same as %= or <p>=. See also the <dyn> option. |
Log |
log() |
log(x) = 5; |
x = exp(5) |
Same as <l>=. |
Relative |
dlog() |
dlog(x) = 0.035; |
x <dyn> = x[-1]*exp(0.035) |
Same as <dl>=.
|
Create a deflated price index (not an existing variable):
time 2010 2013; |
Create a series with a given growth rate:
create x; //only necessary in sim-mode |
Change compared to the reference bank:
create x; //only necessary in sim-mode |
In the last PRT, @x is short for ref:x, that is, x from the Ref databank.
To set for instance a growth rate equal to another growth rate, you can use the <p> operator:
y <p> = pch(x); //or: y %= pch(x), or: pch(y) = pch(x), or: y = y[-1] * x/x[-1] |
To change only one period, you may use:
tg[2020] = %v; //%v is a scalar value |
This will only set the 2020-value, and will work regardless of what the global sample might be. Used like this, at the same time stating a local period inside the <>-option field is not legal (or meaningful). Note that when using SERIES with []-brackets like this, a scalar value (or expression) is expected on the right-hand side of the equation. The above statement is functionally equivalent to the following:
tg <2020 2020> = %v |
The $-operator can be used to "control" expressions, like an implicit IF-statement. For instance:
reset; |
In the y1-statement, y will be 3 if the condition is true (if 'b' is a member of #m and %v has the value 10), and else y will obtain the value 0. In the y2-statement, the whole statement is skipped if the condition is false, and in that case y will not exist at all. Note this conceptual difference regarding $-condition to the left of or to the right of =.
The $-operator can be used to switch between values inside a period, for instance:
reset; |
The y1-statement illustrates the use of the $-operator for switching, and y1 will contain the numbers 110, 110, 111, 111, 110 (the 10's are replaced with 110, and all other values are replaced with 111). The last y2-statement illustrates how to perform the same operation using the iif() function. The operation could alternatively be performed with FOR and IF statements, looping explicitly over each period, but using the $-operator or the iif() function is much more convenient here.
Adding 1000 to a series jx can be done with the + operator, or the <m> option:
jx <2010 2010> += 1000; |
Instead of updating with raw numbers, you may use scalar variables instead (in this case, you have to use parentheses to indicate the list, because the elements are not simple numbers):
%f1 = 0.02; |
Using a list #m:
time 2010 2012; |
Note that 1.02 is implicitly used for all three periods (you do not need to write (1.02, 1.02, 1.02)). Note also the {}-curlies in {#m} = (100, 80, 110);. Without the curlies, #m would become a list of the three values 100, 80, 110, which is not the intention. In <2010 2012> {#m} *= 1.02;, without the curlies, the expression would fail, since a list does not implement the *= operator. Finally, in prt {#m};, without the curlies, Gekko would print the strings 'x1' and 'x2', not the series x1 and x2.
If you use <keep=p>, Gekko will keep the same growth rate in the data, after the time period where the variable is changed.
y <2007 2007 m keep=p> = 0.01; |
This way, y has 0.01 added in 2007 (because of the <m> operator), and in all the subsequent years of data, the old growth rate in y is preserved (which is what the keep option does). Note that keep=p updates the series outside of the indicated period.
In sim-mode, you must first create a non-existing variable, but if the variable name starts with 'xx', it is automatically created:
xxvar = 27; //works in sim-mode without prior create |
If convenient, you may also use wildcard lists:
{'j*'} <2010 2010> = 0; |
This sets all variables in the Work databank beginning with j to 0, for the given period.
You may set timeseries in other databanks than Work, for instance:
bank1:x = 100; |
This will set the variable x to 100 in the bank bank1 (cf. the OPEN statement), provided that the bank is unlocked. If you need to change timeseries in the reference databank, you may use the @-indicator for convenience:
@fy *= 1.03; |
This will increase the variable fy in the Ref databank with 3% over the global sample period.
Examples, array-series
Array-timeseries comply rather tightly with GAMS syntax, to interface more naturally with GAMS files (gdx). But array-timeseries have many other uses, for instance when downloading multi-dimensional data, or reading data from px-files (PC-Axis), cf. the IMPORT statement.
An array-series can be thought of as a super-series, containing sub-series in one or more dimensions, where these sub-series are accessed with (lists of) simple names. For instance, x may be a one-dimensional array-series, containing the sub-series x[a] and x[b]. These sub-series are like any other normal timeseries, just stored inside the array-series. In this sense, x can be thought of as a kind of special map, allowing multiple dimensions, and designed for series access. In older versions of Gekko (prior to 2.3.1), such dimensions would typically be handled by means of naming conventions, for instance using normal series x_a, and x_b instead of x[a] and x[b].
You may use single quotes for element access, so x[a] = x['a'], x[b] = x['b'], etc. Using quotes is the strict form, and using quotes, the element names may include any characters, for instance x['ab ? x22'].
The following is an example of the use of array-series. In the example, #i and #j are lists of strings containing the sets of names spanning the dimensions, in this case a 3 x 3 structure [#i, #j] like this:
[a, a] [a, b] [a, o]
[b, a] [b, b] [b, o]
[o, a] [o, b] [o, o]
The last part of the example below illustrates how to use default sets (via the map #default). In order for default sets to work, the array-series must contain domain information.
#i = a, b, o; //or: ('a', 'b', 'o') |
The summing up with sum() is sometimes called a 'roll-up operation', aggregating rows/columns, whereas for instance x[a, #j] would be a so-called 'slice operation'. The in-built functions getelements() and subseries() can be practical if you need to know more about the elements of the sub-series of a given array-series x (for instance, the sub-series x[a, b] has element a in the first dimension, and element b in the second dimension).
You may $-condition on a variable that reflects sparsity in two or more dimensions, too (you may for instance have 193 countries, where only a subset of all combinations trade with each other). The following example illustrates two-dimensional sparsity:
x = series(2); |
In y1, the condition is on the array-series e, whereas in y2 it is on the nested list #e (in GAMS, a nested list would correspond to a multidimensional set). You may alternatively move the dollar condition to the right, for instance y1 = sum((#i, #j), x[#i, #j] $ (e[#i, #j]));. Note that array-series are a bit more lenient regarding the user of integers, which are automatically interpreted as their corresponding strings. Also beware regarding the array-series example that there are settings that makes it possible to omit stating the 0 combinations for e, if these are numerous (option series array calc missing = zero), cf. this page.
Regarding domains, it is easy to remove a single element from a list of strings with for instance #i.remove(%s), where %s is a string. To remove several elements from a list of strings, you may use #i - #j. Hence these $-conditionals can be used for easy removal/skipping of elements:
z = sum(#j, x['a', #j] $ (#j in #j.remove('b'))); |
To print an array-series x1, use either:
disp x1; //shows info regarding dimensions, elements, etc. |
If you need non-existing array-timeseries elements to be implicitly understood as having value 0, you can use option series array calc missing = zero;. In that case, you may use for instance sum(#j, x1[#i, #j]), even if some of the combinations (sub-series) of #i and #j do not exist in x1.
In general, you may print or plot an array-series without indicating the dimensions. You can assign lists to array-series dimensions and afterwards control which elements are printed/plotted via a special map with the name #default. This can be practical if you typically only want to see some of the elements of an array-series, but not all.
#s = ('e1', 'e2'); |
It can often be practical to put the #default map into the Global databank (that is: global:#default = ...), so that it is generally available irrespective of potential OPEN or READ statements. The #default map shown above will restrict all printing/plotting of array-series that have #s assigned to a dimension as its domain.
If you need to put "normal" timeseries into an array-series or vice versa (or if you are interfacing with multidimensional GAMS/gdx data), you can use the following example:
reset; |
Alias names
It is possible to assign one variable name to another via a special list with the name #alias. This can be practical if, for instance, the users are used to one kind of variable names, but are for instance using a model with another kind of variable names.
option interface alias = yes; //this option must be set |
The #alias list could look like the following file:
--------------- alias.lst -------------------- |
This file is read as a list of lists, equivalent to #alias = (('x1', 'c[s]'), ('x2', 'c[b]'), ('x3', 'y'));. The print prints out x1, x2, and x3 as 100, 200, and 300, respectively, even though the 'real' values are stored inside c[a], c[b], and y.
In Gekko 3.0, series operations are handled more vector-like than in Gekko 2.x. This affects the use of lags in expressions with lagged dependent/endogenous variable:
time 2021 2024; |
The last statement is probably easy enough to understand, but the second-last may seem strange. See much more on this, including illustrations, on the dynamic statements page. There are three easy ways of getting accumulations to work without using the <dyn> tag:
time 2021 2024; |
Still, in some cases it my be practical to be able to use the formulation x = x[-1] + 1 and have it accumulate, for instance if the equation originates from a model equation. For such cases, either use the local option <dyn>, or alternatively use a BLOCK like block series dyn = yes; ... ; end;. Example:
time 2021 2024; |
To guard against errors of this type where the user forgets to use <dyn> or a block series dyn on an expression like x = x[-1] + 1, from version 3.1.7 and onwards, Gekko will check this for the presence of lagged dependent/endogenous variables on the right-hand side and issue a 'dynamics error' if an expression like x = x[-1] + 1 or similar is used without considering the dynamics question. As stated above, there is much more info on this on the dynamic statements page.
Note
See the page with syntax diagrams if the basics of names, expressions, etc. is confusing.
In addition to operators += and *=, you can also use their inverse counterparts: -= and /=. So x -= 2; is the same as x = x - 2;, and x /= 2; is the same as x = x / 2;. This is standard in most computer languages. But please note that x ^= 2; is not the same as x = x ^ 2;, that is, x in the second power.
If any of the right-hand side variables are not found (searching depends upon mode), the statement will exit with an error, unless you set option series array calc missing = ...; or option series normal calc missing = ...;. If some of the variables have missing values (shown as 'M' when printing), the left-hand side will become missing as well (for the periods affected).
You may use m() to indicate a missing value, for instance y = m();.
Regarding $-conditions to the right of =, these always evaluate to 0 if the condition is false. On the other hand, regarding a $-condition to the left of =, the whole statement is skipped if the condition is false. Note however that such skipping does not apply intra-series regarding $ to the left of =. So in a statement like y $ (x > 100) = 2 * x; the y series will attain the value 0 in the periods where x <= 100, and the value 2 * x otherwise. If you need y to remain untouched when x <= 100, you may instead use y = iif(x, '>', 100, 2 * x, y);, cf. the iif() function.
Regarding variable types and the Gekko type system, see the VAR section. In this appendix, variable assignment rules, including variable types, is explained in more detail.
A Gekko array-series can be though of as a nested dictionary/map, cf. this:
•Gekko: m = series(2); m[a, x] = 1; m[a, y] = 2; m[b, x] = 3; m[b, y] = 4; prt m[a, x];
•R: nested named list, m <- list(a = list(x = 1, y = 2), b = list(x = 3, y = 4)); print(m$a$x); print(m[['a']][['x']]);. The elements here are numbers and not sub-series, but the idea is similar.
•Python: nested dict.
Related options
OPTION freq = a; [a|q|m|d|u]
OPTION databank create auto = no; [yes|no]
OPTION series array calc missing = error; [error|m|zero]
BLOCK series dyn = no; [yes|no]. This option can only be set using a block
OPTION series dyn check = yes; [yes|no]
OPTION series normal calc missing = error; [error|m|zero]
Related functions
For array-series: getdomains(), subseries().
Related statements
CREATE, DOC, PRT, VAL, EXPORT<gcm>, EXPORT<flat>