niedziela, 27 grudnia 2020

Aktualizacja R w Debian 10 (Buster)

R w Debianie (wersja 10) jest w wersji 3.5:

> sessionInfo()
R version 3.5.2 (2018-12-20)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Debian GNU/Linux 10 (buster)

other attached packages:
[1] rmarkdown_1.17 ggplot2_3.2.1  ggthemes_4.2.
Opis aktualizacji do wersji 4 jest tutaj. Trzeba dodać deb http://cloud.r-project.org/bin/linux/debian buster-cran40/ do /etc/apt/sources.list:
## https://cran.r-project.org/bin/linux/debian/#debian-buster-stable
## For a backport of R 4.0.1 to buster, please add
## deb http://cloud.r-project.org/bin/linux/debian buster-cran40/  
## /etc/apt/sources.list on your computer.
##
apt update

błąd...

## https://unix.stackexchange.com/questions/559977/not-able-to-install-latest-r-statistical-package-in-debian-stable
apt-key adv --keyserver keys.gnupg.net --recv-key FCAE2A0E115C3D8A
apt update
apt install -t buster-cran40 r-base

## Jako root
update.packages(lib.loc="/usr/local/lib/R/site-library", ask = FALSE,
  checkBuilt = TRUE, Ncpus = 16)

>  sessionInfo()
R version 4.0.3 (2020-10-10)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Debian GNU/Linux 10 (buster)

Wszystko działa do tej pory. Instaluję więcej pakietów:

install.packages("devtools")

Znowu błąd. Trzeba doinstalować pakiet libgit2-dev:

apt install libgit2-dev

install.packages("dplyr")
install.packages("tidyverse")
install.packages("ggthemes")
install.packages("bookdown")
install.packages("ggpubr")
install.packages("ggpubr")
install.packages("ggforce")
## or install.packages(c("ggpubr", "ggforce"))

Na sam koniec się okazuje, że pandoc (w wersji 2.2.1) stał się out-of-sync:

/usr/lib/rstudio/bin/pandoc/pandoc --version
pandoc 2.3.1
/usr/bin/pandoc --version
pandoc 2.2.1
## Po prostu
mv /usr/bin/pandoc /usr/bin/pandoc_2.2.1
ln -s /usr/lib/rstudio/bin/pandoc/pandoc /usr/bin/pandoc

poniedziałek, 14 grudnia 2020

Instalowanie ggforce

Nie szło zainstalować (pakiet R) ggforce na raspberri, bo kompilacja kończyła się zwisem komputera. Coś mnie wreszcie tknęło, że problem może być związany z małą ilością pamięci

free
              total        used    
Mem:         948304      109108 
Swap:        102396       48680

swapon --show
NAME      TYPE SIZE  USED PRIO
/var/swap file 100M 47,6M   -2

Nie wiem kto wymyślił ten 100Mb swap, ale to bez sensu.

## tworzę drugi swap 
dd if=/dev/zero of=/var/swap.img bs=1024k count=1000
mkswap /tmp/swap.img
swapon /tmp/swap.img
free

Teraz ggforce się bezproblemowo instaluje.

## Usuwanie
swapoff -v /tmp/swap.img
sudo rm /tmp/swap.img

wtorek, 8 grudnia 2020

Nagrywanie ekranu

W Linuksie sprawa prosta: recordmydesktop, ale ja potrzebuję też pokazać jak coś wykonać w Windows. Programów jest dużo: ten wbudowany w Windows10 nagrywa nawet OK, ale tylko okno, no a ja potrzebuję cały pulpit; komercyjny recmaker jest też OK, ale (zawsze jest jakieś ale w Windows) w wersji free nagrywa tylko 120 sekund.

W końcu instaluję OBS Studio; dość kobylaste, ale robi wszystko co trzeba. Konfiguruję go, że Alt-R zaczyna nagrywanie a Alt-S kończy.

poniedziałek, 7 grudnia 2020

Clone Garmin settings to another unit

Copy settings from #1 unit

Connect unit via USB. Copy the files from the Settings, Elevations, and Sports folders. Copy the Device.fit file from the Garmin folder.

Copy settings to #2 unit

Connect unit via GPS and copy the backup files into the same folders on it. Put Device.fit into Garmin, Settings.fit into the Settings folder, Elevations.fit into the Elevations folder, and Cycling.fit into the Sports folder.

All settings and menu configurations will now be available in #2 unit, saving you lots of time recreating them from scratch. Probably works with all Garmin Edge units (tested on 500 series).

piątek, 4 grudnia 2020

Vegan Pumpkin Kibbeh z nadzieniem z ciecierzycy orzechowej

Potrawa z dyni więc spróbowałem zrobić (przepis stąd)

Najpierw trzeba zrobić aż trzy przyprawy

Przyprawa 7 przypraw

Składniki: 1/2 szklanki mielonego czarnego pieprzu; 1/2 szklanki kminku mielonego (cumin); 1/2 szklanki papryki (łagodnej); 1/4 szklanki mielonej kolendry; 1/4 szklanki mielonych goździków; 4~łyżeczki mielonej gałki muszkatołowej; 4~łyżeczki mielonego cynamonu; 2~łyżeczki mielonego kardamonu. Wykonanie: zmieszaj i przechowuj w słoiku

Przyprawa Kamouneh Spice Blend [KSB]

Składniki: 2 łyżki nasion kminku; 1 łyżka suszonych płatków róży; 1 łyżka ziaren czarnego pieprzu; 2 łyżeczki suszonego majeranku; 2 łyżeczki suszonej bazylii; 1 łyżeczka suszonej mięty; 1/2 łyżeczki cynamonu; 1 łyżka 7 przypraw; 1 łyżeczka soli. Wykonanie: zmiel w blenerze do średniej granulatury. Przechowuj w lodówce lub w zamkniętym słoiku.

Mieszanka Kamouneh na kibbeh [KK]

Składniki: Skórka 1 pomarańczy; 1 mała czerwona cebula; 1/2 białej cebuli; 1/4 szklanki świeżej mięty; 1/4 szklanki świeżej pietruszki; 1/4 szklanki świeżej bazylii; 1 łyżka suszonego majeranku; 1 łyżka suszonej mięty; 3 łyżki przypraw KSB; 1 łyżka 7 przypraw; 2 łyżeczki soli; 2 łyżeczki mielonego cynamonu; 1 łyżeczka pieprzu czarnego; 1 łyżeczka białego pieprzu; 1/2 ptasiego oka chili (ostrej papryki); 1 szklanka dobrej pszenicy Wykonanie: Cienko obrać pomarańczę (w miarę możliwości unikając białego miąższu) i dobrze zmiksować w robocie kuchennym na drobną konsystencję. Następnie dodaj cebulę, wszystkie świeże zioła i przyprawy oraz sól i zmieszać. Na koniec dodaj drobny bulgur i wszystko razem zmiksuj. Przechowuj w lodówce.

To mi nie wyszło przyznam Zamiast mieszanki wyszła gliniasta ciapa bo ugotowałem kaszę a wygląda że ma być surowa. O tyle mieszanka jest istotna że aż 1,5 szklanki jest dodawana do nadzienia i spodu.

Nadzienie

Składniki: 4 cebule pokrojone w paski; 1/4 szklanki zwykłej oliwy z oliwek; 250 g ugotowanej ciecierzycy, odsączonej; 1 łyżeczka suszonej kolendry; 1/2 łyżeczki kurkumy; 1/2 łyżeczki czarnego pieprzu; 1 łyżeczka mieszanki KSB; 1 łyżeczka 7 przypraw; 1/2 łyżeczki mielonego cynamonu; 2 łyżki sumaka; 3 łyżki melasy z granatów (do dostania na Allegro na przykład); 100 g grubo zmiażdżonych orzechów włoskich; 1/2 szklanki mieszanki KK; 1/2 łyżki soli

Wykonanie: Cebulę pokrój w cienkie paski. Na szerokiej, głębokiej patelni dodaj 1/4 szklanki oliwy z oliwek i usmaż na złoty kolor (20 minut) mieszając.

Dodaj odsączoną, gotowaną ciecierzycę, suszoną kolendrę, kurkumę, czarny pieprz, 7 przypraw i 1/2 łyżki soli i smaż przez kolejne 10 minut na małym ogniu

Dodaj orzechy włoskie, sumak, melasę z granatów i 1/2 szklanki mieszanki kamouneh. Smaż jeszcze przez kilka minut. Przykryj i odłóż na bok...

Spód/wierzch

Składniki: 1,5 kg dyni obranej na kawałki; 2,5 szklanki delikatnej pszenicy bulgur; 1/2 szklanki posiekanej świeżej kolendry kolendry; 2 łyżki mąki pszennej; 1 szklanka mieszanki KK; 1 łyżka soli; Mała miska oleju roślinnego

Wykonanie: Obierz dynię i pokrój na 2-calowe kawałki. Przełóż do garnka i zalej wodą. Gotuj przez około 45 minut.

Odcedź dynię na durszlaku i przełóż do dużej miski, dodaj kaszę bulgur, mąkę, mieszankę kamouneh, posiekaną kolendrą i łyżką soli.

Zetrzyj wszystkie składniki za pomocą tłuczka do ziemniaków, aż nie będzie dużych grudek dyni.

Wykonanie: Dyniowy kibbeh

Rozgrzej piekarnik do 200C

Posmaruj spód szerokiego naczynia do pieczenia niewielką ilością oleju roślinnego, a następnie nałóż 1/2cm warstwę spodu na dno naczynia.

Nałóż warstwę nadzienia i wygładź łyżką.

Nałóż warstwę spodu dyniowej, od czasu do czasu zanurzając dłoń w oleju, aby uniknąć sklejania się i wygładzić górną warstwę

Za pomocą ostrego noża pokrój w kwadraty. Następnie skrop cienką warstwą oleju roślinnego.

Piec przez około 40 minut, aż się zarumieni.

Podawać z zieloną sałatą lub jogurtem i ogórkiem.

Moje uwagi dotyczące przepisu:

Jak dla mnie trochę za dużo pieprzenia się. Bym uprościł: farsz to generalnie cebula+orzechy+ciecierzyca+ i pół szklanki KK. Spód to dynia+kasza+trochę kolendry i szklanka KK.

Problem jest z KK bo jest nietrwałe więc bym przepis zmodyfikował: posiekanie świeże zioła + cebula + kasza + pieprz + cynamon + papryka. Wymieszać, żadnych blenderów... Resztę można sobie darować

KSB i 7S jest w miarę proste (płatki róży można sobie darować)

Upiekłem na papierze żeby się nie pierniczyć z myciem formy później.

Są inne warianty tej potrawy, prostsze z innym nadzieniem.

wtorek, 1 grudnia 2020

Przypadek Pablo Matery

Pablo Matera 27 lat, do niedawna kapitan reprezentacji Argentyny w rugby. W 2012 roku miał 18 lat, pstro w głowie i pisał głupoty nt Murzynów i Boliwijczyków (wygląda że Argentyńczycy nie lubią Boliwijczyków BTW.) Niedawno ktoś odkopał jego wpisy z Twittera sprzed 9 lat. Jest afera (google: Pablo+Matera+racist)... Na razie nie jest już kapitanem, pewnie za chwile w ogóle go nie będzie... UAR czyli związek Rugby w Argentynie się tłumaczy i przeprasza, czyli stara rewolucyjna procedura została wdrożona: samokrytyka a potem i tak w piach...

Wygląda, że już niedługo standardową procedurą przy angażu zawodnika (ogólnie pracownika, bo czemu tylko ma dotyczyć sportowców) będzie staranna weryfikacja jego śladu w Internecie, powiedzmy od ukończenia 7 roku życia. Coś jak RuSHA (Główny Urząd Rasy i Osadnictwa SS) w 3 Rzeszy, tyle, że nie będą badać krwi, ale myśli delikwenta... Ustalą, że na przykład wieku 12 lat coś tam skrobnął nieprawomyślnego w temacie trans -- no niestety kontraktu z panem nie możemy podpisać... Sam pan rozumie że jest to w takiej sytuacji absolutnie niemożliwe...

czwartek, 26 listopada 2020

Manodona

Jednego dnia zmarli Maradona, Christophe Dominici oraz Zenon Plech. A przynajmniej jednego dnia media o tym poinformowały. Plech to lokalna polska gwiazda żużla. Dominici gwiazda francuskiego rugby, uczestnik słynnego meczu z Nową Zelandią w półfinale pucharu świata w 1999r. Maradona to wiadomo i (prawie) każdy czuł się w obowiązku poinformować o tym wydarzeniu swoją publiczność, nawet tacy co mają po 10 folowersów na Twitterze.

Dla mnie Maradona to oszust przede wszystkim i prymityw (w przeciwieństwie do Pelego na przykład). Każdy piłkarz pewnie ma na sumieniu faul w ważnym meczu, typu bramka ze spalonego, ale nie każdy popełnił faul aż tak oczywisty i bezczelny. Że to było dawno to większość nie kojarzy, ale wszyscy na stadionie będący dostatecznie blisko widzieli rękę, bo jak nie zobaczyć czegoś tak ordynarnego za wyjątkiem sędziego głównego + liniowego. Tak się dziwnie złożyło...

W piłce kopanej na turniejach o Puchar Świata jest (była?) w ogóle taka IMO patologia, że sędziują ludzie często słabi, bo z całego świata. Na co dzień sędziuje taki as ligę w Gwatemali dajmy na to, a raz w życiu będzie sędziował Niemcy vs Meksyk. Ten konkretny mecz sędziowali Tunezyjczyk + Bułgar (liniowy). Inne ćwierćfinały sędziowali wtedy: DDR-owiec, Rumun i Kolumbijczyk. No same mocne ligi na co dzień...

El mano to po hiszpańsku ręka jest właśnie. Na zdjęciu rzekomo kontrowersyjna sytuacja. Ona by była kontrowersyjna gdyby wepchnął piłkę w tłoku, albo byłaby wątpliwość czy ręką czy barkiem. Tak bywa... Ale tutaj? Dwóch ludzi skacze do piłki, i wszyscy widzę że ten co ma 160 w kapeluszu przeskakuje bramkarza. No ludzie...

środa, 25 listopada 2020

Szybkie tworzenie formularzy w Google

Szybkie bo za pomocą google apps script (GAS).

Nie miałem do wczoraj świadomości że można automatyzować różne rzeczy korzystając z narzędzia pn Google Apps Script. Konkretnie zaczynamy tu. A nawet konkretniej https://script.google.com/home/my. Oczywiście trzeba być zalogowanym żeby link zadziałał.

No więc kliknąłem nowy projekt a potem zmodyfikowałem przykład z samouczka bo zawierał wszystko co potrzeba:

// Create a new form, then add a checkbox question, a multiple choice question,
// a page break, then a date question and a grid of questions.
function convertToForm(){
var form = FormApp.create('New Form')
form
.setTitle('Badanie wizerunku i marki)
.setDescription('Badamy wizerunek i markę')

 form.addPageBreakItem()
    .setTitle('Część 1/3');               
                
form.addCheckboxItem()
   .setTitle('What condiments would you like on your hot dog?')
   .setChoiceValues(['Ketchup', 'Mustard', 'Relish'])

form.addMultipleChoiceItem()
    .setTitle('Do you prefer cats or dogs?')
    .setChoiceValues(['Cats','Dogs'])
    .showOtherOption(true);
  
form.addPageBreakItem()
    .setTitle('Część 2/3');

form.addDateItem()
    .setTitle('When were you born?');

form.addGridItem()
    .setTitle('Rate your interests')
    .setRows(['Cars', 'Computers', 'Celebrities'])
    .setColumns(['Boring', 'So-so', 'Interesting']);
  
form.addScaleItem()
     .setTitle('Scale item')
     .setLabels('zły', 'dobry')

form.addTextItem()
     .setTitle('Uwagi')
     .setRequired(true)

form.addMultipleChoiceItem()
    .setTitle('Płeć')
    .setChoiceValues(['K','M'])
    .setRequired(true);

form.addTextItem()
     .setTitle('Wiek')
     .setRequired(true)

Logger.log('Published URL: ' + form.getPublishedUrl());
Logger.log('Editor URL: ' + form.getEditUrl());
}

Następnie Save. Uruchamiamy przez Run/Run function. Jeżeli nie będzie błędów pod adresem https://docs.google.com/forms/u/0/ pojawi się formularz. Jak coś nie pasuje, to można resztę doklikać. Długie formularze w ułamku tego czasu który jest potrzebny do ręcznego wklikania da się zrobić...

Farsy ciąg dalszy

Jak wiadomo w wyniku działań pewnego upierdliwego 19 latka okazało się, że sprawozdawczość MinZdrowia/GiS to farsa. Zadeklarowano w urzędzie wzw/ z tym nowe otwarcie: stary system zamknięto (he, he) nowy wystartował 23.11.2020. Przy okazji dodano do puli zakażonych 22594 przypadków wcześniej nie uwzględnionych (około 2,5%). Zatem oficjalnie 23-11-2020 było 10139 zakażeń na stronie MZ; ale wszyscy inni (ECDC na przykład) wpisują 32733 (czyli 10139 + 22594) no bo jak inaczej. W sumie to nawet ma sens, bo przecież baza odpowiada zakażeniem zarejestrowanym a nie, że tak powiem nabytym. Skoro je zarejestrowano 23-11-2020 to tyle ma być...

Pozostaje problem jak to rozliczyć na województwa. No więc ja chciałem rozliczyć tak, że do zakażeń z 23-11-2020 dodam te 22594 w proporcji do wielkości zakażeń z 23-11-2020. Ściągnąłem nowe-dobre dane ze strony wykaz-zarazen-koronawirusem i zacząłem od zsumowania zakażeń w województwach. Się okazało, że jest to inna liczba niż 10139. Kuźwa. W pliku z 24-11-2020 to samo...

No więc miało być lepiej a wyszło tak samo. Uprawnione jest w tym momencie pytanie czy ludzie nie potrafiący liczyć muszą akurat walczyć z pandemią i pracować w GiS/MZ? Załączam rysunki, żeby nie było że zmyślam. Liczenie zakażeń/zgonów wg województw na razie zawieszam skoro nie bardzo wiadomo co liczyć...

poniedziałek, 23 listopada 2020

Tygodniowe dane nt. zgonów z GUS

GUS się wychylił niespodziewanie z dużą paczką danych nt. zgonów. Dane są tygodniowe, w podziale na płcie, regiony w klasyfikacji NUTS oraz 5 letnie grupy wiekowe.

Dane są udostępnione w formacie XSLX w dość niepraktycznej z punktu widzenia przetwarzania strukturze (kolumny to tygodnie/wiersze to różne kategorie agregacji: płeć, wiek, region), który zamieniłem na CSV o następującej prostej 7 kolumnowej strukturze:

year;sex;week;date;age;geo;value

W miarę oczywiste jest, że year to rok, sex to płeć, week to numer tygodnia, date to data pierwszego dnia tygodnia (poniedziałek), geo to identyfikator obszaru a value liczba zgonów odpowiadająca kolumn 1--6. Ten plik jest podzielony na lata bo w całości zajmuje circa 200Mb. Umieściłem go tutaj.

Skrypt też w R wymodziłem co wizualizuje zgony wg grup wieku oraz województw. Ponieważ kombinacji płeć/wiek/region są setki, moje wykresy dotyczą zgonów ogółem/kobiet/mężczyzn w podziale na grupy wiekowe oraz ogółem w podziale na województwa. Każdy wykres zawiera dwa szeregi: liczbę zgonów w 2020 roku oraz średnią liczbę zgonów z lat 2015--2019. Ponadto jest wykres z jedną krzywą: procent liczony dla stosownych tygodni jako liczba zgonów w 2020 przez średnią 5 letnią z lat 2015--2019. Ten wykres występuje też w wariancie skróconym: tylko 6 ostatnich tygodni, co pozwala dodać do punktów wartości liczbowe (które nie zachodzą na siebie).

library("ggplot2")
library("dplyr")
library("scales")
library("ggthemes")
library("ggpubr")
library("tidyr")

picWd <- 12
spanV <- 0.5
GUS.url <- "https://stat.gov.pl/obszary-tematyczne/ludnosc/ludnosc/zgony-wedlug-tygodni,39,2.html"
NIKW.url <- "(c) NI-KW @ github.com/knsm-psw/GUS_mortality"
NIKW <- sprintf ("%s | %s", GUS, NIKW.url)

z <- read.csv("PL-mortality-2015.csv", sep = ';',  header=T, na.string="NA" )
lastO <- last(z$date)
lastT <- last(z$week)

nuts <- c('PL21', 'PL22', 'PL41', 'PL42', 'PL43', 'PL51', 'PL52', 'PL61', 'PL62',
'PL63', 'PL71', 'PL72', 'PL81', 'PL82', 'PL84', 'PL91', 'PL92')

### Ogółem
z00 <- z %>% filter ( sex == 'O'  & geo == 'PL' ) %>% as.data.frame

z0 <- z00 %>% filter ( year >= 2015  & year < 2020 ) %>% as.data.frame
z1 <- z00 %>% filter ( year == 2020 ) %>% as.data.frame

## średnie w okresie 1 -- (n-1)
zz0 <- z0 %>% group_by(age,week) %>% summarise( year = 't19',
  vv = mean(value, na.rm=TRUE)) %>% as.data.frame
zz1 <- z1 %>% group_by(age,week) %>% summarise( year = 't20',
  vv = mean(value, na.rm=TRUE)) %>% as.data.frame
### Połącz
zz1 <- bind_rows(zz0, zz1)

farbe19 <- '#F8766D'
farbe20 <- '#00BFC4'

p1 <- ggplot(zz1, aes(x=week, y=vv, color=year)) +
 geom_smooth(method="loess", se=F, span=spanV, size=.4) +
 geom_point(size=.4, alpha=.5) +
 facet_wrap( ~age, scales = "free_y") +
 xlab(label="") +
 ylab(label="") +
 ##theme_nikw()+
 theme(plot.subtitle=element_text(size=9), legend.position="top")+
 scale_color_manual(name="Rok: ", labels = c("średnia 2015--2019", "2020"), values = c("t19"=farbe19, "t20"=farbe20 )) +
 ggtitle("Zgony wg grup wiekowych (PL/Ogółem)", subtitle=sprintf("%s | ostatni tydzień: %s", NIKW, lastO) )

ggsave(plot=p1, "zgony_PL_by_age_O.png", width=picWd)

### M ###
z00 <- z %>% filter ( sex == 'M'  & geo == 'PL' ) %>% as.data.frame

z0 <- z00 %>% filter ( year >= 2015  & year < 2020 ) %>% as.data.frame
z1 <- z00 %>% filter ( year == 2020 ) %>% as.data.frame

## średnie w okresie 1 -- (n-1)
zz0 <- z0 %>% group_by(age,week) %>% summarise( year = 't19',
  vv = mean(value, na.rm=TRUE)) %>% as.data.frame
zz1 <- z1 %>% group_by(age,week) %>% summarise( year = 't20',
  vv = mean(value, na.rm=TRUE)) %>% as.data.frame
### Połącz
zz1 <- bind_rows(zz0, zz1)

p2 <- ggplot(zz1, aes(x=week, y=vv, group=year, color=year)) +
 geom_smooth(method="loess", se=F, span=spanV, size=.4) +
 geom_point(size=.4, alpha=.5) +
 facet_wrap( ~age, scales = "free_y") +
 xlab(label="") +
 ylab(label="") +
 ##theme_nikw()+
 ##labs(caption=source) +
 theme(plot.subtitle=element_text(size=9), legend.position="top")+
 scale_color_manual(name="Rok: ", labels = c("średnia 2015--2019", "2020"),
   values = c("t19"=farbe19, "t20"=farbe20 )) +
 ggtitle("Zgony wg grup wiekowych (PL/Mężczyźni)", subtitle=sprintf("%s | ostatni tydzień: %s", NIKW, lastO) )

ggsave(plot=p2, "zgony_PL_by_age_M.png", width=picWd)


### K #########################################
z00 <- z %>% filter ( sex == 'K'  & geo == 'PL' ) %>% as.data.frame

z0 <- z00 %>% filter ( year >= 2015  & year < 2020 ) %>% as.data.frame
z1 <- z00 %>% filter ( year == 2020 ) %>% as.data.frame

## średnie w okresie 1 -- (n-1)
zz0 <- z0 %>% group_by(age,week) %>% summarise( year = 't19',
  vv = mean(value, na.rm=TRUE)) %>% as.data.frame
zz1 <- z1 %>% group_by(age,week) %>% summarise( year = 't20',
  vv = mean(value, na.rm=TRUE)) %>% as.data.frame
### Połącz
zz1 <- bind_rows(zz0, zz1)

p3 <- ggplot(zz1, aes(x=week, y=vv, group=year, color=year)) +
 geom_smooth(method="loess", se=F, span=spanV, size=.4) +
 geom_point(size=.4, alpha=.5) +
 facet_wrap( ~age, scales = "free_y") +
 xlab(label="") +
 ylab(label="") +
 ##theme_nikw()+
 ##labs(caption=source) +
 theme(plot.subtitle=element_text(size=9), legend.position="top")+
 scale_color_manual(name="Rok: ", labels = c("średnia 2015--2019", "2020"),
   values = c("t19"=farbe19, "t20"=farbe20 )) +
 ggtitle("Zgony wg grup wiekowych (PL/Kobiety)", subtitle=sprintf("%s | ostatni tydzień: %s", NIKW, lastO) )

ggsave(plot=p3, "zgony_PL_by_age_K.png", width= picWd)

### ogółem wg województw #####################################
n <- read.csv("nuts.csv", sep = ';',  header=T, na.string="NA" )
## dodaj nazwy
z <- left_join(z, n, by='geo')

## wiek razem
z00 <- z %>% filter ( sex == 'O' & geo %in% nuts & age == 'OGÓŁEM') %>% as.data.frame

z0 <- z00 %>% filter ( year >= 2015  & year < 2020 ) %>% as.data.frame
z1 <- z00 %>% filter ( year == 2020 ) %>% as.data.frame

## średnie w okresie 1 -- (n-1)
zz0 <- z0 %>% group_by(name,week) %>%
 summarise( year = 't19', vv = mean(value, na.rm=TRUE)) %>% as.data.frame
 zz1 <- z1 %>% group_by(name,week) %>%
  summarise( year = 't20', vv = mean(value, na.rm=TRUE)) %>% as.data.frame
### Połącz
zz1 <- bind_rows(zz0, zz1)

lastWeek <- last(zz1$week)
firstWeek <- lastWeek - 6

zz1 <- zz1 %>% filter ( week >= firstWeek  ) %>% as.data.frame
print(zz1)

p4 <- ggplot(zz1, aes(x=week, y=vv, group=year, color=year)) +
 geom_smooth(method="loess", se=F, span=spanV, size=.4) +
 geom_point(size=.4, alpha=.5) +
 facet_wrap( ~name, scales = "free_y") +
 xlab(label="") +
 ylab(label="") +
 ##theme_nikw()+
 ##labs(caption=source) +
 theme(plot.subtitle=element_text(size=9), legend.position="top")+
 scale_color_manual(name="Rok: ", labels = c("średnia 2015--2019", "2020"),
   values = c("t19"=farbe19, "t20"=farbe20 )) +
 ggtitle("Zgony wg województw* (PL/Ogółem)", 
   subtitle=sprintf("*wg klasyfikacji NUTS stąd mazowieckie/stołeczne | %s | ostatni tydzień: %s", NIKW, lastO))

ggsave(plot=p4, "zgony_PL_by_woj_O.png", width=picWd)

## jako %% w średniej w poprzednich 5 lat

zz1 <- zz1 %>% spread(year, vv)

zz1$yy <- zz1$t20 / zz1$t19 * 100

p5 <- ggplot(zz1, aes(x=week, y=yy), color=farbe20) +
 geom_smooth(method="loess", se=F, span=spanV, size=.4, color=farbe20) +
 geom_point(size=.4, alpha=.5) +
 facet_wrap( ~name, scales = "fixed") +
 xlab(label="nr tygodnia") +
 ylab(label="%") +
 theme(plot.subtitle=element_text(size=9), legend.position="top")+
 scale_color_manual(name="Rok 2020: ", labels = c("% 2020/(średnia 2015--2015)"),
   values = c("yy"=farbe20 )  ) +
 ggtitle("Zgony wg województw* (PL/Ogółem)", 
   subtitle=sprintf("*wg klasyfikacji NUTS stąd mazowieckie/stołeczne | %s | ostatni tydzień: %s", NIKW, lastO))

ggsave(plot=p5, "zgony_PL_by_woj_P.png", width=picWd)

zz1 <- zz1 %>% filter ( week >= firstWeek  ) %>% as.data.frame

p6 <- ggplot(zz1, aes(x=week, y=yy), color=farbe20) +
 geom_smooth(method="loess", se=F, span=spanV, size=.4, color=farbe20) +
 geom_point(size=.4, alpha=.5) +
 geom_text(aes(label=sprintf("%.1f", yy)), vjust=-1.25, size=1.5) +
 facet_wrap( ~name, scales = "fixed") +
 xlab(label="nr tygodnia") +
 ylab(label="%") +
 theme(plot.subtitle=element_text(size=9), legend.position="top")+
 scale_color_manual(name="Rok 2020: ", labels = c("% 2020/(średnia 2015--2015)"),
   values = c("yy"=farbe20 )  ) +
   ggtitle(sprintf("Zgony wg województw* (PL/Ogółem) tygodnie: %i--%i (%i tydzień zaczyna się %s)",
     firstWeek, lastWeek, lastWeek, lastO), 
   subtitle=sprintf("*wg klasyfikacji NUTS stąd mazowieckie/stołeczne | %s", NIKW))

ggsave(plot=p6, "zgony_PL_by_woj_P6.png", width=picWd)

niedziela, 22 listopada 2020

Znowu bursztyn w Mikoszewie

Pojechaliśmy w tą sobotę z MK zbierać bursztyn do Mikoszewa. Ponieważ miałem zajęcia, to dotarliśmy tam dopiero około 17:00 czyli było już ciemno. Okazało się że jesteśmy trochę późno, bo wysyp był 24h wcześniej (ale chyba niezbyt obfity)...

My zastaliśmy masę patyków na brzegu, a w nich miliony kawałków bursztynu, tyle że takie nalewkowe i mniejsze. Przez dwie godziny nazbieraliśmy tego prawie 600g (ja tym razem więcej bo 320g a MK 260g.)

sobota, 7 listopada 2020

COVID19: współczynnik WZ dla powiatów województwa pomorskiego

Wskaźnik zapadalności (dalej WuZet) dla ostatnich 14 dni po naszemu w oryginale zaś 14-day notification rate of newly reported COVID-19 cases per 100 000 population (Data on 14-day notification rate of new COVID-19 cases and deaths). Nasz wspaniały rząd walczący z COVIDem przyjął w swoim czasie `nową strategię' (od tego czasu już kilka razy odnawianą), a w niej był podział na strefy zielona/żółta/czerwona, definiowane odpowiednio jako wartości WuZet poniżej 6 (zielona); 6--12 (żółta) oraz powyżej 12 (czerwona). Dla Sopotu na przykład, który oficjalnie ma około 35 tys mieszkańców, do wejście do czerwonej strefy wystarczały zatem około 4 zakażenia (w ciągu dwóch tygodni). To wszysto wydaje się dziś śmieszne jak ostatnio zakażeń dziennie potrafi być na poziomie trzy-tygodniowej dawki...

Parę dni temu mój bank danych nt. COVID19 uzupełniłem o dane dotyczące liczby zakażeń w powiatach województwa pomorskiego. Akurat WSSE w Gdańsku takie dane w sposób w miarę porządny publikuje i da się je względnie łatwo odzyskać z raportów publikowanych i archiwizowanych (brawo--na to nie wpadł nawet Minister w MZ) pod adresem http://www.wsse.gda.pl/.

library("dplyr")
library("tidyr")
library("ggplot2")
m1unit <- 100000
# pomorskie_by_powiat.csv pobrane z www.wsse.gda.pl
# format: data;powiat;nc 
d <- read.csv("pomorskie_by_powiat.csv", sep = ';',  header=T, na.string="NA" )

# wartości skumulowane (po powiatach dlatego tak dziwnie)
# replace_na jest potrzebne bo cumsum nie obsługuje NA
e <- d %>% group_by(powiat) %>% dplyr::mutate(tc = cumsum(replace_na(nc, 0)),
tc1m = cumsum(replace_na(nc, 0)) / pop * m1unit
)
## dzień ostatni
day00 <- as.Date(last.obs)
## dwa tygodnie przed ostatnim
day14 <- as.Date(last.obs) - 14
## 4 tygodnie przed ostatnim
day28 <- as.Date(last.obs) - 28

## Stan na dzień ostatni
e00 <- e %>% filter (as.Date(dat) == day00 ) %>%
 group_by(powiat) %>% as.data.frame

## BTW Żeby było dziwniej zapis
## e0 <- e %>% group_by(powiat) %>%  slice_tail(n=1) %>% as.data.frame
## Daje inny wynik, liczby te same, ale porządek wierszy jest inny

## Stan na dzień dwa tygodnie przed
e14 <- e %>% filter (as.Date(dat) == day14 ) %>%
 group_by(powiat) %>% as.data.frame

e14 <- e %>% filter (as.Date(dat) == day14 ) %>%
 group_by(powiat) %>% as.data.frame

## Stan na dzień 4 tygodnie przed
e28 <- e %>% filter (as.Date(dat) == day28 ) %>%
 group_by(powiat) %>% as.data.frame

## nowe zakażenia w tygodniach 3/4
c28 <- e14$tc - e28$tc

## nowe zakażenie w tygodniach 2/1
c14 <- e00$tc - e14$tc
## To samo co c14/c28 ale w przeliczeniu
## na 100 tys:
c28m1 <- e14$tc1m - e28$tc1m
c14m1 <- e00$tc1m - e14$tc1m
## Dynamika zmiana WuZet w dwóch ostatnich okresach
## tj WuZet12 względem WuZet34
#d14v28 <- (c14m1 - c28m1) / c28m1 * 100
d14v28 <- (c14m1/c28m1) * 100
##
## Można sobie teraz c14m1/d14v28 na wykresach przestawić

Na dzień 7 listopada wyniki były takie (jeżeli się nie rąbnąłem w powyższym kodzie):

sprintf("%10.10s = %6.2f | %6.2f | %6.2f - %6.2f | %4i | %4i | %4i (%4i)",
   e00$powiat, d14v28, c14m1, e00$tc1m, e28$tc1m, e00$tc, e14$tc, e28$tc, e00$pop )
 [1] "    Gdynia = 229.15 | 577.74 | 1129.08 - 299.22 | 2781 | 1358 |  737 (246306)"
 [2] "    Gdańsk = 149.50 | 416.37 | 1045.98 - 351.10 | 4856 | 2923 | 1630 (464254)"
 [3] "    Słupsk = 228.26 | 803.59 | 1387.42 - 231.78 | 1269 |  534 |  212 (91465)"
 [4] "     Sopot = 144.90 | 583.03 | 1404.21 - 418.80 |  513 |  300 |  153 (36533)"
 [5] "  tczewski = 437.50 | 905.98 | 1323.60 - 210.53 | 1534 |  484 |  244 (115896)"
 [6] "   gdański = 399.21 | 889.61 | 1353.71 - 241.26 | 1543 |  529 |  275 (113983)"
 [7] "  kartuski = 197.07 | 855.49 | 1846.22 - 556.63 | 2471 | 1326 |  745 (133841)"
 [8] "  bytowski = 268.71 | 998.31 | 1693.33 - 323.50 | 1340 |  550 |  256 (79134)"
 [9] " malborski = 329.61 | 923.16 | 1408.21 - 204.97 |  900 |  310 |  131 (63911)"
[10] "     pucki = 225.35 | 766.35 | 1545.69 - 439.26 | 1309 |  660 |  372 (84687)"
[11] "wejherowsk = 150.71 | 396.16 |  971.92 - 312.90 | 2078 | 1231 |  669 (213803)"
[12] "starogardz = 216.36 | 744.52 | 1388.93 - 300.31 | 1776 |  824 |  384 (127868)"
[13] " chojnicki = 266.33 | 813.36 | 1311.04 - 192.29 | 1275 |  484 |  187 (97251)"
[14] "  sztumski = 309.52 | 619.84 |  979.83 - 159.73 |  411 |  151 |   67 (41946)"
[15] " kwidzyńsk = 251.34 | 563.39 |  973.35 - 185.80 |  812 |  342 |  155 (83423)"
[16] " kościersk = 293.30 | 786.88 | 1392.60 - 337.43 | 1007 |  438 |  244 (72311)"
[17] "nowodworsk = 263.21 | 777.35 | 1273.30 - 200.61 |  457 |  178 |   72 (35891)"
[18] "   słupski = 244.23 | 514.50 |  969.24 - 244.08 |  957 |  449 |  241 (98737)"
[19] "  lęborski = 172.27 | 618.09 | 1135.18 - 158.29 |  753 |  343 |  105 (66333)"
[20] " człuchows = 268.29 | 388.16 |  571.65 -  38.82 |  324 |  104 |   22 (56678)"

Dynamika WuZeta: Gdynia/Gdańsk/Sopot = 129.1%, 149.5% i 144.9%; wartość maksymalna 437% (tczewski); wartość minimalna 150% (wejherowski). Najnowsze wartości współczynnika WuZet: Gdynia/Gdańsk/Sopot= 577.7, 416.4 oraz 583.0; wartość maksymalna 998,3 (bytowski); wartość minimalna 388.1 (człuchowski). Dane i skrypty są tutaj.

poniedziałek, 2 listopada 2020

Prognoza falowania Bałtyku


Oryginalny plik prognozy

Wycięty fragment

`Podglądówka' całej prognozy

Są na stronie http://www.meteo.pl/ udostępnione 120h prognozy falowania Bałtyku w postaci rysunków. Na tych rysunkach wysokość fal jest odwzorowana kolorem od czerwonego do jasno niebieskiego (najmniejsze). Kierunek fal jest odwzorowany strzałkami. Są też dostępne dane historyczne.

Wszystko jest łatwe do pobrania, bo nazwa pliku historycznego to na przykład wavehgt_0W_2015122200_015-00.png czyli wavehgt_0W_YYYYMMDD00_0HH-00.png (gdzie HH to 03/09/15/21). Pliki prognoz mają też nazwy wg schematu: wavehgt_0W_YYYYMMDD00_HHH-00.png, tyle że HHH się zmienia w zakresie 12--120 co trzy (godziny).

Pobieram wgetem 8 plików 12--120. Teraz trzeba ustalić jakie są kolory na obrazku i wysłać komunikat (analiza strzałek to beznadzieja sprawa, nawet się nie zabieram.)

Zaczynam od wycięcia interesującego mnie fragmentu Bałtyku:

## 8 plików 12--120
convert wavehgt_0W_YYYYMMDD00_HHH-00.png-00.png -crop 65x86+280+575 \
 wavehgt_0W_YYYYMMDD00_HHH-00.png-00D.png
## Wypisz kolory z rysunku:
convert wavehgt_0W_YYYYMMDD00_HHH-00.png-00D.png \
  -define histogram:unique-colors=true -format %c histogram:info:- | \
  getMaxFala.pl 
## Łączę w jeden:
montage PLIKI... -tile 2x4 -border 4 -geometry 480  S_YYYMMDD.png

Plik getMaxFala.pl zwraca kolor odpowiadający za najwyższą falę. Szczęśliwie tych kolorów za dużo nie jest, więc sprawdzanie jest enumeratywne

Mając najwyższe fale w horyzoncie 12--120 ustalamy max z tych 8 liczb i wypisujemy komunikat (Perl):

print "Max: $maxFF expected (in 120h time window)\n";
print "Details: ";
for $f (sort keys %faleMax) { print "$f = $faleMax{$f} /" }
print "\nAmber likelihood: ";
if ($maxFF < 3) { print "NONE\n" } 
elsif ($maxFF < 4) { print "VERY SMALL\n" }
elsif ($maxFF < 5) { print "SMALL\n" }
elsif ($maxFF < 5) { print "MEDIUM\n" }
elsif ($maxFF < 6) { print "LARGE\n" }
else  { print "Amber: HUGE"}
print "http://pinkaccordions.homelinux.org/fale/forecast/S_${yyyymmdd}.png\n";
print "Bye...\n";

Prawdę powiedziawszy polecenia convert/mogrify/wget też są `wbudowane' w skrypt Perla. Całe zadanie realizuje jeden skrypt, który w efekcie wypisuje na ekran powyższy komunikat:

  ## Fragment skryptu
  $GETMAXFALA='/home/pi/bin/getMaxFala.pl';
  ## ... ##
for $h (@Hours) {
    $hrNo = sprintf "%03.3i", $h;
    $url= "$URL/${yyyymmdd}00/wavehgt_0W_${yyyymmdd}00_${hrNo}-00.png";
    ###print STDERR "$url\n";
    system ("wget $url -O W_${yyyymmdd}00_${hrNo}-00.png");
    system ("convert W_${yyyymmdd}00_${hrNo}-00.png -crop 280x330+125+350 -fill red" .
        " -annotate +140+360 '$monthNo/${dayNo}+${hrNo}' -pointsize 24 -fill blue" .
        " W_${yyyymmdd}00_${hrNo}-00_C.png");
    #### Miniatura zatoki
    system ("convert W_${yyyymmdd}00_${hrNo}-00.png -crop 65x86+280+575 W_${yyyymmdd}00_${hrNo}-00_D.png");
    $maxFala = `convert "W_${yyyymmdd}00_${hrNo}-00_D.png" -define histogram:unique-colors=true
       -format %c histogram:info:- | $GETMAXFALA`;
    chomp($maxFala); 
    $files .= "W_${yyyymmdd}00_${hrNo}-00_C.png ";
    $files_S .= "W_${yyyymmdd}00_${hrNo}-00_D.png ";
    $faleMax{"+${hrNo}"}=$maxFala;
}

system ("montage $files -tile 2x4 -border 4 -geometry 480  W_${yyyymmdd}.png" );
system ("montage $files_S -tile 2x4 -border 4 -geometry 480  S_${yyyymmdd}.png" );
$maxFF = max(values(%faleMax)); chomp($maxFF);
  ## .. ##

Ten komunikat (w potoku) czyta inny skrypt i wysyła listy do zainteresowanych.

Covid19 wg kontynentów

Skrypt rysujący wykres łącznej liczby zakażeń i zgonów wg kontynentów. Przy okazji podjąłem nieśmiajłą próbę zapanowania nad (jednolitym) wyglądem. Udając, że istnieje coś takiego jak Narodowy Instytut Korona Wirusa stworzyłem dla niego wizualizację, którą dodaje się do wykresu w postaci jednego polecenia. To jedno polecenie to funkcja theme_nikw:

#!/usr/bin/env Rscript
#
library("ggplot2")
library("dplyr")
library("scales")
library("ggthemes")
library("ggpubr")
#
theme_nikw <- function(){
 theme_wsj() %+replace%
 theme(
  panel.grid.minor = element_line(size = 0.25, linetype = 'solid', colour = "cornsilk3"),
  panel.grid.major = element_line(size = 0.25, linetype = 'solid', colour = "cornsilk3"),
  ##
  axis.text.x  = element_text(size = 6 ),
  axis.text.y  = element_text(size = 6 ),
  ## https://stackoverflow.com/questions/14379737/how-can-i-make-xlab-and-ylab-visible-when-using-theme-wsj-ggthemes
  axis.title  = element_text(family="sans", size=6)
  ## Poniższe natomiast nie działa:
  #axis.title.x  = element_text(size = 6 ),
  #axis.title.y  = element_text(size = 6 ),
  ## margin=margin(r=, t=, b = 3, l=, unit="pt")
  plot.title=element_text(family="sans", size=14, hjust=0, margin=margin(b = 3, unit="pt")),
  plot.subtitle=element_text(family="sans", size=8, hjust=0),
  legend.title=element_text(family="sans", size=8),
  plot.caption = element_text(family="sans", size = 6)
  )
}

Uprzedzając wydarzenia, kolejna funkcja notin służy do tego co funkcja in tyle, że zamiast zwracać wartości pasujące, zwraca te które nie pasują:

## https://stackoverflow.com/questions/34444295/how-to-specify-does-not-contain-in-dplyr-filter
`%notin%` = function(x,y) !(x %in% y)

Teraz ustawiam różne wartości globalne (options służy do wyłączenia zapisu liczb w trybie scientic notation).

options(scipen = 999) ## albo options(scipen = 999, digits=3)
note <- "(c) NI-KW @ github.com/knsm-psw/NI-KW"
today <- Sys.Date()
tt <- format(today, "%Y-%m-%d")
spanV <- 0.25
mainBreaks <- "4 weeks"
mainColor <- "deeppink"
loessColor <- "deeppink"

## Przed/po \n musi być odstęp inaczej nie działa
## url <- "https://www.ecdc.europa.eu/en/publications-data/ \n download-todays-data-geographic-distribution-covid-19-cases-worldwide"
url <- "www.ecdc.europa.eu/en/publications-data/download-todays-data-geographic-distribution-covid-19-cases-worldwide"
surl <- sprintf ("Data retrived from %s", url)

Czytanie zbiorów danych. Dane dotyczące zakażeń pochodzą ze strony ECDC (European Center for Disease Prevention and Control). Dane dotyczące ludności też z tej strony tyle, że wydłubałem je raz i trzymam w oddzielnym pliku zamiast powielać niepotrzebnie w pliku z danymi o COVID19 (jak to robi ECDC)

## info o wielkości populacji i kontynencie
c <- read.csv("ecdc_countries.csv", sep = ';',  header=T, na.string="NA" )
## dane dzienne/cały świat (ECDC)
d <- read.csv("covid19_C.csv", sep = ';',  header=T, na.string="NA", 
   colClasses = c('factor', 'factor', 'factor', 'character', 'character', 'numeric', 'numeric'));

Złączenie zbiorów w oparciu o kolumnę id:

d <- left_join(d, c, by = "id")

Parę rzeczy trzeba uporządkować. Po pierwsze usunąć wpisy zawierające kraj bez ISO-kodu (coś jest nie tak z transformacją XSLX na CSV, muszę to sprawdzić). Po drugie czasami liczby zakażeń/śmierci są ujemne bo dane zostają zrewidowane. Nie jest to najlepsze rozwiązanie, ale żeby przynajmniej na pierwszy rzut oka wykres nie wyglądał absurdalnie to zamieniam liczby ujemne na NA:

# czyszczenie
# tylko kraje z ID
d <- d %>% filter(!is.na(id))
# zamień na liczby (był problem z czytaniem CSV bezpośrednio)
d$newc <- as.numeric(d$newc)
d$newd <- as.numeric(d$newd)

# Dane bezsensowne zamień na NA
# Zdarzają się ujemne bo kraje zmieniają klasyfikację/przeliczają
d$newc[ (d$newc < 0) ] <- NA
d$newd[ (d$newd < 0) ] <- NA

Liczę wartości na 1/mln. Obliczam dzienne wartości zakażeń/śmierci na 1mln wg kontynentów

## zgony na 1mln
d$tot1m <- d$totald/d$popData2019 * 1000000
## Oblicz sumy dla kontynentów
## Kontynentów jest więcej niż 5:-) w zbiorze (jakieś śmieci)
continents <- c('Africa', 'America', 'Asia', 'Europe', 'Oceania')
## ogółem wg 
cont <- d %>% filter (continent %in% continents) %>% group_by(date,continent) %>% summarise(
  tc = sum(newc, na.rm=TRUE),
  td = sum(newd, na.rm=TRUE),
  tc1m = sum(newc, na.rm=TRUE)/sum(popData2019, na.rm=TRUE) * 1000000,
  td1m = sum(newd, na.rm=TRUE)/sum(popData2019, na.rm=TRUE) * 1000000
  ) %>% as.data.frame
## str(tc)

## Z ciekawości
noncont <- d %>% filter (continent %notin% continents) %>% as.data.frame
print(noncont)

Wykreślam wykresy. Punkty przestawiają dane dzienne. Krzywa loess trend. Źródło danych jest w podtytule. W napisie umieszczanym pod wykresem jest informacja o autorze i link do repozytorium githuba (polecenie labs(caption=note))

pd1 <- ggplot(cont, aes(x= as.Date(date, format="%Y-%m-%d"), color = continent, y=tc)) +
 geom_point(aes(y=tc, color = continent), size=.4, alpha=.5) +
 geom_smooth(method="loess", se=F, span=spanV, aes(fill=continent, y=tc, group=continent)) +
 xlab(label="") + ylab(label="cases") + 
 scale_x_date( labels = date_format("%m/%d"), breaks = mainBreaks) +
 theme_nikw() +
 labs(caption=note) +
 ggtitle(sprintf ("COVID19: cases (%s)", last.obs), subtitle=sprintf("%s", surl))

pd2 <- ggplot(cont, aes(x= as.Date(date, format="%Y-%m-%d"), , color = continent, y=td)) +
 geom_point(aes(y=td, color = continent), size=.4, alpha=.5) +
 geom_smooth(method="loess", se=F, span=spanV, aes(fill=continent, y=td, group=continent)) +
 xlab(label="") + ylab(label="deaths") + 
 scale_x_date( labels = date_format("%m/%d"), breaks = mainBreaks) +
 theme(plot.subtitle=element_text(size=8, hjust=0, color="black")) +
 theme_nikw() +
 labs(caption=note) +
 ggtitle(sprintf ("COVID19: deaths (%s)", last.obs), subtitle=sprintf("%s", surl))

ggsave(plot=pd1, file="tc_by_c.png", width=10)
ggsave(plot=pd2, file="td_by_c.png", width=10)

pd1m <- ggplot(cont, aes(x= as.Date(date, format="%Y-%m-%d"), color = continent, y=tc1m)) +
 geom_point(aes(y=tc1m, color = continent), size=.4, alpha=.5) +
 geom_smooth(method="loess", se=F, span=spanV, aes(y=tc1m )) +
 xlab(label="") + ylab(label="cases") + 
 scale_x_date( labels = date_format("%m/%d"), breaks = mainBreaks) +
 theme_nikw() +
 labs(caption=note) +
 ggtitle(sprintf ("COVID19: cases per 1mln (%s)", last.obs), subtitle=sprintf("%s", surl))

pd2m <- ggplot(cont, aes(x= as.Date(date, format="%Y-%m-%d"), color = continent, y=td1m)) +
 geom_point(aes(y=td1m, color = continent), size=.4, alpha=.5) +
 geom_smooth(method="loess", se=F, span=spanV, aes(y=td1m )) +
 xlab(label="") + ylab(label="deaths") + 
 scale_x_date( labels = date_format("%m/%d"), breaks = mainBreaks) +
 theme_nikw() +
 labs(caption=note) +
 ggtitle(sprintf ("COVID19: deaths per 1mln(%s)", last.obs), subtitle=sprintf("%s", surl))

ggsave(plot=pd1m, file="tc1m_by_c.png", width=10)
ggsave(plot=pd2m, file="td1m_by_c.png", width=10)

Wszystko. Skrypt jest wykonywany automatycznie o ustalonej godzinie, po czym wykresy wysyłane są na twitterowe konto "Instytutu".

sobota, 31 października 2020

Koniec PiSu?

Taka książka jest pn 'Koniec PiSu' (Mrozowski rozmawia z Kamińskim). Całkiem nadająca się do czytania BTW, bo tak jak nie cenię autorów, to w tym przypadku nie poszli w tanie hejterstwo. Książka z 2012 roku, okazawszy się raczej marnym proroctwem, stała się przedmiotem śmichów-chichów, ale wygląda że -- być może -- co się odwlecze to nie uciecze.

Od razu zaznaczę, że od zawsze popieram PiS a młodszym przypomnę, że do roku 2015 była to beznadziejna sprawa--zawsze wpiernicz i nigdy nie miałem wątpliwości na kogo głosować. Do teraz...

Teraz bym nie zagłosował, co więcej nawet gdyby wybory się odbyły za trzy lata to na dziś wygląda że też bym nie zagłosował. Nie to że mi się spodobał p. Budka czy p. Hołownia. Nie, nie głosowałbym wcale. Moi idole przegięli pałę...

Po pierwsze i najważniejsze: nie potrafię wytłumaczyć co konkretnie chciał osiągnąć Jarosław Kaczyński proponując ustawę w/s ochrony praw zwierząt. Nie można z piątku na sobotę powiedzieć ludziom i to ludziom ze środowiska, bez którego nie byłoby zwycięstwa partii PiS w ostatnich wyborach: Mamy w dupie czy wzieliście kredyty na 5 lat czy na 15 lat, macie rok na to żeby się zwijać. Nie mogę tego nazwać inaczej niż bezczelnością/butą/oderwaniem się od rzeczywistości. Nie znajduję też innego wytłumaczenia, że taki pomysł w ogóle się pojawił, jak uwiąd intelektualny JK...

Ustawa oprócz tego, że szkodzi interesom ekonomicznym wsi (i nie tylko) jest głupia w każdym praktycznie aspekcie, oprócz tego, pod którym jest szerzej znana. Bo znana jest jako futerkowa czyli zakazująca hodowli zwierząt futerkowych, tyle że akurat zakaz hodowli zwierząt futerkowych byłby OK (pod warunkiem oczywiście wyrównania strat ekonomicznych hodowcom). To co uważam za największy idiotyzm `ustawy futerkowej', to zakaz uboju rytualnego. Lewacka logika 101: multikulti ale bez halal. Bardzo was lubimy, ale tego/tamtego wam nie wolno. Wygląda że na starość p. Bóg odebrał rozum prezesowi. Nie jest naszą sprawą ocena/zakazy tego co 1/3 ludzkości uważa za normalne.

Po drugie: tzw. rekonstrukcja rządu. Zapowiadana, odwlekana, wreszcie zrealizowana w jakimś dziwnym trybie. Świetnych ministrów usunięto, zastępując ich jeszcze lepszymi (ironia); podpisano uroczyste i kuriozalne porozumienie. Kuriozalne bo przecież nie programowe--program był ogłoszony w 2019 a my zawsze dotrzymujemy słowa. Więc w TV pokazano uroczyste porozumienie w/s podziału stanowisk. Tzn. pokazano jak podpisują, co podpisują już nie pokazano. No wspaniale... Bezczelność i buta #2.

A że p. Bóg karze za głupotę i bezczelność to ledwo poprzydzielano fotele nowym/starym (złotousty-Gowin) zaczęła się pandemia. Gołym okiem widać panikę i brak planów tych nowych wspaniałych jeszcze lepszych od starych wspaniałych. W lecie kiedy był czas na przygotowanie się (choćby w trybie symulacji -- co zrobić jak będzie druga fala epidemii) to rządzący zajęci byli `rekonstrukcją'/ustawą futerkową. Teraz improwizują.

Jeden tylko przykład: do 29 października zapowiadają w telewizjach, że nie będzie zakazu wstępu na cmentarze; 30 października premier oznajmia że będzie zakaz. Kłamie przy tym bezczelnie, że mieli nadzieję, że pandemia wygaśnie (na jakiej podstawie? Że się cud Boski zdarzy?) Potem widocznie komuś w KPRM się przypomina, że na lodzie została kolejna grupa przedsiębiorców a premier nic im nie obiecał, więc wieczorem naprędce wydają uzupełniający komunikat, że im się też zrekompensuje, ale w jaki sposób konkretnie, to będzie później (bo teraz to sami nie wiemy?). Następnego dnia jest komunikat w/s tego później: Agencja Restrukturyzacji i Modernizacji Rolnictwa będzie skupować kwiaty (są już żarty, że w grudniu będą odkupować choinki i karpie). Ponieważ większość tych przedsiębiorców to drobni handlarze prowadzący działalność nierejestrowaną/ryczałt/handel obwoźny, to ciekawe jak Agencja ustali komu/ile się należy. Kto bezczelny dostanie, kto uczciwy obejdzie się smakiem?

Jak to nie jebnie to będzie cud-boski. Ale jak ten cud się zrealizuje to będzie tylko trwanie, ten rząd już niczego nie zrealizuje, więc nie wiadomo co lepsze: trwanie czy upadek.

Nawiasem i na koniec: akurat protesty po wyroku TK ws aborcji eugenicznej (wbrew wielkim nadziejom antyPiSu) mają -- moim zdaniem -- niewielkie szanse na sukces. Gołym okiem widać głupotę protestujących (wulgarność, atakowanie kościołów i podobnych obiektów, które należy zostawić w spokoju a demonstrować gdzie indziej, bezsensowne blokowanie ruchu na ulicach itd/itp). Sama szefowa, bliżej mi nieznana 41-letnia Marta Wypierdalać-Lampart wygląda na osobę mocno intelektualnie ograniczoną za to o dużych ambicjach (nieciekawy mix jak wiadomo). Sam fakt że w wieku 41-lat niewiele osiągnęła wskazuje, że coś z nią jest nie tak. A co konkretnie to niedługo się okaże, jak to wszystko pizdnie. Ot KOD/Kijowski season 2020, skończą podobnie...

Dopisek z ostatniej chwili: ustawa futerkowa z powodu której: 1) grożono rozpadem koalicji; 2) usunięto ze stanowiska ministra rolnictwa; 3) zawieszono iluś tam posłów, idzie do kibla. Czyli była do dupy. Czyli `nieomylny/genialny' prezes...?

wtorek, 20 października 2020

Eldorado w Mikoszewie

Mój bursztynowy przywódca M. już na początku tygodnia uprzedził mnie o nadchodzącym sztormie, który miał ucichnąć pod koniec tygodnia. Faktycznie wiało i do tego prawie-że-idealnie bo z północnego wchodu. W czwartek 15.10 wiatr ucichł więc pojechałem rowerem na Sobieszewo (po drodze zaglądając też na Brzeźno). Że to względnie daleko i mi się nie chciało jechać na Orle, to tym razem pojechałem na parking w Sobieszewie (wejście numer 16). Stali tam już ludzie z podbierakami, ale niewiele się jeszcze działo. Kolega M. zarządził wyjazd na wieczór...

Jak przyjechaliśmy było jeszcze szaro ale szybko zapadła noc. Fala była jeszcze duża i trzeba było uważać. Udało mi się wyłowić 17g bryłkę i parę jeszcze ładnych kawałków, po czym złamałem podbierak. Bez podbieraka nie szło już tak dobrze ale trochę dozbierałem więc generalnie byliśmy zadowoleni, zwłaszcza że w tym roku do tej pory praktycznie zero mimo kilku prób. Bursztyn wymywało dosłownie na odcinku 30m; jak przestało poszliśmy do domu. Rano w piątek 17.10 powtórka, ale było słabo. Wg. M. za duża fala była ciągle--umówiliśmy się na wieczór.

Wieczorem też nieszczególnie. Mało i małe. Wracamy do domu jest 22:00. A M. żebyśmy jechali do Mikoszewa. Ja na to że mi się nie chce, ale że namawiał to mówię OK. Pierwszy raz jedziemy na Mikoszewo ale trafiamy bez pudła-- jest 23:00 jak wychodzimy na plażę. Kupa luda. Cała plaża na fioletowo. W świetle latarki UV widać pełno leżącego na piasku bursztynu--takie kawałki wielkości pestki od jabłka albo trochę większe (i mniejsze). Są tego miliony... Większe też się trafiają ale oczywiście trzeba wejść do wody. Zbieramy do 1:00 w nocy, w domu jestem o 2:00. Mam 170g uzbierane (Sobieszewo/Mikoszewo)

W Sobotę M. nie może, jadę sam rano rowerem z kaloszami w plecaku. Primo chcę zobaczyć jak wygląda plaża bo wczoraj to byliśmy w nocy. Po drugie porobić zdjęcia mając nadzieję na dzikie tłumy + (być może) policję (pandemia jest). Przyjeżdżam 9:30-- plaża cała w śmieciach. Ludzi jest sporo ale nie aż tak za bardzo (potem doszło więcej, tyle że dużo spacerowiczów i słoikowców tj. zbieraczy okazjonalnych, bez sprzętu). Ubieram kalosze i zbieram z brzegu oraz kopię w grubej warstwie śmieci. Nb. że nie mam woderów, to akurat no-problem, bo woda jest tak mętna że i tak wszyscy co stoją w wodzie zbierają max 2m od linii brzegowej, patrząc w stronę plaży. A jest mętna, bo pływa pełno wodorostów o konsystencji rozmoczonej waty (które w świetle UV wczoraj wyglądały jak buraki, ponieważ zielony zmienia się w bordowy)

Do 13:00 w tym stylu i tym sprzętem uzbierałem 340g! Melduję M. co widziałem. Od razu chce jechać, ja mniej bo od 30 godzin na nogach! Pojechaliśmy wieczorem. Dozbierałem następnych 190g. Bursztyn ciągle wymywa i jest fala. Okruchów jest tyle na piasku, że można by do rana siedzieć i kilo uzbierać (tylko po co?)

Niedziela 18.10. Jeszcze raz do Mikoszewa. Tym razem z Elką i Jankiem. Nie ma fali. W wodzie już nic nie ma--czysta. Na brzegu jedno miejsce gdzie grubo śmieci--tam kopią i znajdują większe kawałki takie wielkości ziarnka grochu. W innych miejscach generalnie przekopana albo śmiecie odpłynęły. Mimo to 120g zebraliśmy...

Reasumując w ten szalony weekend uzbierałem 170+190+340+120 = prawie 900g bursztynu. Smak sukcesu psuje fakt, że 90% to drobnica. Inni też się obłowili sądząc po wpisach na FB grupie zbieraczy. Mocny początek sezonu.

Zdjęcia są tutaj

Poprawianie czytelności obrazków za pomocą Imagemagick


Dokument oryginalny

Wersja BW

Wersja BW pogrubiona

Wersja bez nagłówka

Wersja ostateczna

Dziś (drugi dzień używania) tesseract wyłożył się na względnie prostym rysunku pn. dzienny raport o koronawirusie:

tesseract 20201020T080001_raport.png 00001
##  pomija dwie pierwsze liczby, zawiera wiele nieistotnych znaków

tesseract 20201020T080001_raport.png 00002 -c preserve_interword_spaces=1 --psm 6
## prawie dobrze zdekodował liczby:
## 8 962         725 289447 44675 95956
## ale w pliku są jeszcze inne znaki

tesseract 20201020T080001_raport.png 0003 --psm 6 -c tessedit_char_whitelist=0123456789 \
-c preserve_interword_spaces=1
## w zasadzie nic nie zmienia

convert 20201020T080001_raport.png -threshold 85%  raport-bw.png
tesseract raport-bw.png raport-bw --psm 6
# pierwszy wiersz zamienia rysunek na b-w
# tesseract działa prawie prawidłowo, tj. zwraca:
#
# DZIENNY RAPORT 0 KORONAWIRUSIE
# ? 0 N ?
# 8 962 725 289447 44675 95956
#
# pierwsza liczba ma ekstra odstę

convert 20201020T080001_raport.png  -threshold 85% \
  -morphology erode diamond raport-bw-m.png
tesseract raport-bw-m.png raport-bw-m --psm 6
# DZIENNY RAPORT 0 KORONAWIRUSIE
# ? \ (ae \\ (@
# 8962 725 289447 44675 95956
# a
# IN.
#

convert 20201020T080001_raport.png  -threshold 85% \
  -crop 1140x75+50+400
  -morphology erode diamond raport-bw-mc.png
tesseract raport-bw-mc.png raport-bw-mc --psm 6
# Jeden prawidłowy wiersz
# 8962 725 289447 44675 95956

Opcja -morphology erode diamond pogruba; opcja -crop 1140x75+50+400 wycina prostokąt 1140x75 pikseli pomijając pierwszych 50/400 pikseli (poziom/pion). Wartości 1140/75/50/400 znalazłem wczytując plik PNG do gimpa.

No więc wersja na dziś to nie:

## zamalowanie na biało wszystkiego nad liczbami:
convert PLIK.png -draw "rectangle 0,0 1200,380" -fill white PLIK_0.png

ale:

convert PLIK.png -threshold 85% -crop 1140x75+50+400 \
  -morphology erode diamond PLIK_0.png

Po odczytaniu danych są one dopisywane do pliku CSV, następnie tworzone są wykres (za pomocą R), które w ostatnim kroku są wysyłane na Twittera.

Ciekawe jak to będzie długo działać, bo mam mocne wrażenie że osoby odpowiedzialne za udostępnianie danych w MinZdrowia to amatorzy/dyletanci.

Zobacz też tutaj.

Przy okazji instalowanie tesseracta w systemie Debian to:

apt install tesseract-ocr libtesseract-dev
## poniższe jest opcjonalne:  
apt install tesseract-ocr-pol tesseract-ocr-eng \
  tesseract-ocr-ger tesseract-ocr-deu

środa, 14 października 2020

Odzyskiwanie danych nt COVID119 z twitów Ministerstwa Zdrowia


Dokument #1

Dokument #2

Pobielenie

Dokument gotowy do OCR

Że się pandemia rozwija zachciało mi się odzyskać dane podawane przez polskie Ministerstwo Zdrowia na Twitterze w formie komunikatów obrazkowych. Otóż raz dziennie o 10:00 pojawia się rysunek zawierający dane dotyczące zajętych łóżek dedykowanych COVID-19, zajętych respiratorów, liczby osób objętych kwarantanną i jeszcze dwóch innych rzeczy. Ponadto, raz na tydzień w poniedziałek pojawia się rysunek z liczbą testów w podziale na województwa. Tych danych nie ma na stronie MZ, a jeżeli są to tak publikowane, że ja nie potrafię tego odszukać. Że MZ udostępnia dane obrazkowo za pośrednictwem amerykańskiej firmy zamiast w sposób opisany ustawą o dostępnie do informacji publicznej to oczywiście skandal i żenada.

Cały stream https://twitter.com/MZ_GOV_PL to ja mam ściągnięty. Tam nie ma obrazków; są URLe do obrazków, które można łatwo pobrać. Ja to robię na raty, najpierw json do csv:

#!/usr/bin/perl
# Zamiana Json -> CSV
use JSON;
use Time::Piece;
use open ":encoding(utf8)";
use open IN => ":encoding(utf8)", OUT => ":utf8";
binmode(STDOUT, ":utf8");

print "id;date;repid;text\n";

while (<>) { chomp();
  $tweet =  $_;

  my $json = decode_json( $tweet );
  $tid = $json->{"id"};
  $dat = $json->{"created_at"};
  $dat = Time::Piece->strptime($dat,
   "%a %b %d %H:%M:%S %z %Y")->strftime("%Y-%m-%dT%H:%M:%S");

  $mmm = $json->{"entities"}{"media"}; ## lista-haszy

  for $mm ( @{$mmm} ) { $media = $mm->{media_url} ;
    ## id-tweeta;data;url-do-rysunku
    print "$tid;$dat;$media\n";  }
}

Teraz można ściągnąć rysunku zwykłym wgetem albo curlem, pobierając URLa z cvsa. Rysunki o łóżkach i wentylatorach pojawiają się codziennie około 10:30 (czyli 8:30 GMT). Rysunki o liczbie testów wg województw w poniedziałki generalnie około 16:00. Więc prostym skryptem Perla ściągam poniedziałkowe rysunki opublikowane po 13:30GMT oraz wszystkie po 8:00GMT a przed 10:00GMT. Po ściągnięciu oglądam i wywalam nierelewantne.

OCR robię programem tesseract:

tesseract PLIK.png PLIK

Powstanie PLIK.txt a w nim tekst z rysunku. Z danymi poniedziałkowymi był problem, tesseract się gubił i PLIK.txt nic nie zawierał Żeby mu pomóc najpierw upraszczałem rysunek:

#!/bin/bash
## pomaluj na biało fragmenty nie zawierające liczb
convert "$1" -fill white -draw "rectangle 0,0 1200,115" \
  -draw "rectangle 0,640 200,675"  \
  -draw "rectangle 350,115 830,540" PLIK_0.png
## zamień wszystkie kolory na biały za wyjątkiem czarnego:
convert PLIK_0.png -fuzz 30% -fill white +opaque black PLIK_1.png
## zrób OCR
tesseract PLIK_1.png PLIK_1
## oczyść i dopisz wiersz do WYNIKI.txt
grep '[0-9]' PLIK_1.txt | \
awk '{gsub(/[ \t]/, ""); l = l ";" $0 }; END{print l}' > WYNIKI.txt

Jeżeli powyższy skrypt nazywa się png2txt.sh to teraz:

for x in *.png; do
   if [ -f $x ] ; then
        png2txt.sh $x
   fi
done

Raporty poniedziałkowe zaczęły być wysyłane od 11 maja 2020. Raporty codzienne pobrałem od początku lipca. W szczególności rysunki `poniedziałkowe' zawierają dane kumulowane. Kiedy na podstawie tych danych utworzyłem dane `tygodniowe' (jako różnica między stanem na bieżący tydzień minus stanem na poprzedni tydzień), to dla województwa świętokrzyskiego i raportu z 10 sierpnia rezultat okazał się ujemny i do tego ogromny. Licznik cofnęło mówiąc kolokwialnie.

Po wpisaniu do google testy+świętokrzyskie się okazało, że sprawa jest znana: przez 2 miesiące województwo świętokrzyskie podawało dane ewidentnie z sufitu raportując 100% wzrost tydzień/tydzień przy maksymalnym dla następnego województwa poziomie 15%... Jak po 2 miesiącach tej twórczości doszli do absurdalnej liczby ktoś się w MZ połapał i napisał (na Twitterze), że należy odjąć te lipne 230 tys...

No to tak z grubsza wygląda rzetelność danych n/t COVID19 w PL...

Pozyskane dane są: tutaj.

wtorek, 6 października 2020

Rektor UG Gwizdała (były)

Pracowałem na WZ/UG to mnie nie dziwi. Z mediów się dowiedziałem że rektor UG okazał się marnym plagiatorem, w sumie bez dorobku (brak publikacji zagranicznych). Marnym w tym sensie także, że szedł na rympał z gołą dupą czyli z ww. żenującym dorobkiem i do tego jeszcze przepisanym w części od kogoś (szczegóły tutaj.)

Szokujące może być (ale tylko dla kogoś kto nie wie jak środowisko nauki jest zdemoralizowane, szczególnie w PL), że powyższe nie przeszkadzało wcale Radzie Wydziału Zarządzania i osobiście dziekanowi ww wydziału (chyba podwładnemu zresztą ww. Gwizdały) promować tego pana. Więcej wszystkie te opłacane przez podatnika i stojące na straży jakości instytucje typu Rada Wydziału, pierdzilion recenzentów + Centralna Komisja d/s czegoś tam jakoś nie dały rady odsiać ziarna od plew, że dopiero donos do kancelarii Prezydenta zakończył wspaniałą karierę magnificencji.

sobota, 26 września 2020

Kaszebe Runda 2020 (pandemiczna)


Mijamy Betlejem

Zdecydowałem się na start na średnim dystansie. Trochę poruta, ale 200 km to dla mnie ciut za dużo zwłaszcza w tym roku. Na stronie jest 1164 zgłoszeń z czego 1010 opłaconych. Do tego: każdy wchodzący do biura zobowiązany jest do poddania się pomiarowi temperatury, zasłonięcia ust i nosa i utrzymania dystansu społecznego w wyznaczonych strefach bezpieczeństwa. Biorąc pod uwagę, że te pomiary i strefy mogą spowodować tłok i bałagan na starcie, zarządzam pobudkę na 4:00 rano.

No więc pojechaliśmy z Elką 4:30 do Kościerzyny żeby na spokojnie wystartować z pierwszą grupą o 7:00 (średni dystans czyli 125km oficjalnie ale chyba w rzeczywistości 5km mniej). Przyjechaliśmy na miejsce jakoś tak 5:30. Generalnie pustki. Z tą temperaturą to lipa była--nikt nie mierzy. Ale za to start kawałek od biura. Zanim się przebrałem, przyczepiłem numer, znalazłem start itd. to w sumie już było 20 minut przed siódmą.

Na starcie tłoku nie ma. Pojechałem jak planowałem z pierwszą grupą--nie wiem czy nas 15 było, no może. Po chwili już było tak na oko z 10. W tym składzie dojechaliśmy do pierwszego bufetu, bo ten wyścig ma taką oto specyfikę że są po drodze bufety gdzie można się pożywić regionalnymi specjałami. Tym razem z tych specjałów to był tylko smalec+ogórek konserwowy+jajecznica. Słabo ale, że nie jadłem śniadania ani nic nie wziąłem do jedzenia, to zjadłem trochę tej jajecznicy + kawałek chleba i ogórek (słony) Większość z naszej grupy na tym bufecie została (konsumując?) a dalej pojechaliśmy w trzech.

Następny przystanek też smalec+ogórek, tyle że kawy nie ma. Pojechaliśmy już dalej we dwóch. Trzeci bufet olaliśmy antycypując że tam też smalec. Zresztą tłum tam był, bo trasa już była wspólna od pewnego czasu z dystansem 55km. BTW dzięki temu zrobiło się trochę mniej nudniej, bo co chwila kogoś wyprzedzaliśmy. Ostatni bufet stajemy bo trzeba coś jednak zjeść. Znowu smalec, więc chwilę tylko tu stoimy i jedziemy dalej. Wreszcie za Betlejem (jest taka wieś na Kaszubach) dogoniliśmy gościa o porównywalnym z naszym tempie. Więc dalej we trzech ciagle mijając tych zmagających się z 55km.

Meta tak z zaskoczenia przyszła, bo patrząc na GPSa myślałem że jeszcze trochę mam do przejechania. A tu za zakrętem jakaś tablica świetlna się pojawiła. To chyba meta jest? W rzeczy samej...

Podium nazwijmy to (gdzie medale dawali) było w jeszcze jednym miejscu, a makaron jeszcze w innym dawali. Że widziałem kilkunastu ludzi, którzy wyglądali na takich co jadą 120km, a 3/4 trasy przejechaliśmy w dwóch/trzech to myślałem, że frekwencja katastrofa a ja tym samym osiągnąłem światowy wynik :-) ale nie -- okazało się, że w kategorii jechało prawie 200, a ja skończyłem na 21 miejscu. Całkiem nieźle anyway...

Po wyścigu i zjedzeniu makaronu wróciłem do domu DK20. Razem wyszło zatem 120+65 = 185km czyli prawie tyle co max-dystans w Kaszebe Runda, ale przyznam się że od Rębiechowa, to ledwo kręciłem pedałami i całe szczęście że od tego miejsca do domu to generalnie z góry jest. Kompletnie uszło ze mnie powietrze. Jednak dystans średni to była racjonalna decyzja...

Po długiej przerwie skorzystałem ze swojej kamery. Wyszło 90Gb filmu, który zamieniłem na 6Gb w niskiej rozdzielczości podzielone na trzy kawałki. Dodałem dane telemetryczne z pliku FIT za pomocą VirbEdit Garmina (próbowałem konkurencyjnego dashware, ale poległem na synchronizacji śladu z wideo; dla mnie to niewykonalne w dashware.)

No więc wyniki są tutaj (skopiowane ze strony organizatora i posortowane). Mój wynik konkretnie to: 27.67km/h i czas 4:20 Ślad GPS tutaj a film z trasy (w trzech częściach) tutaj tutaj oraz tutaj.

piątek, 25 września 2020

Zmarli/zakażeni i zmarli/1mln

W mediach podają albo to co było wczoraj, albo razem od początku świata. Zwłaszcza to łącznie od początku ma niewielką wartość, bo są kraje gdzie pandemia wygasa a są gdzie wręcz przeciwnie. Co z tego, że we Włoszech liczba ofiar to 35 tysięcy jak od miesięcy jest to 5--25 osób dziennie czyli tyle co PL, gdzie zmarło do tej pory kilkanaście razy mniej chorych. Podobnie w Chinach, gdzie cała afera się zaczęła, zmarło we wrześniu 15 osób. Media ponadto lubią duże liczby więc zwykle podają zmarłych, a nie zmarłych na 1mln ludności. Nawet w danych ECDC nie ma tego wskaźnika zresztą. Nie ma też wskaźnika zgony/przypadki, który mocno niefachowo i zgrubie pokazywałby skalę zagrożenia (im większa wartość tym więcej zarażonych umiera)

Taka filozofia stoi za skryptem załączonym niżej. Liczy on zmarłych/1mln ludności oraz współczynnik zgony/przypadki dla 14 ostatnich dni. Wartości przedstawia na wykresach: punktowym, histogramie oraz pudełkowym. W podziale na kontynenty i/lub kraje OECD/pozostałe. Ponieważ krajów na świecie jest ponad 200, to nie pokazuje danych dla krajów w których liczba zmarłych jest mniejsza od 100, co eliminuje małe kraje albo takie, w których liczba ofiar COVID19 też jest mała.

Wynik obok. Skrypt poniżej

#!/usr/bin/env Rscript
#
library("ggplot2")
library("dplyr")
#

## Space before/after \n otherwise \n is ignored
url <- "https://www.ecdc.europa.eu/en/publications-data/ \n download-todays-data-geographic-distribution-covid-19-cases-worldwide"
surl <- sprintf ("retrived %s from %s", tt, url)

## population/continent data:
c <- read.csv("ecdc_countries_names.csv", sep = ';',  header=T, na.string="NA" )

## COVID data
d <- read.csv("covid19_C.csv", sep = ';',  header=T, na.string="NA", 
   colClasses = c('factor', 'factor', 'factor', 'character', 'character', 'numeric', 'numeric'));

# today <- Sys.Date()
# rather newest date in data set:
today <- as.Date(last(d$date))
# 14 days ago
first.day <- today - 14
tt<- format(today, "%Y-%m-%d")
fd<- format(first.day, "%Y-%m-%d")

# select last row for every country
##t <- d %>% group_by(id) %>%  top_n(1, date) %>% as.data.frame
## top_n jest obsolete w nowym dplyr
t <- d %>% group_by(id) %>%  slice_tail(n=1) %>% as.data.frame

# cont. select id/totald columns, rename totald to alld
t <- t %>% select(id, totald) %>% rename(alld = totald) %>% as.data.frame
# join t to d:
d <- left_join(d, t, by = "id")
## extra column alld = number of deaths at `today'
##str(d)

# only countries with > 99 deaths:
d <- d %>% filter (alld > 99 ) %>% as.data.frame

## OECD membership data
o <- read.csv("OECD-list.csv", sep = ';',  header=T, na.string="NA" )

## Remove rows with NA in ID column (just to be sure)
## d <- d %>% filter(!is.na(id))

d$newc <- as.numeric(d$newc)
d$newd <- as.numeric(d$newd)

cat ("### Cleaning newc/newd: assign NA to negatives...\n")
d$newc[ (d$newc < 0) ] <- NA
d$newd[ (d$newd < 0) ] <- NA

## Filter last 14 days only 
d <- d %>% filter(as.Date(date, format="%Y-%m-%d") > fd) %>% as.data.frame
## Last day
last.obs <- last(d$date)
## Format lable firstDay--lastDay (for title):
last14 <- sprintf ("%s--%s", fd, last.obs)

## Sum-up cases and deaths
t <- d %>% group_by(id) %>% summarise( tc = sum(newc, na.rm=TRUE), td = sum(newd, na.rm=TRUE)) %>% as.data.frame 
## Add country populations
t <- left_join(t, c, by = "id")
## Add OECD membership
## if membership column is not NA = OECD member
t <- left_join(t, o, by = "id") 

t$access[ (t$access > 0) ] <- 'oecd'
t$access[ (is.na(t$access)) ] <- 'non-oecd'
str(t)

## Deaths/Cases ratio %%
t$totr <- t$td/t$tc * 100
## Deaths/1mln
t$tot1m <- t$td/t$popData2019 * 1000000

## PL row
dPL <- t[t$id=="PL",]
##str(dPL)
## Extract ratios for PL
totr.PL <- dPL$totr
totr.PL
tot1m.PL <- dPL$tot1m

## Set scales
pScale <- seq(0,max(t$totr, na.rm=T), by=1)
mScale <- seq(0,max(t$tot1m, na.rm=T), by=10)

## Graphs
## 1. deaths/cases ratio (dot-plot)
## color=as.factor(access)): draw OECD/nonOECD with separate colors
p1 <- ggplot(t, aes(x = reorder(country, totr) )) +
  ### One group/one color:
  ##geom_point(aes(y = totr, color='navyblue'), size=1) +
  ### Groups with different colors:
  geom_point(aes(y = totr, color=as.factor(access)), size=1) +
  xlab("country") +
  ylab("%") +
  ggtitle(sprintf("Covid19 death cases ratio (%s)", last14),
    subtitle="Only countries with 100 deaths and more | pale red line = PL level") +
  theme(axis.text = element_text(size = 4)) +
  theme(plot.title = element_text(hjust = 0.5)) +
  ##theme(legend.position="none") +
  scale_color_discrete(name = "Membership") +
  ## Indicate PL-level of  d/c ratio:
  geom_hline(yintercept = totr.PL, color="red", alpha=.25, size=1) +
  labs(caption=surl) +
  scale_y_continuous(breaks=pScale) +
  ##coord_flip(ylim = c(0, 15))
  coord_flip()

ggsave(plot=p1, file="totr_p14.png", width=10)

## 2. deaths/cases ratio (histogram)
p2 <- ggplot(t, aes(x = totr) ) + 
 geom_histogram(binwidth = 0.5, fill='navyblue', alpha=.5) +
 ###
 ylab("N") +
 xlab("%") +
 ggtitle(sprintf("Covid19 deaths/cases ratio (%s)", last14),
   subtitle="Only countries with 100 deaths and more | pale red line = PL level") +
 scale_x_continuous(breaks=pScale) +
 scale_y_continuous(breaks=seq(0, 40, by=2)) +
 geom_vline(xintercept = totr.PL, color="red", alpha=.25, size=1) +
 ##coord_cartesian(ylim = c(0, 30), xlim=c(0, 8))
 labs(caption=surl)

ggsave(plot=p2, file="totr_h14.png", width=10)

## 3. deaths/1m (dot-plot)
## color=as.factor(continent)): draw continents with separate colors
p3 <- ggplot(t, aes(x = reorder(country, tot1m) )) +
  geom_point(aes(y = tot1m, color=as.factor(continent)), size =1 ) +
  xlab("") +
  ylab("deaths") +
  ggtitle(sprintf("Covid19 deaths per 1 million (%s)", last14),
    subtitle="Only countries with 100 deaths and more | red pale line = PL level") +
  theme(axis.text = element_text(size = 6)) +
  geom_hline(yintercept = tot1m.PL, color="red", alpha=.25, size=1) +
  labs(caption=surl) +
  scale_color_discrete(name = "Continent") +
  scale_y_continuous(breaks=mScale) +
  coord_flip()

ggsave(plot=p3, file="totr_m14.png", height=10)

## 3. deaths/1m (box-plots for continents)
p4 <- ggplot(t, aes(x=as.factor(continent), y=tot1m, fill=as.factor(continent))) + 
 geom_boxplot() +
 ylab("deaths") +
 xlab("continent") +
 ggtitle(sprintf("Covid19 deaths per 1 million (%s)", last14),
  subtitle="Only countries with 100 deaths and more") +
 labs(caption=surl) +
 scale_y_continuous(breaks=mScale) +
 theme(axis.text = element_text(size = 6)) +
 theme(legend.position="none")

ggsave(plot=p4, file="totr_c14.png", width=10)

## Mean/Median/IQR per continent
ts <-  t %>% group_by(as.factor(continent)) %>% 
    summarise(Mean=mean(tot1m), Median=median(tot1m), Iqr=IQR(tot1m))
ts

Wartość zmarli/przypadki zależy ewidentnie od słynnej liczby testów. Taki Jemen wygląda, że wcale nie testuje więc ma mało przypadków. Z drugiej strony ci co dużo testują mają dużo przypadków, w tym słynne bezobjawowe więc też nie bardzo wiadomo co to oznacza w praktyce. Bo chyba się testuje po coś, a nie żeby testować dla samego testowania. Więc: dużo testów -- na mój niefachowy rozum -- to wczesne wykrycie, a mało to (w teorii przynajmniej) dużo nieujawnionych/nieizolowanych zarażonych, co powinno dawać w konsekwencji coraz większą/lawinowo większą liczbę przypadków, a co za tym idzie zgonów a tak nie jest.

Konkretyzując rysunek: Ameryka na czele liczby zgonów z medianą prawie 30/1mln, Europa poniżej 3,5/1mln a Azja coś koło 1,4/1mln (średnie odpowiednio 26,4/6,00/5,1.)

Przy okazji się okazało, że mam dplyra w wersji 0.8 co jest najnowszą wersją w moim Debianie. No i w tej wersji 0.8 nie ma funkcji slice_head() oraz slice_tail(), która jest mi teraz potrzebna. Z instalowaniem pakietów R w Linuksie to różnie bywa, ale znalazłem na stronie Faster package installs on Linux with Package Manager beta release coś takiego:

RStudio Package Manager 1.0.12 introduces beta support for the binary format of R packages. In this binary format, the repository's packages are already compiled and can be `installed' much faster -- `installing' the package amounts to just downloading the binary!

In a configured environment, users can access these packages using `install.packages' without any changes to their workflow. For example, if the user is using R 3.5 on Ubuntu 16 (Xenial):

> install.packages("dplyr", repos = "https://demo.rstudiopm.com/cran/__linux__/xenial/latest")

Zadziałało, ale (bo jest zawsze jakieś ale) powtórzenie powyższego na raspberry pi nie zadziała. Wszystko się instaluje tyle że architektury się nie zgadzają. Więc i tak musiałem wykonać

> install.packages("dplyr")
## sprawdzenie wersji pakietów
> sessionInfo()

Instalacja trwała 4 minuty i zakończyła się sukcesem.

czwartek, 17 września 2020

Jeszcze jeden skrypt do wizualizacji danych nt COVID19

Jeszcze jeden skrypt do rysowania danych nt COVID19:

#!/usr/bin/env Rscript
# Przekazywanie argumentów z wiersza poleceń
# np.: Rscript --vanilla c19.R --iso PL -clean
library("optparse")
#
library("ggplot2")
library("dplyr")
library("scales")
library("ggpubr")
#
# parametr wygładzania (loess)
spanV <- 0.25
# UWAGA: przed/po \n musi być odstęp inaczej nie działa
surl <- "https://www.ecdc.europa.eu/en/publications-data/ \n download-todays-data-geographic-distribution-covid-19-cases-worldwide"
c0 <- 'PL'

option_list <- list(
  make_option(c("-i", "--iso"), action="store", type="character", default=c0, help="country ISO2 code"),
  make_option(c("-c", "--clean"), action="store_true", default=T, help="extra clean data?")
); 
 
opt_parser <- OptionParser(option_list=option_list);
opt <- parse_args(opt_parser);

c0 <- opt$iso
dataClean <- opt$clean

# wczytanie danych
# date;id;country;newc;newd;totalc;totald
d <- read.csv("covid19_C.csv", sep = ';',  header=T, na.string="NA", 
   colClasses = c('factor', 'factor', 'factor', 'character', 'character', 'numeric', 'numeric'));

str(d)

d$newc <- as.numeric(d$newc)
d$newd <- as.numeric(d$newd)

## Dane zawierają wartości ujemne co jest bez sensu
## z opcją --clean te ujemne wartości są zamieniane na NA
if ( dataClean ) {
  cat ("### Cleaning newc/newd: assign NA to negatives...\n")
  d$newc[ (d$newc < 0) ] <- NA
  d$newd[ (d$newd < 0) ] <- NA
}

## Współczynniki zmarli/zakażeni
d$newr <- d$newd/d$newc * 100
d$totr <- d$totald/d$totalc * 100

## Wartości > 50% są zamieniane na NA (zwykle >50% wynika z błędnych danych)
if ( dataClean ) {
  cat ("### Cleaning newc/newd: assign NA to newr/totr higher than 50...\n")
  d$newr[ (d$newr > 50) ] <- NA
  d$totr[ (d$totr > 50) ] <- NA
}

## Pomiń obserwacje wcześniejsze niż 15/02
d <- d %>% filter(as.Date(date, format="%Y-%m-%d") > "2020-02-15") %>% as.data.frame

d0 <- d %>% filter (id == c0) %>% as.data.frame
t0 <- d0 %>% group_by(id) %>%  summarise(cc = sum(newc, na.rm=T), dd=sum(newd, na.rm=T))

lab0c <- toString(paste (sep=" = ", t0$id, t0$cc))
lab0d <- toString(paste (sep=" = ", t0$id, t0$dd))

## koniecznie dodać na.rm=T bo inaczej zwraca NA (jeżeli znajdzie NA)
maxCC <- max (d0$newc, na.rm=T)
maxDD <- max (d0$newd, na.rm=T)
maxRR <- max (d0$totr, na.rm=T)

last.obs <- last(d0$date)
first.date <- first(d0$date)
fstDay <- as.Date(first.date)
last.totr <- last(d0$totr)
max.newr <- max(d0$newr, na.rm=T)

## Przykład dodania 14 dni do daty
## srcDay <- as.Date(first.date) +14
## https://stackoverflow.com/questions/10322035/r-adding-days-to-a-date
srcDay <- as.Date(last.obs)

## Nazwa pliku wynikowego
## c19_ISO2_DATA.png, gdzie DATA jest datą ostatniej obserwacji
## np.: c19_SE_2020-09-16.png
c0o <- sprintf ("c19_%s_%s.png", c0, last.obs)

## Rysunek1: nowe przypadki
pc0 <- ggplot(d0, aes(x= as.Date(date, format="%Y-%m-%d"), y=newc)) + 
 geom_point(aes(group = id, color = id), size=.8) +
 geom_smooth(method="loess", se=F, span=spanV) +
 theme(plot.subtitle=element_text(size=8, hjust=0, color="black")) +
 scale_x_date( labels = date_format("%m/%d"), breaks = "2 weeks") +
 annotate("text", x = fstDay, y = 0.95 * maxCC, 
  label = sprintf("Total: %i cases", t0$cc), hjust = 0, vjust=0,
  alpha=0.3, color='steelblue', size=6) +
  xlab(label="") +
 ## Nie drukuj legendy
 theme(legend.position="none") +
 ggtitle(sprintf("%s: new confirmed cases (%s)", t0$id, last.obs))

## Rysunek2: nowe zgony
pd0 <- ggplot(d0, aes(x= as.Date(date, format="%Y-%m-%d"), y=newd)) + 
 geom_point(aes(group = id, color = id), size=.8) +
 geom_smooth(method="loess", se=F, span=spanV) +
 theme(plot.subtitle=element_text(size=8, hjust=0, color="black")) +
 annotate("text", x = fstDay, y = 0.95 * maxDD, label = sprintf("Total: %i deaths", t0$dd), hjust = 0, vjust=0,
  alpha=0.3, color='steelblue', size=6) +
 scale_x_date( labels = date_format("%m/%d"), breaks = "2 weeks") +
 xlab(label="") +
 theme(legend.position="none") +
 ggtitle(sprintf ("%s: deaths (%s)", t0$id, last.obs))

## Rysunek3: nowe zgony/przypadki *100%
pr0 <- ggplot(d0, aes(x= as.Date(date, format="%Y-%m-%d"), y=newr)) + 
 geom_point(aes(group = id, color = id), size=.8) +
 geom_smooth(method="loess", se=F, span=spanV) +
 theme(plot.subtitle=element_text(size=8, hjust=0, color="black")) +
 annotate("text", x = fstDay, y = 0.95 * max.newr, label = sprintf("Maximum: %.2f %%", max.newr), hjust = 0, vjust=0,
  alpha=0.3, color='steelblue', size=6) +
 scale_x_date( labels = date_format("%m/%d"), breaks = "2 weeks") +
 xlab(label="") +
 ylab(label="%") +
 theme(legend.position="none") +
 ggtitle(sprintf ("%s: deaths/cases %% (%s)", t0$id, last.obs) )

## Rysunek4: łączne zgony/przypadki *100%
prt0 <- ggplot(d0, aes(x= as.Date(date, format="%Y-%m-%d"), y=totr)) + 
 geom_point(aes(group = id, color = id), size=.8) +
 geom_smooth(method="loess", se=F, span=spanV) +
 theme(plot.subtitle=element_text(size=8, hjust=0, color="black")) +
 annotate("text", x = fstDay, y = 0.95 * maxRR, label = sprintf("Average: %.2f %%", last.totr), hjust = 0, vjust=0,
  alpha=0.3, color='steelblue', size=6) +
 scale_x_date( labels = date_format("%m/%d"), breaks = "2 weeks") +
 xlab(label="") +
 ylab(label="%") +
 theme(legend.position="none") +
 annotate("text", x = srcDay, y = 0, label = surl, hjust = 1, alpha=.3, size=3) +
 ggtitle(sprintf ("%s: total deaths/cases %% (%s)", t0$id, last.obs) )

p00 <- ggarrange(pc0,pd0, pr0, prt0, ncol=2, nrow=2)

ggsave(plot=p00, c0o, width=15)

Użycie:

Rscript --vanilla c19.R --iso PL

albo:

for i in 'AU' 'BR' 'IN' 'US' 'ES' 'SE' 'PL' 'DE' 'GB'; 
 Rscript --vanilla c19.R --iso $i
done

Przykładowy wynik dla Hiszpanii

Jak widać jakość danych jest katastrofalna: pojawiające się liczne zera to w rzeczywistości brak danych. Zwykle sobota/niedziela zero a potem sruuuu 30 tysięcy bo za trzy dni razem. Wszyscy są zmęczeni w tej Hiszpanii pandemią i nawet nie chce im się danych podsyłać do ECDC?

środa, 16 września 2020

Rosyjskie dane dotyczące urodzin i zgonów

Reuters donosi, że While Russia has confirmed the world's fourth largest tally of coronavirus cases, it has a relatively low death toll from the associated disease, COVID-19[...] But data released by the Rosstat State Statistics Service on Sept. 4 show there were 57,800 excess deaths between May and July, the peak of the outbreak. The figure was calculated by comparing fatalities over those three months in 2020 with the average number of May-July deaths between 2015 and 2019. The excess total is more than three times greater than the official May-July COVID-19 death toll of 15,955. Co mnie zmotywowało do poszukania danych źródłowych.

Okazało się że dość prosto jest je pobrać ze strony Urzędu Statystycznego Federacji Rosyjskiej (https://eng.gks.ru/ a konkretnie https://showdata.gks.ru/finder/) Ponieważ okazało się to aż tak proste, to pobrałem nie tylko zgony ale także urodzenia. W okresie 2015--2020 (miesięcznie).

Dane są w formacie XSLX. Kolumny to miesiące wiersze poszczególne regiony i ogółem Federacja. Eksportuję do CSV używając LibreOffice i wybieram wiersz z danymi dla całej federacji. W rezultacie mam trzy wiersze: rok-miesiąc, urodzenia i zgony. Obracam moim super skryptem do transpozycji plików CSV:

month;urodzenia;zgony
2015-01-01;149269.99;174722.99
2015-02-01;144591.99;156737
2015-03-01;160974.99;175809.99
...

Trzy wykresy liniowe przedstawiające dynamikę zgonów, urodzin i przyrostu naturalnego (czyli różnicy między urodzinami a zgonami)

library("dplyr")
library("ggplot2")
library("scales")
spanV <- 0.5

## przyrost naturalny
d <- read.csv("UZRT.csv", sep = ';', header=T, na.string="NA");
d$diff <- d$urodzenia - d$zgony

pz <- ggplot(d, aes(x= as.Date(month), y=zgony)) + 
 geom_line(color="steelblue", size=.6, alpha=.5) +
 geom_point(color="steelblue", size=.8) +
 ## Trend 
 geom_smooth(method="loess", se=F, span=spanV, color="red3") +
 ## Skala na osi X-ów
 scale_x_date( labels = date_format("%y/%m"), breaks = "4 months") +
 xlab(label="") +
 ylab(label="deaths") +
  geom_vline(xintercept = as.Date("2015-07-01"), alpha=.25, size=1) +
  geom_vline(xintercept = as.Date("2016-07-01"), alpha=.25, size=1) +
  geom_vline(xintercept = as.Date("2017-07-01"), alpha=.25, size=1)+
  geom_vline(xintercept = as.Date("2018-07-01"), alpha=.25, size=1) +
  geom_vline(xintercept = as.Date("2019-07-01"), alpha=.25, size=1) +
  geom_vline(xintercept = as.Date("2020-07-01"), alpha=.25, size=1) +
 labs(caption="https://showdata.gks.ru/finder/") +
 ggtitle("Russian Federation: Deaths (ths)",  
  subtitle= sprintf("red line: loess with span=%.2f ; gray vertical: july", spanV)) 

pu <- ggplot(d, aes(x= as.Date(month), y=urodzenia)) + 
 geom_line(color="steelblue", size=.6, alpha=.5) +
 geom_point(color="steelblue", size=.8) +
 geom_smooth(method="loess", se=F, span=spanV, color="red3") +
 scale_x_date( labels = date_format("%y/%m"), breaks = "4 months") +
 xlab(label="") +
 ylab(label="births") +
 labs(caption="https://showdata.gks.ru/finder/") +
  geom_vline(xintercept = as.Date("2015-08-01"), alpha=.25, size=1) +
  geom_vline(xintercept = as.Date("2016-08-01"), alpha=.25, size=1) +
  geom_vline(xintercept = as.Date("2017-08-01"), alpha=.25, size=1)+
  geom_vline(xintercept = as.Date("2018-08-01"), alpha=.25, size=1) +
  geom_vline(xintercept = as.Date("2019-08-01"), alpha=.25, size=1) +
  geom_vline(xintercept = as.Date("2020-08-01"), alpha=.25, size=1) +
 ggtitle("Russian Federation: Births (ths)",
  subtitle= sprintf("red line: loess with span=%.2f ; gray vertical: august", spanV)) 

pp <- ggplot(d, aes(x= as.Date(month), y=diff)) + 
 geom_line(color="steelblue", size=.6, alpha=.5) +
 geom_point(color="steelblue", size=.8) +
 geom_smooth(method="loess", se=F, span=spanV, color="red3") +
 scale_x_date( labels = date_format("%y/%m"), breaks = "4 months") +
  geom_vline(xintercept = as.Date("2015-05-01"), alpha=.25, size=1) +
  geom_vline(xintercept = as.Date("2016-05-01"), alpha=.25, size=1) +
  geom_vline(xintercept = as.Date("2017-05-01"), alpha=.25, size=1)+
  geom_vline(xintercept = as.Date("2018-05-01"), alpha=.25, size=1) +
  geom_vline(xintercept = as.Date("2019-05-01"), alpha=.25, size=1) +
  geom_vline(xintercept = as.Date("2020-05-01"), alpha=.25, size=1) +
 xlab(label="") +
 ylab(label="balance") +
 labs(caption="https://showdata.gks.ru/finder/") +
 ggtitle("Russian Federation: natural balance (ths)", 
  subtitle= sprintf("red line: loess with span=%.2f ; gray vertical: may", spanV))

Wykres liniowy dla miesięcy jednoimiennych (MJ):

library(ggplot2)
library(dplyr)
library(tidyr)
library(scales)
options(dplyr.print_max = 1e9)
size0 <- 1.2
size1 <- 0.6
size2 <- 0.6

tt <- read.csv("UZRT.csv", sep = ';',  header=T, na.string="NA");
tt$rok <- substr(tt$month,  1, 4)

tt$yyyymmdd <- as.Date(sprintf ("%s-%s", substr(tt$month,  6, 7),
substr(tt$month,  9, 10) ), format="%m-%d")

## Obliczenie przyrostu naturalnego
tt$diff <- tt$urodzenia - tt$zgony

## Obliczenie 
tt.yy2020 <- tt %>% filter(rok==2020) %>% as.data.frame
tt.yy2019 <- tt %>% filter(rok==2019) %>% as.data.frame
tt.yy2018 <- tt %>% filter(rok==2018) %>% as.data.frame
tt.yy2017 <- tt %>% filter(rok==2017) %>% as.data.frame
tt.yy2016 <- tt %>% filter(rok==2016) %>% as.data.frame
tt.yy2015 <- tt %>% filter(rok==2015) %>% as.data.frame

pf <- ggplot() +
    ggtitle("Russian Federation: births 2015--2020") +
    geom_line( data = tt.yy2020, mapping = aes(x=yyyymmdd, y = urodzenia,  colour = '2020'), alpha=.5, size=size0) +
    geom_point( data = tt.yy2020, mapping = aes(x=yyyymmdd, y = urodzenia,  colour = '2020'), alpha=.5, size=size0) +
    geom_line( data = tt.yy2019, mapping = aes(x=yyyymmdd, y = urodzenia,  colour = '2019'), alpha=.5, size=size1) +
    geom_point( data = tt.yy2019, mapping = aes(x=yyyymmdd, y = urodzenia,  colour = '2019'), alpha=.5, size=size0) +
    geom_line( data = tt.yy2018, mapping = aes(x=yyyymmdd, y = urodzenia,  colour = '2018'), alpha=.5, size=size1) +
    geom_point( data = tt.yy2018, mapping = aes(x=yyyymmdd, y = urodzenia,  colour = '2018'), alpha=.5, size=size1) +
    geom_line( data = tt.yy2017, mapping = aes(x=yyyymmdd, y = urodzenia,  colour = '2017'), alpha=.5, size=size1) +
    geom_point( data = tt.yy2017, mapping = aes(x=yyyymmdd, y = urodzenia,  colour = '2017'), alpha=.5, size=size1) +
    ###
    geom_line( data = tt.yy2016, mapping = aes(x=yyyymmdd, y = urodzenia,  colour = '2016'), alpha=.5, size=size1) +
    geom_point( data = tt.yy2016, mapping = aes(x=yyyymmdd, y = urodzenia,  colour = '2016'), alpha=.5, size=size1) +
    geom_line( data = tt.yy2015, mapping = aes(x=yyyymmdd, y = urodzenia,  colour = '2015'), alpha=.5, size=size2) +
    geom_point( data = tt.yy2015, mapping = aes(x=yyyymmdd, y = urodzenia,  colour = '2015'), alpha=.5, size=size1) +
    scale_x_date( labels = date_format("%y/%m"), breaks = "2 months") +
    labs(caption="https://showdata.gks.ru/finder/") +
    ylab(label="ths") +
    xlab(label="") +
    labs(colour = "") +
    theme(legend.position="top") +
    theme(legend.text=element_text(size=10));

qf <- ggplot() +
    ggtitle("Russian Federation: deaths 2015-2020") +
    geom_line( data = tt.yy2020, mapping = aes(x=yyyymmdd, y = zgony,  colour = '2020'), alpha=.5, size=size0) +
    geom_point( data = tt.yy2020, mapping = aes(x=yyyymmdd, y = zgony,  colour = '2020'), alpha=.5, size=size0) +
    geom_line( data = tt.yy2019, mapping = aes(x=yyyymmdd, y = zgony,  colour = '2019'), alpha=.5, size=size1) +
    geom_point( data = tt.yy2019, mapping = aes(x=yyyymmdd, y = zgony,  colour = '2019'), alpha=.5, size=size0) +
    geom_line( data = tt.yy2018, mapping = aes(x=yyyymmdd, y = zgony,  colour = '2018'), alpha=.5, size=size1) +
    geom_point( data = tt.yy2018, mapping = aes(x=yyyymmdd, y = zgony,  colour = '2018'), alpha=.5, size=size1) +
    geom_line( data = tt.yy2017, mapping = aes(x=yyyymmdd, y = zgony,  colour = '2017'), alpha=.5, size=size1) +
    geom_point( data = tt.yy2017, mapping = aes(x=yyyymmdd, y = zgony,  colour = '2017'), alpha=.5, size=size1) +
    ###
    geom_line( data = tt.yy2016, mapping = aes(x=yyyymmdd, y = zgony,  colour = '2016'), alpha=.5, size=size1) +
    geom_point( data = tt.yy2016, mapping = aes(x=yyyymmdd, y = zgony,  colour = '2016'), alpha=.5, size=size1) +
    geom_line( data = tt.yy2015, mapping = aes(x=yyyymmdd, y = zgony,  colour = '2015'), alpha=.5, size=size2) +
    geom_point( data = tt.yy2015, mapping = aes(x=yyyymmdd, y = zgony,  colour = '2015'), alpha=.5, size=size2) +
    scale_x_date( labels = date_format("%y/%m"), breaks = "2 months") +
    labs(caption="https://showdata.gks.ru/finder/") +
    ylab(label="ths") +
    xlab(label="") +
    labs(colour = "") +
    theme(legend.position="top") +
    theme(legend.text=element_text(size=10));

qf <- ggplot() +
    ggtitle("Russian Federation: natural balance 2015-2020") +
    geom_line( data = tt.yy2020, mapping = aes(x=yyyymmdd, y = diff,  colour = '2020'), alpha=.5, size=size0) +
    geom_point( data = tt.yy2020, mapping = aes(x=yyyymmdd, y = diff,  colour = '2020'), alpha=.5, size=size0) +
    geom_line( data = tt.yy2019, mapping = aes(x=yyyymmdd, y = diff,  colour = '2019'), alpha=.5, size=size1) +
    geom_point( data = tt.yy2019, mapping = aes(x=yyyymmdd, y = diff,  colour = '2019'), alpha=.5, size=size0) +
    geom_line( data = tt.yy2018, mapping = aes(x=yyyymmdd, y = diff,  colour = '2018'), alpha=.5, size=size1) +
    geom_point( data = tt.yy2018, mapping = aes(x=yyyymmdd, y = diff,  colour = '2018'), alpha=.5, size=size1) +
    geom_line( data = tt.yy2017, mapping = aes(x=yyyymmdd, y = diff,  colour = '2017'), alpha=.5, size=size1) +
    geom_point( data = tt.yy2017, mapping = aes(x=yyyymmdd, y = diff,  colour = '2017'), alpha=.5, size=size1) +
    ###
    geom_line( data = tt.yy2016, mapping = aes(x=yyyymmdd, y = diff,  colour = '2016'), alpha=.5, size=size1) +
    geom_point( data = tt.yy2016, mapping = aes(x=yyyymmdd, y = diff,  colour = '2016'), alpha=.5, size=size1) +
    geom_line( data = tt.yy2015, mapping = aes(x=yyyymmdd, y = diff,  colour = '2015'), alpha=.5, size=size2) +
    geom_point( data = tt.yy2015, mapping = aes(x=yyyymmdd, y = diff,  colour = '2015'), alpha=.5, size=size2) +
    scale_x_date( labels = date_format("%y/%m"), breaks = "2 months") +
    labs(caption="https://showdata.gks.ru/finder/") +
    ylab(label="ths") +
    xlab(label="") +
    labs(colour = "") +
    theme(legend.position="top") +
    theme(legend.text=element_text(size=10));

Wreszcie wykres kombinowany liniowo-słupkowy dla miesięcy jednoimiennych (MJ). Linie to 1) średnia liczby zgonów w latach 2015--2020, 2) średnia liczba zgonów w latach 2015--2020 plus/minus odchylenie standardowe liczby zgonów (dla MJ); 3) liczba zgonów w roku 2020. Wykres słupkowy to różnica między liczbą zgonów w roku 2020 a maksymalną liczbą zgonów w latach 2015-2019 ORAZ liczba zgonów z powodu COVID19 (z bazy ECDC). Ten skrypt korzysta z danych z pliku covid_ru.csv, który powstał przez zagregowanie danych dziennych dostępnych ze strony ECDC (dla Federacji Rosyjskiej) oraz policzenie średnich/odchyleń dla jednoimiennych miesięcy z danych w pliku UZRT.csv

library(ggplot2)
library(dplyr)
library(tidyr)
library(scales)
options(dplyr.print_max = 1e9)

#
tt <- read.csv("covid_ru.csv", sep = ';',  header=T, na.string="NA");
tt$diff <- tt$urodzenia - tt$zgony

cols <- c("mean19"="navyblue","mean+sd19"="#3591d1", "mean-sd19"="#3591d1",
  "deaths20"='brown4', "diff19"="red", "c19d"='cyan')

pf <- ggplot(data=tt) +
    ggtitle("Russian Federation: deaths 2015--2020", 
             subtitle='mean19/sd19: mean/sd 2015--19; diff19: 2020 - max(2015-019); deaths20: deaths 2020; c19d: C19 deaths') +
    geom_line(mapping = aes(x=yyyymmdd, y = mean,  colour = 'mean19'), alpha=.5, size=.8) +
    geom_line(mapping = aes(x=yyyymmdd, y = mean + sd,  colour = 'mean+sd19'), alpha=.5, size=.8) +
    geom_line(mapping = aes(x=yyyymmdd, y = mean - sd,  colour = 'mean-sd19'), alpha=.5, size=.8) +
    geom_text(mapping = aes(x=yyyymmdd, label=difflabel, y=diff), vjust=-1.0, size=3 ) +
    ##geom_line(mapping = aes(x=yyyymmdd, y = c19d,  colour = 'c19d'), alpha=.5, size=1.2) +
    labs(caption = "Source: https://showdata.gks.ru/finder/; https://www.ecdc.europa.eu/en/covid-19-pandemic") +
    ##
    geom_bar(mapping = aes(x=yyyymmdd, y = diff, fill = 'diff19' ), color='brown', stat="identity", alpha=.25) +
    geom_bar(mapping = aes(x=yyyymmdd, y = c19d, fill = 'c19d' ), color ='navyblue', stat="identity", alpha=.25) +
    ###
    geom_line(mapping = aes(x=yyyymmdd, y = deaths,  colour = 'deaths20'), alpha=.5, size=1.2) +
    scale_x_date( labels = date_format("%y/%m"), breaks = "2 months") +
    ylab(label="ths") +
    xlab(label="") +
    ##labs(colour = "Legend") +
    scale_colour_manual(name="Lines: ", values=cols) +
    scale_fill_manual(name="Bars: ", values=cols) +
    theme(legend.position="right") +
    theme(legend.text=element_text(size=12));

Skrypt pomocniczy liczący średnie 2015--2019 dla miesięcy jednoimiennych itp rzeczy

library(dplyr)
library(tidyr)
## Agreguje zgodny wg miesięcy jednoimiennych (MJ) dla lat 2015-19
## drukuje średnią/sd/max dla MJ
## Drukuje z2020 - max(z2015--2019)

tt <- read.csv("UZRT.csv", sep = ';',  header=T, na.string="NA");

tt$rok <- substr(tt$month,  1, 4)
tt$yyyymmdd <- as.Date(sprintf ("%s-%s", substr(tt$month,  6, 7), substr(tt$month,  9, 10) ), format="%m-%d")

tt$diff <- tt$urodzenia - tt$zgony

tt.yy2020 <- tt %>% filter(rok==2020) %>% as.data.frame
tt.yy2019 <- tt %>% filter(rok==2019) %>% as.data.frame
tt.yy2018 <- tt %>% filter(rok==2018) %>% as.data.frame
tt.yy2017 <- tt %>% filter(rok==2017) %>% as.data.frame
tt.yy2016 <- tt %>% filter(rok==2016) %>% as.data.frame
tt.yy2015 <- tt %>% filter(rok==2015) %>% as.data.frame

## max zgony
tt.yy2020$max.so.far <-  pmax (tt.yy2019$zgony, tt.yy2018$zgony,
  tt.yy2017$zgony, tt.yy2016$zgony, tt.yy2015$zgony)

## średnia
tt.yy2020$mean <-  (tt.yy2019$zgony + tt.yy2018$zgony
  + tt.yy2017$zgony + tt.yy2016$zgony + tt.yy2015$zgony)/5

## odchylenie std:
tt.yy2020$sqmean <-  (tt.yy2019$zgony^2 + tt.yy2018$zgony^2 +
   tt.yy2017$zgony^2 + tt.yy2016$zgony^2 + tt.yy2015$zgony^2)/5
tt.yy2020$sq <- sqrt(tt.yy2020$sqmean - tt.yy2020$mean^2)

tt.yy2020$diff20 <- tt.yy2020$zgony - tt.yy2020$max.so.far

sprintf ("%s;%.2f;%.2f;%.2f;%.2f", 
    tt.yy2020$yyyymmdd, tt.yy2020$diff20, tt.yy2020$sqmean, tt.yy2020$sq, tt.yy2020$max.so.far )