9.0 KiB
Julia for Data Analysis
Chapter 2: Getting started with Julia
Bogumił Kamiński
https://github.com/bkamins/JuliaForDataAnalysis
Outline
- Values
- Variables
- Control flow 3.1. Conditional evaluation 3.2. Loops 3.3. Compound expressions
- Defining functions
- Scoping rules
Values
- A value is a representation of some entity that is stored in computer’s memory and can be manipulated by Julia program.
- Every value is a result of evaluation of some Julia expression.
- Example values created by evaluation of literals:
julia> 0.1
0.1
julia> "Hello world!"
"Hello world!"
julia> [1, 2, 3]
3-element Vector{Int64}:
1
2
3
Types
- Each value has a type, which you can check using the
typeoffunction. - When you define a function you optionally can define the types of arguments that the function accepts.
- Example types of values:
julia> typeof(0.1)
Float64
julia> typeof("Hello world!")
String
julia> typeof([1, 2, 3])
Vector{Int64} (alias for Array{Int64, 1})
Checking memory layout of numbers
- Numbers in Julia can have different types (e.g.
Int64,Float64,Int8). - Each such type can use a different memory layout to represent the same mathematical value.
- Examples of representation of one:
julia> bitstring(1)
"0000000000000000000000000000000000000000000000000000000000000001"
julia> bitstring(1.0)
"0011111111110000000000000000000000000000000000000000000000000000"
julia> bitstring(Int8(1))
"00000001"
Integer type
On 64-bit machines integers in Julia by default use 64-bit
representation (Int64 type). As a shorthand you can use
Int alias type name instead:
julia> Int
Int64
Container types typically have parameters in Julia
julia> typeof([1, 2, 3])
Vector{Int64} (alias for Array{Int64, 1})
Vector{Int64}tells us that[1, 2, 3]is a vector that can store integer numbers; theInt64part is a parameter ofVector;Array{Int64, 1}is another way to write the same; now we have two parameters:- element type that our array can store (
Int64, an integer); - dimension of our array (
1, a vector).
- element type that our array can store (
Checking if value has some type
You can check if some value has some type using the isa
operator:
julia> [1, 2, 3] isa Vector{Int}
true
julia> [1, 2, 3] isa Array{Int64, 1}
true
Variables
- You can bind a value to a variable name using the assignment
operator
=:
julia> x = 1
1
julia> y = [1, 2, 3]
3-element Vector{Int64}:
1
2
3
- The process of binding does not involve copying of values. Python also follows this approach. In R this is not the case.
Only values have types in Julia
You can, in general assign values of different types to the same variable name:
julia> x = 1
1
julia> typeof(x)
Int64
julia> x = 0.1
0.1
julia> typeof(x)
Float64
Although this is allowed it is usually not recommended.
You can use Unicode characters in variable names
julia> Kamiński = 1
1
julia> x₁ = 0.5
0.5
julia> ε = 0.0001
0.0001
In Julia REPL, VS Code ect., you can easily type such characters:
help?> ₁
"₁" can be typed by \_1<tab>
help?> ε
"ε" can be typed by \varepsilon<tab>
The if statement
julia> x = -7
-7
julia> if x > 0
println("positive")
elseif x < 0
println("negative")
elseif x == 0
println("zero")
else
println("unexpected condition")
end
negative
Conditions must be Boolean values
julia> x = -7
-7
julia> if x
println("condition was true")
end
ERROR: TypeError: non-boolean (Int64) used in boolean context
Be careful with numeric comparisons of floats
julia> NaN > 0
false
julia> NaN < 0
false
julia> NaN == 0
false
julia> NaN != 0
true
julia> NaN != NaN
true
julia> 0.1 + 0.2 == 0.3
false
Combining logical
conditions with && and ||
julia> x = -7
-7
julia> x > 0 && x < 10
false
julia> x < 0 || log(x) > 10
true
Short-circut evaluation
&& and || evaluate only as many
conditions (starting from the leftmost) as is needed to determine the
logical value of the whole expression.
julia> x = -7
-7
julia> log(x)
ERROR: DomainError with -7.0:
log will only return a complex result if called with a complex argument. Try log(Complex(x)).
julia> x < 0 || log(x) > 10
true
One-line conditional
evaluation using && and ||
The codes below use short-circut evaluation:
julia> x = -7
-7
julia> x < 0 && println(x^2)
49
julia> iseven(x) || println("x is odd")
x is odd
Ternary operator
Julia supports the ternary operator borrowed from the C programming language.
x > 0 ? sqrt(x) : sqrt(-x)
is equivalent to writing:
if x > 0
sqrt(x)
else
sqrt(-x)
end
Conditional statements return a value
julia> x = -4.0
-4.0
julia> y = if x > 0
sqrt(x)
else
sqrt(-x)
end
2.0
julia> y
2.0
Example for loop
julia> for i in [1, 2, 3]
println(i, " is ", isodd(i) ? "odd" : "even")
end
1 is odd
2 is even
3 is odd
Example while loop
julia> i = 1
1
julia> while i < 4
println(i, " is ", isodd(i) ? "odd" : "even")
global i += 1
end
1 is odd
2 is even
3 is odd
We use global keyword in the loop as we want to update
the i variable which is in global scope.
Standard
break and continue keywords are supported in
loops
julia> i = 0
0
julia> while true
global i += 1
i > 6 && break
isodd(i) && continue
println(i, " is even")
end
2 is even
4 is even
6 is even
Compound expression
using begin-end block
julia> x = -7
-7
julia> x < 0 && begin
println(x)
x += 1
println(x)
2 * x
end
-7
-6
-12
The value of the compound expression is the value of the last
expression inside it (-12 in our case).
Compound expression using
semicolon ;
If your code is short you can wrap several expressions in parentheses
and separate them using semicolon ;:
julia> x = -7
-7
julia> x > 0 ? (println(x); x) : (x += 1; println(x); x)
-5
-5
You can
use the function keyword to define a function
julia> function times_two(x)
return 2 * x
end
times_two (generic function with 1 method)
julia> times_two(10)
20
Functions
allow positional and keyword arguments separated by ; with
optional default values
julia> function compose(x, y=10; a, b=10)
return x, y, a, b
end
compose (generic function with 2 methods)
julia> compose(1, 2; a=3, b=4)
(1, 2, 3, 4)
julia> compose(1, 2; a=3)
(1, 2, 3, 10)
julia> compose(1; a=3)
(1, 10, 3, 10)
julia> compose(1)
ERROR: UndefKeywordError: keyword argument a not assigned
julia> compose(; a=3)
ERROR: MethodError: no method matching g(; a=3)
Passing arguments to functions in Julia
If you pass a value to a function Julia performs a binding of the function argument name to this value. This feature is called pass-by-sharing and means that Julia never copies data when arguments are passed to a function.
This is a behavior that you might know from Python, but is different from e.g., R, where copying of function arguments is performed.
Short syntax for creation simple functions
You can use the assignment operator to create one-line functions:
julia> times_two(x) = 2 * x
times_two (generic function with 1 method)
julia> compose(x, y=10; a, b=10) = x, y, a, b
compose (generic function with 2 methods)
Functions are first class objects in Julia
You can pass functions as arguments to other functions:
julia> map(times_two, [1, 2, 3])
3-element Vector{Int64}:
2
4
6
You can
define anonymous functions using the -> operator
julia> map(x -> 2 * x, [1, 2, 3])
3-element Vector{Int64}:
2
4
6
julia> sum(x -> x ^ 2, [1, 2, 3])
14
Julia supports do
blocks
If a function takes another function as its first argument you can
conveniently define it using a do block:
julia> sum([1, 2, 3]) do x
println("processing ", x)
return x ^ 2
end
processing 1
processing 2
processing 3
14
The ! character in
function names
Often you will see an exclamation mark (!) at the end of
the function name, e.g., sort!. There is a convention that
developers are recommended to add ! at the end of the
functions they create if such functions modify their arguments.
julia> x = [5, 1, 3, 2];
julia> sort(x); # returns a new vector; x is not changed
julia> sort!(x); # changes x in-place
Variable scoping rules in Julia (simplified)
The following constructs we have learned till now create a new scope
(local scope): * functions, anonymous functions,
do-end blocks; * for and
while loops.
Notably the if blocks and the
begin-end blocks do not introduce a new scope.
This means that variables defined in such blocks leak out to the
enclosing scope.