Skip to content

Commit e2059d4

Browse files
Add files via upload
Added and example model for the epidemic in France.
1 parent 3caf1cb commit e2059d4

14 files changed

Lines changed: 203 additions & 0 deletions
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
%% Modelling COVID-19 with a General Branching Process (GBP), also called Crump-Mode-Jagers branching process
2+
% The example is built for the specifics of the data and the quarantine in Bulgaria.
3+
% It includes 3 scenarios for the future R0
4+
5+
%% load the branching process simulator
6+
addpath('../../') % adds the function BranchingProcessSimulator to the path
7+
addpath('../') % adds the functions confInterval and readCSVData to the path
8+
9+
%% read and plot the data for the chosen country
10+
% save the data from: https://opendata.ecdc.europa.eu/covid19/casedistribution/csv
11+
% then write in the file data.csv
12+
[dates, newcases_hist, totalcases_hist] = readCSVData('../data.csv', 'France');
13+
14+
% the first sevral cases seem controlled and should be excluded as a beginning of the epidemics
15+
% the epidemics starts on :
16+
ep_start = find(newcases_hist==0, 1, 'last')+1;
17+
dates=dates(ep_start:end);
18+
newcases_hist=newcases_hist(ep_start:end);
19+
totalcases_hist=totalcases_hist(ep_start:end);
20+
%% Build a branching model
21+
detection_time = 8; % assumes 2 days after the symptoms develop (on average) the person is tested for corona
22+
num_days_passed = dates(end)-dates(1) + 1 + detection_time; % days passed since the infection began = days since first case + detection time needed
23+
24+
sim_num=1000; % number of simulations to perform
25+
horizon=90; % horizon in days, after the last available data
26+
T=num_days_passed+horizon; % the simulation period for the process, from the first infected to horizon
27+
h=0.5; % the time step is 0.5 day
28+
omega=60; % no one is infected for more than omega days, technical parameter that does not need to be accurate, smaller -> faster speed,
29+
% but it needs to be at least as large that the P(being infected after omega) is very close to zero.
30+
31+
%% model of S, survivability function, NOT to be mistaken with the percentage of people who survive the virus!
32+
% S is virus specific and represents the survivability function of the virus inside the body of a person
33+
% Here it is assumed normally distributed where the values mu and sigma are taken from the article:
34+
% "Feasability of controlling 2019-nCoV outbreaks by isolation of cases and contacts", J. Hellewell, S. Abbot, et al.
35+
% https://doi.org/10.1101/2020.02.08.20021162
36+
%
37+
% average time for recovery in the article is 5.8 + 9.1 = 14.9, std = sqrt(2.6^2 + 19.53) = 5.1274
38+
% however the data in bulgaria (recovery rates and active cases) seems to show that people need more time to clear the virus out of their body
39+
% even after they are already healthy and recovered. Here a value of 25 days is used for the average days to clear the virus.
40+
% As worldometers.info writes, the active cases and recovories depend highly on the way we count a recovery.
41+
% In Bulgaria it is counted after a person tests negative twice for the virus.
42+
S=(1-normcdf(0:h:omega, 30, 5.1274)')./(1-normcdf(0,30, 5.1274)); % trimmed normal life length with average of 14 days until clearance
43+
S(end)=0; % the chance of surviving after age omega is zero
44+
% If you want to try exponential distribution instead, use S=(1-expcdf(0:h:omega, 14.9)')./normcdf(0,omega, 14.9); % exponential with average of 14 days
45+
46+
%% other parameters for the simulator
47+
U=1; % no types - no mutations, there is a possibility to include a second, quarantined type here with a probability to become quarantined
48+
49+
H=[0, 1]'; % people infect only 1 other person when infection happens, no multiple infections
50+
51+
% Im=[];
52+
% there is no immigration in the example, but it could be included as a random variable representing the number of individuals returning to Bulgaria
53+
% which are infected with the virus
54+
55+
% the initial number of infections is uniformly distributed with respect to "days passed since infection" (i.e. "age" of the infection)
56+
% those infections have uniformly distributed "ages" on every call of the Z_0 function
57+
age_struct_pdf=unifpdf(0:h:omega, 0, 5.8)';
58+
age_struct_prob=age_struct_pdf./(sum(age_struct_pdf));
59+
60+
Z_0=@()(mnrnd(2, age_struct_prob)');
61+
62+
%% MAIN SCENARIO, model of the Point process mu
63+
% values are taken from the article https://doi.org/10.1101/2020.02.08.20021162
64+
% from onset to isolation 3.83 (var = 5.99)
65+
% incubation 5.8 (std = 2.6)
66+
% average time of infecting other people is 5.8 + 3.83 = 9.63, std = sqrt(2.6^2 + 5.99) = 3.5707
67+
68+
% R0 - average number of infected people from a single person, conditional on that person staying infected
69+
% distribution of the infection days is taken to be gamma-like and the point process integrates to R0:
70+
% Expectation = k * theta = 9.63, Variance = k * theta^2 = 3.5707^2 => theta = 3.5707^2 / 9.63 = 1.3240, k = 9.63/1.3240 = 7.2734
71+
% Here we assume we have 2 periods of the epidemic - before the people started distancing themselves and after.
72+
% R0 is chosen to fit what happened already
73+
%R0 = [ ones(1,T/h+1-(7+days_before_quarantine+horizon-detection_time)/h)*0.85 ones(1,(horizon-detection_time)/h)*0.85];
74+
% T/7 % number of weeks since the beginning
75+
76+
R0 = [linspace(12, 12, 7/h) linspace(12, 12, 7/h) linspace(12, 12, 7/h) linspace(12, 7.5, 7/h) ...
77+
linspace(7.5, 4, 7/h) linspace(4, 2, 7/h) linspace(2, 1, 7/h) linspace(1, 0.6, 7/h)...
78+
linspace(0.6, 0.5, T/h+1-(8*7+horizon-detection_time)/h) linspace(0.5, 0.5, (horizon-detection_time)/h)];
79+
mu_covid_pdf=gampdf(0:h:omega, 7.2734, 1.3240)';
80+
mu_matrix=zeros(size(mu_covid_pdf,1), 1, T/h+1);
81+
mu_matrix(:,1,:)=R0.*repmat(mu_covid_pdf/(sum(mu_covid_pdf)*h),1, T/h+1);
82+
mu=@()(mu_matrix); % the input format for mu is described in the BranchingProcessSimulator.m
83+
84+
% we can also calculate the age structure of the population which is of interest in this case
85+
% we can use it to calculate the percentage of people on working age, for example
86+
% to get the age structure use: [ActiveCases, ActiveCasesByType, ActiveCasesByAge, TotalCases, TotalCasesByTypes] = BranchingProcessSimulator(sim_num, T, h, S, H, U, Z_0, mu, 'GetAgeStructure', true);
87+
[ActiveCases, ~, ~, TotalCases, ~] = BranchingProcessSimulator(sim_num, T, h, S, H, U, Z_0, mu, 'GetAgeStructure', false, 'approx_limit', 5);
88+
NewCases = diff(TotalCases');
89+
% CuredCases = TotalCases - ActiveCases;
90+
91+
% converts from h period of time to daily period of time
92+
NewCasesDaily = squeeze(sum(reshape(NewCases, [2, size(NewCases,1)*h, size(NewCases,2)])))';
93+
TotalCasesDaily= TotalCases(:, 1:(1/h):T/h);
94+
ActiveCasesDaily= ActiveCases(:, 1:(1/h):T/h);
95+
96+
% build and save plots
97+
buildPlots(NewCasesDaily, TotalCasesDaily, ActiveCasesDaily, newcases_hist, dates, horizon, detection_time, ...
98+
'MainScenario', 'No change in R_0 (no change in measures)');
99+
100+
%% OPTIMISTIC SCENARIO model of the Point process mu, Optimistic Scenario, R0 changes from 0.5 to 0.3, from now on
101+
R0 = [linspace(12, 12, 7/h) linspace(12, 12, 7/h) linspace(12, 12, 7/h) linspace(12, 7.5, 7/h) ...
102+
linspace(7.5, 4, 7/h) linspace(4, 2, 7/h) linspace(2, 1, 7/h) linspace(1, 0.6, 7/h)...
103+
linspace(0.6, 0.5, T/h+1-(8*7+horizon-detection_time)/h) linspace(0.3, 0.3, (horizon-detection_time)/h)];
104+
mu_covid_pdf=gampdf(0:h:omega, 7.2734, 1.3240)';
105+
mu_matrix=zeros(size(mu_covid_pdf,1), 1, T/h+1);
106+
mu_matrix(:,1,:)=R0.*repmat(mu_covid_pdf/(sum(mu_covid_pdf)*h),1, T/h+1);
107+
mu_optimistic=@()(mu_matrix); % the input format for mu is described in the BranchingProcessSimulator.m
108+
109+
[ActiveCases_optimistic, ~, ~, TotalCases_optimistic, ~] = BranchingProcessSimulator(sim_num, T, h, S, H, U, Z_0, mu_optimistic, 'GetAgeStructure', false);
110+
NewCases_optimistic = diff(TotalCases_optimistic');
111+
% CuredCases = TotalCases - ActiveCases;
112+
113+
% converts from h period of time to daily period of time
114+
NewCasesDaily_optimistic = squeeze(sum(reshape(NewCases_optimistic, [2, size(NewCases_optimistic,1)*h, size(NewCases_optimistic,2)])))';
115+
TotalCasesDaily_optimistic= TotalCases(:, 1:(1/h):T/h);
116+
ActiveCasesDaily_optimistic= ActiveCases(:, 1:(1/h):T/h);
117+
118+
% build and save plots
119+
buildPlots(NewCasesDaily_optimistic, TotalCasesDaily_optimistic, ActiveCasesDaily_optimistic, newcases_hist, dates, horizon, detection_time, ...
120+
'OptimisticScenario', 'Decline in R_0 (better results from measures)');
121+
122+
%% PESSIMISTIC SCENARIO model of the Point process mu, Pessimistic Scenario, R0 changes from 0.5 to 0.8, from now on
123+
R0 = [linspace(12, 12, 7/h) linspace(12, 12, 7/h) linspace(12, 12, 7/h) linspace(12, 7.5, 7/h) ...
124+
linspace(7.5, 4, 7/h) linspace(4, 2, 7/h) linspace(2, 1, 7/h) linspace(1, 0.6, 7/h)...
125+
linspace(0.6, 0.5, T/h+1-(8*7+horizon-detection_time)/h) linspace(0.8, 0.8, (horizon-detection_time)/h)];
126+
mu_covid_pdf=gampdf(0:h:omega, 7.2734, 1.3240)';
127+
mu_matrix=zeros(size(mu_covid_pdf,1), 1, T/h+1);
128+
mu_matrix(:,1,:)=R0.*repmat(mu_covid_pdf/(sum(mu_covid_pdf)*h),1, T/h+1);
129+
mu_pessimistic=@()(mu_matrix); % the input format for mu is described in the BranchingProcessSimulator.m
130+
131+
[ActiveCases_pessimistic, ~, ~, TotalCases_pessimistic, ~] = BranchingProcessSimulator(sim_num, T, h, S, H, U, Z_0, mu_pessimistic, 'GetAgeStructure', false);
132+
NewCases_pessimistic = diff(TotalCases_pessimistic');
133+
% CuredCases = TotalCases - ActiveCases;
134+
135+
% converts from h period of time to daily period of time
136+
NewCasesDaily_pessimistic = squeeze(sum(reshape(NewCases_pessimistic, [2, size(NewCases_pessimistic,1)*h, size(NewCases_pessimistic,2)])))';
137+
TotalCasesDaily_pessimistic= TotalCases(:, 1:(1/h):T/h);
138+
ActiveCasesDaily_pessimistic= ActiveCases(:, 1:(1/h):T/h);
139+
140+
% build and save plots
141+
buildPlots(NewCasesDaily_pessimistic, TotalCasesDaily_pessimistic, ActiveCasesDaily_pessimistic, newcases_hist, dates, horizon, detection_time, ...
142+
'PessimisticScenario', 'Increase in R_0 (worse results from measures)');
143+
144+
%% Comparison of scenarios
145+
[NewCasesDaily_mean, NewCasesDaily_lower, NewCasesDaily_upper, NewCasesDaily_median]=confInterval(NewCasesDaily, 0.10);
146+
[NewCasesDaily_optimisic_mean, NewCasesDaily_optimisic_lower, NewCasesDaily_optimisic_upper, NewCasesDaily_optimisic_median]=confInterval(NewCasesDaily_optimistic, 0.10);
147+
[NewCasesDaily_pessimistic_mean, NewCasesDaily_pessimistic_lower, NewCasesDaily_pessimistic_upper, NewCasesDaily_pessimistic_median]=confInterval(NewCasesDaily_pessimistic, 0.10);
148+
149+
line_wd=2.5;
150+
figure('visible','on', 'Units','pixels','OuterPosition',[0 0 1280 1024]);
151+
set(gca,'FontSize',16)
152+
hold on
153+
h_hist=plot(dates, newcases_hist, 'Color', [0, 0, 0, 0.7],'LineWidth', 1.5);
154+
h_main=plot(dates(1):dates(end)+horizon, NewCasesDaily_median(detection_time:end-1), '-', 'Color', [0, 0, 0.5, 0.5], 'LineWidth', line_wd);
155+
h_optimistic=plot(dates(end):dates(end)+horizon, NewCasesDaily_optimisic_median(end-horizon-1:end-1), '-', 'Color', [0, 0.5, 0, 0.5], 'LineWidth', line_wd);
156+
h_pessimistic=plot(dates(end):dates(end)+horizon, NewCasesDaily_pessimistic_median(end-horizon-1:end-1), '-', 'Color', [0.5, 0, 0, 0.5], 'LineWidth', line_wd);
157+
h_CI=plot(dates(1):dates(end)+horizon, NewCasesDaily_lower(detection_time:end-1), '--', 'Color', [0,155/255,1,1], 'LineWidth', line_wd);
158+
plot(dates(1):dates(end)+horizon, NewCasesDaily_upper(detection_time:end-1), '--', 'Color', [0,155/255,1,1], 'LineWidth', line_wd);
159+
legend([h_main, h_optimistic, h_pessimistic, h_hist, h_CI], 'Main Scenario', 'Optimistic Scenario', 'Pessimistic Scenario', 'Observed new daily cases', '90% conf. interval', 'Location', 'NorthWest')
160+
xtickangle(90)
161+
x_ticks = xticks;
162+
xticks(x_ticks(1):7:x_ticks(end))
163+
dateaxis('X',2)
164+
ylabel('\bf{New Daily Cases (Observed)}')
165+
xlabel('\bf{Date}')
166+
title('Forecasts of observed New Daily Cases (by scenario)')
167+
print(strcat('./Figures/forecast_newcases_by_scenario'), '-dpng', '-r0')
168+
169+
%% Shows the input to the branching process
170+
%%% MU, POINT PROCESS
171+
line_wd=2.5;
172+
figure('visible','on', 'Units','pixels','OuterPosition',[0 0 1280 1024]);
173+
set(gca,'FontSize',16)
174+
hold on
175+
mu_forecast = squeeze(mu());
176+
plot(0:h:omega, mu_forecast(:, end), 'Color', [0.5,0,0,0.5], 'LineWidth', line_wd);
177+
ylabel('Point process density')
178+
xlabel('Days')
179+
title('Probability for spreading the infection, by days')
180+
print('./Figures/PointProcessDensity', '-dpng', '-r0')
181+
182+
%%% Infected period distribution
183+
line_wd=2.5;
184+
figure('visible','on', 'Units','pixels','OuterPosition',[0 0 1280 1024]);
185+
set(gca,'FontSize',16)
186+
hold on
187+
plot(0:h:omega, [diff(1-S); 0]./h, 'Color', [0,0,0.5,0.5], 'LineWidth', line_wd);
188+
ylabel('Infected period PDF')
189+
xlabel('Days')
190+
title('Probability for getting cleared of the virus, by days')
191+
print('./Figures/InfectedPeriodPDF', '-dpng', '-r0')
192+
193+
%% save the results
194+
% save(strcat('results_', num2str(sim_num)))
195+
33.7 KB
Loading
31.5 KB
Loading
53.9 KB
Loading
54.2 KB
Loading
54.1 KB
Loading
77.5 KB
Loading
76.7 KB
Loading
80.2 KB
Loading
77.5 KB
Loading

0 commit comments

Comments
 (0)