= "1.5" x
"1.5"
This document contains exercises that are intended to reinforce the concepts presented in Module 1. Each exercise should take <5 minutes to complete and there may be more than one way to approach each problem.
x
and assign it a value of 1.5
as a String
.= "1.5" x
"1.5"
x
to a numeric type that will preserve the decimal value, then assign the result to a variable y
.= parse(Float64, x) y
1.5
z
and assign it the numeric value 1.5
.= 1.5 z
1.5
y
equal to z
?== z y
true
y
identical to z
?=== z y
true
false || true && true && false || true
.4
4
true
true
true && true
true && false
false || false
false || true
true
false
-> true
) by adding ≤3 characters.false || true && true && false || !true
false || true && true && false || true) !(
false
1::Int
1.0::Float64
'c'::Char
true::Bool
true
1.0
is both a Number
and Float64
by returning a boolean value of true
.1.0 isa Number
1.0 isa Float64
true
Explain why 1.0
is both a Number
and a Float64
.
1.0
is, by definition, a floating point number.Float64
is a subtype
of the AbstractType
, Number
, thus, 1.0
is also a Number
.Explain the difference between Missing
and missing
.
Missing
is a type used to represent missing values.missing
is an instance of the type Missing
.Write an expression that evaluates whether nothing
and missing
are equivalent.
isequal(nothing, missing)
false
= join(rand('A':'Z', 1000)) s
"KCJQXYCXPDERBAXSPZMCXBPDDCQRUZTIMJNAKKWSKGYKGUYPBRAXYKNHEEAOAJZULHOGHJAKMHFPSRTXZBZNHSCHWMSVCFZCMSKRUYCLSIXXINPDUDEKCAUQVGVLJHYRHRBJZIBILQGDMWWHRYJKPIYRVGULFJRVLGVJTUQQOROTZJVSXSVNJYVEBZNLIFVJSJPCYVJFXPNBOAGKBVRHZLPEMPXRIHRATOWLEGGHIKOXOVSYDGLRSPQCBONRSZBLWPLADWBANVFLBJS" ⋯ 459 bytes ⋯ "WUCOXRUUDLZJWMYUCKFHOIJGLOJIKLPDXIFQLPWEXYTNPYKJHIJVOWWWMJIBACSSTZEQLBECDJIWKWTAUKFSKJZMRCHSQFBWEDFQRUDCOFXZGRIWTLRGOZNKSCPSMSGXBFLALARMNVTEAPZTDUFEUYVDAIDQBLJOIXVFRUKRBCHRCFQASKEJQLPDNHHJUGCIZWPIHUWXHJAYAAXHTMSWDEIUXDATLKPTPFUDBSGPSRHZFQSNVBGJHQXHNTEEDFUGTJTGAWYHKVOSYZ"
'A'
in s
. If the search returns nothing
, choose a different character until the function returns a valid index. If changed, be sure to use the new character instead of 'A'
and 'a'
(e.g., 'B'
, 'b'
) for the remaining prompts in this exercise.findfirst('A', s)
14
'a'
.findfirst('a', lowercase(s))
14
'A'
in s
.findlast('A', s)
991
s
that occur between the first and last indices identified above.chop(s, head = findfirst('A', s), tail = 1000 - findlast('A', s))
"XSPZMCXBPDDCQRUZTIMJNAKKWSKGYKGUYPBRAXYKNHEEAOAJZULHOGHJAKMHFPSRTXZBZNHSCHWMSVCFZCMSKRUYCLSIXXINPDUDEKCAUQVGVLJHYRHRBJZIBILQGDMWWHRYJKPIYRVGULFJRVLGVJTUQQOROTZJVSXSVNJYVEBZNLIFVJSJPCYVJFXPNBOAGKBVRHZLPEMPXRIHRATOWLEGGHIKOXOVSYDGLRSPQCBONRSZBLWPLADWBANVFLBJSWVGCDDGWPLCSKP" ⋯ 436 bytes ⋯ "EULLRRNMLWUCOXRUUDLZJWMYUCKFHOIJGLOJIKLPDXIFQLPWEXYTNPYKJHIJVOWWWMJIBACSSTZEQLBECDJIWKWTAUKFSKJZMRCHSQFBWEDFQRUDCOFXZGRIWTLRGOZNKSCPSMSGXBFLALARMNVTEAPZTDUFEUYVDAIDQBLJOIXVFRUKRBCHRCFQASKEJQLPDNHHJUGCIZWPIHUWXHJAYAAXHTMSWDEIUXDATLKPTPFUDBSGPSRHZFQSNVBGJHQXHNTEEDFUGTJTGA"
s
contains a string "AA"
.occursin("AA", s)
true
Which of Julia’s four basic data structures (Tuple
, NamedTuple
, Dictionary
, and Array
) are mutable? Which are indexable?
Create a Dictionary
, d1
, with Integer keys 1
, 2
, 3
, corresponding to values: "my string"
, 99
, and a tuple ("x", "y", "z")
.
= Dict(1 => "my string", 2 => 99, 3 => ("x", "y", "z")) d1
Dict{Int64, Any} with 3 entries:
2 => 99
3 => ("x", "y", "z")
1 => "my string"
d1
into two variables, a
and b
, then use those variables to create a NamedTuple
, ntp
.= d1[3]
_, a, b = (; a, b) ntp
(a = "y", b = "z")
ntp.b
to 5
; make sure the update is reflected in ntp
.= merge(ntp, (; b = 5)) ntp
(a = "y", b = 5)
Dictionary
, d2
, from ntp
where each key is of type Symbol
and each value is of type Any
.= Dict{Symbol,Any}(pairs(ntp)) d2
Dict{Symbol, Any} with 2 entries:
:a => "y"
:b => 5
c
exists in d2
where c::Symbol
; the expression should return a boolean value.haskey(d2, :c)
false
c
, to d2
, assign it a tuple containing a single value, 1
, then verify the type of value stored in c
using the appropriate function.:c] = (1,)
d2[typeof(d2[:c])
Tuple{Int64}
X
with values ranging from 100
to 900
.= collect(reshape(100:100:900, 3, 3)) X
3×3 Matrix{Int64}:
100 400 700
200 500 800
300 600 900
X
that will return a scalar value of 100
; list 4 of them below.
X[begin]1]
X[1, 1]
X[.==100][1] X[X
100
X
as both a column vector and a row vector.1, :]
X[1], :] X[[
1×3 Matrix{Int64}:
100 400 700
X
with the values 2, 5, 8
(if you get an error here, read it carefully and apply the suggested fix)..= 0
X zeros(Int, 3, 3) == X
true
true
.# Answer here
zeros(Int, 3, 3) == X # check after modifying X
true
# 5 10
# 15 20
# 25 30
# 35 40
[5 10]
[15 20]
[25 30]
[35 40]
[ ]
4×2 Matrix{Int64}:
5 10
15 20
25 30
35 40
Range
s.5:10:35 10:10:40] [
4×2 Matrix{Int64}:
5 10
15 20
25 30
35 40
x
and assign it a value missing
. Write a simple if
statement that will print the value of x
to the REPL if it is missing
.= missing
x
if ismissing(x)
println("x = $x")
end
x = missing
if
statement above using a short-circuiting logical operator.ismissing(x) && println("x = $x")
ismissing(x) || println("x = $x") !
x = missing
x = missing
if
statement below. Rewrite the expression using the ternary operator (<cond> ? <true statement> : <false statement>
).#=
if a < 5
println("$a < 5")
else
println("$a ≥ 10")
end
=#
= 10
a < 5 ? println("$a < 5") : println("$a ≥ 10") a
10 ≥ 10
if-else
statement above was changed to an if-elseif
statement. Rewrite the expression using the ternary operator.#=
if a < 5
println("$a < 5")
elseif a < 10
println("$a < 10")
else
println("$a ≥ 10")
end
=#
= 10
a < 5 ? println("$a < 5") : a < 10 ? println("$a < 10") : println("$a ≥ 10") a
10 ≥ 10
v
, of Int64
values and a counter, i = 0
. Using a while
loop, increase the value of i
by 2
until i > 20
and store the result of each iteration in v
.= Int64[]
v = 0
i
while i ≤ 20
global i += 2
push!(v, i)
end
Dictionary
, d
, then use a for
loop to populate it with the keys and values of the NamedTuple
, ntp
, below.= (; a = 100, b = "mystring", c = false)
ntp = Dict()
d
for (k, v) in zip(keys(ntp), values(ntp))
= v
d[k] end
Consider the comprehension below. In a few words, explain what is happening inside the comprehension.
x^2+1
is being applied to each element in the range 1:20 to create a column vector, A
, with 20 elements.= [x^2 + 1 for x = 1:20] A
20-element Vector{Int64}:
2
5
10
17
26
37
50
65
82
101
122
145
170
197
226
257
290
325
362
401
Array
, B
, of Int64
values, then use a nested for
loop to fill B
, row-wise, with the elements of A
.= Array{Int64}(undef, 4, 5)
B
= 0
n for i = 1:4, j = 1:5
+= 1
n global B[i, j] = A[n]
end
B
column-wise with the elements of A
.= 0
n for j = 1:5, i = 1:4
+= 1
n global B[i, j] = A[n]
end
Array
, C
, that is type Int64
and contains only zeros. Use a single (non-nested) for
loop to fill C
, column-wise with the elements of A
.= zeros(Int64, 4, 5)
C for i in eachindex(A)
global C[i] = A[i]
end
B
and C
to check for equality; how would you interpret the resulting BitMatrix
?.== C B
4×5 BitMatrix:
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
The Cockcroft-Gault formula provides an estimate of glomerular filtration rate (GFR) by calculating serum creatinine clearance (CrCL). While imperfect, CrCL is the standard metric used to guide dose adjustments for renal impairment.
The goal of this exercise is to implement the CG formula as a function and apply it across a range of values. A commonly used version of the formula is given below.
CrCL (mL/min) = (140-age)/scr * tbw/72 * 0.85^isfemale
Where:
age
(years)scr
(serum creatinine, mg/dL)tbw
(total body weight, kg)isfemale
(female sex, true/false)crcl1
, using inline assignment and positional arguments.crcl1(age, scr, tbw, isfemale) = (140 - age) / scr * tbw / 72 * 0.85^isfemale
crcl1 (generic function with 1 method)
crcl1
for a 41
y/o male patient weighing 95.6
kg with a SCr of 1.2
mg/dLcrcl1(41, 1.2, 95.6, false)
109.54166666666666
crcl1
for the same patient, but pass in, "41"
, for age
. Read the error message carefully; if you did not know the cause up front, could you interpret the message and identify the problem?crcl1("41", 1.2, 95.6, false)
MethodError: no method matching -(::Int64, ::String) Closest candidates are: -(::Real, ::Complex{Bool}) @ Base complex.jl:321 -(::Integer, ::Rational) @ Base rational.jl:350 -(::Union{Int16, Int32, Int64, Int8}, ::BigInt) @ Base gmp.jl:558 ... Stacktrace: [1] crcl1(age::String, scr::Float64, tbw::Float64, isfemale::Bool) @ Main.Notebook ~/run/_work/PumasTutorials.jl/PumasTutorials.jl/tutorials/LearningPaths/01-LP/01-Module/mod1-exercises-solutions.qmd:482 [2] top-level scope @ ~/run/_work/PumasTutorials.jl/PumasTutorials.jl/tutorials/LearningPaths/01-LP/01-Module/mod1-exercises-solutions.qmd:497
crcl1
for the same patient, but pass in missing
for isfemale
. Did you expect an error message, or a missing
return value? Why?crcl1(41, 1.2, 95.6, missing)
missing
crcl2
, using the function
keyword and kwargs. Set the default value for isfemale
to false
. Before calculating CrCL, the function should check whether the value of each continuous variable is a Number
; if not, it should return a missing
value. (hint: there are several ways to write the check; consider reviewing, ?isa
and ?all
)function crcl2(; age, scr, tbw, isfemale = false)
all(x -> isa(x, Number), [age, scr, tbw]) || return missing
return (140 - age) / scr * tbw / 72 * 0.85^isfemale
end
crcl2 (generic function with 1 method)
crcl2
for the same patient, making sure to test the “normal” case, then a case where age
, scr
, or tbw
is not a Number
.crcl2(; age = 41, scr = 1.2, tbw = 95.6)
crcl2(; age = missing, scr = 1.2, tbw = 95.6)
missing
crcl3
using kwargs and type assertion for each argument. Set the default value for isfemale
to false
.function crcl3(; age::Real, scr::Real, tbw::Real, isfemale::Bool = false)
return (140 - age) / scr * tbw / 72 * 0.85^isfemale
end
crcl3 (generic function with 1 method)
crcl3
for the same patient using both a “normal” and “error” case.crcl3(; age = 41, scr = 1.2, tbw = 95.6)
crcl3(; age = missing, scr = 1.2, tbw = 95.6)
TypeError: in keyword argument age, expected Real, got a value of type Missing Stacktrace: [1] top-level scope @ ~/run/_work/PumasTutorials.jl/PumasTutorials.jl/tutorials/LearningPaths/01-LP/01-Module/mod1-exercises-solutions.qmd:544
Note: It is good practice to include basic error-handling in your code, but the way you define “basic” will evolve as you become more comfortable with Julia. Simply returning a missing value for common errors as in crcl2
, is a valid approach, especially if you’re working alone and your code is well-documented. In larger projects, type assertion (e.g., crcl3
) and more advanced error-handling workflows (e.g., try-catch
, throw
, @assert
) become more important. When starting out, you can worry less about error handling since the code you write will naturally become more elegant (and less error-prone) over time.
crcl3
to calculate CrCL across a range of scr
values (0.5:0.05:1.25
) for a 30
y/o female weighing 145
lbs.crcl3(; age = 30, scr, tbw = 145 / 2.2, isfemale = true) for scr = 0.5:0.05:1.25] [
16-element Vector{Float64}:
171.18055555555554
155.61868686868684
142.65046296296296
131.6773504273504
122.27182539682539
114.12037037037035
106.98784722222221
100.69444444444443
95.1003086419753
90.09502923976609
85.59027777777777
81.51455026455027
77.80934343434342
74.42632850241546
71.32523148148148
68.47222222222223
Explore the code below, then try to articulate what is happening with each component. If you encounter a function or symbol that is unfamiliar, check the help menu, ?
. The goal here is to provide a few examples for generating and manipulating data based up on the techniques outlined so far.
Distributions
package is included in the Pumas application and provides access to the Normal
and Bernoulli
distributions.patients
is an empty Dictionary
that will be used to store information about each individual patient once it’s created in the for
loop.for
loop will create 10 patients based upon the length of range 1:10.age
and tbw
are randomly sampled from a distribution (uniform, normal, bernoulli).input
is a NamedTuple
that is used to store patient characteristics for use elsewhere in the loop.output
is the result of the crcl3
function; recall that crcl3
accepts 4 kwargs corresponding the keys in input
. The splatting operator ...
“opens” input
and matches its keys to the appropriate kwarg.for
loop creates a key i
in patients
; the value is a NamedTuple
of the input and output for crcl3
.patients[1]
is accessing the first patient’s data; note this is not indexing, the keys in patients
are integers._in
is a vector of NamedTuple
s, each corresponding to an input
. The underscore _
in _in
is common style convention denoting a “temporary” or “intermediate” variable._weights
is a vector of individual weights.mean
expression uses a generator to calculate mean body weight.# load package needed for Normal, Bernoulli distributions
using Distributions
# a data structure for storing "patients"
= Dict()
patients
# a loop to create 10 patients with randomly sampled characteristics
for i = 1:10
# random input
= rand(19:92)
age = rand(Normal(0.9, 0.2))
scr = rand(Normal(72, 20))
tbw = rand(Bernoulli())
isfemale
# save the input in a data structure for later
= (; age, scr, tbw, isfemale)
input
# generate output
= crcl3(; input...)
output
# save both as a entry in patients with a corresponding key
= (; input, output)
patients[i] end
1] # key, *not* index
patients[
= [v.input for v in values(patients)]
_in
= [i.tbw for i in _in]
_weights
mean((i.tbw for i in _in))
75.52446003937078
m
, below. Execute the code in the begin
block and then programmatically check the number of methods associated with m
.begin
m(; x::Real) = x + 3
m(; x::AbstractString) = parse(Float64, x) + 3
end
methods(m)
What happens if you execute the call below? Why?
m
has no positional arguments, so the second definition simply overwrites the first, since the “number” of positional argument remains zero.MethodError
occurred because m
was expecting an argument of type AbstractString
.m(x = 3)
TypeError: in keyword argument x, expected AbstractString, got a value of type Int64 Stacktrace: [1] top-level scope @ ~/run/_work/PumasTutorials.jl/PumasTutorials.jl/tutorials/LearningPaths/01-LP/01-Module/mod1-exercises-solutions.qmd:654
Redefine m
to create the two intended methods above. How many total methods will m
have? Why? What happens if you try to set m = nothing
? Why?
m
will have 3 methods, the first was defined above with a single kwarg, x
.y
; one method accepts Real
values and the other accepts AbstractString
sm(y::Real) = y + 3
m(y::AbstractString) = parse(Float64, y) + 3
m (generic function with 3 methods)
x = [1,2,3]
. Then, define a function that adds 3
to each element of x
and returns the sum of its elements. The function should modify x
in-place and follow the style convention for mutating functions.= [1, 2, 3]
x function add3!(y)
.+= 3
y return sum(y)
end
add3! (generic function with 1 method)
function add3(y)
= y .+ 3
y return sum(y)
end
add3 (generic function with 1 method)