Skip to content

add concept exercise problematic-probabilities #966

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,18 @@
],
"status": "beta"
},
{
"slug": "problematic-probabilities",
"name": "Problematic Probabilities",
"uuid": "f3fe4029-03fd-4dde-aeeb-a05f4873f377",
"concepts": [
"rational-numbers"
],
"prerequisites": [
"loops"
],
"status": "beta"
},
{
"slug": "old-elyses-enchantments",
"name": "Old Elyse's Enchantments",
Expand Down
25 changes: 25 additions & 0 deletions exercises/concept/problematic-probabilities/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Hints

## 1. Give the ratio of successes to trials

- Create `Rational` numbers using the successes as the numerator and the trials as the denominator.
- Loops, comprehensions or vector operations can be employed to create the new array.

## 2. Find the real probabilities associated with successes to trials

- Create `Float` numbers using the successes as the numerator and the trials as the denominator.
- Loops, comprehensions or vector operations can be employed to create the new array.

## 3. Check the mean of the probabilities

- The mean is found by summing the data and dividing by the number of events.
- Use your `rationalize` and `probabilities` functions.
- If a discrepancy is found between using floats and rationals, return the rational number.
- Loops or vector operations can be employed.

## 4. Check the independent probability

- Probabilities of independent events can be multiplied together to find the total probability of those events happening together.
- Use your `rationalize` and `probabilities` functions.
- If a discrepancy is found between using floats and rationals, return the rational number.
- Loops or vector operations can be employed.
73 changes: 73 additions & 0 deletions exercises/concept/problematic-probabilities/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Instructions

A research organization has noticed that some of its junior researchers have been using less-than-optimal methods in their analysis.
One of these methods is to immediately convert discrete probabilites into floats (i.e. real numbers) before doing further calculations.

Senior researchers prefer to use rational numbers in the calculations, which may provide higher precision, and would like to know if the poor practices of the junior researchers have affected the studies' outcomes.

There were two types of studies done:
- Many tests were run, each returning a discrete probability for success, and then the mean of these probabilities was calculated.
- Many independent events were tested, and the total probability for all events to occur was calculated by multiplying them together.

The senior researchers are asking you to write functions which can help them determine if there are rounding errors in the analyses from using floats, and asking you provide a precise rational number for the outcome if there are errors.

## 1. Give the ratio of successes to trials

The `rationalize(successes, trials)` function takes two arrays, `successes` and `trials`.
For an index `i`, `successes[i]` corresponds to the number of successes which occurred in `trials[i]` number of trials.
The function returns an array of rational numbers of the successes over the number of trials, in the same order as the input arrays.

```julia-repl
julia> rationalize([1, 2, 3], [4, 5, 6])
3-element Vector{Rational{Int64}}:
1//4
2//5
1//2
```

## 2. Find the real probabilities associated with successes to trials

Similarly, the `probabilities(successes, trials)` function takes two arrays, `successes` and `trials`.
It returns an array of floats of the successes over the number of trials, in the same order as the input arrays.

```julia-repl
julia> probabilities(([1, 2, 3], [4, 5, 6]))
3-element Vector{Float64}:
0.25
0.4
0.5
```

## 3. Check the mean of the probabilities

The `checkmean(successes, trials)` takes the two arrays, `successes` and `trials`.
It checks the mean of the real probabilities against the mean of the rational probabilities.
- If the two probabilities are equal, `checkmean` returns `true`
- If the two probabilites are different, `checkmean` returns the rational probability.

```julia-repl
julia> successes, trials = [9, 4, 7, 8, 6], [22, 22, 11, 17, 12];
julia> checkmean(successes, trials)
true

julia> successes, trials = [6, 5, 9, 8, 9], [21, 19, 13, 25, 22];
julia> checkmean(sucesses, trials)
1873629//4754750
```

## 4. Check the independent probability

The `checkprob(successes, trials)` takes the two arrays, `successes` and `trials`.
It checks the total probability of the float probabilities against the total probability of the rational probabilities.
- If the two probabilities are equal, `checkprob` returns `true`
- If the two probabilites are different, `checkprob` returns the rational probability.

```julia-repl
julia> successes, trials = [2, 9, 4, 4, 5], [15, 11, 17, 19, 15];
julia> checkprob(successes, trials)
true

julia> successes, trials = [9, 8, 5, 4, 3], [22, 14, 19, 25, 18];
julia> checkprob(sucesses, trials)
12//7315
```
67 changes: 67 additions & 0 deletions exercises/concept/problematic-probabilities/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Introduction

`Rational numbers` are fractions with an integer numerator divided by an integer denominator.

For example, we can store `2//3` as an exact fraction instead of the approximate `Float64` value `0.6666...`

The advantage is that (except in the extreme case of *integer overflow*) a rational number will remain exact, avoiding the rounding errors that are often inevitable with floating-point numbers.

Rational numbers are quite a simple numeric type and aim to work much as you would expect.
Because they have been a standard type in Julia since the early versions, most functions will accept them as input in the same way as integers and floats.

## Creating rational numbers

Creation is as simple as using `//` between two integers.

```julia-repl
julia> 3//4
3//4

julia> a = 3; b = 4;

julia> a//b
3//4
```

Common factors are automatically removed, converting the fraction to its "lowest terms": the smallest integers that accurately represent the fraction, and with a non-negative denominator.

```julia-repl
julia> 5//15
1//3

julia> 5//-15
-1//3
```

## Arithmetic with rational numbers

The usual `arithmetic operators` `+ - * / ^ %` work with rationals, essentially the same as with other numeric types.

Integers and other `Rational`s can be included and give a `Rational` result.
Including a `float` in the expression results in `float` output, with a consequent (possible) loss in precision.

If a `float` is desired, simply use the `float()` function to convert a rational.
It is quite normal to use rational numbers to preserve precision through a long calculation, then convert to a float at the end.

```julia-repl
julia> 3//4 + 1//3 # addition
13//12

julia> 3//4 * 1//3 # multiplication
1//4

julia> 3//4 / 1//3 # division
9//4

julia> 3//4 ^ 2 # exponentiation
3//16

julia> 3//4 + 5 # rational and int => rational
23//4

julia> 3//4 + 5.3 # rational and float => float
6.05

julia> float(3//4) # casting
0.75
```
17 changes: 17 additions & 0 deletions exercises/concept/problematic-probabilities/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"authors": [
"depial"
],
"files": {
"solution": [
"problematic-probabilities.jl"
],
"test": [
"runtests.jl"
],
"exemplar": [
".meta/exemplar.jl"
]
},
"blurb": "Help make statistics more accurate with rational numbers"
}
27 changes: 27 additions & 0 deletions exercises/concept/problematic-probabilities/.meta/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Design

## Goal

The goal of this exercise is to introduce the student to rational numbers.

## Learning objectives

- Understand the use of rational numbers
- Understand basic construction and arithmetic operations
- Become more familiar with the key use case of rational numbers: mitigation of rounding errors

## Out of scope

- Subtlties of conversion from rational to floating-point numbers
- Infinite rationals
- `NaN`s

## Concepts

The Concepts this exercise unlocks are:

-

## Prerequisites

- `loops`
12 changes: 12 additions & 0 deletions exercises/concept/problematic-probabilities/.meta/exemplar.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
rationalize = .//
probabilities = ./

function checkmean(successes, trials)
r, p = sum(rationalize(successes, trials)) // length(trials), sum(probabilities(successes, trials)) / length(trials)
float(r) == p || r
end

function checkprob(successes, trials)
r, p = prod(rationalize(successes, trials)), prod(probabilities(successes, trials))
float(r) == p || r
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
function rationalize(successes, trials)

end

function probabilities(successes, trials)

end

function checkmean(successes, trials)

end

function checkprob(successes, trials)

end
43 changes: 43 additions & 0 deletions exercises/concept/problematic-probabilities/runtests.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Test

include("problematic-probabilities.jl")

@testset verbose = true "tests" begin
@testset "rationalize" begin
@test rationalize([2], [4]) == [1//2]
@test rationalize([1, 2, 3, 4], [2, 3, 4, 5]) == [1//2, 2//3, 3//4, 4//5]
end

@testset "probabilities" begin
@test probabilities([2], [4]) == [2/4]
@test probabilities([1, 2, 3, 4], [2, 3, 4, 5]) == [1/2, 2/3, 3/4, 4/5]
end

@testset "average check" begin
successes, trials = [8, 1, 4, 4, 4], [13, 24, 16, 23, 18]
@test checkmean(successes, trials)

successes, trials = [9, 8, 6, 8, 3], [18, 15, 23, 19, 14]
@test checkmean(successes, trials)

successes, trials = [1, 9, 7, 7, 8], [13, 16, 13, 12, 21]
@test checkmean(successes, trials) == 3119//7280

successes, trials = [1, 6, 8, 6, 5], [20, 15, 18, 11, 25]
@test checkmean(successes, trials) == 3247//9900
end

@testset "probability check" begin
successes, trials = [4, 2, 4, 3, 2], [18, 20, 18, 25, 22]
@test checkprob(successes, trials)

successes, trials = [9, 1, 8, 4, 3], [14, 22, 15, 15, 20]
@test checkprob(successes, trials)

successes, trials = [6, 2, 2, 9, 8], [18, 23, 12, 18, 16]
@test checkprob(successes, trials) == 1//828

successes, trials = [9, 3, 7, 6, 7], [11, 17, 13, 13, 17]
@test checkprob(successes, trials) == 7938//537251
end
end