Autorem jest studentka pierwszego roku na Akademii Górniczo-Hutniczej im. Stanisława Staszica w Krakowie
Wydział: Geodezji Górniczej i Inżynierii Środowiska
Kierunek: Geoinformacja
E-mail: pmajdak@student.agh.edu.pl
Poniżej zostały wypisane wszystkie pakiety potrzebne do wykonania tego projektu:
library(tidyverse)
library(openxlsx)
library(leaflet)
library(janitor)
library(worldmet)
library(corrplot)
library(Hmisc)
library(caret)
library(parallel)
library(doParallel)
library(lubridate)
library(ggplot2)
library(ggpmisc)
library(rmarkdown)
library(knitr)Stworzono mapkę interaktywną aktywnych stacji w pakiecie Leaflet w oparciu o wymienione poniżej warunki. Stacje mają być/mieć:
Poniższy kod przedstawia sposób pobrania i wczytania metadanych stacji. Jako, że RStudio nie obsługuje polskich znaków, usunięto je z nazw kolumn. Wyselekcjonowano stacje kontenerowe i odrzucono te o niepoprawnej lokalizacji geograficznej. Wybrano kolory, jakie będzie miała legenda mapy.
download.file("http://powietrze.gios.gov.pl/pjp/archives/downloadFile/305", destfile="metadane.zip", mode = "wb", method = "wininet")
gios_inv <- read.xlsx(xlsxFile = unzip("metadane.zip"))
colnames(gios_inv)[c(7,13)] <- c("Data.zamkniecia", "Miejscowosc")
gios_inv$Data.uruchomienia <- convertToDate(gios_inv$Data.uruchomienia, format="%Y-%m-%d")
gios_inv$Data.zamkniecia <- convertToDate(gios_inv$Data.zamkniecia, format="%Y-%m-%d")
colnames(gios_inv)[15:16] <- c("Szerokosc", "Dlugosc")
gios_inv %>%
filter(Szerokosc != -999 & Dlugosc != -999 & Rodzaj.stacji == "kontenerowa stacjonarna") -> gios_inv
desc <- paste(paste(
gios_inv$Kod.stacji,
paste("Miejscowość:", gios_inv$Miejscowosc),
paste("Data uruchomienia:", gios_inv$Data.uruchomienia),
paste("Data zamknięcia:", gios_inv$Data.zamkniecia),
paste("Typ stacji:", gios_inv$Typ.stacji),
paste("Typ obszaru:", gios_inv$Typ.obszaru),
paste("Współrzędne:", gios_inv$Dlugosc,"E, ", gios_inv$Szerokosc,"N"),
sep = "<br/>"
))
pal <- colorFactor(c("blue", "red", "darkgreen"), domain = c("tło", "komunikacyjna", "przemysłowa"))
## Tworzenie mapki
lt <- leaflet() %>%
addTiles() %>%
addCircleMarkers(data=gios_inv,
lng= ~ Dlugosc,
lat= ~ Szerokosc,
popup = desc,
fillOpacity = 1,
radius = 3,
color = ~ pal(Typ.stacji)) %>%
addLegend('bottomright', colors = c("green", "red", "blue"), labels = c("tło", "przemysłowa", "komunikacyjna"),
opacity = 1)
ltWybrano stację znajdującą się nad Morzem Bałtyckim, dokładniej mówiąc – nad Zatoką Gdańską, w mieście Gdańsk. Stacja ma kod PmGdaPoWie01. Za pomocą poniższego kodu sprawdzono jej metadane:
| Nr | Kod.stacji | Kod.międzynarodowy | Nazwa.stacji | Stary.Kod.stacji | Data.uruchomienia | Data.zamkniecia | Status | Typ.stacji | Typ.obszaru | Rodzaj.stacji | Województwo | Miejscowosc | Ulica | Szerokosc | Dlugosc |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 744 | PmGdaPoWie01 | PL0045A | AM1 Gdańsk Śródmieście | Pm.a01a | 1996-10-01 | NA | aktywny | tło | miejski | kontenerowa stacjonarna | POMORSKIE | Gdańsk | ul. Powstańców Warszawskich | 54.35334 | 18.63528 |
Do pobrania całej bazy danych (lata 2000-2018) i jej przygotowania wykorzystano poniższe funkcje. Najpierw pobrano linki ze strony GIOŚ, w których znajdują się pliki z poszczególnymi latami danych:
linki <- data.frame(link = c(paste0("http://powietrze.gios.gov.pl/pjp/archives/downloadFile/",
c(223, 224, 225, 226, 202, 203, 227, 228, 229, 230,
231, 232, 233, 234, 302, 236, 242, 262, 303))),
rok = 2000:2018) %>%
mutate_all(as.character)Pierwsza funkcja pobiera archiwum danych z portalu GIOŚ, rozpakowuje je do wskazanego folderu (argument rok) oraz zwraca wektor zawierający wykaz plików znajdujących się w danym archiwum.
gios_download <- function(url, rok = "2018", ...) {
if (!is.character(url) | !is.character(rok)) {
stopifnot("Wartości argumentów: `url` i `rok` muszą być typu character") }
if (length(url) != 1 | length(rok) != 1) {
stopifnot("Argumenty: `url` i `rok` przyjmują tylko pojedyncze wartości") }
zip <- paste0(rok,".zip")
download.file(url, zip, quiet = T, mode = "wb", method = "wininet")
unzip(zipfile = zip, exdir = rok)
file.remove(zip)
pliki <- dir(paste0(rok, "/"))
pliki <- pliki[!str_detect(pliki, "Jony")]
pliki <- pliki[!str_detect(pliki, "epozycj")]
return(pliki)
}Funkcja została przetestowana przez pobranie danych dla roku 2018
pliki_2018 <- gios_download(url = "http://powietrze.gios.gov.pl/pjp/archives/downloadFile/303", rok = "2018")
###Powyższą funkcję można wykorzystać do pobrania całej bazy naraz.
pliki_all <- map2(.x = as.list(linki[,1]),
.y = as.list(linki[,2]),
.f = gios_download,
mode = "wb",
method = "wininet")Kolejna funkcja zakłada, że pozyskane wcześniej dane znajdują się w folderze roboczym. Pliki dla każdego roku znajdują się w osobnym folderze, którego nazwą jest kolejny rok.
Najpierw wczytano pliki do R, a następnie użyto drugiej funkcji, aby dane mogły być gotowe do przetwarzania. Wynikiem jest obiekt typu tibble.
names(pliki_all) <- paste0("R",linki[,2])
gios_read <- function(nazwa, czas_mu = "24g", sciezka= "") {
lok <- getwd()
setwd(dir = paste0(sciezka,substr(nazwa, 1, 4), "/"))
if(str_sub(nazwa, 1,4) %in% c(2016, 2017, 2018)) {
startRow = 2 ; end_row = 4
} else if(str_sub(nazwa, 1,4) %in% c(2000:2015)) {
startRow = 1 ; end_row = 2
}
dane <- openxlsx::read.xlsx(nazwa, startRow = startRow, colNames = T)
colnames(dane)[1] <- "date"
sub = dane[1,2]
dane <- map_df(.x = dane[-c(1:end_row),], .f = str_replace, pattern = ",", replacement = ".")
if (czas_mu == "1g") {
dane <- dane %>%
mutate(date = as.numeric(date),
date = janitor::excel_numeric_to_date(date, include_time = T,
date_system = "modern",
tz = "UTC") - 3600,
date = lubridate::round_date(date, "hour"))
} else if(czas_mu == "24g") {
dane$date <- as.Date(as.numeric(dane$date), origin = "1899-12-30", tz = "UTC")
}
dane <- dane %>%
mutate_if(is.character,
as.double) %>%
gather(key = "kod",
value = "obs", -date) %>%
mutate(sub = sub,
obs = round(obs, 6)) %>%
.[,c("kod", "sub", "date", "obs")]
setwd(lok)
return(dane)
}Wykonano test dla 1-godzinnych danych, które będą potrzebne przy dalszej analizie. Wyszukano plików z pomiarami PM10 co godzinę, a następnie wczytano ich zawartość do jednej zmiennej.
list_PM10_1h <- map(.x = pliki_all,
.f = ~ .[str_detect(., pattern = "PM10_1g")]) %>%
flatten_chr()
PM10_1h <- map_df(.x = list_PM10_1h, .f = gios_read, czas_mu = "1g")Użyto funkcji filter w celu znalezienia obserwacji pasujących do wybranej stacji. Jako że miała ona kiedyś inną nazwę trzeba uwzględnić też tą już nieobowiązującą:
Za pomocą pakietu worldmet znalezino stację meterologiczną znajdującą się najbliżej wybranej stacji jakości powietrza. Następnie pobrano dane meterologiczne z bazy ISD NOAA dla lat z przedziału 2005-2018. W tym przypadku najbliższa stacja znajdowała się w pobliżu Portu Lotniczego Gdańsk im. Lecha Wałęsy, oddalona od stacji jakości powietrza o 11.3 km.
## # A tibble: 10 x 15
## USAF WBAN STATION CTRY ST CALL latitude longitude `ELEV(M)` BEGIN
## <chr> <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl> <date>
## 1 1215~ 99999 LECHA ~ PL <NA> EPGD 54.4 18.5 149 1931-01-01
## 2 1214~ 99999 PRUSZC~ PL <NA> EPPR 54.2 18.7 6.4 2002-06-01
## 3 1215~ 99999 GDANSK~ PL <NA> <NA> 54.3 18.9 9 1982-09-01
## 4 1214~ 99999 OKSYWIE PL <NA> <NA> 54.6 18.5 70 2002-06-01
## 5 1213~ 99999 HEL PL <NA> <NA> 54.6 18.8 3 1955-07-01
## 6 1237~ 99999 MALBORK PL <NA> EPMB 54.0 19.1 4.9 2005-01-01
## 7 1213~ 99999 LEBUNIA PL <NA> EPCE 54.4 17.8 151 2002-06-01
## 8 1216~ 99999 ELBLAG~ PL <NA> EPEL 54.2 19.5 43 1933-01-02
## 9 1212~ 99999 LEBORK PL <NA> <NA> 54.6 17.8 41 1953-04-01
## 10 1212~ 99999 LEBA PL <NA> <NA> 54.8 17.5 6 1973-01-01
## # ... with 5 more variables: END <date>, code <chr>, longR <dbl>, latR <dbl>,
## # dist <dbl>
Kolejnym krokiem było połączenie pomiarów meteorologicznych z danymi o stężeniach PM10 dla wybranych lat. Aby czas w obu danych się zgadzał trzeba dodać godzinę w danych meteorologicznych.
gdansk_met <- importNOAA(code="121500-99999", year=seq(2005,2018, by=1))
gdansk_met$date <- gdansk_met$date + 3600
dane <- inner_join(gdansk_met, gdansk_PM10)Wykorzystano las losowy (random forest) w celu zaprogramowania wartości stężenia pyłu PM10 w zależności od warunków meteorologicznych na danym obszarze.
Pierwszym krokiem było sprawdzenie, czy nie ma braków danych, gdyż w tej metodzie wymagane jest posiadanie kompletnych wierszy obserwacji oraz zmiennych, które sa istotne dla tworzonego modelu. Jako, że kolumny visibility oraz atmos_pres posiadały znaczne braki danych, zdecydowano się je usunąć. Następnie za pomocą funkcji complete.cases() wybrano tylko te wiersze, które mają kompletne dane.
Utworzono wykres, który pokazuje, ile pomiarów było w poszczególnych latach od 2015 do 2018.
dane_rf$years <- format(dane_rf$date, "%Y")
dane_rf %>%
group_by(years) %>%
summarise(liczba_pomiarow = n()) -> dane_rf_summary
wyk1 <-ggplot(dane_rf_summary, aes(x=years, y=liczba_pomiarow, color=liczba_pomiarow)) +
geom_point(size=3, show.legend = FALSE) +
theme_minimal() +
coord_flip()
wyk1Jak widać, ilość rekordów pomiarowych wacha się od około 3700 do 5200. Podzielono zbiór na dwa podzbiory:
dane_rf %>%
filter(years>2015 & years<2018) %>%
select(-date, -years) -> dane_rf_train
dane_rf %>%
filter(years==2018) %>%
select(-years) -> dane_rf_testMacierz korelacji liniowej Pearsona służy do pokazania zależności między parametrami. Współczynnik korelacji może przybierać wartości w zakresie od -1 do 1. W celu zwiększenia czytelności zmieniono nazwy kolumn na polskie odpowiedniki.
dane_rfc <- dane_rf_train
colnames(dane_rfc) <- c("Kierunek wiatru","Prędkość wiatru","Podstawa chmur",
"Temperatura", "Punkt rosy",
"Wilgotność względna", "Stężenie PM10")
col <- colorRampPalette(c("#BB4444", "#EE9988", "#FFFFFF", "#77AADD", "#4477AA"))
corr <- rcorr(as.matrix(dane_rfc))
corr_r <- corr$r
corr_p <- corr$P
corrplot(corr_r, method = "color", col = col(200),
type = "upper", order = "hclust", addCoef.col = "black", diag = FALSE,
tl.col = "black", tl.srt = 45, p.mat = corr_p, sig.level = 0.05)Można zauważyć, że wszystkie korelacje między stężeniem pyłu PM10 a innymi zmiennymi są statystycznie istotne. Jednak żaden związek się nie wyróżnia, wszystkie są na umiarkowanym poziomie. Najwyższa korelacja pułu PM10 jest z podstawą chmur.
Do uruchomienia lasu losowego wykorzystano pakiet caret. Ustawiono metodę tzw. “kroswalidacji”, czyli samo-testowania się modelu w trakcie jego budowy. W modelu wykorzystano wszystkie zmienne oprócz tej objaśnianej - PM10. Uruchomiono także klaster obliczeniowy z wszystkich dostępnych rdzeni.
cross.walid <- trainControl(method = "cv", number = 5, allowParallel = T, returnResamp = 'all')
tunegrid <- expand.grid(.mtry = c(1:6))
cluster <- makeCluster(detectCores() - 1)
registerDoParallel(cluster)
model_rf <- train(obs ~ .,
data = dane_rf_train,
method = "rf",
replace = T,
ntree = 100,
metric = 'RMSE',
tuneGrid = tunegrid,
trControl = cross.walid)
stopCluster(cluster)
registerDoSEQ()
model_rf## Random Forest
##
## 9804 samples
## 6 predictor
##
## No pre-processing
## Resampling: Cross-Validated (5 fold)
## Summary of sample sizes: 7843, 7843, 7844, 7843, 7843
## Resampling results across tuning parameters:
##
## mtry RMSE Rsquared MAE
## 1 12.67305 0.5689482 8.312695
## 2 12.32689 0.5857032 7.978540
## 3 12.32706 0.5853855 7.977057
## 4 12.42131 0.5793072 8.005592
## 5 12.43681 0.5787013 8.031056
## 6 12.52416 0.5730743 8.100871
##
## RMSE was used to select the optimal model using the smallest value.
## The final value used for the model was mtry = 2.
Za pomocą poniższego kodu sprawdzono, jak zachowuje się model na danych, które nie były wykorzystane do jego uczenia. Wykorzystano go do oszacowania stężenia pyłu PM10 na zbiorze testowym.
Utworzono dwa wykresy obrazujące zależność wartości stężenia PM10 zaobserwowanego na stacji jakości powietrza od zamodelowanej jego wartości.
Najpierw sporządzono wykres rozrzutu, który pokazuje, jak mocno odchylone są wyniki prognoz od pomiarów stężeń PM10.
wyk2 <-ggplot(data=dane_rf_test, aes(x=obs, y=mod))+
geom_point(alpha=0.6, color="darkred") +
geom_smooth(method="lm", formula = y~x-1) +
geom_abline(intercept=0, col="grey", linetype="dashed", size=1) +
stat_poly_eq(aes(label = paste(..eq.label.., ..rr.label.., sep = "~~~")),
label.x.npc = "right", label.y.npc = 0,
formula = y~x-1, parse = TRUE, size = 5) +
xlab("Stężenie PM10 [µg/m3] (Obserwacja)") +
ylab("Stężenie PM10 [µg/m3] (Prognoza)") +
ggtitle("Wykres rozrzutu prognozy od obserwacji dla stężeń PM10\nna stacji w Gdańsku w roku 2018") +
theme_minimal()
wyk2Przerywana linia obrazuje model idealny, gdzie prognoza jest równa pomiarowi, a ciągła pokazuje linię trendu uzyskaną z badanych danych, o równaniu widocznym na wykresie.
Można łatwo zauważyć, że prognoza jest zaniżona w stosunku do pomiarów stężenia PM10 na stacji w Gdańsku, co jest szczególnie widoczne w przypadku najwyższych stężeń.
Ostatnim wykresem jest wykres trendu, który pozwala na ogólną ocenę zgodności prognoz z obserwacjami stężeń PM10.
# Porównanie serii danych w danym fragmencie roku
wyk3 <- ggplot(data = dane_rf_test) +
geom_path(aes(x = date, y = obs, color = "Obserwacje"), size = 1.5) +
geom_path(aes(x = date, y = mod, color = "Model"), size = 1.5, alpha=0.6) +
scale_x_datetime(limits = ymd_h(c("2018-01-01 00", "2018-01-31 23"))) +
scale_y_continuous(limits=c(0,250)) +
scale_color_manual(values=c("Obserwacje"="grey","Model"="red"))+
xlab("Data") +
ylab("Stężenie PM10 [µg/m3]") +
ggtitle("Wykres trendu prognozy od obserwacji dla stężeń PM10\nna stacji w Dąbrowie Górniczej w roku 2018") +
theme_minimal() +
theme(legend.position="top",legend.title=element_blank())
wyk3Prognozy na wykresie są zakreślone linią czerwoną, natomiast obserwacje stężeń PM10 - linią szarą. Można potwierdzić wniosek wysunięty na podstawie poprzedniego wykresu - najwyższe wartości stężeń są zaniżane, Można też zauważyć, że najniższe wartości są nieco zawyżane. Jednak można stwierdzić, że ogólny trend jest zachowany.