In [2]:
using Plots
ts_length = 100
epsilon_values = rand(ts_length)
plot(epsilon_values, color="blue")
Out[2]:
The effect of the statement using Plots
is to make all the names exported by the Plots
module available in the global scope.
If you prefer to be more selective you can replace using Plosts
with: import Plots: plot
Now only the plot
function is accessible.
Since our program uses only the plot function from this module, either would have worked in the previous example.
The function call epsilon_values = randn(ts_length)
creates one of the most fundamental Julia data types: an array.
In [3]:
typeof(epsilon_values)
Out[3]:
In [4]:
epsilon_values
Out[4]:
The information from typeof()
tells us that epsiolon_values
is an array of 64 bit floating point values, of dimension 1.
Julia arrays are quite flexible — they can store heterogeneous data for example:
In [5]:
x = [10, "foo", false]
Out[5]:
Notice now that the data type is recorded as Any
, since the array contains mixed data.
The first element of x
is an integer.
In [6]:
typeof(x[1])
Out[6]:
The second is a string.
In [7]:
typeof(x[2])
Out[7]:
The third is the boolean value false
.
In [8]:
typeof(x[3])
Out[8]:
Notice from the above that
Julia contains many functions for acting on arrays — we'll review them later.
For now here's several examples, applied to the same list x = [10, "foo", false]
In [9]:
length(x)
Out[9]:
In [10]:
pop!(x)
Out[10]:
In [11]:
x
Out[11]:
In [12]:
push!(x, "bar")
Out[12]:
In [13]:
x
Out[13]:
The first example just returns the length of the list.
The second pop!()
, pops the last element off the list and returns it.
In doing so it changes the list (by dropping the last element).
Because of this we call pop!
a mutating method.
It's conventional in Julia that mutating methods end in !
to remind the user that the function has other effects beyond just returning a value.
The function push!()
is similar, except that it appends its second argument to the array.
In [14]:
ts_length = 100
epsilon_values = Array{Float64}(ts_length)
for i in 1:ts_length
epsilon_values[i] = randn()
end
plot(epsilon_values, color="red")
Out[14]:
Here we first declared epsilon_values
to be an empty array for storing 64 bit floating point numbers.
The for
loop then populates this array by successive calls to randn()
.
randn()
returns a single floatLike all code blocks in Julia, the end of the for
loop code block (which is just one line here) is indicated by the keyword end
.
The word in
from the for
loop can be replaced by symbol =
.
The expression 1:ts_length
creates an iterator that is looped over — in this case the integers from 1
to ts_length
.
Iterators are memory efficinet because the elements are generated on the fly rather than stored in memory.
In Julia you can also loop directly over arrays themselves, like so
In [15]:
words = ["foo", "bar"]
for word in words
println("Hello $word")
end
In [16]:
ts_length = 100
epsilon_values = Array{Float64}(ts_length)
i = 1
while i <= ts_length
epsilon_values[i] = randn()
i = i + 1
end
plot(epsilon_values, color="green")
Out[16]:
The next example does the same thing with a condition and the break
statement.
In [17]:
ts_length = 100
epsilon_values = Array{Float64}(ts_length)
i = 1
while true
epsilon_values[i] = randn()
i = i + 1
if i > ts_length
break
end
end
plot(epsilon_values, color="black")
Out[17]:
In [18]:
function generate_data(n)
epsilon_values = Array{Float64}(n)
for i in 1:n
epsilon_values[i] = randn()
end
return epsilon_values
end
ts_length = 100
data = generate_data(ts_length)
plot(data, color="brown")
Out[18]:
Here
function
is a Julia keyword that indicates the start of a function definitiongenerate_data
is an arbitrary name for the functionreturn
is a keyword indicating the return value
In [19]:
ts_length = 100
data = randn(ts_length)
plot(data, color="grey")
Out[19]:
Let's make slightly more useful function.
This function will be passed a choice of probability distribution and respond by plotting a histogram of observations.
In doing so we'll make use of the Distributions package
In [20]:
Pkg.add("Distributions")
Here's the code
In [21]:
using Distributions
function plot_histogram(distribution, n)
epsilon_values = rand(distribution, n) # n draws from distribution
histogram(epsilon_values)
end
lp = Laplace()
plot_histogram(lp, 500)
Out[21]:
Let's have a casual discussion of how all this works while leaving technical details for later in the lectures.
First, lp = Laplace()
creates an instance of a data type defined in the Distributions module that represents the Laplace distribution.
The name lp
is bound to this object.
When we make the function call plot_histogram(lp, 500)
the code in the body of the function plot_histogram
is run with
distribution
bound to the name object as lp
n
bound to the integer 500
In [22]:
rand(3)
Out[22]:
On the other hand, distribution
points to a data type representing the Laplace distribution that has been defined in a third party package.
So how can it be that rand()
is able to take this kind of object as an argument and return the output that we want?
The answer in a nutshell is multiple dispatch
This refers to the idea that functions in Julia can have different behaviour depending on the particular arguments that they're passed.
Hence in Julia we can take an existing function and give it a new behaviour by defining how it acts on a new type of object.
The interpreter knows which function definition to apply in a given setting by looking at the types of the objects the function is called on.
In Julia these alternative versions of a function are called methods
In [33]:
function factorial2(n)
k = 1
for i in 1:n
k = k * i
end
return k
end
print(factorial(4),'\n',factorial2(4))
The binomial random variable $Y \sim Bin(n,p)$ represents:
Using only rand()
from the set of Julia's build-in random number generators (not the Distributions package), write a function binomial_rv
such that binomial_rv(n, p)
generates one draw of $Y$
Hint: If $U$ is uniform on (0,1) and $P \in (0,1)$, then the expression U < p
evaluates to true
with probability p
In [35]:
function binomial_rv(n, p)
count = 0
U = rand(n)
for i in 1:n
if U[i] < p
count += 1
end
end
return count
end
for j in 1:25
b = binomial_rv(10, 0.5)
print("$b, ")
end
Compute an approximation to $\pi$ using Monte Carlo.
For random number generations use only rand()
Your hints are as follows:
In [38]:
n = 10000000
count = 0
for i in 1:n
u, v = rand(2)
d = sqrt((u - 0.5)^2 + (v - 0.5)^2) # Distance from middle of square
if d < 0.5
count += 1
end
end
area_estimate = count / n
print(area_estimate * 4) # Dividing by radius**2
In [57]:
payoff = 0
count = 0
print("Count = ")
for i in 1:10
x = rand()
if x > 0.5
count += 1
else
count = 0
end
print(count)
if count >= 3
payoff += 1
end
end
print("\nPayoff = $payoff")
In [58]:
payoff = 0
count = 0
print("Count = ")
for i in 1:10
x = rand()
count = x < 0.5 ? count + 1 : 0
print(count)
if count >= 3
payoff += 1
end
end
print("\nPayoff = $payoff")
In [63]:
# Set parameters
alpha = 0.9
T = 200
# Make an array of 200 zeroes
x = zeros(T + 1)
# Construct `for` loop
for t in 1:T
x[t+1] = alpha * x[t] + randn()
end
# Plot graph
plot(x)
Out[63]:
Plot three simulated time series, one for each of the cases $\alpha = 0$, $\alpha = 0.8$ and $\alpha = 0.98$
In particular, you should produce (modulo randomness) a figure that looks as follows:
(The figure illustrates how time series with the same one-step-ahead conditional volatilities, as these three processes havem can have very different unconditional volatilities)
In [67]:
alphas = [0.0, 0.8, 0.98]
T = 200
series = []
labels = []
for alpha in alphas
x = zeros(T + 1)
x[1] = 0
for t in 1:T
x[t+1] = alpha * x[t] + randn()
end
push!(series, x)
push!(labels, "alpha = $alpha")
end
plot(series, label=reshape(labels, 1, length(labels)))
Out[67]:
In [68]:
?reshape
Out[68]:
In [ ]: