WIZUALIZACJE DANYCH Z COVID-19 W NOWEJ ODSŁONIE

Autorem tego wpisu jest Michał Hałoń, doktorant na Wydziale EiTI Politechniki Warszawskiej. Kontynuuje on temat wizualizacji danych dotyczących rozwoju COVID-19 w poszczególnych krajach. Wykorzystany będzie nowy typ wykresu dostępny w programie MATLAB, który stanowi alternatywę do najpopularniejszych wizualizacji przedstawianych w mediach oraz dostępnych w Internecie.

Możliwość wykorzystania wykresów typu swarm pojawiła się wraz z wydaniem MATLAB 2020b jako jedna z wielu nowości dotyczących metod prezentowania danych (obok np. rysunków bąbelkowych). Służą do tego celu funkcje swarmchart (wersja 2D) oraz swarmchart3 (wersja 3D). Wykorzystując pierwszą wymienioną funkcję, naszym celem będzie zwizualizowanie dostępnych w danych w następujący sposób:

Przedstawia on wizualizację rozkładu przeskalowanych dziennych potwierdzonych przypadków zachorowania na COVID-19 dla wybranych krajów. Każdy narysowany punkt (marker) oznacza pojedynczy wynik zachorowań dla danego państwa. Miesiąc, w którym uzyskano taką liczbę zachorowań można odczytać z kolorowej legendy po prawej stronie. Im szerszy rozrzut kolorowych punktów dla danego poziomu, tym więcej przypadków z tego zakresu zanotowano.

Posiadając podstawową wiedzę na temat wykresów tego typu, możemy wyciągnąć kilka wniosków na temat historii zachorowań w wybranych czterech krajach:

  • na Białorusi liczba zachorowań w listopadzie osiągnęła poziom z maja oraz czerwca 2020 roku,
  • w Rosji dzienna liczba zachorowań w listopadzie 2020 roku znacznie przewyższa przypadki z poprzednich miesięcy,
  • w Albanii zdecydowana większość dziennych zachorowań nie przekracza połowy maksymalnej wartości zanotowanej w listopadzie 2020 roku, w którym nastąpił dynamiczny wzrost zachorowań,
  • w Algierii dotychczasowy szczyt zachorowań przypadł na lipiec oraz sierpień, natomiast kilka przypadków z listopada 2020 roku osiągnęło podobne wartości (co może sygnalizować nadejście drugiego szczytu).

Przejdźmy do omówienia kodu pozwalającego na przygotowanie przedstawionego powyżej wykresu. W pierwszej kolejności pobieramy oraz przetwarzamy dane udostępniane przez Uniwersytet John’a Hopkins’a w Baltimore (więcej na temat repozytorium można przeczytać w jednym z poprzednich wpisów). Już na tym etapie trafiamy na pierwsze wyzwanie: do wykonania wizualizacji potrzebujemy danych dotyczących całego kraju, jednak w uzyskanej tabeli dla niektórych państw zachorowania podane są dla mniejszych jednostek terytorialnych (np. prowincji czy stanów), co można zauważyć poniżej (kolumna CountryRegion):

Musimy w jakiś sposób zsumować dane pochodzące z różnych części państwa. Można tego dokonać wykorzystując funkcję varfun, dzięki której zagregujemy dzienne zachorowania dla wierszy charakteryzujących się tą samą nazwą w kolumnie CountryRegion:

% Sumowanie danych dotyczące zachorowań w różnych prowincjach/stanach tego
% samego kraju.

tableConfirmed = varfun(@sum,tableConfirmed,'GroupingVariables','CountryRegion');

W rezultacie otrzymujemy tabelę w pożądanej formie, a ponadto z nowej kolumny GroupCount możemy odczytać liczbę sumowanych wierszy dla każdego kraju:

Kolejnym wyzwaniem jest modyfikacja danych dotyczących liczby zachorowań – z kolumn przechowujących sumę potwierdzonych przypadków od początku pandemii do danego dnia, na liczbę potwierdzonych zachorowań danego dnia:

% Konwersja danych z kolumn przechowujących liczbę potwierdzonych zakażeń
% od początku do danego dnia, na liczbę potwierdzonych zakażeń danego dnia

for i = size(tableConfirmed,2):-1:3
    tableConfirmed{:,i} = tableConfirmed{:,i} - tableConfirmed{:,i-1};
end

Posiadając tabelę w takiej formie możemy wybrać do wizualizacji dane konkretnych państw, utworzyć z nich jeden wektor, a następnie przygotować dwa dodatkowe wektory - zawierający nazwy państw oraz przechowujący etykiety czasowe dla danych umieszczonych w pierwszym wektorze:

% Wybór listy krajów do wizualizacji zakażeń na przestrzeni czasu

countries = ["Poland", "Italy", "Ukraine", "Switzerland"];

% Zapisanie danych dotyczących zakażeń jedynie dla wybranych krajów

selected_countries = tableConfirmed(ismember(tableConfirmed.CountryRegion, countries),:);

% Zapełnienie wektorów danymi dla każdego państwa

for i = 1:numel(countries)

    % Wskazanie indeksu wskazującego dane dla analizowanego kraju

    country_index = selected_countries.CountryRegion == countries(i);

    % wczytanie etykiet nazw państw

    x = [x repmat(countries(i),1,(size(tableConfirmed,2)-1))];

    % wczytanie danych dotyczących dziennych zachorowań

    y = [y selected_countries{country_index,2:end}];

    % wczytanie etykiet czasowych w formacie 'miesiąc rok', np. 'May 2020'

    c = ;

end

Możemy teraz przystąpić do wykreślenia pierwszego wykresu typu swarm wykorzystując uzyskane wektory:

% Wybór zestawu kolorów do wizualizacji

colormap(jet(numel(unique(c))));

% Wizualizacja uzyskanych danych za pomocą wykresu swarmchart

swarmchart(x,y,50,c,'filled', 'MarkerFaceAlpha', 0.6);

% Ustawienie parametrów paska koloru (legendy)

colorbar('TickLabels', unique(c), 'TickLength', 0)

Z uwagi na duże różnice w dziennych zachorowaniach pomiędzy państwami oraz w celu zyskania na czytelności wykresu możemy wyświetlane dane przeskalować dzieląc wartość dziennych zachorowań przez wartość największej liczby dziennych przypadków dla danego państwa. Tracimy w ten sposób informację o skali zachorowań, zyskujemy natomiast dokładniejszy wgląd w przebieg pandemii:

% Obliczenie maksymalnej dziennej liczby zakażeń dla każdego państwa

max_confirmed = max(table2array(selected_countries(:,2:end)),[],2);

Zmienną max_confirmed wykorzystujemy następnie przy zapełnianiu wektora przechowującego dane dotyczące zachorowań (w pętli for):

% wczytanie oraz podzielenie danych przez największą dzienną liczbę
% zachorowań

y = [y selected_countries{country_index,2:end}/max_confirmed(country_index)];

Zmodyfikowane dane wizualizujemy:

Zmieniając kraje do wizualizacji, tło wykresu na czarne oraz usuwając opcję dotyczącą przezroczystości punktów uzyskamy wykres przedstawiony na początku wpisu.
Wykresy typu swarm stanowią kolejną ciekawą metodę wizualizacji danych, która od niedawna dostępna jest w oprogramowaniu MATLAB. Analizując zachorowania dotyczące kolejnych dostępnych krajów można zauważyć dużą różnorodność przebiegu pandemii, co odzwierciedla się w zróżnicowanych kształtach wykresów oraz rozłożeniu kolorów.
Zachęcam do dalszej eksploracji dostępnych danych, modyfikacji udostępnionego skryptu oraz zapoznania się z pozostałymi możliwościami oferowanymi przez funkcję swarmchart. Zmieniając m.in. mapę kolorów (z jet na cool) można uzyskać poniższy wykres:

Poniżej znajduje się kod całego programu:

% Program pobiera dane dotyczące zachorowań na COVID-19 oraz dokonuje ich
% wizualizacji z wykorzystaniem wykresu typu 'swarm plot' (z podziałem na
% zachorowania wewnątrz państw).
%
% Import danych przygotowany na podstawie funkcji getDataCOVID() autorstwa
% E. Cheynet'a [1] oraz artykułu dr inż. Piotra Burnosa na MATLABlogu [2].
% Dane dotyczące potwierdzonej liczby zachorowań na COVID-19 pobierane są  
% z bazy danych Johns Hopkins University [3].
%
% Referencje:
% [1] https://www.mathworks.com/matlabcentral/fileexchange/74545-generalized-seir-epidemic-model-fitting-and-computation
%[2] http://matlablog.ont.com.pl/covid-19-na-mapach-matlaba/
% [3] https://github.com/CSSEGISandData/COVID-19

clear all; close all; clc

%% Ustawienie parametrów importu danych
% Liczba dni do analizy od początku rejestracji danych

Ndays = floor(now)-datenum(2020,01,22)-1; % minus jeden dzień, gdyż 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("uint32",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
% 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/time_series_covid19_confirmed_global.csv';

% Wczytanie pobranych danych do pliku 'dummy.csv', który zostanie później
% wykorzystany do stworzenia tabeli

websave('dummy.csv',address);

% Wczytanie danych z pliku 'dummy.csv' do tabeli tableConfirmed

tableConfirmed =readtable('dummy.csv', opts);

% Odczyt dat i ich konwersja na postać standardową

fid = fopen('dummy.csv');
time = textscan(fid,repmat('%s',1,size(tableConfirmed,2)), 1, 'Delimiter',',');
time(1:4)=[];
time = datetime([time{1:end}])+years(2000);
fclose(fid);

%% Przetwarzanie wczytanych danych
% Usunięcie z uzyskanej tabeli pierwszego wiersza oraz kolumn, które
% przechowują dane nieprzydatne w docelowych wizualizacjach

tableConfirmed = tableConfirmed(2:end,[2 5:end]);

% Sumowanie danych dotyczące zachorowań w różnych prowincjach/stanach tego
% samego kraju.

tableConfirmed = varfun(@sum,tableConfirmed,'GroupingVariables','CountryRegion');

% Usunięcie kolumny przechowującej informację o liczbie prowincji/stanów
% dla każdego kraju

tableConfirmed(:,2) = [];

% Konwersja danych z kolumn przechowujących liczbę potwierdzonych zakażeń
% od początku do danego dnia, na liczbę potwierdzonych zakażeń danego dnia

for i = size(tableConfirmed,2):-1:3
    tableConfirmed{:,i} = tableConfirmed{:,i} - tableConfirmed{:,i-1};
end

%% Wybór państw oraz przygotowanie danych do wizualizacj
% Wybór listy krajów do wizualizacji zakażeń na przestrzeni czasu

countries = ["Iceland", "India", "US", "Iran"];

% Zapisanie danych dotyczących zakażeń jedynie dla wybranych krajów

selected_countries = tableConfirmed(ismember(tableConfirmed.CountryRegion, countries),:);

% Obliczenie maksymalnej dziennej liczby zakażeń dla każdego państwa

max_confirmed = max(table2array(selected_countries(:,2:end)),[],2);

% Przygotowanie pustych wektorów do zapełnienia danymi
% Wektor zawierający etykiety - nazwy państw dla danych w wektorze y

x = [];

% Wektor zawierający etykiety czasowe dla danych w wektorze y

c = [];

% Wektor przechowujący dzienne liczby zachorowań

y = [];

% Zapełnienie wektorów danymi dla każdego państwa

for i = 1:numel(countries)
    % Wskazanie indeksu wskazującego dane dla analizowanego kraju
    country_index = selected_countries.CountryRegion == countries(i);
    % wczytanie etykiet nazw państw
    x = [x repmat(countries(i),1,(size(tableConfirmed,2)-1))];
    % wczytanie oraz podzielenie danych przez największą dzienną liczbę
    % zachorowań
    y = [y selected_countries{country_index,2:end}/max_confirmed(country_index)];
    % wczytanie etykiet czasowych w formacie 'miesiąc rok', np. 'May 2020'
    c = ;
end

% Konwersja na dane typu 'categorical'

x = categorical(x,countries);
c = categorical(c, unique(c, 'stable'));

%% Wizualizacja danych
% Wybór zestawu kolorów do wizualizacji

colormap(cool(numel(unique(c))));

% Wizualizacja uzyskanych danych za pomocą wykresu swarmchart

swarmchart(x,y,50,c,'filled', 'MarkerFaceAlpha', 0.6);

% Ustalenie zakresu wyświetlanych danych na pionowej osi

ylim([ -0.05 1.05])

% Podpis pionowej osi

ylabel("Skalowane dzienne przypadki")

% Ustawienie koloru tła wykresu na czarny

set(gca,'Color','k')

% Ustawienie parametrów paska koloru (legendy)

colorbar('TickLabels', unique(c), 'TickLength', 0)

(Visited 127 times, 1 visits today)

Dodaj komentarz

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