Skip to content

Commit 4f682fe

Browse files
committed
Commit for verion 0.6.0
- Bibliography section in the end of documentation - Added references - Included all authors
2 parents 5fcc198 + c1c6cf2 commit 4f682fe

28 files changed

Lines changed: 3540 additions & 17 deletions

.github/workflows/Format-PR.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
name: Format Suggestions
2+
on:
3+
pull_request:
4+
jobs:
5+
code-style:
6+
runs-on: ubuntu-latest
7+
steps:
8+
- uses: julia-actions/julia-format@v2

.github/workflows/Format.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Formatter
2+
on:
3+
schedule:
4+
- cron: '0 0 * * *'
5+
push:
6+
branches:
7+
- 'main'
8+
tags: ['*']
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: julia-actions/setup-julia@v1
15+
with:
16+
version: '1'
17+
arch: x64
18+
show-versioninfo: true
19+
- uses: julia-actions/cache@v1
20+
- name: Install JuliaFormatter and format
21+
shell: julia --color=yes {0}
22+
run: |
23+
using Pkg
24+
Pkg.add("JuliaFormatter")
25+
using JuliaFormatter
26+
format(".")
27+
28+
# https://github.com/marketplace/actions/create-pull-request
29+
# https://github.com/peter-evans/create-pull-request#reference-example
30+
- name: Create Pull Request
31+
id: cpr
32+
uses: peter-evans/create-pull-request@v6
33+
with:
34+
token: ${{ secrets.GITHUB_TOKEN }}
35+
commit-message: Format .jl files
36+
title: 'Automatic JuliaFormatter.jl run'
37+
branch: auto-juliaformatter-pr
38+
delete-branch: true
39+
labels: formatting, automated pr, no changelog
40+
- name: Check outputs
41+
run: |
42+
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
43+
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"

Project.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,22 @@ authors = ["Guillaume Dalle", "José Kling", "Julien Chevallier", "Ryan Senne"]
66
[deps]
77
DensityInterface = "b429d917-457f-4dbc-8f4c-0cc954292b1d"
88
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
9+
Integrals = "de52edbc-65ea-441a-8357-d3a637375a31"
910
HypothesisTests = "09f84164-cd44-5f33-b23f-e6b0d136a0d5"
1011
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
12+
Optim = "429524aa-4258-5aef-a3af-852621145aeb"
1113
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1214
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1315
StatsAPI = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
1416

1517
[compat]
1618
DensityInterface = "0.4"
1719
Distributions = "0.25"
20+
Integrals = "4.7.1"
1821
HypothesisTests = "0.11.5"
1922
JuliaFormatter = "2.2.0"
2023
LinearAlgebra = "1.10"
24+
Optim = "1.7"
2125
Random = "1.10"
2226
Statistics = "1.10"
2327
StatsAPI = "1.6"
@@ -30,9 +34,10 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
3034
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
3135
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
3236
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
37+
Optim = "429524aa-4258-5aef-a3af-852621145aeb"
3338
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
3439
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
3540
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3641

3742
[targets]
38-
test = ["Aqua", "Distributions", "Documenter", "ForwardDiff", "JuliaFormatter", "LinearAlgebra", "Random", "Statistics", "Test"]
43+
test = ["Aqua", "Distributions", "Documenter", "ForwardDiff", "JuliaFormatter", "LinearAlgebra", "Optim", "Random", "Statistics", "Test"]

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.8157372.svg)](https://doi.org/10.5281/zenodo.8157372)
1111
[![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle)
1212
[![Aqua QA](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)
13+
[![JET](https://img.shields.io/badge/%F0%9F%9B%A9%EF%B8%8F_tested_with-JET.jl-233f9a)](https://github.com/aviatesk/JET.jl)
1314

1415
A package for temporal point process modeling, simulation and inference.
1516

docs/Project.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
[deps]
2+
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
23
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
34
DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244"
5+
HypothesisTests = "09f84164-cd44-5f33-b23f-e6b0d136a0d5"
6+
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
7+
LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589"
8+
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
49
PointProcesses = "af0b7596-9bb0-472a-a012-63904f2b4c55"
510
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
11+
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
12+
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
13+
StatsAPI = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
14+
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
615

716
[sources]
817
PointProcesses = {path = ".."}

docs/examples/Basics.jl

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
# ## The basics of Point Process Modeling
2+
#
3+
# This tutorial provides a brief introduction to point process modeling using PointProcesses.jl.
4+
# We will cover the basic math of point processes, how to define basic models, simulate data,
5+
# and fit models to observed data. For further, and more advanced pedagogy, please refer to
6+
# any of the following great resources:
7+
#
8+
# - [DaleyJones2013](@cite)
9+
# - [Cox2018](@cite)
10+
#
11+
# The focus of this tutorial is intentionally narrow: we aim to build intuition by starting
12+
# with the simplest point process model and making the connection between
13+
# mathematical definitions, simulation, and likelihood-based inference explicit.
14+
15+
# ### Basic Point Process Concepts
16+
#
17+
# A point process is a mathematical model for random events occurring in time or space.
18+
# In this tutorial, we focus on **temporal point processes**, where events are represented
19+
# as a set of event times:
20+
#
21+
# {t₁, t₂, …, tₙ} ⊂ [0, T]
22+
#
23+
# The key components of a point process are:
24+
#
25+
# - **Events**: The occurrences we are modeling, represented as points in time.
26+
# - **Counting Process**: N(t), the number of events that have occurred up to time t.
27+
# - **Intensity Function**: A (possibly time- and history-dependent) function λ(t | 𝓗ₜ)
28+
# describing the instantaneous rate at which events occur.
29+
# - **History**: 𝓗ₜ, the record of events strictly before time t.
30+
31+
# Intuitively, the intensity function λ(t | 𝓗ₜ) satisfies
32+
#
33+
# λ(t | 𝓗ₜ) Δt ≈ P(event in [t, t + Δt) | 𝓗ₜ)
34+
#
35+
# for small Δt. Different choices of λ give rise to different classes of point processes:
36+
#
37+
# - **Homogeneous Poisson process**: λ(t) = λ (constant)
38+
# - **Inhomogeneous Poisson process**: λ(t) varies with time.
39+
# - **History-dependent processes**: λ(t | 𝓗ₜ) depends on past events (e.g. Hawkes processes)
40+
41+
# ### The Homogeneous Poisson Process
42+
#
43+
# The simplest and most widely used point process is the **homogeneous Poisson process**.
44+
# It is defined by a single parameter λ > 0, the constant event rate, and is characterized by:
45+
#
46+
# 1. **Independent increments**: The numbers of events in disjoint time intervals are independent.
47+
# 2. **Stationary increments**: The distribution of events depends only on the length of the interval.
48+
# 3. **Orderliness**: The probability of more than one event occurring in a small interval is negligible.
49+
#
50+
# Despite its simplicity, the homogeneous Poisson process provides a useful baseline model
51+
# and a reference point for more expressive point process models. Let's start simulating data using PointProcesses.jl
52+
using PointProcesses
53+
using Distributions
54+
using HypothesisTests
55+
using StatsBase
56+
using Plots
57+
58+
using StableRNGs # hide
59+
rng = StableRNG(67); # hide
60+
61+
# Usually we observe some data, typically, these will be event times. Assume, we have seen the following event times, from a cultured retinal ganglion cell neuron with no input stimulus:
62+
waiting_times = rand(rng, InverseGaussian(0.2, 0.5), 50) # hide
63+
event_times = cumsum(waiting_times) # hide
64+
65+
h = History(event_times, 0.0, maximum(event_times) + 1.0)
66+
67+
# Let's visualize our event times as a raster plot:
68+
raster = plot(;
69+
xlabel="Time",
70+
ylabel="Events",
71+
title="Raster Plot of Event Times",
72+
ylim=(-0.1, 1),
73+
yticks=false,
74+
)
75+
for time in h.times
76+
vline!([time]; color=:blue, linewidth=2, label=false)
77+
end
78+
79+
raster
80+
81+
# Looking at this plot, it's difficult to tell whether the event times are consistent with a homogeneous Poisson process.
82+
# We can bin the data in small time windows to get a sense of the event rate over time. From the assumptions above, we expect the distribution of
83+
# counts to be roughly Poisson distributed with rate λ * Δt in each bin of width Δt.
84+
bin_width = 1.0
85+
bins = collect(h.tmin:bin_width:h.tmax) # bin edges
86+
counts = fit(Histogram, h.times, bins).weights; # counts per bin
87+
88+
# Plot counts over time (counts/bin_width is a crude rate estimate)
89+
bin_centers = (bins[1:(end - 1)] .+ bins[2:end]) ./ 2
90+
91+
p_counts = plot(
92+
bin_centers,
93+
counts;
94+
seriestype=:bar,
95+
xlabel="Time",
96+
ylabel="Count per bin",
97+
title="Binned event counts",
98+
)
99+
100+
p_counts
101+
102+
# This is still difficult to tell. To get a better sense of whether the data is consistent with a homogeneous Poisson process, we can consider an alternative view of the homogeneous
103+
# point process based on the distribution of its waiting times. We discuss this below.
104+
105+
# ### Two Equivalent Views
106+
#
107+
# The homogeneous Poisson process admits two equivalent and complementary descriptions:
108+
#
109+
# **(1) Inter-event times**
110+
# The waiting times between successive events are independent and identically distributed:
111+
#
112+
# τₖ = tₖ - tₖ₋₁ ∼ Exponential(λ)
113+
#
114+
# This view is particularly convenient for simulation.
115+
#
116+
# **(2) Counting process**
117+
# The total number of events in a time window of length T is distributed as:
118+
#
119+
# N(T) ∼ Poisson(λ T)
120+
#
121+
# Conditioned on N(T) = n, the event times are uniformly distributed on [0, T].
122+
#
123+
# These two perspectives define the same stochastic process and will both be useful
124+
# for understanding likelihood-based inference.
125+
126+
# Lets now calculate the waiting times and see how "exponential" they look:
127+
waiting_times = diff(vcat(h.tmin, h.times)); # includes waiting time from tmin to first event
128+
129+
#- Rate estimate λ̂ = n / T
130+
T = duration(h) # should be h.tmax - h.tmin
131+
n = length(h) # number of events
132+
λ_est = n / T
133+
134+
p_waiting = histogram(
135+
waiting_times;
136+
bins=10,
137+
normalize=:pdf,
138+
xlabel="Waiting time",
139+
ylabel="Density",
140+
title="Histogram of Waiting Times",
141+
label="Empirical",
142+
)
143+
144+
x = range(0; stop=maximum(waiting_times), length=400)
145+
plot!(
146+
p_waiting,
147+
x,
148+
pdf.(Exponential(1 / λ_est), x);
149+
label="Exponential(λ̂=$(round(λ_est, digits=2)))",
150+
linewidth=2,
151+
color=:red,
152+
)
153+
154+
p_waiting
155+
156+
# This could be maybe exponential, but there is some signs that it is not. For example, why do we not see any waiting times below approximately 0.1s?
157+
# Let's fit the actual model now using PointProcesses.jl and run some formal statistical tests.
158+
159+
# ### Fitting a Homogeneous Poisson Process
160+
pp_model = fit(PoissonProcess{Float64,Dirac{Nothing}}, h)
161+
162+
println("Estimated rate λ̂ = ", pp_model.λ) # hide
163+
164+
# We can now use this fitted model to run some statistical tests to see whether the homogeneous Poisson process is a good fit to the data. First let's plot a qq-plot
165+
# of the waiting times against the expected exponential distribution.
166+
q = range(0.01, 0.99; length=200)
167+
emp_q = quantile(waiting_times, q)
168+
theo_q = quantile.(Exponential(1 / λ_est), q) # Exp with mean 1/λ_est
169+
170+
pqq = plot(
171+
theo_q,
172+
emp_q;
173+
seriestype=:scatter,
174+
xlabel="Theoretical quantiles: Exp(mean=1/λ̂)",
175+
ylabel="Empirical waiting-time quantiles",
176+
title="QQ plot: waiting times vs fitted exponential",
177+
aspect_ratio=:equal,
178+
label=false,
179+
xlim=(0, 1),
180+
ylim=(0, 1),
181+
)
182+
plot!(pqq, theo_q, theo_q; label=false) # y=x line
183+
pqq
184+
185+
# The QQ-plot shows some deviations from the expected exponential distribution, particularly at the lower quantiles. We can use time-rescaling
186+
# to formally test the goodness-of-fit of the homogeneous Poisson process model. The time-rescaling theorem states that if we transform the event times tᵢ using the integrated
187+
# intensity function, the transformed times should be distributed as a homogeneous Poisson process with unit rate. Luckily PointProcesses.jl provides a convenient function to do this transformation for us:
188+
transformed_times = time_change(h, pp_model)
189+
transformed_waiting_times = diff(vcat(transformed_times.tmin, transformed_times.times))
190+
191+
# Under the null hypothesis that the data is generated by the fitted homogeneous Poisson process, these transformed waiting times should be i.i.d. Exp(1) distributed.
192+
193+
ks_test = ExactOneSampleKSTest(transformed_waiting_times, Exponential(1.0))
194+
println("KS test p-value: ", pvalue(ks_test)) # hide
195+
196+
# The KS test p-value is very small, indicating that we can reject the null hypothesis that the data was generated by a homogeneous Poisson process at conventional significance levels.
197+
# This suggests that the homogeneous Poisson process is not an adequate model for the observed event times. There are several possible reasons for this, including:
198+
# - The event rate may not be constant over time (inhomogeneous Poisson process)
199+
# - There may be history dependence (e.g., refractory periods, bursting)
200+
# But this is a topic for another tutorial!

0 commit comments

Comments
 (0)