MATLAB i COVID-19 - CZĘŚĆ 2

Tym razem zajmiemy się lepszym zobrazowaniem dynamiki rozwoju pandemii oraz „unormujemy” czas na wykresach, dzięki czemu będzie można odpowiedzieć na pytanie: „co by było gdyby choroba zaczęła się rozwijać w różnych krajach w tym samym momencie?”

Po poprzednim wpisie dotarły do mnie uwagi, że zaprezentowane wykresy nie do końca wiarygodnie obrazują stan rozprzestrzeniania się pandemii oraz liczność nowych zakażeń, wyzdrowień i zgonów. Głównym argumentem była niewiarygodność danych, a szczególnie mała liczba przeprowadzanych testów na obecność wirusa w danym kraju.

Cóż, wyniki analizy są tak wiarygodne, jak dostarczone dane.  W niczym nie ujmuje to przyjętej metodologii badawczej. Dlatego na programy prezentowane w postach, które dotyczą problemu pandemii należy patrzeć się od strony programowo/obliczeniowej.

Wróćmy jednak do naszego programu. W poprzednim poście skupiłem się na dynamice wskaźników związanych z obecnością wirusa. Jednak rysowanie wykresów w funkcji czasu, jakkolwiek daje pewien obraz sytuacji, to utrudnia analizę. Po pierwsze dlatego, że w różnych krajach początek zakażeń miał miejsce w różnym czasie, a po drugie dlatego, że na podstawie analizy wykresów w funkcji czasu trudno jest oszacować ogólny trend.  

Mimo tego, spróbujmy „wyrównać” czas wystąpienia wirusa w różnych krajach do tej samej chwili czasu. To pozwoli na bezpośrednie porównanie dynamiki wzrostu wskaźników, bez względu na czas wystąpienia choroby. Załóżmy więc, że poziomem odniesienia jest co najmniej 100 stwierdzonych przypadków koronawirusa w danym kraju, i wyrównajmy wykresy względem tej chwili czasu:

[Indx_Poland]  = find(Poland_confirmed{:,5:end}>100);


Poland_confirmed(:,5:end) = array2table([Poland_confirmed{:, (5+Indx_Poland(1)):end} nan(1,Indx_Poland(1))]);
Poland_deaths(:,5:end) = array2table([Poland_deaths{:, (5+Indx_Poland(1)):end} nan(1,Indx_Poland(1))]);
Poland_recovered(:,5:end) = array2table([Poland_recovered{:, (5+Indx_Poland(1)):end} nan(1,Indx_Poland(1))]);

Poniższe rysunki obrazują liczbę osób zarażonych i zgonów w sytuacji gdyby choroba rozpoczęła się we wszystkich krajach w tym samym dniu 22 stycznia.

Obrazowanie dynamiki wzrostu w funkcji czasu, nawet jeśli unormujemy czas wystąpienia danego poziomu zakażeń na wykresach nie jest najlepszym pomysłem. Istotna jest liczba nowych zakażeń w stosunku do stwierdzonych zakażeń. Ta względna liczba daje obraz dynamiki przyrostu (lub zmniejszania się) liczby osób chorych. Podsumowując, program z poprzedniego postu, można ulepszyć w następujący sposób:

Używamy wyłącznie skali logarytmicznej na obydwu osiach – pozwala to na lepsze zobrazowanie różnic pomiędzy krajami o skrajnie różnej liczbie przypadków dotkniętych COVID-19. Małe liczby są „skalowane” do góry, a duże lepiej „obrazowane” na tle liczb małych. W tej skali przyrost wykładniczy ma charakter prostej.

Analizujemy przyrosty liczby chorych/wyleczonych/zgonów zamiast wartości bezwzględnych przypadków – umożliwia to lepiej zobrazować trend i sprawdzić czy liczba osób chorych ma tendencję wzrostową czy malejącą.

Czas nie ma znaczenia – dynamika wzrostu liczby osób zakażonych nie zależy wprost od czasu (czy to jest styczeń czy luty) ale warunków środowiskowych, społecznych, stopnia ochrony społeczeństwa, itp. Dlatego  zamiast rysować wykres w funkcji czasu lepiej jest narysować przyrost liczby nowych przypadków w funkcji wszystkich stwierdzonych przypadków. To swego rodzaju normowanie daje lepszy obraz dynamiki rozwoju pandemii.

Na rysunkach czarna linia prosta reprezentuje trend o stałej liczbie przyrostu chorych/zgonów. Wynika to wprost z modelu wykładniczego, który opisuje dynamikę rozprzestrzeniania się wirusa. W skali logarytmicznej przyrosty wykładnicze o takim samym tempie wzrostu reprezentuje prosta. A zatem dla danych z różnych krajów odstępstwo trendu powyżej tej linii oznacza wzrost liczby przypadków, a poniżej tej linii oznacza, że choroba wyhamowuje. Dla Włoch i Szwajcarii widać wyraźnie spadek liczby nowych osób chorych oraz zgonów. W Polsce ten trend dopiero się zaczyna. Bądźmy dobrej myśli!

Poniżej znajduje się cały zmodyfikowany program.

clear all; close all; clc

% Program na podstawie skryptu E. Cheynet'a i funkcji:
% [tableConfirmed,tableDeaths,tableRecovered,time] = getDataCOVID()
% Program pobiera dane dotyczące liczby zachorowań na COVID-19 z bazy
% danych uniwersytetu John'a Hopkins'a oraz wykonuje prostą analizę danych.
% 
% Referencje:
% [1] https://github.com/CSSEGISandData/COVID-19


%% Parametry importu danych

% Liczba dni do analizy od początku rejestracji danych
Ndays = floor(datenum(now))-datenum(2020,01,22)-1; % minus jeden dzień bo baza danych jest odświeżana co 24h

% Ustawienie parametrów importu danych
opts = delimitedTextImportOptions("NumVariables", Ndays+5);

% Dzięki użyciu tabeli, każdej zmiennej można nadać nazwę symboliczną 
% i się nią posługiwać w programie
opts.VariableNames = ["ProvinceState", "CountryRegion", "Lat", "Long", repmat("data",1,Ndays+1)];

% Definicja typu każdej zmiennej, dwie pierwsze to stringi, reszta wartości numeryczne: 
opts.VariableTypes = ["string", "string", repmat("double",1,Ndays+3)];

% Pozostałe ustawienia importu danych
opts.ExtraColumnsRule = "ignore";   % w przypadku występowania dodatkowych kolumn - ignorujemy je
opts.EmptyLineRule = "read";        % jeżeli w danych występują puste linie to je odczytujemy

%% Import danych

% Dane podzielone na trzy grupy: zarażeni, zgony i wyleczenia
status = {'confirmed','deaths','recovered'};

% Adres repozytorium Uniwersytetu John'a Hopkins'a
address = 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/';
ext = '.csv';

% Odczyt danych 
for ii = 1:numel(status)
    
    filename = ['time_series_covid19_',status{ii},'_global'];
    fullName = [address,filename,ext];
    urlwrite(fullName,'dummy.csv');     % import danych do pliku, którego zawartość
    % będzie następnie konwertowana na tabelę:
        
    if strcmpi(status{ii},'Confirmed')
        tableConfirmed =readtable('dummy.csv', opts);
        
    elseif strcmpi(status{ii},'Deaths')
        tableDeaths =readtable('dummy.csv', opts);
        
    elseif strcmpi(status{ii},'Recovered')
        tableRecovered =readtable('dummy.csv', opts);
    else
        error('Unknown status')
    end
end

% Odczyt daty i jej konwersja na postać standardową
fid = fopen('dummy.csv');
time = textscan(fid,repmat('%s',1,size(tableRecovered,2)), 1, 'Delimiter',',');
time(1:4)=[];
time = datetime([time{1:end}])+years(2000);
fclose(fid);

% delete('dummy.csv'); % plik można skasować lub zostawić (przydatne przy
% braku połączenia z Internetem


%% Analiza danych 

% Dane dla Polski
Poland_population = 38*10^6;
Poland_confirmed = tableConfirmed(tableConfirmed.CountryRegion == 'Poland',:);
Poland_deaths = tableDeaths(tableConfirmed.CountryRegion == 'Poland',:);
Poland_recovered = tableRecovered(tableConfirmed.CountryRegion == 'Poland',:);

% Normowanie czasu 
[Indx_Poland]  = find(Poland_confirmed{:,5:end}>100);
Poland_confirmed(:,5:end) = array2table([Poland_confirmed{:, (5+Indx_Poland(1)):end} nan(1,Indx_Poland(1))]);
Poland_deaths(:,5:end) = array2table([Poland_deaths{:, (5+Indx_Poland(1)):end} nan(1,Indx_Poland(1))]);
Poland_recovered(:,5:end) = array2table([Poland_recovered{:, (5+Indx_Poland(1)):end} nan(1,Indx_Poland(1))]);

% Procent zachorowań w stosunku do liczby mieszkańców
Poland_conf_ratio = 100*Poland_confirmed{:,5:end}/Poland_population;
Poland_deaths_ratio = 100*Poland_deaths{:,5:end}/Poland_population;
Poland_recovered_ratio = 100*Poland_recovered{:,5:end}/Poland_population;
% Śmiertelność
Poland_mortality_ratio = 100*Poland_deaths{:,5:end}./(Poland_recovered{:,5:end}+Poland_confirmed{:,5:end});

% Dane dla kraju nr 1
Country1 = 'Italy'
Country1_population = 60.4*10^6;
Country1_confirmed = tableConfirmed(tableConfirmed.CountryRegion == Country1,:);
Country1_deaths = tableDeaths(tableConfirmed.CountryRegion == Country1,:);
Country1_recovered = tableRecovered(tableConfirmed.CountryRegion == Country1,:);

% Normowanie czasu
[Indx_Country1]  = find(Country1_confirmed{:,5:end}>100);
Country1_confirmed(:,5:end) = array2table([Country1_confirmed{:, (5+Indx_Country1(1)):end} nan(1,Indx_Country1(1))]);
Country1_deaths(:,5:end) = array2table([Country1_deaths{:, (5+Indx_Country1(1)):end} nan(1,Indx_Country1(1))]);
Country1_recovered(:,5:end) = array2table([Country1_recovered{:, (5+Indx_Country1(1)):end} nan(1,Indx_Country1(1))]);

% Procent zachorowań w stosunku do liczby mieszkańców
Country1_conf_ratio = 100*Country1_confirmed{:,5:end} / Country1_population; 
Country1_deaths_ratio = 100*Country1_deaths{:,5:end} / Country1_population; 
Country1_recovered_ratio = 100*Country1_recovered{:,5:end} / Country1_population; 
% Śmiertelność
Country1_mortality_ratio = 100*Country1_deaths{:,5:end}./(Country1_recovered{:,5:end}+Country1_confirmed{:,5:end});

% Dane dla kraju nr 2
Country2 = 'Switzerland'
Country2_population = 8.5*10^6;
Country2_confirmed = tableConfirmed(tableConfirmed.CountryRegion == Country2,:);
Country2_deaths = tableDeaths(tableConfirmed.CountryRegion == Country2,:);
Country2_recovered = tableRecovered(tableConfirmed.CountryRegion == Country2,:);

% Normowanie czasu
[Indx_Country2]  = find(Country2_confirmed{:,5:end}>100);
Country2_confirmed(:,5:end) = array2table([Country2_confirmed{:, (5+Indx_Country2(1)):end} nan(1,Indx_Country2(1))]);
Country2_deaths(:,5:end) = array2table([Country2_deaths{:, (5+Indx_Country2(1)):end} nan(1,Indx_Country2(1))]);
Country2_recovered(:,5:end) = array2table([Country2_recovered{:, (5+Indx_Country2(1)):end} nan(1,Indx_Country2(1))]);

% Procent zachorowań w stosunku do liczby mieszkańców
Country2_conf_ratio = 100*Country2_confirmed{:,5:end} / Country2_population; 
Country2_deaths_ratio = 100*Country2_deaths{:,5:end} / Country2_population; 
Country2_recovered_ratio = 100*Country2_recovered{:,5:end} / Country2_population; 
% Śmiertelność
Country2_mortality_ratio = 100*Country2_deaths{:,5:end}./(Country2_recovered{:,5:end}+Country2_confirmed{:,5:end});


% Tempo przyrostu zakażeń
delta_Poland = diff(Poland_confirmed{1,5:end});
delta_Country1 = diff(Country1_confirmed{1,5:end});
delta_Country2 = diff(Country2_confirmed{1,5:end});

% Tempo przyrostu zgonów
delta_deaths_Poland = diff(Poland_deaths{1,5:end});
delta_deaths_Country1 = diff(Country1_deaths{1,5:end});
delta_deaths_Country2 = diff(Country2_deaths{1,5:end});


%% Rysunki

% Polska w skali liniowej
figure; subplot(211); plot(time, Poland_confirmed{:,5:end}, time, Poland_deaths{:,5:end}, time, Poland_recovered{:,5:end})
my_figure('Czas [dni]', 'Liczba osób', 'Polska'); grid on;
legend('Zarażeni','Zgony','Wyzdrowienia','Location','northwest')

subplot(212); semilogy(time, Poland_conf_ratio, time, Poland_deaths_ratio, time, Poland_recovered_ratio)
my_figure('Czas [dni]', 'Procent populacji [%]', ' '); grid on;

% Polska w skali logarytmicznej
figure; subplot(211); semilogy(time, Poland_confirmed{:,5:end}, time, Poland_deaths{:,5:end}, time, Poland_recovered{:,5:end})
my_figure('Czas [dni]', 'Liczba osób', 'Polska'); grid on;
legend('Zarażeni','Zgony','Wyzdrowienia','Location','northwest')

subplot(212); semilogy(time, Poland_conf_ratio, time, Poland_deaths_ratio, time, Poland_recovered_ratio)
my_figure('Czas [dni]', 'Procent populacji [%]', ' '); grid on;

% Zarażeni w skali liniowej
figure; subplot(211); plot(time, Poland_confirmed{:,5:end}, time, Country1_confirmed{:,5:end}, time, Country2_confirmed{:,5:end})
my_figure('Czas [dni]', 'Liczba zarażonych', 'Przypadki potwierdzone'); grid on;
legend('Polska','Włochy','Szwajcaria','Location','northwest')

subplot(212); plot(time, Poland_conf_ratio, time, Country1_conf_ratio, time, Country2_conf_ratio)
my_figure('Czas [dni]', 'Procent populacji [%]', ' '); grid on;

% Zarażeni w skali logarytmicznej
figure; subplot(211); semilogy(time, Poland_confirmed{:,5:end}, time, Country1_confirmed{:,5:end}, time, Country2_confirmed{:,5:end})
my_figure('Czas [dni]', 'Liczba zarażonych', 'Przypadki potwierdzone'); grid on;
legend('Polska','Włochy','Szwajcaria','Location','northwest')

subplot(212); semilogy(time, Poland_conf_ratio, time, Country1_conf_ratio, time, Country2_conf_ratio)
my_figure('Czas [dni]', 'Procent populacji [%]', ' '); grid on;

% Szybkość zakażeń
figure; subplot(211); plot(time(2:end), delta_Poland, time(2:end), delta_Country1, time(2:end), delta_Country2)
my_figure('Czas [dni]', 'Liczba zarażonych/dzień', 'Przypadki potwierdzone - tempo wzrostu na dzień'); grid on;
legend('Polska','Włochy','Szwajcaria','Location','northwest')

subplot(212); semilogy(time(2:end), delta_Poland, time(2:end), delta_Country1, time(2:end), delta_Country2)
my_figure('Czas [dni]', 'Liczba zarażonych/dzień', ' '); grid on;

% Zgony w skali logarytmicznej
figure; subplot(211); semilogy(time, Poland_deaths{:,5:end}, time, Country1_deaths{:,5:end}, time, Country2_deaths{:,5:end})
my_figure('Czas [dni]', 'Liczba zgonów', 'Zgony'); grid on;
legend('Polska','Włochy','Szwajcaria','Location','northwest')

subplot(212); semilogy(time, Poland_deaths_ratio, time, Country1_deaths_ratio, time, Country2_deaths_ratio)
my_figure('Czas [dni]', 'Procent populacji [%]', ' '); grid on;

% Szybkość zgonów
figure; subplot(211); plot(time(2:end), delta_deaths_Poland, time(2:end), delta_deaths_Country1, time(2:end), delta_deaths_Country2)
my_figure('Czas [dni]', 'Liczba zgonów/dzień', 'Zgony - tempo wzrostu na dzień'); grid on;
legend('Polska','Włochy','Szwajcaria','Location','northwest')

subplot(212); semilogy(time(2:end), delta_deaths_Poland, time(2:end), delta_deaths_Country1, time(2:end), delta_deaths_Country2)
my_figure('Czas [dni]', 'Liczba zgonów/dzień', ' '); grid on;

% Śmiertelność w skali logarytmicznej
figure;  plot(time, Poland_mortality_ratio, time, Country1_mortality_ratio, time, Country2_mortality_ratio)
my_figure('Czas [dni]', 'Śmiertelność [%]', 'Śmiertelność'); grid on;
legend('Polska','Włochy','Szwajcaria','Location','northwest')

% Wyzdrowieni w skali logarytmicznej
figure; subplot(211); semilogy(time, Poland_recovered{:,5:end}, time, Country1_recovered{:,5:end}, time, Country2_recovered{:,5:end})
my_figure('Czas [dni]', 'Liczba wyzdrowiałych', 'Wyzdrowienia'); grid on;
legend('Polska','Włochy','Szwajcaria','Location','northwest')

subplot(212); semilogy(time, Poland_recovered_ratio, time, Country1_recovered_ratio, time, Country2_recovered_ratio)
my_figure('Czas [dni]', 'Procent populacji [%]', ' '); grid on;


%% Nowe rysunki

% Przypadki potwierdzone
figure; loglog(Poland_confirmed{:,6:end}, delta_Poland, Country1_confirmed{:,6:end}, delta_Country1, Country2_confirmed{:,6:end}, delta_Country2)
my_figure('Przypadki potwierdzone', 'Przyp. potw. na dzień', '--'); grid on;
legend('Polska','Włochy', 'Szwajcaria', 'northwest')

% Zgony
figure; loglog(Poland_deaths{:,6:end}, delta_deaths_Poland, Country1_deaths{:,6:end}, delta_deaths_Country1, Country2_deaths{:,6:end}, delta_deaths_Country2)
my_figure('Zgony', 'Zgony na dzień', '--'); grid on;
legend('Polska','Włochy', 'Szwajcaria', 'northwest')



function my_figure(opisX, opisY, tytul)
%% Zmienione argumenty wejściowe funkcji na opisy osi!

%% CREATEFIGURE2(X1, Y1) <-- Taka była pierwotna składnia funkcji.
%  X1:  vector of x data
%  Y1:  vector of y data

%  Auto-generated by MATLAB on 18-Mar-2020 10:38:53

% Create figure
% figure1 = figure; % To usuwamy, gdyż rysunek będzie tworzony 
                    % na zewnątrz funkcji

% Create axes
% axes1 = axes;     % To również usuwamy, gdyż obiekt axes, jest już
                    % stworzony na aktywnym rysunku
axes1 = gca;
hold(axes1,'on');

r = groot;
monitors = r.MonitorPositions;
figure1 = gcf; 
figure1.Position = [monitors(1,1)+200 monitors(2,4)/5 660 820];

% Create plot
% plot(X1,Y1,'LineWidth',2);    % To samo dotyczy linii

% Create ylabel
% ylabel({'Opis osi Y'});       % Podmieniamy, tak aby móc wprowadzać
ylabel(opisY);                  % swój tekst. Podobnie z xlabel i title

% Create xlabel
xlabel(opisX);

% Create title
title(tytul);

% Uncomment the following line to preserve the X-limits of the axes
% xlim(axes1,[0 1]);
box(axes1,'on');
% Set the remaining axes properties
set(axes1,'FontName','Century Gothic','FontSize',12,...
    'LabelFontSizeMultiplier',1.2,'TitleFontSizeMultiplier',1.2,'XGrid','on',...
    'LineWidth',1,'TickLength',[0.02 0.025],'XMinorTick','on','YGrid','on','YMinorTick','on');

%% Jeżeli rysunek zawiera więcej niż jedną linia, konieczna jest pętla:

liczba_lini = size(axes1.Children);
for k = 1 : liczba_lini
    axes1.Children(k,1).LineWidth = 2;
end

end


(Visited 238 times, 1 visits today)

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *