Data structures

Arrays

Arrays are one of the most important data structures for economists. They are equivalent of vectors and matrices in Matlab or R. Arrays are multi-dimensional objects containing numbers (or different objects).

A vector is a one-dimensional array. To get a row vector you use the following convention:

A = [1 2 3]
1×3 Matrix{Int64}:
 1  2  3

You get column vectors by using ; as the separator of elements:

B = [1; 2; 3]
3-element Vector{Int64}:
 1
 2
 3

All elements of arrays must be of the same type. In case of type incompatibility across elements, coercion is performed:

@show c = [1 π 2.3]

@show typeof(c) #It's Float64 despite the first element being an integer.
c = [1 π 2.3] = [1.0 3.141592653589793 2.3]
typeof(c) = Matrix{Float64}
Matrix{Float64} (alias for Array{Float64, 2})
X = [1 2 3;
     3 4 5]
2×3 Matrix{Int64}:
 1  2  3
 3  4  5

It is a good practice to initialize arrays before assigning values to their elements. To create an empty one-dimensional array with K elements of type TYPE you use Array{TYPE}(undef, K). Below an illustrating example:

Array{Float64}(undef, 10)
10-element Vector{Float64}:
 3.5e-323
 8.4e-323
 9.4e-323
 1.1e-322
 1.2e-322
 1.24e-322
 1.43e-322
 1.5e-322
 1.53e-322
 5.0e-324

Argument undef means that nothing particular is assigned to all elements. Instead, existing values stored previously in the memory are assigned.

To create an array of dimensions \(K\times N\times S\times W\) you use Array{TYPE}(undef, K, N, S, W). Below an illustrating example:

Array{Int}(undef, 2, 3, 2, 2)
2×3×2×2 Array{Int64, 4}:
[:, :, 1, 1] =
 1  3  5
 2  4  6

[:, :, 2, 1] =
 7   9  11
 8  10  12

[:, :, 1, 2] =
 13  15  17
 14  16  18

[:, :, 2, 2] =
 19  21  23
 20  22  24

To get an array with all elements constant, you use command fill:

A = fill(π^2, 4, 3)
4×3 Matrix{Float64}:
 9.8696  9.8696  9.8696
 9.8696  9.8696  9.8696
 9.8696  9.8696  9.8696
 9.8696  9.8696  9.8696

To get an object of the same size and data type, function similar can be useful. Elements of the new objects will contain information previously allocated in the memory.

B = similar(A)
4×3 Matrix{Float64}:
 5.0e-324  2.5e-323  4.4e-323
 1.0e-323  3.0e-323  5.0e-323
 1.5e-323  3.5e-323  5.4e-323
 2.0e-323  4.0e-323  6.0e-323

Arrays with zero elements can be constructed with zeros:

zeros(3,2)
3×2 Matrix{Float64}:
 0.0  0.0
 0.0  0.0
 0.0  0.0

To get an identity square matrix of dimension \(K\) function I from package LinearAlgebra can be used:

using LinearAlgebra
I(5)
5×5 Diagonal{Bool, Vector{Bool}}:
 1  ⋅  ⋅  ⋅  ⋅
 ⋅  1  ⋅  ⋅  ⋅
 ⋅  ⋅  1  ⋅  ⋅
 ⋅  ⋅  ⋅  1  ⋅
 ⋅  ⋅  ⋅  ⋅  1

Simple array operations

Except square-bracket notations, accessing arrays in Julia is almost one-to-one analogy to Matlab:

A = [1 2 3 4 5;
     6 7 5 9 12]
2×5 Matrix{Int64}:
 1  2  3  4   5
 6  7  5  9  12
size(A)
(2, 5)
A[1,:] #1st row
5-element Vector{Int64}:
 1
 2
 3
 4
 5
A[:,1] #1st column
2-element Vector{Int64}:
 1
 6
A[:,[1,3]] #1st and 3rd column
2×2 Matrix{Int64}:
 1  3
 6  5
A[:,3:end] #from 3rd to the LAST columns
2×3 Matrix{Int64}:
 3  4   5
 5  9  12
A[1,3:end] #1st row and from 3rd to the last columns
3-element Vector{Int64}:
 3
 4
 5

Note

What can be quite natural for users of such languages as R, Matlab, or Fortran, array indexing in Julia starts from \(1.\) This means that you get the fist element of an array X by calling X[1]. This is different from such languages as Python, C++, or Java, where array indexing starts from \(0.\) For instance, in Python the fist element of an array X is obtained by running X[0]. There are arguments for and against both conventions, which very often leads to heated debates, comparable to the editor war.

Just like with scalars, arithmetic operations on arrays of the same dimensions work in an analogous way:

A = [1 2;3 4];
B = I(2);
A + B
2×2 Matrix{Int64}:
 2  2
 3  5
A*A
2×2 Matrix{Int64}:
  7  10
 15  22
A
2×2 Matrix{Int64}:
 1  2
 3  4
A' #transposition
2×2 adjoint(::Matrix{Int64}) with eltype Int64:
 1  3
 2  4
A^(-1) #invert matrix
2×2 Matrix{Float64}:
 -2.0   1.0
  1.5  -0.5
maximum(A) #notice that `max`` works in a bit different way than in Matlab!
4
A^(-1)*A 
2×2 Matrix{Float64}:
 1.0          0.0
 2.22045e-16  1.0
kron(A,I(3))
6×6 Matrix{Int64}:
 1  0  0  2  0  0
 0  1  0  0  2  0
 0  0  1  0  0  2
 3  0  0  4  0  0
 0  3  0  0  4  0
 0  0  3  0  0  4

Nonetheless, if we try to add an object of incompatible dimensions (including scalars), we will get an error message, just like in an example below:

A + 1
MethodError: no method matching +(::Matrix{Int64}, ::Int64)
For element-wise addition, use broadcasting with dot syntax: array .+ scalar
Closest candidates are:
  +(::Any, ::Any, ::Any, ::Any...) at operators.jl:560
  +(::T, ::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} at int.jl:87
  +(::Rational, ::Integer) at rational.jl:288
  ...

Stacktrace:
 [1] top-level scope
   @ In[27]:1
 [2] eval
   @ ./boot.jl:360 [inlined]
 [3] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
   @ Base ./loading.jl:1094

This can be perceived as a particularly bizzare and excessively formal thing. That said, at the development stage there were reasons for this. Furthermore, notice that in Mathematics this type of operations, \(\mathbb{A}+1\), in most cases is also forbidden. One (extremely inefficient but didactically useful) solution to this problem is to increase the size of the smaller object by using repeat. This function constructs an array by repeating the input array a given number of times in each out of the provided dimension.

Thus, to compute \(\mathbb{A}+1,\) we have to increase the size of \(1\) in such a way that the new object fits the size of \(\mathbb{A}\). As we remember the size of \(\mathbb{A}\) is \(2\times 2.\) Just to double check, let’s use function size.

size(A)
(2, 2)

Below you can find repeat at play:

repeat([1], 3)
3-element Vector{Int64}:
 1
 1
 1
repeat([1], 3, 2)
3×2 Matrix{Int64}:
 1  1
 1  1
 1  1
repeat([1], 4, 3, 2)
4×3×2 Array{Int64, 3}:
[:, :, 1] =
 1  1  1
 1  1  1
 1  1  1
 1  1  1

[:, :, 2] =
 1  1  1
 1  1  1
 1  1  1
 1  1  1
repeat([1; 2], 2, 3) #input argument: column vector
4×3 Matrix{Int64}:
 1  1  1
 2  2  2
 1  1  1
 2  2  2
repeat([1 2], 2, 3) #input argument: row vector
2×6 Matrix{Int64}:
 1  2  1  2  1  2
 1  2  1  2  1  2

Consequently, we are in the position to compute \(\mathbb{A}+1:\)

A + repeat([1], 2, 2)
2×2 Matrix{Int64}:
 2  3
 4  5

An addition of two arrays of different sizes can be conducted analogously:

A + repeat([0; 1], 1, 2)
2×2 Matrix{Int64}:
 1  2
 4  5

Don’t do this in your daily workflow

A much more recommended and efficient way to operate on objects of different sizes is to use broadcasting or mapping. I discuss it in more details in one of the further secions.

Assignment operator (=) and arrays: easy for Pythonistas, confusing for Matlabians

What can be quite confusing at first for people with the Matlab background is how the assignment operator (=) works with arrays in Julia. In Matlab if we want to create a matrix V1 that is a copy of another matrix V0 it is enough to write:

    V1=V0

Henceforth, there are two separate matrices in the memory, V1 and V0. Any changes in either of those will not affect the other one. In Julia this works in a very different way. Exactly like in Python, running V1=V0 will create a new pointer to the memory allocated for V0. Both objects, V1 and V0, share the same information. Consequently, any changes in the memory through accessing V1 will affect the data pointed by V0, and vice versa. Below you can find an illustration of this language feature.

@show V₀ = [1 2 3];
V₀ = [1 2 3] = [1 2 3]
@show V₁ = V₀;
V₁ = V₀ = [1 2 3]

Running V₁ = V₀ will create a new pointer to the memory associated with V₀. Now changes in either of those objects will affect both pointers:

@show V₁[1] = 420;
@show V₁;
@show V₀;

@show V₀[3] = 128;
@show V₁;
@show V₀;
V₁[1] = 420 = 420
V₁ = [420 2 128]
V₀ = [420 2 128]
V₀[3] = 128 = 128
V₁ = [420 2 128]
V₀ = [420 2 128]

This feature of Julia can be especially confusing in iterative procedures with updating values, such as value function iterations. In this case, trying to create a copy of one array but making rather a new pointer instead will result in meeting the convergence criterion immediately after the first iteration (terrible sentence). Obviously, this will not be correct.

A remedy to this issue can be simply addressed by using deepcopy, which creates a new object in the memory that contains the same information as the copied object.

@show V₀ = [1 2 3];
@show V₁ = deepcopy(V₀);
V₀ = [1 2 3] = [1 2 3]
V₁ = deepcopy(V₀) = [1 2 3]

Now changes in either of those objects will not affect values pointed by the other object:

@show V₁[1] = 420;
@show V₁;
@show V₀;

@show V₀[3] = 128;
@show V₁;
@show V₀;
V₁[1] = 420 = 420
V₁ = [420 2 3]
V₀ = [1 2 3]
V₀[3] = 128 = 128
V₁ = [420 2 3]
V₀ = [1 2 128]

An important thing that should be also stressed is that this feature works only in the presence of assignments without additional operations. If, for instance, code V₁ = 2*V₀ involves two operations: (1) multiplication *(2,V₀); and (2) making pointer V₁ to memory containing results from computing *(2,V₀) in the past. This means that memory spaces pointed by V₁ and by V₀ are two different things.

@show V₀ = [1 2 3];
@show V₁ = 2*V₀;
V₀ = [1 2 3] = [1 2 3]
V₁ = 2V₀ = [2 4 6]
@show V₁[1] = 420;
@show V₁;
@show V₀;

@show V₀[3] = 128;
@show V₁;
@show V₀;
V₁[1] = 420 = 420
V₁ = [420 4 6]
V₀ = [1 2 3]
V₀[3] = 128 = 128
V₁ = [420 4 6]
V₀ = [1 2 128]

Dictionaries

Dictionaries are very useful objects that are particularly useful in my daily workflow. They contain different objects and data structures of those types can be very different. Dictionaries are initialized with Dict and the list of the elements take arguments in the form "name_of_the_object" => object. An illustration below:

my_first_dict = Dict("X" => 2,
                     "Y" => 1:10)
Dict{String, Any} with 2 entries:
  "Y" => 1:10
  "X" => 2

Alternatively you can use a bit different notation, which I personally prefer (and I am not sure why): Dict([ ("name_of_the_object1", object1) ]). For example, if we want to create an object containing all parameters of our model, dictionaries are good candidates for it:

params = Dict([ 
                ("β" , (1/1.04)^(1/12)),
                ("b" , [.55 0]) ,
                ("vec_states" , 1:2) ,
                ("π", [1:10 20:29])
            ])
Dict{String, Any} with 4 entries:
  "b"          => [0.55 0.0]
  "π"          => [1 20; 2 21; … ; 9 28; 10 29]
  "vec_states" => 1:2
  "β"          => 0.996737

Accessing elements of dictionaries has some similarities with arrays. The main difference is that we have to use names in quotates instead of indices:

@show params["β"]
params["π"]
params["β"] = 0.9967369426185624
10×2 Matrix{Int64}:
  1  20
  2  21
  3  22
  4  23
  5  24
  6  25
  7  26
  8  27
  9  28
 10  29

We can normally work on objects inside dictionaries. Suppose we want to increase values of all rows but the last one of the first column of array π by one we can do it by:

params["π"][1:(end-1),1]  .+= 1;
params["π"]
10×2 Matrix{Int64}:
  2  20
  3  21
  4  22
  5  23
  6  24
  7  25
  8  26
  9  27
 10  28
 10  29

We can also add new elements to existing dictionaries. It is super simple:

params["Σ"] = 12
12

Now in in params there is a new object Σ:

params 
Dict{String, Any} with 5 entries:
  "Σ"          => 12
  "b"          => [0.55 0.0]
  "π"          => [2 20; 3 21; … ; 10 28; 10 29]
  "vec_states" => 1:2
  "β"          => 0.996737

In my own workflow, dictionaries are super useful for putting all blocks of models into one object. I create a dictionary economy with all parameters, value functions, simulations inside. Thanks to this during analyses I can explore different parametrizations, counterfactual calibrations and what not in a very organized way.

Get used to assignments in Julia

You have to remember the assignment operator = with whole dictionaries works in a very similar way as with arrays, i.e. it refers to memory containing information rather than information itself.

equation_copy = equation;

println("Before assignment:")
@show equation["c"];
@show equation_copy["c"];


println("ASSIGNMENT: equation_copy[\"c\"] = 420")
equation_copy["c"] = 420;

println("After assignment:")
@show equation["c"];
@show equation_copy["c"];
Before assignment:
equation["c"] = π
equation_copy["c"] = π
ASSIGNMENT: equation_copy["c"] = 420
After assignment:
equation["c"] = 420
equation_copy["c"] = 420

Too many repetitions of dictionary names? @unpack should help!

Example 1

Suppose that we have a dictionary equation containing a range of argumnents \(\mathbf{x}=\left(1+\frac{(i-1)}{10}\right)_{i=1}^{100}\) and values of coefficients \((a=1,b=12,c=\pi)\) characterizing a certain quadratic equation \(ax^2 + bx +c.\)

Then, our dictionary will be of the following form:

equation  = Dict([
                    ("x" , collect(range(1, step=.1, length=100)) ),
                    ("a", 1),
                    ("b", 12),
                    ("c", π)
                ])
Dict{String, Any} with 4 entries:
  "c" => π
  "x" => [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9  …  10.0, 10.1, 10.2…
  "b" => 12
  "a" => 1

Example 2

Now suppose we want to evaluate the function calibrated with \((a=1,b=12,c=\pi)\) at \(\mathbf{x}.\)

One way to do it is as follows:

equation["y"] = @. equation["a"]*equation["x"]^2 + equation["b"]*equation["x"] + equation["c"]
100-element Vector{Float64}:
  16.141592653589793
  17.551592653589793
  18.981592653589793
  20.431592653589796
  21.90159265358979
  23.391592653589793
  24.9015926535898
  26.431592653589792
  27.981592653589797
  29.55159265358979
  31.141592653589793
  32.751592653589796
  34.38159265358979
   ⋮
 216.78159265358983
 219.95159265358978
 223.14159265358978
 226.35159265358976
 229.58159265358978
 232.8315926535898
 236.10159265358982
 239.39159265358978
 242.70159265358978
 246.03159265358974
 249.38159265358982
 252.7515926535898

The code works but it does not have the nicest look and might be difficult to change. In this case, macro @unpack from package Parameters can be helpful in making the code more legible. This macro has the following syntax: @unpack object_1, object_2 = dictionary_1. @unpack extracts objects with names object_1 and object_2 from dictionary_1 and makes them available in the current local scope. dictionary_1 might have other objects inside but @unpack extracts only those explicitly mentioned objects. Below you can see how it works

@show a #object a is only defined in dictionary equation, so you cannot access it
UndefVarError: a not defined

Stacktrace:
 [1] top-level scope
   @ show.jl:955
 [2] eval
   @ ./boot.jl:360 [inlined]
 [3] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
   @ Base ./loading.jl:1094
using Parameters
@unpack a, b, c, x = equation
@show a #now it works
a = 1
1

Now to evaluate our quadratic function at \(\mathbb{x}\) we can do it by:

equation["y"] = @. a*x^2 + b*x + c
100-element Vector{Float64}:
  16.141592653589793
  17.551592653589793
  18.981592653589793
  20.431592653589796
  21.90159265358979
  23.391592653589793
  24.9015926535898
  26.431592653589792
  27.981592653589797
  29.55159265358979
  31.141592653589793
  32.751592653589796
  34.38159265358979
   ⋮
 216.78159265358983
 219.95159265358978
 223.14159265358978
 226.35159265358976
 229.58159265358978
 232.8315926535898
 236.10159265358982
 239.39159265358978
 242.70159265358978
 246.03159265358974
 249.38159265358982
 252.7515926535898

You have to remember that in @unpack, the assignment (=) operator works normally… for Julia. This means that if object_1 is a scalar while object_2 is an array, working with those objects can be very different. This topic was discussed in this section.

Below you can find an example illustrating those differences:

@show equation["a"];
@show a;

@show a += 1;

@show equation["a"]; # `a += 1;` didn't change  the value of `equation["a"]`
@show a;             # `a += 1;`        changed the value of `a`
equation["a"] = 1
a = 1
a += 1 = 2
equation["a"] = 1
a = 2
2
@show equation["x"];
@show x;


@show x .+= 1;

@show equation["x"]; # `x .+= 1;`        CHANGED the value of `equation["x"]`
@show x;             # `x .+= 1;`        changed the value of `x`
equation["x"] = [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, 5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 5.9, 6.0, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8, 6.9, 7.0, 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9, 9.0, 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7, 9.8, 9.9, 10.0, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9]
x = [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, 5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 5.9, 6.0, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8, 6.9, 7.0, 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9, 9.0, 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7, 9.8, 9.9, 10.0, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9]
x .+= 1 = [2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, 5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 5.9, 6.0, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8, 6.9, 7.0, 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9, 9.0, 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7, 9.8, 9.9, 10.0, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9, 11.0, 11.1, 11.2, 11.3, 11.4, 11.5, 11.6, 11.7, 11.8, 11.9]
equation["x"] = [2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, 5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 5.9, 6.0, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8, 6.9, 7.0, 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9, 9.0, 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7, 9.8, 9.9, 10.0, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9, 11.0, 11.1, 11.2, 11.3, 11.4, 11.5, 11.6, 11.7, 11.8, 11.9]
x = [2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, 5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 5.9, 6.0, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8, 6.9, 7.0, 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9, 9.0, 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7, 9.8, 9.9, 10.0, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9, 11.0, 11.1, 11.2, 11.3, 11.4, 11.5, 11.6, 11.7, 11.8, 11.9]
100-element Vector{Float64}:
  2.0
  2.1
  2.2
  2.3
  2.4
  2.5
  2.6
  2.7
  2.8
  2.9
  3.0
  3.1
  3.2
  ⋮
 10.8
 10.9
 11.0
 11.1
 11.2
 11.3
 11.4
 11.5
 11.6
 11.7
 11.8
 11.9

While a+=1 did not have impact on equation["a"], x .+= 1 affected equation["x"]. It is important to remember this feature of the language.

Ranges

Ranges are quite intuitive objects. They collect elements from a certain range of values. They can be created either by using : operator (just like in R and Matlab) or by using range function. For instance, if we want to create a range from 5 through 120, we can use two options:

@show 5:120;
@show range(5, stop=120);
5:120 = 5:120
range(5, stop = 120) = 5:120

Notice that you are not allowed to use range(5,120) because at least one of length, stop, or step must be specified explicitely.

If we want, we can set the size step. Again, it can be done in two ways:

@show 5:.5:120;
@show range(5, stop=120, step=.5);
5:0.5:120 = 5.0:0.5:120.0
range(5, stop = 120, step = 0.5) = 5.0:0.5:120.0

You can perform normal arithmetic operations:

(1:5)*2
2:2:10

However, be careful about the order of operations. Arithmetic operations have a higher priority in Julia. For Matlab users that is the same, but it might be problematic for R users, where the order is different. In Juliaif you type 1:5+1, you will give you 1:6, while in R you will get 2:6.

1:5+1
1:6

Another important difference between other languages and Julia is the fact that arrays and ranges are not compatible with each other. This means that arithmetic operations will give you an error message, even if objects are of the same size:

1:3 + [1; 2; 3]
MethodError: no method matching +(::Int64, ::Vector{Int64})

For element-wise addition, use broadcasting with dot syntax: scalar .+ array

Closest candidates are:

  +(::Any, ::Any, ::Any, ::Any...) at operators.jl:560

  +(::T, ::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} at int.jl:87

  +(::Union{Int16, Int32, Int64, Int8}, ::BigInt) at gmp.jl:534

  ...



Stacktrace:

 [1] top-level scope

   @ In[293]:1

 [2] eval

   @ ./boot.jl:360 [inlined]

 [3] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)

   @ Base ./loading.jl:1094

You can extract all element from a range and create an array by using collect:

collect(1:3)
3-element Vector{Int64}:
 1
 2
 3

In this case, arithmetic operations are allowed:

collect(1:3) + [1; 2; 3]
3-element Vector{Int64}:
 2
 4
 6

Tuples

To be written. An example of a tuple:

(1,2,3)
(1, 2, 3)

Sets

To be written