Your interest in computational work will determine how much of this guide you read.
optional
sectionsTry not to let the amount of information overwhelm you.
The notebook is meant to be used as a reference
The slides / short notes presented here are the compulsory part of the tutorial.
Here are some of the focus areas
It is preferred that you install the programs on your computer.
This requires that you install Anaconda and Julia.
Here is a link that explains the installation process.
However, if you are finding it difficult to get things working you may try the other options.
I will make a brief video on how to install Anaconda and link it to Julia.
optional
)Before we start our discussion, let us try and run our first Julia program.
For those that have done programming before, this normally entails writing a piece of code that gives us the output Hello World!
.
In Julia this is super easy to do.
println("Hello World!")
Hello World!
Julia has many useful packages. If we want to include a specific package then we can do the following,
import Pkg
Pkg.add("PackageName")
using PackageName
import Pkg
Pkg.add("Plots") # Package for plotting
using Plots
After having successfully written your Hello World!
code in Julia, a natural place to continue your journey is with variables.
A variable in a programming language is going to be some sort of symbol that we assign some value.
x = 2 # We assign the value of 2 to the variable x
2
typeof(x) # Command to find the type of the x variable
Int64
We see that the type of the variable is Int64
.
What is an Int64
?
We can now work with x
as if it represents the value of 2
.
Since an integer is a number, we can perform basic mathematical operations.
y = x + 2
4
typeof(y)
Int64
We can reassign the variable x
to another value, even with another type.
x = 3.1345
3.1345
typeof(x)
Float64
Now x
is a floating point number.
What is a floating point number?
This is an approximation to a decimal (or real) number.
There are several important data types that are at the core of computing. Some of these include,
We can perform basic arithmetic operations.
Operators perform operations.
These common operators are called the arithmetic operators.
Expression | Name | Description |
---|---|---|
x + y |
binary plus | performs addition |
x - y |
binary minus | performs subtraction |
x * y |
times | performs multiplication |
x / y |
divide | performs division |
x ÷ y |
integer divide | x / y , truncated to an integer |
x \ y |
inverse divide | equivalent to y / x |
x ^ y |
power | raises x to the y th power |
x % y |
remainder | equivalent to rem(x,y) |
Here are some simple examples that utilise these arithmetic operators.
x = 2; y = 10;
x * y
20
x ^ y
1024
y / x # Note that division converts integers to floats
5.0
2x - 3y
-26
x // y
1//5
Augmentation operators will be especially important in the section on control flow.
x += 1 # same as x = x + 1
3
x *= 2 # same as x = x * 2
6
x /= 2 # same as x = x / 2
3.0
These operators help to generate true and false values for our conditional statements
Operator | Name |
---|---|
== |
equality |
!= , ≠ |
inequality |
< |
less than |
<= , ≤ |
less than or equal to |
> |
greater than |
>= , ≥ |
greater than or equal to |
x = 3; y = 2;
x < y # If x is less than y this will return true, otherwise false
false
x != y # If x is not equal to y then this will return true, otherwise false
true
x == y # If x is equal to y this will return true, otherwise false
false
There are several types of containers, such as arrays and tuples, in Julia.
These containers contain data of different types.
We explore some of the most commonly used containers here.
Note that we have both mutable and immutable containers in Julia.
Let us start with one of the basic types of containers, which are referred to as tuples.
These containers are immutable, ordered and of a fixed length.
x = (10, 20, 30)
(10, 20, 30)
x[1] # First element of the tuple
10
a, b, c = x # With this method of unpacking we have that a = 10, b = 20, c = 30
(10, 20, 30)
a
10
One of the most important containers in Julia are arrays.
You will use tuples and arrays quite frequently in your code.
An array is a multi-dimensional grid of values.
Vectors and matrices, such as those from mathematics, are types of arrays in Julia.
vectors
)¶A vector is a one-dimensional array.
A column (row) vector is similar to a column (row) of values that you would have in Excel.
Column vector is a list of values separated with commas.
Row vector is a list of values separated with spaces.
col_vector = [1, "abc"] # example of a column vector (one dimensional array)
2-element Vector{Any}: 1 "abc"
not_col_vector = 1, "abc" # this creates a tuple! Remember the closing brackets for a vector.
(1, "abc")
row_vector = [1 "abc"] # example of a row vector (one dimensional array)
1×2 Matrix{Any}: 1 "abc"
vectors
)¶The big difference between a tuple and array is that we can change the values of the array.
Below is an example where we change the first component of the array.
This means that arrays are mutable.
col_vector[1] = "def"
"def"
col_vector
2-element Vector{Any}: "def" "abc"
You can now see that the first element of the vector has changed from 1
to def
.
push!()
¶We can use the push!()
function to add values to this vector.
This grows the size of the vector.
You might notice the !
operator after push
.
This exclamation mark doesn't do anything particularly special in Julia.
It is a coding convention to let the user know that the input is going to be altered / changed.
In our case it lets us know that the vector is going to be mutated.
Let us illustrate with an example.
push!()
¶Let us illustrate how the push!()
functions works with an example.
push!(col_vector, "hij") # col_vector is mutated here. It changes from 2 element vector to 3 element vector.
3-element Vector{Any}: "def" "abc" "hij"
push!(col_vector, "klm") # We can repeat and it will keep adding to the vector.
4-element Vector{Any}: "def" "abc" "hij" "klm"
One easy way to generate an array is using a sequence.
We show multiple ways below to do this.
Note: If you want to store the values in an array, you need to use the collect()
function.
seq_x = 1:10:21 # This is a sequence that starts at one and ends at 21 with an increment of 10.
1:10:21
collect(seq_x) # Collects the values for the sequence into a vector
3-element Vector{Int64}: 1 11 21
seq_y = range(1, stop = 21, length = 3) # Another way to do the same as above
1.0:10.0:21.0
For creation of arrays we frequently use functions like zeros()
, ones()
, fill()
and rand()
.
zeros(3) # Creates a column vector of zeros with length 3.
3-element Vector{Float64}: 0.0 0.0 0.0
zeros(Int, 3) # We can specify that the zeros need to be of type `Int64`.
3-element Vector{Int64}: 0 0 0
ones(3) # Same thing as `zeros()`, but fills with ones
3-element Vector{Float64}: 1.0 1.0 1.0
For creation of arrays we frequently use functions like zeros()
, ones()
, fill()
and rand()
.
fill(2, 3) # Fill a three element column with the value of `2`
3-element Vector{Int64}: 2 2 2
rand(3) # Values chosen will lie between zero and one. chosen with equal probability.
3-element Vector{Float64}: 0.6267475868435357 0.44769771206451825 0.08307481784542958
randn(3) # Values chosen randomly from Normal distribution
3-element Vector{Float64}: 0.8870407071510156 -2.000543066533293 -0.15256766820653117
matrices
)¶We can also create matrices (two dimensional arrays) in Julia.
A matrix has both rows and columns.
This would be like a table in Excel with rows and columns.
To create a matrix we separate rows by spaces and columns by semicolons.
matrix_x = [1 2 3; 4 5 6; 7 8 9] # Rows separated by spaces, columns separated by semicolons.
3×3 Matrix{Int64}: 1 2 3 4 5 6 7 8 9
matrix_y = [1 2 3;
4 5 6;
7 8 9] # Another way to write the matrix above
3×3 Matrix{Int64}: 1 2 3 4 5 6 7 8 9
matrices
)¶Those of you who have done statistics or mathematics know how important matrices are.
Matrices are a fundamental part of linear algebra.
Linear algebra is a super important area in mathematics (one of my favourites).
There is an optional
section on linear algebra in the full tutorial notes.
If you want to do Honours in Economics, then I suggest getting comfortable with linear algebra!
We can also create two dimensional arrays with zeros()
, ones()
, fill()
and rand()
.
zeros(3, 3)
3×3 Matrix{Float64}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
ones(3, 3)
3×3 Matrix{Float64}: 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
randn(3, 3)
3×3 Matrix{Float64}: -0.911564 1.75852 0.575633 -0.671038 -0.956759 0.207368 0.0351418 -1.21317 0.159683
Remember from before that we can extract value from containers.
col_vector[1] # Extract the first value
"def"
matrix_x[2, 2] # Retrieve the value in the second row and second column of the matrix
5
col_vector[2:end] # Extracts all the values from the second to the end of the vector
3-element Vector{Any}: "abc" "hij" "klm"
col_vector[:, 1] # Provides all the values of the first column
4-element Vector{Any}: "def" "abc" "hij" "klm"
One important topic that we need to think about is broadcasting
.
Suppose you have a particular vector, such as [1, 2, 3]
.
You might want to apply a certain operation to each of the elements in that vector (elementwise).
Perhaps you want to find out what the sin
of each of those values are independently?
How would you do this? Well you could write you own loop that does this.
We will cover loops in a bit, so don't be concerned if you don't understand.
x_vec = [1.0, 2.0, 3.0];
# Loop for elementwise sin operation on `x` vector.
y_vec = similar(x_vec)
for (i, x) in enumerate(x_vec)
y_vec[i] = sin(x)
end
y_vec # This now gives you sin of the `x` vector
3-element Vector{Float64}: 0.8414709848078965 0.9092974268256817 0.1411200080598672
Writing a loop like this for a simple operation seems wasteful.
Instead of writing the loop, we can use the dot operator
.
The dot operator
broadcasts the function across the elements of the array.
Let us see some examples.
sin.(x_vec) # Notice the dot operator. What happens without the dot operator?
3-element Vector{Float64}: 0.8414709848078965 0.9092974268256817 0.1411200080598672
(x_vec).^2
3-element Vector{Float64}: 1.0 4.0 9.0
(x_vec) .* (x_vec)
3-element Vector{Float64}: 1.0 4.0 9.0
In this section we will be looking at conditional statements and loops.
Conditional statements provide branches to a program depending on a certain condition.
The most recognisable conditional statement is the if-else
statement.
x = 1
if x < 2
print("first")
elseif x > 4
print("second")
elseif x < 0
print("third")
else
print("fourth")
end
first
Avoid repeating code at ALL COSTS. Do not repeat yourself. This is the DRY principle in coding.
We illustrate this with an example.
x = [0,1,2,3,4]
y_1 = [] # empty array (list in python)
append!(y_1, x[1] ^ 2)
append!(y_1, x[2] ^ 2)
append!(y_1, x[3] ^ 2)
append!(y_1, x[4] ^ 2)
append!(y_1, x[5] ^ 2)
5-element Vector{Any}: 0 1 4 9 16
In order to fill out the y_1
array we had to write the same command five times.
Notice that the only thing that changes is the value of the index from the x
array
There must be easier way to fill this y_1
array.
We will try to use a for
loop to fill this array.
x = [0,1,2,3,4]
y_1 = [] # Let's empty this array again and try another method
for i in x
append!(y_1, i ^ 2)
println(y_1)
end
Any[0] Any[0, 1] Any[0, 1, 4] Any[0, 1, 4, 9] Any[0, 1, 4, 9, 16]
There is a more compact way to fill our array using the for loop from above.
This is known as an array comprehension.
The code for the comprehension is as follows.
y_1 = [i^2 for i in x]
5-element Vector{Int64}: 0 1 4 9 16
Functions are important to writing complex code.
You should always write in terms of functions if possible.
From mathematics we know that a function is an abstraction that takes in some input and returns an output.
In the world of computing the idea is similar.
Let us take a look at some functions below.
The most common to way to write a function is as follows,
function f(x) # function header
return x ^ 2 # body of the function
end
f (generic function with 1 method)
This function accepts one input and returns one output.
Namely, it takes the value of x
and then squares it.
f(2)
4
With the following function we have two inputs, x
and y
, that return one output.
function k(x, y)
return x ^ 2 + y ^ 2
end
k (generic function with 1 method)
k(2, 2)
8
return
?¶return
basically instructs the program to exit the function and return the specified value.
function return_example_1(x)
if x > 5
return x + 5 # this will exit the function immediately
println(x - 9) # this will never execute, it is after the return
end
return x / 5 # if x <= then this will be returned
end
return_example_1 (generic function with 1 method)
return_example_1(1)
0.2
return_example_1(10) # notice that the `println(x - 9)` doesn't execute
15
Global variables are generally considered to be a bad idea.
If you want to be a good programmer, you need to understand the basics of local scoping.
Most of the code in the notebook has been contained in the global scope.
If you are writing scripts and source code you must wrap code in functions to avoid variables entering the global scope.
When you copy variables inside functions, they become local and the function is said to become a closure.
We will show what we mean through some examples.
a = 2 # global variable
function f(x)
return x ^ a
end;
function g(x; a = 2)
return x ^ a
end;
function h(x)
a = 2 # a is local
return x ^ a
end;
f(2), g(2), h(2)
(4, 4, 4)
What happens in our previous example if we change the value for a
?
Which of the outputs for the functions will be changed?
Can you see why this might be problematic?
a = 3
3
f(2), g(2), h(2)
(8, 4, 4)
Let us show the basic principles of plotting in Julia.
xs = 0:0.1:20;
ys = sin.(xs); # we are using dot syntax - indicates broadcasting over the entire sequence
plot(xs, ys)
┌ Info: Precompiling GR_jll [d2c73de3-f751-5644-a686-071e5b155ba9] └ @ Base loading.jl:1423
plot(xs, ys, color = :steelblue, title = "Our first plot", legend = false, lw = 1.5) # lw stands for line width
Another nice feature of plotting is the ability to overlay plots on top of each other.
ys_1 = sin.(xs);
ys_2 = cos.(xs);
plot(xs, ys_1, lw = 1.5, ls = :solid)
plot!(xs, ys_2, lw = 1.5, ls = :dash)