Skip to content
This repository has been archived by the owner on Dec 11, 2022. It is now read-only.

Commit

Permalink
Merge pull request #61 from EcoJulia/tp/bioclim
Browse files Browse the repository at this point in the history
Add a SDM example
  • Loading branch information
tpoisot authored Feb 28, 2021
2 parents 6b5770c + ad5143e commit d714156
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 8 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "SimpleSDMLayers"
uuid = "2c645270-77db-11e9-22c3-0f302a89c64c"
authors = ["Timothée Poisot <[email protected]>", "Gabriel Dansereau <[email protected]>"]
version = "0.4.5"
version = "0.4.4"

[deps]
ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3"
Expand Down
5 changes: 1 addition & 4 deletions docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,4 @@ StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd"
GBIF = "ee291a33-5a6c-5552-a3c8-0f29a1181037"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"

[compat]
GBIF = "0.2, 0.3"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
3 changes: 3 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ makedocs(
"Sliding window analysis" => "examples/slidingwindow.md",
"Landcover data" => "examples/landcover.md",
"Landcover consensus" => "examples/consensus.md"
],
"Building SDMs" => [
"BIOCLIM from scratch" => "sdm/bioclim.md"
]
]
)
Expand Down
104 changes: 104 additions & 0 deletions docs/src/sdm/bioclim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Writing BIOCLIM from scratch

In this example, we will write the BIOCLIM species distribution model using
`SimpleSDMLayers.jl` and `GBIF.jl`.

```@example bioclim
using SimpleSDMLayers
using GBIF
using Plots
using StatsBase
using Statistics
```

We can get some occurrences for the taxon of interest:

```@example bioclim
obs = occurrences(
GBIF.taxon("Alces alces", strict=true),
"hasCoordinate" => "true",
"continent" => "EUROPE",
"limit" => 50
)
while length(obs) < 500
occurrences!(obs)
end
```

This query uses a range for the longitude and latitude, so as to make sure that
we get a relatively small region. Before we get the layers, we will figure out
the bounding box for the observations - just to make sure that we will have
something large enough, we will add a 2 degrees padding around it:

```@example bioclim
left, right = extrema([o.longitude for o in obs]) .+ (-2,2)
bottom, top = extrema([o.latitude for o in obs]) .+ (-2,2)
```

With this information in hand, we can start getting our variables. In this
example, we will take all worldclim data, at the default 10 arc minute
resolution:

```@example bioclim
predictors = worldclim(1:19; left=left, right=right, bottom=bottom, top=top)
```

The point of BIOCLIM (the model, not the dataset) is that the score assigned to
a pixel is maximal is this pixel is the *median* value for a given variable -
therefore, we need to measure the cumulative density function for every pixel in
every variable.

```@example bioclim
_pixel_score(x) = x > 0.5 ? 1.0-x : 2.0x
function SDM(layer::T, observations::GBIFRecords) where {T <: SimpleSDMLayer}
qf = ecdf(layer[observations]) # We only want the observed values
return (_pixel_score∘qf)
end
```

Note that we use the ∘ (`\circ`) operator to chain the quantile estimation and
the pixel scoring, which requires Julia 1.4. This function returns a *model*,
*i.e.* a function that we can broadcast to a given layer, which might not be the
same one we used for the training.

The next step in BIOCLIM is to get the *minimum* suitability across all layers
for every pixel. Because we have a `min` method defined for a pair of layers, we
can call `minimum` on an array of layers:

```@example bioclim
function SDM(predictors::Vector{T}, models) where {T <: SimpleSDMLayer}
@assert length(models) == length(predictors)
return minimum([broadcast(models[i], predictors[i]) for i in 1:length(predictors)])
end
```

The advantage of this approach is that we can call the `SDM` method for
prediction on a smaller layer, or a different layer. This can allow us to do
thing like stitching layers together with `hcat` and `vcat` to use
multi-threading, or use a different resolution for the prediction than we did
for the training.

```@example bioclim
models = [SDM(predictor, obs) for predictor in predictors]
prediction = SDM(predictors, models)
```

Just because we may want to visualize this result in a transformed way, *i.e.*
by looking at the quantiles of suitability, we can call the `rescale!` function:

```@example bioclim
rescale!(prediction, collect(0.0:0.1:1.0))
```

This map can be plotted as we would normally do:

```@example bioclim
plot(prediction, frame=:box, clim=(0,1), c=:bamako)
scatter!([(o.longitude, o.latitude) for o in obs], ms=4, c=:orange, lab="")
xaxis!("Longitude")
yaxis!("Latitude")
```

And there it is! A simple way to write the BIOCLIM model by building on the
integration between SimpleSDMLayers and GBIF.
7 changes: 5 additions & 2 deletions src/operations/rescale.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Copying version of `rescale!`.
function rescale(layer::TI, template::TJ) where {TI <: SimpleSDMLayer, TJ <: SimpleSDMLayer}
l = copy(layer)
return rescale!(l, extrema(template))
return l
end

"""
Expand All @@ -40,7 +41,8 @@ Copying version of `rescale!`.
"""
function rescale(layer::TI, t::Tuple{T,T}) where {TI <: SimpleSDMLayer, T <: Number}
l = copy(layer)
return rescale!(l, t)
rescale!(l, t)
return l
end


Expand All @@ -67,5 +69,6 @@ Copying version of `rescale!`.
"""
function rescale(layer::T, p::Vector{TI}) where {T <: SimpleSDMLayer, TI <: AbstractFloat}
l = copy(layer)
return rescale!(l, p)
rescale!(l, p)
return l
end
2 changes: 1 addition & 1 deletion src/operations/sliding.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function slidingwindow(layer::LT, f::FT, d::IT) where {LT <: SimpleSDMLayer, FT
end

for p1 in pixels
ok = filter(p2 -> haversine(p2.first, p1.first) < 100.0, pixels)
ok = filter(p2 -> haversine(p2.first, p1.first) < d, pixels)
val = [p2.second for p2 in ok]
N[p1.first...] = f(val)
end
Expand Down

2 comments on commit d714156

@tpoisot
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register()

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/30946

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.4.4 -m "<description of version>" d7141564b758209ee22f0ec8871623abaf14b07f
git push origin v0.4.4

Please sign in to comment.