Computational Analysis of Social Complexity
Fall 2025, Spencer Lyon
Prerequisites
- Laptop or personal computer with internet connection
Outcomes
- Understand the main benefits and features of Julia
- See how to define variables, functions, and types in Julia
- Install commonly used packages for Graphs, DataFrames, Plotting and more
References
- Packages and Software Engineering sections of QuantEcon julia lectures
- Julia documentation
- Documentation for packages: Graphs, DataFrames, Plots
What is Julia?¶
- Julia is a relatively new programming language (first public release in 2012, 1.0 release in 2018)
- General purpose, but specializes in numerical computation
- Leverages advanced compiler technology to generate very efficient code
- It can be as clear to read and write as Python, and as quick to evaluate as C!
Core Types¶
- We’ll start by learning about the core datatypes built in to Julia
- Along the way we’ll pick up some of the key syntax elements
- We will move quickly, so some prior programming experience would be helpful
Numbers¶
- Let’s start with numbers
- To work with a number, just type it!
4242- We can also do basic arithmetic in the way you would expect
10 * 3301 + 23- So far we’ve worked with integers (whole numbers)
- Julia can also work with numbers containing a decimal
- In Julia these are called floating point numbers
1.234 ^ 2.2 # use `^` for exponentiation, not `**` like in python1.5881567008330448553.34 / 12.942.89457364341085- We can mix and match integers and floats
25 / 2.510.025 / 2 # dividing integers returns a float (notice the `.`)12.5- notice we used
#to define a comment
Text Data¶
- Not all data is numerical
- Some is textual
- To represent text in Julia we use a
String - To define a String we use quotation marks (
") as below
"My name is Spencer""My name is Spencer""1" # an integer in a string"1"- You cannot use single quotes for strings as in other languages (like Python or Javascript)
- Go ahead... try it by removing the
#and excuting the cell below
# 'hello'"""
This
is
also
a
string
""""This\n\nis\n\nalso\n\na\n\nstring\n"Arrays¶
- When doing numerical work, we often need to deal with multiple pieces of data at the same time
- In Julia the default way of doing this is to use an array
- Arrays are defined with
[and]as below
[1, 2, 3.14] # a 3 element array3-element Vector{Float64}:
1.0
2.0
3.14[1 2 3] # a 1x3 matrix1×3 Matrix{Int64}:
1 2 3[1 2; 3 4] # a 2x2 matrix2×2 Matrix{Int64}:
1 2
3 4[1 2
3 4] # another way to write a 2x2 matrix2×2 Matrix{Int64}:
1 2
3 4[1 "hello"; 2 "world"] # a 2x2 matrix with int and string2×2 Matrix{Any}:
1 "hello"
2 "world"Accessing array items¶
- We can use
[N]to access the Nth element - We can also use
[i:j]to access itemsithroughj - Finally we can use
[[n1, n2]]to access the n1th and n2th elements
[100, 101, 102, 103][2]101[100, 101, 102, 103][2:4]3-element Vector{Int64}:
101
102
103[100, 101, 102, 103][[1, 3]]2-element Vector{Int64}:
100
102- Note that unlike Python, Julia starts counting at 1
- Also note that
endcan be used to refer to the last element,end-1to second to last, and so on
[1, 2, 3, 4][end]4[1, 2, 3, 4][end-2]2Tuples¶
- There is another data type for holding “lists” of data called a tuple
- Tuples are create using parenthesis instead of square brackets as follows
(1, 2, 3, "hello")(1, 2, 3, "hello")("hello", 5)("hello", 5)("hello", 5)[2]5- The main differences between tuples and arrays are
- Tuples are meant to hold immutable or non-changing data
- Tuples aren’t usually meant for computation or linear algebra
Dictionary¶
- Very often in programming we want to be able to associate a key or name to a specific value
- One data type for doing that is a Dict
- Dicts are created with the somewhat inconvenient syntax
Dict(name => value, ...)where the...means we can repeat the pattern multiple times - They keys and values can be of any type
Dict("x" => 1, 2 => "y", ["w", "z"] => [1, 2, 3])Dict{Any, Any} with 3 entries:
2 => "y"
["w", "z"] => [1, 2, 3]
"x" => 1# use `[name]` to access element with `name`
Dict("x" => 1, 2 => "y", ["w", "z"] => [1, 2, 3])[2]"y"Dict("x" => 1, "y" =>2)Dict{String, Int64} with 2 entries:
"x" => 1
"y" => 2- Dictionaries are often used for passing around groups of parameters
- We’ll see examples later on
Named Tuples¶
The final “collection” we’ll talk about is the named tuple
It is a hybrid between a tuple and a dictionary
To create them we use the synax
(name = value, ...)They names or keys need to be just names (not numbers or arrays). The values can be anything
(x = 1, y = 2, z="hello")(x = 1, y = 2, z = "hello")(x = 1, y = 2, z="hello").z # use `.name` to access item"hello"- Named tuples are a newer feature of Julia
- They are often used for the same purpsoes as dictionaries because the syntax is much cleaner
Variables¶
- Often when programming, we need to refer to the same piece of data more than once
- To do this we use a variable
- Variables are defined using an
=, as inname = value
x = 11y = 4242x + y # 'use' or 'refer to' x and y43m1 = [1 0; 0 1]2×2 Matrix{Int64}:
1 0
0 1m2 = [1 2; 3 4]2×2 Matrix{Int64}:
1 2
3 4m1 * m2 # matrix multiplication2×2 Matrix{Int64}:
1 2
3 4m2 * m2 # again -- but with something besides identity matrix!2×2 Matrix{Int64}:
7 10
15 22d = Dict("X" => 1, "Y" => 2)Dict{String, Int64} with 2 entries:
"Y" => 2
"X" => 1d["X"]1Functions¶
- Most Julia programs do more than basic arithmetic operations on data
- To apply an operation to a piece of data, we call a function
- To call a function we use the
function_name(data1, data2) - A very handy function is the
typeoffunction
typeof(1)Int64typeof(2.0)Float64typeof([1,2,3])Vector{Int64} (alias for Array{Int64, 1})typeof([1 2; 3 4.0])Matrix{Float64} (alias for Array{Float64, 2})- Many standard operations are built in to Julia as functions
sum([1, 2, 3]) # compute sum of array of numbers6inv([1 2; 3 4]) # matrix inverse2×2 Matrix{Float64}:
-2.0 1.0
1.5 -0.5size([1 2; 3 4]) # number of (rows, columns) in matrix(2, 2)length([1, 2, 3]) # number of elements in array3length([1 2; 3 4]) # returns total number of elements in a Matrix4rand(2, 2, 2) # a 2x2x2 array of random numbers, sampled from uniform[0,1] dist2×2×2 Array{Float64, 3}:
[:, :, 1] =
0.362076 0.860015
0.297667 0.572139
[:, :, 2] =
0.775214 0.145788
0.815195 0.820176- Julia has 1000s of functions
- We’ll learn more as we go along...
- Just watch for the pattern with parentisis:
name(args)
Defining Functions¶
- Functions are used to execute a predefined set of operations
- Defining our own funcitons allows us to break programs into small, easily written an understood components
- We define functions using the syntax
function name(arg1, arg2)
# steps
endfunction mean(x)
total = sum(x)
N = length(x)
total / N
endmean (generic function with 1 method)mean([1, 2, 3])2.0# mean of 1000 random samples from U[0,1] -- should be ~ 0.5
mean(rand(1000))0.48944092617754337- If a function only contains one line of code, you can also use a shorthand notation:
function_name(arg1, arg2) = # stepadd_two(x) = x + 2add_two (generic function with 1 method)add_two(40)42Getting help for functions¶
- Given that there are so many functions, sometimes it is hard to remember exactly what a function does
- Thankfully we can get help from Julia
- If we type
?function_name, Julia will present us with documentation about the function
?mapParseError:
# Error @ /Users/sglyon/Teaching/UCF/CAP-6318/book-myst/week01/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_Y145sZmlsZQ==.jl:1:1
?map
╙ ── not a unary operator
Stacktrace:
[1] top-level scope
@ ~/Teaching/UCF/CAP-6318/book-myst/week01/jl_notebook_cell_df34fa98e69747e1a8f8a730347b8e2f_Y145sZmlsZQ==.jl:1?extremaControl Flow¶
- Julia has the basic elements of control flow:
if-elsestatementsforloops
if 1 > 2 # no parenthesis needed
println("what???")
else # else is optional
return mean([1, 2, 3])
print("phew")
end # all "blocks" terminate with word `end`2.0for i in 1:5 # range of numbers 1 to 5
println(i, " ", i^2)
end1 1
2 4
3 9
4 16
5 25
- We will see many more examples as we go forward
Packages¶
- Julia comes ready to go with many powerful functions and data types
- However, there is a very active community of Julia programmers who are experts in different subfields of science and engineering
- This has led to the development of vibrant and exciting ecosystem of packages or toolboxes for performing specific tasks
- We can access these routines by using Julia packages
Loading packages¶
- By default Julia ships with a “standard library”
- These are packages that come bundled with Julia itself and are pre-installed
- To load a package and all of its types/functions use the
usingkeyword - For example, we can load the
Datespackage and start using it
using Datest1 = Dates.now()2024-08-19T20:39:10.885Dates.format(t1, "yyyy-mm-dd")"2024-08-19"t2 = Dates.now()2024-08-19T20:39:11.197t2 > t1truet3 = DateTime(1776, 7, 4)1776-07-04T00:00:00"America is $(t1 - t3) ($(floor(t1 - t3, Dates.Day))) old""America is 7830160750885 milliseconds (90626 days) old"Installing Packages¶
- In addition to the standard library, we can also use packages created by other Julia users
- To use a 3rd party package, we first need to install it
- There are two ways to do this
1
]add PackageName2
using Pkg # a standard library package
Pkg.add("PackageName")Let’s try them both
]add Plots Resolving package versions...
No Changes to `~/.julia/environments/v1.10/Project.toml`
No Changes to `~/.julia/environments/v1.10/Manifest.toml`
using Pkg
Pkg.add("DataFrames") Resolving package versions...
No Changes to `~/.julia/environments/v1.10/Project.toml`
No Changes to `~/.julia/environments/v1.10/Manifest.toml`
- After installing packages, we can load and use them just as we did the standard library packages
using Plots # Python: from Plots import *plot([sin, cos], -2pi, 2pi)Loading...
using DataFrames
df = DataFrame(c1=1:10, c2=(1:10).^2)Loading...
Package Composability¶
- One unique feature sof Julia is that most of the language itself, in addition to packages, are written in Julia
- For other languages like Python or R the “built in” part of the language is often written in another language like C
- This difference has a large impact for Julia users
- Built in code and user code (including packages) are given the same “treatment”
- Anything the language creators can do, so can you
- A practical implication of this is that packages can operate on built in types (like we saw in our examples above) as well as types from other packages
- Let’s see what this looks like by plotting a DataFrame
# install "StatsPlots", which links Plots and DataFrames
Pkg.add("StatsPlots")
using StatsPlots Resolving package versions...
No Changes to `~/.julia/environments/v1.10/Project.toml`
No Changes to `~/.julia/environments/v1.10/Manifest.toml`
@df df scatter(:c1, :c2)Loading...
Pkg.add("RDatasets") # common datasets from R programming language
using RDatasets
school = RDatasets.dataset("mlmRev","Hsb82")
@df school density(:MAch, group = :Sx)Loading...
@df school density(:MAch, group = (:Sx, :Sector))Loading...