Julia: Tutorial & Code-Collection¶
Source: https://github.com/markomlikota/CodingSoftware
MIT License, © Marko Mlikota, https://markomlikota.github.io
Futher Object-Types¶
Knowing the object-types we discussed so far suffices for most (standard) tasks.
However, there are a few more object-types in Julia worth knowing about.
In [1]:
# Let's install the new packages needed in this section:
Pkg.add("DataFrames")
Resolving package versions... Installed InvertedIndices ───────────── v1.3.0 Installed DataValueInterfaces ───────── v1.0.0 Installed SentinelArrays ────────────── v1.4.1 Installed TableTraits ───────────────── v1.0.1 Installed PooledArrays ──────────────── v1.4.3 Installed InlineStrings ─────────────── v1.4.0 Installed Crayons ───────────────────── v4.1.1 Installed Tables ────────────────────── v1.11.1 Installed DataFrames ────────────────── v1.6.1 Installed IteratorInterfaceExtensions ─ v1.0.0 Installed StringManipulation ────────── v0.3.4 Installed PrettyTables ──────────────── v2.3.1 Updating `/opt/conda/julia/environments/v1.10/Project.toml` [a93c6f00] + DataFrames v1.6.1 Updating `/opt/conda/julia/environments/v1.10/Manifest.toml` [a8cc5b0e] + Crayons v4.1.1 [a93c6f00] + DataFrames v1.6.1 [e2d170a0] + DataValueInterfaces v1.0.0 [842dd82b] + InlineStrings v1.4.0 [41ab1584] + InvertedIndices v1.3.0 [82899510] + IteratorInterfaceExtensions v1.0.0 [2dfb63ee] + PooledArrays v1.4.3 [08abe8d2] + PrettyTables v2.3.1 [91c51154] + SentinelArrays v1.4.1 [892a3eda] + StringManipulation v0.3.4 [3783bdb8] + TableTraits v1.0.1 [bd369af6] + Tables v1.11.1 Precompiling project... ✓ IteratorInterfaceExtensions ✓ DataValueInterfaces ✓ InvertedIndices ✓ PooledArrays ✓ TableTraits ✓ InlineStrings ✓ SentinelArrays ✓ Crayons ✓ Tables ✓ StringManipulation ✓ CategoricalArrays → CategoricalArraysSentinelArraysExt ✓ PrettyTables ✓ DataFrames ✓ Latexify → DataFramesExt 14 dependencies successfully precompiled in 45 seconds. 193 already precompiled. 1 skipped during auto due to previous errors.
Expressions & Symbols¶
In [2]:
# Julia has object types "Symbol" and "Expr" (expression),
# whereby symbols are essentially building blocks of expressions.
# They allow us to use strings to execute commands and, more generally,
# to define and call objects.
In [3]:
# Construction and evaluation of expressions:
# Simple construction of expressions:
exA = :((4+5)/2)
# or:
exA = Meta.parse("(4+5)/2")
# Construction of longer expressions:
exB = quote
x = 4
y = 5
(x+y)/2
end
# It is of type "Expr":
typeof(exA)
# Evaluate expression:
eval(exA)
Out[3]:
4.5
In [4]:
# We can let our expressions depend on objects we create.
# This can be done in two ways.
# When we write the object name with a dollar sign in front,
# we include the current value of that object in our expression.
# When we write the object name without a dollar sign in front,
# we link our expression to them so that the object's value
# at the time of execution matters.
a = 3
b = 8
exA = :($a + b) # or:
exA = Meta.parse("$a + b")
println("The expression 'exA' reads: ", exA)
# (value of b doesn't matter; b doesn't even need to exist when defining exA)
a = 2
b = 7
eval(exA)
The expression 'exA' reads: 3 + b
Out[4]:
10
In [5]:
# Using expressions, we can define objects:
exA = Meta.parse("x_4 = 3.74")
eval(exA)
# Sidenote: to define strings, need to use triple quotation marks
exB = Meta.parse(""" s_4 = "3.74" """)
eval(exB)
# This is useful if e.g. we create many variables x_1, x_2, x_3, ... in a loop;
# we would type something like
ii = 4 # this is the iterator in the loop
value = 3.74
Meta.parse(string("x_",ii," = ",value))
Out[5]:
:(x_4 = 3.74)
In [6]:
# A symbol allows us to call objects using strings:
sym1 = Symbol("x","_",4)
typeof(sym1)
eval(sym1)
# This is useful if we call many variables x_1, x_2, ... in a loop;
# we'd type something like
eval(Symbol("x","_",ii))
# Such "dynamic" object-creation and -calling is referred to as "metaprogramming".
Out[6]:
3.74
Tuples¶
In [7]:
# A tuple (like a vector) is an ordered collection of elements (of any type)
# that (unlike a vector) cannot be changed once they are defined.
# Using tuples rather than vectors for immutable collections of elements
# tends to lead to more efficient code.
ta = ("hello", 2025, true, 2, 5//8)
# We can turn a vector into a tuple:
vx = ["hello",2025]
tuple(vx...)
# A tuple is indexed just like a vector:
ta[2]
ta[end]
ta[2:4]
# We cannot change its elements ...:
ta[2] = 2024
# ... but we can overwrite the object:
ta = (2,3,4)
# We can apply functions to a tuple:
sqrt.(ta)
# We can also create a vector of tuples:
vt = Vector{Tuple}(undef,1)
vt[1] = ("hello", 2025)
push!(vt,("hi", 2024))
MethodError: no method matching setindex!(::Tuple{String, Int64, Bool, Int64, Rational{Int64}}, ::Int64, ::Int64) Stacktrace: [1] top-level scope @ In[7]:18
Dataframes¶
In [8]:
# Dataframes are essentially like matrices with named columns/rows.
# There are many pre-defined commands to manipulate them (see package documentation).
# I find them useful for reading from and writing to csv or excel files
# as well as for printing information as nice tables in the REPL.
# (By habit, I stick to dataframes in R for raw data manipulation.)
using DataFrames
# Create an empty data frame:
dfData1 = DataFrame(Stat = String[], b_ = Float64[], k_ = Float64[])
# Add a row at the end:
push!(dfData1, ("Mean",0.67,1.34))
# Add (squeeze in) a row at position 1:
insert!(dfData1, 1, ("Median",0.65,1.33))
# Add a column called "Periods":
dfData1[!, "Periods"] = ["period1","period1"]
Out[8]:
2-element Vector{String}: "period1" "period1"
In [9]:
# To access elements, index dataframe just like a matrix: e.g.
dfData1[1,2:3]
# Access column names:
names(dfData1)
# Rename columns:
rename!(dfData1, names(dfData1) .=> Symbol.(["stats","b_", "k_","t"]))
Out[9]:
2×4 DataFrame
Row | stats | b_ | k_ | t |
---|---|---|---|---|
String | Float64 | Float64 | String | |
1 | Median | 0.65 | 1.33 | period1 |
2 | Mean | 0.67 | 1.34 | period1 |
In [10]:
# Convert dataframe to matrix:
Matrix{Float64}(dfData1[:,2:3])
Out[10]:
2×2 Matrix{Float64}: 0.65 1.33 0.67 1.34
In [11]:
# Convert matrix to dataframe:
mData = [2.0 3 4; 3 4 5]
dfData = DataFrame(mData, :auto)
# with labelled columns:
vLabels = ["a","b","c"]
dfData = DataFrame(mData, Symbol.(vLabels))
Out[11]:
2×3 DataFrame
Row | a | b | c |
---|---|---|---|
Float64 | Float64 | Float64 | |
1 | 2.0 | 3.0 | 4.0 |
2 | 3.0 | 4.0 | 5.0 |
Custom Types (Structures)¶
In [12]:
# We can also define own object-types, so-called "structures" or "structs".
# For example:
struct mystruct3
year::Int
quarter::Int
region::String
consumption::Float64
gdp::Float64
end
# Just like when defining functions, typing "::" and the type is not necessary;
# the struct will default to type "Any".
# By default, a struct is immutable.
# To define a mutable struct, type "mutable struct mystruct3".
In [13]:
# Create an object of this type:
strCountry1 = mystruct3(2025, 4, "Europe", 0.74, 0.89)
# Access the field "gdp" of this object:
strCountry1.gdp
# Remind yourself which fields are available:
fieldnames(mystruct3)
Out[13]:
(:year, :quarter, :region, :consumption, :gdp)
In [14]:
# Create a 10x1 vector with elements of type "mystruct3":
vStrCountries = Vector{mystruct3}(undef,10)
# Fill in first element:
vStrCountries[1] = mystruct3(2025, 4, "Europe", 0.74, 0.89)
Out[14]:
mystruct3(2025, 4, "Europe", 0.74, 0.89)
Try Exercises 8.a.i - 8.a.iv