x = "1.5""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.x = "1.5""1.5"
x to a numeric type that will preserve the decimal value, then assign the result to a variable y.y = parse(Float64, x)1.5
z and assign it the numeric value 1.5.z = 1.51.5
y equal to z?y == ztrue
y identical to z?y === ztrue
false || true && true && false || true.44
truetrue
true && true
true && false
false || false
false || truetrue
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::Booltrue
1.0 is both a Number and Float64 by returning a boolean value of true.1.0 isa Number
1.0 isa Float64true
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
s = join(rand('A':'Z', 1000))"QFMMSCIFFWBHVDUZOWOJFILNLLVLNJHGPZMGIIHAUUFQOTWBGHPLFYDMBBJXIHCTNRRUOAOWGLAYZBNSPTHUWOPKXOILINYQLZEPENAHXVMJSGQSZZUDRKIWTCWJVSMBFARWAOUFYHZZKAZNYZWLBRYMYMATKOVSFBAFJPGUYOIHDEZMRINSZOSLKPXTFSJQMSRTMNFNAPBLTEFULUAJCJIAFKMTRLDZKPOWUDBEFROKBQOPBNBIGIXCQWSTHXKBGRNWBBIDCRSZKZA" ⋯ 459 bytes ⋯ "FDXXZONCRDFBPFJEZHKMJIOQCKCTYMXALIPIEONJOWXUGPDIBENQWJSSGHHRZLWMETEONEAWHCVDWJOTOTDREEMZBLIUQBPBZKFCNKEDMIOWMEGXHTSLQIQRTKHYLOVFSMVKVSZKVDUACUXIHEDFYTRVELVKATYIHHSNDHOLUIWHGQROJRGIDPQEMGGFUEXAVMTRWLABZJZYFAMBPISDUPEQANRRAOAGUPJFJOALUDPSQUAGJNARHNPGTWDCAIOYJVXTUGDZRDQWBD"
'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)40
'a'.findfirst('a', lowercase(s))40
'A' in s.findlast('A', s)983
s that occur between the first and last indices identified above.chop(s, head = findfirst('A', s), tail = 1000 - findlast('A', s))"UUFQOTWBGHPLFYDMBBJXIHCTNRRUOAOWGLAYZBNSPTHUWOPKXOILINYQLZEPENAHXVMJSGQSZZUDRKIWTCWJVSMBFARWAOUFYHZZKAZNYZWLBRYMYMATKOVSFBAFJPGUYOIHDEZMRINSZOSLKPXTFSJQMSRTMNFNAPBLTEFULUAJCJIAFKMTRLDZKPOWUDBEFROKBQOPBNBIGIXCQWSTHXKBGRNWBBIDCRSZKZAPLXKWQFKQTLTZAZLPEMYJWFQJAWYMILYDHWQDZSM" ⋯ 402 bytes ⋯ "GAAFQLGUDSZFLSVVNFDXXZONCRDFBPFJEZHKMJIOQCKCTYMXALIPIEONJOWXUGPDIBENQWJSSGHHRZLWMETEONEAWHCVDWJOTOTDREEMZBLIUQBPBZKFCNKEDMIOWMEGXHTSLQIQRTKHYLOVFSMVKVSZKVDUACUXIHEDFYTRVELVKATYIHHSNDHOLUIWHGQROJRGIDPQEMGGFUEXAVMTRWLABZJZYFAMBPISDUPEQANRRAOAGUPJFJOALUDPSQUAGJNARHNPGTWDCA"
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").
d1 = Dict(1 => "my string", 2 => 99, 3 => ("x", "y", "z"))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._, a, b = d1[3]
ntp = (; a, b)(a = "y", b = "z")
ntp.b to 5; make sure the update is reflected in ntp.ntp = merge(ntp, (; b = 5))(a = "y", b = 5)
Dictionary, d2, from ntp where each key is of type Symbol and each value is of type Any.d2 = Dict{Symbol,Any}(pairs(ntp))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.d2[:c] = (1,)
typeof(d2[:c])Tuple{Int64}
X with values ranging from 100 to 900.X = collect(reshape(100:100:900, 3, 3))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]
X[1]
X[1, 1]
X[X.==100][1]100
X as both a column vector and a row vector.X[1, :]
X[[1], :]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).X .= 0
zeros(Int, 3, 3) == Xtrue
true.# Answer here
zeros(Int, 3, 3) == X # check after modifying Xtrue
# 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
Ranges.[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.x = missing
if ismissing(x)
println("x = $x")
endx = 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
=#
a = 10
a < 5 ? println("$a < 5") : println("$a ≥ 10")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
=#
a = 10
a < 5 ? println("$a < 5") : a < 10 ? println("$a < 10") : println("$a ≥ 10")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.v = Int64[]
i = 0
while i ≤ 20
global i += 2
push!(v, i)
endDictionary, d, then use a for loop to populate it with the keys and values of the NamedTuple, ntp, below.ntp = (; a = 100, b = "mystring", c = false)
d = Dict()
for (k, v) in zip(keys(ntp), values(ntp))
d[k] = v
endConsider 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.A = [x^2 + 1 for x = 1:20]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.B = Array{Int64}(undef, 4, 5)
n = 0
for i = 1:4, j = 1:5
n += 1
global B[i, j] = A[n]
endB column-wise with the elements of A.n = 0
for j = 1:5, i = 1:4
n += 1
global B[i, j] = A[n]
endArray, 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.C = zeros(Int64, 4, 5)
for i in eachindex(A)
global C[i] = A[i]
endB and C to check for equality; how would you interpret the resulting BitMatrix?B .== C4×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^isfemalecrcl1 (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) The function `-` exists, but no method is defined for this combination of argument types. Closest candidates are: -(::Real, ::Complex{Bool}) @ Base complex.jl:324 -(::Integer, ::Rational) @ Base rational.jl:380 -(::Union{Int16, Int32, Int64, Int8}, ::BigInt) @ Base gmp.jl:553 ... 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:649 [2] top-level scope @ ~/run/_work/PumasTutorials.jl/PumasTutorials.jl/tutorials/LearningPaths/01-LP/01-Module/mod1-exercises-solutions.qmd:672
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
endcrcl2 (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
endcrcl3 (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:739
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 NamedTuples, 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 Pumas
# a data structure for storing "patients"
patients = Dict()
# a loop to create 10 patients with randomly sampled characteristics
for i = 1:10
# random input
age = rand(19:92)
scr = rand(Normal(0.9, 0.2))
tbw = rand(Normal(72, 20))
isfemale = rand(Bernoulli())
# save the input in a data structure for later
input = (; age, scr, tbw, isfemale)
# generate output
output = crcl3(; input...)
# save both as a entry in patients with a corresponding key
patients[i] = (; input, output)
end
patients[1] # key, *not* index
_in = [v.input for v in values(patients)]
_weights = [i.tbw for i in _in]
mean((i.tbw for i in _in))72.18494200898098
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:865
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 AbstractStringsm(y::Real) = y + 3
m(y::AbstractString) = parse(Float64, y) + 3m (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.x = [1, 2, 3]
function add3!(y)
y .+= 3
return sum(y)
endadd3! (generic function with 1 method)
function add3(y)
y = y .+ 3
return sum(y)
endadd3 (generic function with 1 method)