Skip to content

Commit

Permalink
Merge pull request #33 from thetreep/displayed-risk
Browse files Browse the repository at this point in the history
Use linear functions for displayed risk
  • Loading branch information
fxaguessy committed Jun 19, 2020
2 parents 4d845e7 + 9facc93 commit e65bbf1
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 6 deletions.
46 changes: 41 additions & 5 deletions job/risk.go
Expand Up @@ -50,6 +50,9 @@ func (j *RiskJob) ComputeRisk(segs []covidtracker.Segment, protects []covidtrack
if err := j.aggregateSegmentRisk(r); err != nil {
return nil, fmt.Errorf("cannot aggregate risk of %d segments: %s", len(segs), err)
}
if err := j.computeDisplayedRisk(r); err != nil {
return nil, fmt.Errorf("cannot compute displayed risk: %s", err)
}
if err := j.aggregateReport(r, protects); err != nil {
return nil, fmt.Errorf("cannot compute report: %s", err)
}
Expand Down Expand Up @@ -210,11 +213,27 @@ func (j *RiskJob) aggregateSegmentRisk(risk *covidtracker.Risk) error {
}
risk.RiskLevel = probaUnionIndepSlice(0, probasSegment)
risk.ConfidenceLevel = 1 - risk.RiskLevel
risk.DisplayedRisk = risk.RiskLevel
// we chose to display risk using a log scale and such a formula d = (1/1-log(r)) to accentuate week values of probability
// (as a probability of 0.1 may be something risky)
if risk.RiskLevel > 0 {
risk.DisplayedRisk = 1 / (1. - (math.Log10(risk.RiskLevel)))

return nil
}

// we chose to display the risk in gauge using a continuous function piecewise linear from (0;0) to (1;1)
// It allows to represent different risk level that better (?) represent the "perceived" risk versus effective risk
// (as a probability of 0.1 may be something risky)
func (j *RiskJob) computeDisplayedRisk(risk *covidtracker.Risk) error {
switch {
case risk.RiskLevel <= 0:
risk.DisplayedRisk = 0
case risk.RiskLevel < 0.01:
risk.DisplayedRisk = yOnLine(risk.RiskLevel, 0, 0, 0.01, 0.2)
case risk.RiskLevel < 0.05:
risk.DisplayedRisk = yOnLine(risk.RiskLevel, 0.01, 0.2, 0.05, 0.4)
case risk.RiskLevel < 0.1:
risk.DisplayedRisk = yOnLine(risk.RiskLevel, 0.05, 0.4, 0.1, 0.6)
case risk.RiskLevel < 0.3:
risk.DisplayedRisk = yOnLine(risk.RiskLevel, 0.1, 0.6, 0.3, 0.8)
case risk.RiskLevel <= 1:
risk.DisplayedRisk = yOnLine(risk.RiskLevel, 0.3, 0.8, 1, 1)
}
return nil
}
Expand Down Expand Up @@ -348,3 +367,20 @@ func probaUnionIndepSlice(fromIndex int, probas []float64) float64 {
}
return probaUnionIndep(probas[fromIndex], probaUnionIndepSlice(fromIndex+1, probas))
}

// Compute the Y value on a line passing through (x1;y1) (x2;y2)
func yOnLine(x, x1, y1, x2, y2 float64) float64 {
a := slope(x1, y1, x2, y2)
b := intercept(a, x1, y1)
return a*x + b
}

// Compute the slope of a line passing through (x1;y1) (x2;y2)
func slope(x1, y1, x2, y2 float64) float64 {
return (y2 - y1) / (x2 - x1)
}

// Compute the y-intercept of a line passing through (x1;y1) (x2;y2)
func intercept(slope, x, y float64) float64 {
return y - slope*x
}
23 changes: 22 additions & 1 deletion job/risk_test.go
Expand Up @@ -94,7 +94,7 @@ func TestComputeRisk(t *testing.T) {
NoticeDate: dep,
ConfidenceLevel: 0.9580939763332113,
RiskLevel: 0.04190602366678872,
DisplayedRisk: 0.42057033991661624,
DisplayedRisk: 0.35953011833394366,
BySegments: []covidtracker.RiskSegment{{
Segment: &covidtracker.Segment{
Origin: paris,
Expand Down Expand Up @@ -137,3 +137,24 @@ func TestComputeRisk(t *testing.T) {
}
}
}

func TestYOnLine(t *testing.T) {
tcases := []struct {
x float64
x1, y1 float64
x2, y2 float64
expectY float64
}{
{x: 0, x1: 0, y1: 0, x2: 4, y2: 4, expectY: 0},
{x: 1, x1: 0, y1: 0, x2: 4, y2: 4, expectY: 1},
{x: 0, x1: 0, y1: 5, x2: 4, y2: 4, expectY: 5},
{x: 4, x1: 0, y1: 5, x2: 4, y2: 4, expectY: 4},
{x: 4, x1: 2, y1: 6, x2: 12, y2: 11, expectY: 7},
}
for i, tcase := range tcases {
y := yOnLine(tcase.x, tcase.x1, tcase.y1, tcase.x2, tcase.y2)
if got, want := y, tcase.expectY; got != want {
t.Fatalf("%d: got %f, want %f", i+1, got, want)
}
}
}

0 comments on commit e65bbf1

Please sign in to comment.