diff --git a/docs/06-ggplot2.Rmd b/docs/06-ggplot2.Rmd index d580ef6..3ef9db0 100644 --- a/docs/06-ggplot2.Rmd +++ b/docs/06-ggplot2.Rmd @@ -12,6 +12,9 @@ Powyżej wspomnieliśmy o gramatyce grafiki. Dla dokładniejszego uporządkowani Na początku, aby móc tworzyć wizualizacje, musimy załadować pakiet oraz bibilotekę *ggplot2*. Warto zwrócić uwagę, że *ggplot2* posiada również szereg wbudowanych zestawów danych. Aby pokazać możliwości jakie oferuje nam ggplot, przeprowadzimy symulację danych mpg dostępnych w R. +```{r , include = F} +knitr::opts_chunk$set(fig.path = 'main_files/') +``` ```{r start, include=TRUE} library(ggplot2) @@ -34,7 +37,7 @@ Składnia wykresów w ggplot polega na warstwowym budowaniu wykresów. Do dodawania nowych warstw do wykresu używamy symbolu "+" . -```{r first plot, fig.height = 2, fig.width = 2, fig.align = "center"} +```{r } # Budujemy podstawę wykresu, określając z jakich danych będziemy korzytsać ggplot(mpg) @@ -82,10 +85,7 @@ Każda warstwa geom może mieć swoją własną specyfikację aes. Możemy zdecy -```{r mapping plot, fig.height = 2.5, fig.width = 2.5, fig.align = "center"} - - - +```{r } # Punkty na wykresie będą kwadratami ggplot(mpg, aes(x = displ, y = hwy)) + geom_point(shape = 0)+ @@ -141,7 +141,7 @@ oraz wielu innych, które powstają m.in. przez zastosowanie funcji: *geom_area* Poniżej przedstawione są dwa przykładowe typy wykresów. Pierwszy narysowany przy użyciu funkcji *geom_smooth*, która służy do rysowania wygładzonych linii, np. dla prostych trendów. Drugi wykres powstał przy zastosowaniu funkcji *geom_histogram*. -```{r geom plot, fig.height = 2.5, fig.width = 5, fig.align = "center", echo = FALSE, message = FALSE} +```{r geomplot} @@ -164,7 +164,7 @@ W pakiecie *ggplot2* bardzo prosto możemy łączyć ze sobą różne geometrie -```{r, two geoms on plot, fig.height = 2.5, fig.width = 5, fig.align = "center", message = FALSE} +```{r } @@ -209,7 +209,7 @@ Każda geometria w *ggplot2* ma ustawione domyślne położenie różnych elemen -```{r dopasowanie położenia, fig.height = 2.5, fig.width = 5, fig.align = "center", echo = FALSE} +```{r } ggplot(mpg, aes(x = class)) + geom_bar() ``` @@ -218,7 +218,7 @@ ggplot(mpg, aes(x = class)) + Jeżeli teraz do mapowania dodamy opcję *fill = dvr*, to każdy prostokąt będzie reprezentował kombinację wartości class oraz dvr. -```{r dopasowanie położenia2, fig.height = 2.5, fig.width = 5, fig.align = "center", echo = FALSE} +```{r } ggplot(mpg, aes(x = class, fill = drv)) + geom_bar()+ theme(axis.text.x = element_text(angle = 90))+ @@ -231,7 +231,7 @@ Takie przedstawienie danych nie dla każdego może być czytelne, dlatego możem -```{r dopasowanie położenia3, fig.height = 2.5, fig.width = 5, fig.align = "center", echo = FALSE} +```{r } ggplot(mpg, aes(x = class, fill = drv)) + geom_bar(position = "dodge")+ ggtitle(" Plot for dodge position")+ @@ -267,7 +267,7 @@ W *ggplot2* z łatwością także dodamy etykiety tekstowe oraz adnotacje. Do wy -```{r, zarządzanaie osiami współrzędnych, fig.height = 2.5, fig.width = 4, fig.align = "center"} +```{r } ggplot(mpg, aes(x = displ, y = hwy, color = class)) + geom_point() + labs(title = "Fuel Efficiency by Engine Power", @@ -305,7 +305,7 @@ Panele to sposoby grupowania wykresu danych w wiele różnych części ze wzglę -```{r, panele, fig.height = 2.5, fig.width = 4, fig.align = "center"} +```{r } ggplot(mpg, aes(x = displ, y = hwy)) + geom_point() + facet_grid(~ class) diff --git "a/docs/_bookdown_files/_main_files/figure-html/dopasowanie po\305\202o\305\274enia-1.png" "b/docs/_bookdown_files/_main_files/figure-html/dopasowanie po\305\202o\305\274enia-1.png" deleted file mode 100644 index 53ae0df..0000000 Binary files "a/docs/_bookdown_files/_main_files/figure-html/dopasowanie po\305\202o\305\274enia-1.png" and /dev/null differ diff --git "a/docs/_bookdown_files/_main_files/figure-html/dopasowanie po\305\202o\305\274enia2-1.png" "b/docs/_bookdown_files/_main_files/figure-html/dopasowanie po\305\202o\305\274enia2-1.png" deleted file mode 100644 index 78d0668..0000000 Binary files "a/docs/_bookdown_files/_main_files/figure-html/dopasowanie po\305\202o\305\274enia2-1.png" and /dev/null differ diff --git "a/docs/_bookdown_files/_main_files/figure-html/dopasowanie po\305\202o\305\274enia3-1.png" "b/docs/_bookdown_files/_main_files/figure-html/dopasowanie po\305\202o\305\274enia3-1.png" deleted file mode 100644 index 925ed38..0000000 Binary files "a/docs/_bookdown_files/_main_files/figure-html/dopasowanie po\305\202o\305\274enia3-1.png" and /dev/null differ diff --git "a/docs/_bookdown_files/_main_files/figure-html/dopasowanie po\305\202o\305\274enia3-2.png" "b/docs/_bookdown_files/_main_files/figure-html/dopasowanie po\305\202o\305\274enia3-2.png" deleted file mode 100644 index db584e1..0000000 Binary files "a/docs/_bookdown_files/_main_files/figure-html/dopasowanie po\305\202o\305\274enia3-2.png" and /dev/null differ diff --git a/docs/_bookdown_files/_main_files/figure-html/first plot-1.png b/docs/_bookdown_files/_main_files/figure-html/first plot-1.png deleted file mode 100644 index 192e82c..0000000 Binary files a/docs/_bookdown_files/_main_files/figure-html/first plot-1.png and /dev/null differ diff --git a/docs/_bookdown_files/_main_files/figure-html/first plot-2.png b/docs/_bookdown_files/_main_files/figure-html/first plot-2.png deleted file mode 100644 index 7212b79..0000000 Binary files a/docs/_bookdown_files/_main_files/figure-html/first plot-2.png and /dev/null differ diff --git a/docs/_bookdown_files/_main_files/figure-html/first plot-3.png b/docs/_bookdown_files/_main_files/figure-html/first plot-3.png deleted file mode 100644 index 83cc79c..0000000 Binary files a/docs/_bookdown_files/_main_files/figure-html/first plot-3.png and /dev/null differ diff --git a/docs/_bookdown_files/_main_files/figure-html/first plot-4.png b/docs/_bookdown_files/_main_files/figure-html/first plot-4.png deleted file mode 100644 index ba37f1e..0000000 Binary files a/docs/_bookdown_files/_main_files/figure-html/first plot-4.png and /dev/null differ diff --git a/docs/_bookdown_files/_main_files/figure-html/geom plot-1.png b/docs/_bookdown_files/_main_files/figure-html/geom plot-1.png deleted file mode 100644 index f52bb6e..0000000 Binary files a/docs/_bookdown_files/_main_files/figure-html/geom plot-1.png and /dev/null differ diff --git a/docs/_bookdown_files/_main_files/figure-html/geom plot-2.png b/docs/_bookdown_files/_main_files/figure-html/geom plot-2.png deleted file mode 100644 index 59ab9c5..0000000 Binary files a/docs/_bookdown_files/_main_files/figure-html/geom plot-2.png and /dev/null differ diff --git a/docs/_bookdown_files/_main_files/figure-html/geomplot-1.png b/docs/_bookdown_files/_main_files/figure-html/geomplot-1.png new file mode 100644 index 0000000..6384ed4 Binary files /dev/null and b/docs/_bookdown_files/_main_files/figure-html/geomplot-1.png differ diff --git a/docs/_bookdown_files/_main_files/figure-html/geomplot-2.png b/docs/_bookdown_files/_main_files/figure-html/geomplot-2.png new file mode 100644 index 0000000..3f67980 Binary files /dev/null and b/docs/_bookdown_files/_main_files/figure-html/geomplot-2.png differ diff --git a/docs/_bookdown_files/_main_files/figure-html/mapping plot-1.png b/docs/_bookdown_files/_main_files/figure-html/mapping plot-1.png deleted file mode 100644 index e9a9752..0000000 Binary files a/docs/_bookdown_files/_main_files/figure-html/mapping plot-1.png and /dev/null differ diff --git a/docs/_bookdown_files/_main_files/figure-html/mapping plot-2.png b/docs/_bookdown_files/_main_files/figure-html/mapping plot-2.png deleted file mode 100644 index 1fab50f..0000000 Binary files a/docs/_bookdown_files/_main_files/figure-html/mapping plot-2.png and /dev/null differ diff --git a/docs/_bookdown_files/_main_files/figure-html/panele-1.png b/docs/_bookdown_files/_main_files/figure-html/panele-1.png deleted file mode 100644 index b9e0176..0000000 Binary files a/docs/_bookdown_files/_main_files/figure-html/panele-1.png and /dev/null differ diff --git a/docs/_bookdown_files/_main_files/figure-html/two geoms on plot-1.png b/docs/_bookdown_files/_main_files/figure-html/two geoms on plot-1.png deleted file mode 100644 index 36fda8d..0000000 Binary files a/docs/_bookdown_files/_main_files/figure-html/two geoms on plot-1.png and /dev/null differ diff --git a/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-124-1.png b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-124-1.png new file mode 100644 index 0000000..121d39b Binary files /dev/null and b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-124-1.png differ diff --git a/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-124-2.png b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-124-2.png new file mode 100644 index 0000000..476be5a Binary files /dev/null and b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-124-2.png differ diff --git a/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-124-3.png b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-124-3.png new file mode 100644 index 0000000..488a113 Binary files /dev/null and b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-124-3.png differ diff --git a/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-124-4.png b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-124-4.png new file mode 100644 index 0000000..815912f Binary files /dev/null and b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-124-4.png differ diff --git a/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-125-1.png b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-125-1.png new file mode 100644 index 0000000..60700b9 Binary files /dev/null and b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-125-1.png differ diff --git a/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-125-2.png b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-125-2.png new file mode 100644 index 0000000..a300fc2 Binary files /dev/null and b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-125-2.png differ diff --git a/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-126-1.png b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-126-1.png new file mode 100644 index 0000000..5f83e99 Binary files /dev/null and b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-126-1.png differ diff --git a/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-127-1.png b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-127-1.png new file mode 100644 index 0000000..f6ebcd9 Binary files /dev/null and b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-127-1.png differ diff --git a/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-128-1.png b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-128-1.png new file mode 100644 index 0000000..5acc733 Binary files /dev/null and b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-128-1.png differ diff --git a/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-129-1.png b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-129-1.png new file mode 100644 index 0000000..41a201e Binary files /dev/null and b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-129-1.png differ diff --git a/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-129-2.png b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-129-2.png new file mode 100644 index 0000000..8e345e6 Binary files /dev/null and b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-129-2.png differ diff --git a/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-130-1.png b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-130-1.png new file mode 100644 index 0000000..d366494 Binary files /dev/null and b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-130-1.png differ diff --git a/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-131-1.png b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-131-1.png new file mode 100644 index 0000000..7a31065 Binary files /dev/null and b/docs/_bookdown_files/_main_files/figure-html/unnamed-chunk-131-1.png differ diff --git "a/docs/_bookdown_files/_main_files/figure-html/zarz\304\205dzanaie osiami wsp\303\263\305\202rz\304\231dnych-1.png" "b/docs/_bookdown_files/_main_files/figure-html/zarz\304\205dzanaie osiami wsp\303\263\305\202rz\304\231dnych-1.png" deleted file mode 100644 index 903f0b8..0000000 Binary files "a/docs/_bookdown_files/_main_files/figure-html/zarz\304\205dzanaie osiami wsp\303\263\305\202rz\304\231dnych-1.png" and /dev/null differ diff --git a/docs/czyste-dane.html b/docs/czyste-dane.html index bf9c24f..24df38e 100644 --- a/docs/czyste-dane.html +++ b/docs/czyste-dane.html @@ -23,7 +23,7 @@ - + @@ -1576,7 +1576,8 @@

6.5.1 Operacje na napisach

Przykład:

as.character(list(1L, mean, NULL, pi, FALSE))
-
## [1] "1"                                       "function (x, ...) \nUseMethod(\"mean\")" "NULL"                                    "3.14159265358979"                       
+
## [1] "1"                                       "function (x, ...) \nUseMethod(\"mean\")"
+## [3] "NULL"                                    "3.14159265358979"                       
 ## [5] "FALSE"
x <-data.frame(a=c(TRUE, FALSE, FALSE), b=as.integer(c(1, 2, 3)))
 as.character(x)
@@ -1610,27 +1611,27 @@

6.5.2 Operacje na datach
(data <- Sys.Date())
-
## [1] "2023-10-12"
+
## [1] "2023-11-14"
(czas <- Sys.time())
-
## [1] "2023-10-12 16:27:08 CEST"
+
## [1] "2023-11-14 13:44:20 CET"
  1. Operacje arytmetyczne na datach – dodawanie, odejmowanie i porównywanie.

Przykład:

data
-
## [1] "2023-10-12"
+
## [1] "2023-11-14"
data-365
-
## [1] "2022-10-12"
+
## [1] "2022-11-14"
data+365
-
## [1] "2024-10-11"
+
## [1] "2024-11-13"
(d <- data-as.Date('2021-01-01'))
-
## Time difference of 1014 days
+
## Time difference of 1047 days
  1. Do konwersji do napisu może służyć przeciążona wersja metody format(), której wywołanie jest tożsame z wywołaniem funkcji strftime() (ang. string-format-time).

Przykład:

strftime(czas, '%Y-%m-%d %H:%M:%S %Z')  
-
## [1] "2023-10-12 16:27:08 CEST"
+
## [1] "2023-11-14 13:44:20 CET"
  1. Do znajdowania “najstarszej” i “najmłodszej” daty używamy funkcji max() oraz min().

  2. Do pracy ze strefami czasowymi możemy używać poniższych funkcji:

  3. diff --git a/docs/czysty-i-wydajny-kod-w-r.html b/docs/czysty-i-wydajny-kod-w-r.html index 4e02a93..5215da5 100644 --- a/docs/czysty-i-wydajny-kod-w-r.html +++ b/docs/czysty-i-wydajny-kod-w-r.html @@ -23,7 +23,7 @@ - + @@ -377,28 +377,28 @@

    8.1.5 Funkcje
    calculate_conf_interval = function(sample, alpha) {
    -  len = length(sample)
    -  successes = length(sample[sample == 1])
    -  mi = successes / n
    -  se = sqrt(mi * (1 - mi) / len)
    -  quantile = qt(1 - alpha / 2, len - 1)
    -  left = mi - quantile * se
    -  right = mi + quantile * se
    -  return(c(left, right))
    -}
    +
    calculate_conf_interval = function(sample, alpha) {
    +  len = length(sample)
    +  successes = length(sample[sample == 1])
    +  mi = successes / n
    +  se = sqrt(mi * (1 - mi) / len)
    +  quantile = qt(1 - alpha / 2, len - 1)
    +  left = mi - quantile * se
    +  right = mi + quantile * se
    +  return(c(left, right))
    +}

    Przykładowa funkcja, napisana w ,,nieładny’’ sposób:

    -
    func= function(x,y,temp1,temp2){
    -  n =length(x)
    -  s <-length(x[x==1])
    -  m = s/n
    -  sgm = sqrt(mi *(1- m)/n)
    -  q<-qt(1 - y /2,len-1)
    -  tmp = (s + 0.5 * q ^ 2) /(n + q ^ 2)
    -  se = sqrt(tmp *(1 - tmp)/ (n+ q^2))
    -  l<- tmp- q* se
    -  r = tmp + q*se
    -return(c(l,r))}
    +
    func= function(x,y,temp1,temp2){
    +  n =length(x)
    +  s <-length(x[x==1])
    +  m = s/n
    +  sgm = sqrt(mi *(1- m)/n)
    +  q<-qt(1 - y /2,len-1)
    +  tmp = (s + 0.5 * q ^ 2) /(n + q ^ 2)
    +  se = sqrt(tmp *(1 - tmp)/ (n+ q^2))
    +  l<- tmp- q* se
    +  r = tmp + q*se
    +return(c(l,r))}

    Główne problemy: - czasem przypisanie jest za pomocą =, czasem <-,
    - brak spacji po przecinkach,
    diff --git a/docs/eksploracyjna-analiza-danych.html b/docs/eksploracyjna-analiza-danych.html index e83c42e..eb41cbb 100644 --- a/docs/eksploracyjna-analiza-danych.html +++ b/docs/eksploracyjna-analiza-danych.html @@ -23,7 +23,7 @@ - + diff --git a/docs/index.html b/docs/index.html index cedca1d..e3b7631 100644 --- a/docs/index.html +++ b/docs/index.html @@ -23,7 +23,7 @@ - + @@ -316,7 +316,7 @@

    Notatki z laboratoriów ,,Programowanie i analiza danych w R’’

    Instytut Matematyczny, Uniwersytet Wrocławski

    Mateusz Staniak

    -

    2023-10-12

    +

    2023-11-14

    1 Wstęp

    diff --git "a/docs/interaktywna-wizualizacja-danych-z-pakietem-shiny-interfejs-u\305\274ytkownika.html" "b/docs/interaktywna-wizualizacja-danych-z-pakietem-shiny-interfejs-u\305\274ytkownika.html" index 7fc9cf7..64c2111 100644 --- "a/docs/interaktywna-wizualizacja-danych-z-pakietem-shiny-interfejs-u\305\274ytkownika.html" +++ "b/docs/interaktywna-wizualizacja-danych-z-pakietem-shiny-interfejs-u\305\274ytkownika.html" @@ -23,7 +23,7 @@ - + @@ -323,16 +323,16 @@

    9.1 Wstęp
    library(shiny)
    -library(shinyWidgets)
    -library(shinydashboard)

    +
    library(shiny)
    +library(shinyWidgets)
    +library(shinydashboard)

    9.2 Tworzenie UI

    Do budowania podstawowego interfejsu w shiny będziemy korzystać z funkcji fluidPage, w której tworzymy cały UI. Wszystkie informacje o rodzajach wprowadznych danych, strukturze wyświetlanych danych oraz szeroko rozumianej estetyce aplikacji będą zawarte wewnątrz tej funkcji.

    -
    ui <- fluidPage(
    - # coś
    -)
    +
    ui <- fluidPage(
    + # coś
    +)

    9.3 Układ strony

    @@ -355,52 +355,52 @@

    9.4.0.1 Przykładowe elementy wej
    • textInput - funkcja tworząca pole, w którym użytkownik może wprowadzić dowolny tekst,
    -
    ui <- fluidPage(
    -  # Okienko do wpisywania tekstu
    -  textInput("nazwa_inputu_1", "Tekst wyświetlany w aplikacji")
    -)
    +
    ui <- fluidPage(
    +  # Okienko do wpisywania tekstu
    +  textInput("nazwa_inputu_1", "Tekst wyświetlany w aplikacji")
    +)
    • numericInput - funkcja tworząca pole, w którym użytkownik może wprowadzić wartość liczbową,
    -
    ui <- fluidPage(
    -  # Okienko do wpisywania liczb
    -  numericInput("nazwa_inputu_2", "Tekst wyświetlany w aplikacji",
    -              # Wartość domyślna
    -              value = 10)
    -)
    +
    ui <- fluidPage(
    +  # Okienko do wpisywania liczb
    +  numericInput("nazwa_inputu_2", "Tekst wyświetlany w aplikacji",
    +              # Wartość domyślna
    +              value = 10)
    +)
    • selectInput - funkcja tworząca listę, z której użytkownik może dokonać wyboru - domyślnie parametr multiple umożliwia wybór jednej pozycji z listy,
    -
    ui <- fluidPage(
    -  # Możliwość wybrania z listy
    -   selectInput("nazwa_inputu_3", "Tekst wyświetlany w aplikacji", 
    -               # Lista możliwości do wyboru
    -               choices = c("Wybór_1", "Wybór_2"))
    -)
    +
    ui <- fluidPage(
    +  # Możliwość wybrania z listy
    +   selectInput("nazwa_inputu_3", "Tekst wyświetlany w aplikacji", 
    +               # Lista możliwości do wyboru
    +               choices = c("Wybór_1", "Wybór_2"))
    +)
    • sliderInput - funkcja tworząca suwak umożliwiający użytkownikowi wybór zakresu interesujących go wartości,
    -
    ui <- fluidPage(
    -  # Suwak do wyboru wartości
    -  sliderInput("nazwa_inputu_4", "Tekst wyświetlany w aplikacji",
    -              # Wartość domyślna
    -              value = 1,
    -              # Wartość minimalna
    -              min = 0,
    -              # Wartość maksymalna
    -              max = 10)
    -)
    +
    ui <- fluidPage(
    +  # Suwak do wyboru wartości
    +  sliderInput("nazwa_inputu_4", "Tekst wyświetlany w aplikacji",
    +              # Wartość domyślna
    +              value = 1,
    +              # Wartość minimalna
    +              min = 0,
    +              # Wartość maksymalna
    +              max = 10)
    +)
    • dateRangeInput - funkcja tworząca pole wyboru zakresu interesujących dat.
    -
    ui <- fluidPage(
    -  # Pole wyboru zakresu dat
    -  dateRangeInput("nazwa_inputu_5", "Tekst wyświetlany w aplikacji",
    -                 # Data początkowa
    -                 start = "2001-01-01",
    -                 # Data końcowa
    -                 end   = "2010-12-31")
    -)
    +
    ui <- fluidPage(
    +  # Pole wyboru zakresu dat
    +  dateRangeInput("nazwa_inputu_5", "Tekst wyświetlany w aplikacji",
    +                 # Data początkowa
    +                 start = "2001-01-01",
    +                 # Data końcowa
    +                 end   = "2010-12-31")
    +)

    9.4.0.2 Przykładowe elementy wyjścia

    @@ -408,72 +408,72 @@

    9.4.0.2 Przykładowe elementy wyj
    • textOutput - funkcja wyświetlająca tekst,
    -
    ui <- fluidPage(
    -  # Wyświetla tekst, który stworzyliśmy w serwerze pod daną nazwą
    -  textOutput("nazwa_outputu_1")
    -)
    +
    ui <- fluidPage(
    +  # Wyświetla tekst, który stworzyliśmy w serwerze pod daną nazwą
    +  textOutput("nazwa_outputu_1")
    +)
    • tableOutput - podstawowa funkcja wyświetlająca tabelę,
    -
    ui <- fluidPage(
    -  # Wyświetla tabelę stworzoną w serwerze pod daną nazwą
    -  tableOutput("nazwa_outputu_2")
    -)
    +
    ui <- fluidPage(
    +  # Wyświetla tabelę stworzoną w serwerze pod daną nazwą
    +  tableOutput("nazwa_outputu_2")
    +)
    • DTOutput - funkcja wyświetlająca interaktywną ramkę danych z użyciem pakietu data.table,
    -
    ui <- fluidPage(
    -  # Interaktywna ramka danych z użyciem data.table
    -  DT::DTOutput("nazwa_outputu_3")
    -)
    +
    ui <- fluidPage(
    +  # Interaktywna ramka danych z użyciem data.table
    +  DT::DTOutput("nazwa_outputu_3")
    +)
    • plotOutput - funkcja wyświetlająca wykres.
    -
    ui <- fluidPage(
    -  # Wyświetla wykres stworzony w serwerze
    -  plotOutput("nazwa_outputu_4",
    -             # Szerokość wykresu
    -             width = "100%", 
    -             # Wysokość wykresu
    -             height = "400px")
    -)
    +
    ui <- fluidPage(
    +  # Wyświetla wykres stworzony w serwerze
    +  plotOutput("nazwa_outputu_4",
    +             # Szerokość wykresu
    +             width = "100%", 
    +             # Wysokość wykresu
    +             height = "400px")
    +)

    9.5 Przykład użycia

    Oczywiście powyższe kody były jedynie fragmentami większej całości. Poniżej możemy zobaczyć przykładowy kod obrazujący strukturę budowy interfejsu. Rzeczą, o której należy pamiętać jest oddzielanie funkcji przecinkami.

    -
    ui <- fluidPage( 
    -  # Tytuł
    -  titlePanel("Tytuł"),
    -  
    -  # To co będzie wyświetlone z boku interfejsu
    -  sidebarLayout(
    -    
    -    # Panel boczny
    -    sidebarPanel(
    -      
    -      # Pierwszy input - wybór
    -      selectInput("nazwa_inputu_1", "Tekst wyświetlany w aplikacji", 
    -                  choices = c("Wybór_1", "Wybór_2")),
    -      # Drugi input - suwak
    -      sliderInput("nazwa_inputu_2", "Tekst wyświetlany w aplikacji",
    -                  value = 1, min = 0, max = 10) 
    -    ),
    -  
    -  # Główny panel
    -  mainPanel( 
    -    
    -    # Tworzymy zakładki
    -    tabsetPanel( 
    -      
    -      # Pierwsza zakładka - wykres
    -      tabPanel("Tytuł wykresu", plotOutput("nazwa_outputu_1")),
    -      # Druga zakładka - ramka danych
    -      tabPanel("Tytuł ramki", DT::DTOutput("nazwa_outputu_2"))
    -      )
    -    )
    -  )
    -)
    +
    ui <- fluidPage( 
    +  # Tytuł
    +  titlePanel("Tytuł"),
    +  
    +  # To co będzie wyświetlone z boku interfejsu
    +  sidebarLayout(
    +    
    +    # Panel boczny
    +    sidebarPanel(
    +      
    +      # Pierwszy input - wybór
    +      selectInput("nazwa_inputu_1", "Tekst wyświetlany w aplikacji", 
    +                  choices = c("Wybór_1", "Wybór_2")),
    +      # Drugi input - suwak
    +      sliderInput("nazwa_inputu_2", "Tekst wyświetlany w aplikacji",
    +                  value = 1, min = 0, max = 10) 
    +    ),
    +  
    +  # Główny panel
    +  mainPanel( 
    +    
    +    # Tworzymy zakładki
    +    tabsetPanel( 
    +      
    +      # Pierwsza zakładka - wykres
    +      tabPanel("Tytuł wykresu", plotOutput("nazwa_outputu_1")),
    +      # Druga zakładka - ramka danych
    +      tabPanel("Tytuł ramki", DT::DTOutput("nazwa_outputu_2"))
    +      )
    +    )
    +  )
    +)

    Dodatkowo warto zdawać sobie sprawę, że po wprowadzeniu danych przez użytkownika outputy aktualizują się automatycznie, dlatego często przydatne jest programowanie reaktywne z funkcją observeEvent oraz użycie actionButton, który pozwala na wykonanie danego działania dopiero po kliknięciu odpowiedniego przycisku przez użytkownika.

    diff --git a/docs/interaktywna-wizualizacja-danych-z-pakietem-shiny-strona-serwerowa.html b/docs/interaktywna-wizualizacja-danych-z-pakietem-shiny-strona-serwerowa.html index e381466..a3ca857 100644 --- a/docs/interaktywna-wizualizacja-danych-z-pakietem-shiny-strona-serwerowa.html +++ b/docs/interaktywna-wizualizacja-danych-z-pakietem-shiny-strona-serwerowa.html @@ -23,7 +23,7 @@ - + diff --git a/docs/main_files/geomplot-1.png b/docs/main_files/geomplot-1.png new file mode 100644 index 0000000..6384ed4 Binary files /dev/null and b/docs/main_files/geomplot-1.png differ diff --git a/docs/main_files/geomplot-2.png b/docs/main_files/geomplot-2.png new file mode 100644 index 0000000..3f67980 Binary files /dev/null and b/docs/main_files/geomplot-2.png differ diff --git a/docs/main_files/unnamed-chunk-125-1.png b/docs/main_files/unnamed-chunk-125-1.png new file mode 100644 index 0000000..121d39b Binary files /dev/null and b/docs/main_files/unnamed-chunk-125-1.png differ diff --git a/docs/main_files/unnamed-chunk-125-2.png b/docs/main_files/unnamed-chunk-125-2.png new file mode 100644 index 0000000..476be5a Binary files /dev/null and b/docs/main_files/unnamed-chunk-125-2.png differ diff --git a/docs/main_files/unnamed-chunk-125-3.png b/docs/main_files/unnamed-chunk-125-3.png new file mode 100644 index 0000000..488a113 Binary files /dev/null and b/docs/main_files/unnamed-chunk-125-3.png differ diff --git a/docs/main_files/unnamed-chunk-125-4.png b/docs/main_files/unnamed-chunk-125-4.png new file mode 100644 index 0000000..815912f Binary files /dev/null and b/docs/main_files/unnamed-chunk-125-4.png differ diff --git a/docs/main_files/unnamed-chunk-126-1.png b/docs/main_files/unnamed-chunk-126-1.png new file mode 100644 index 0000000..60700b9 Binary files /dev/null and b/docs/main_files/unnamed-chunk-126-1.png differ diff --git a/docs/main_files/unnamed-chunk-126-2.png b/docs/main_files/unnamed-chunk-126-2.png new file mode 100644 index 0000000..a300fc2 Binary files /dev/null and b/docs/main_files/unnamed-chunk-126-2.png differ diff --git a/docs/main_files/unnamed-chunk-127-1.png b/docs/main_files/unnamed-chunk-127-1.png new file mode 100644 index 0000000..5f83e99 Binary files /dev/null and b/docs/main_files/unnamed-chunk-127-1.png differ diff --git a/docs/main_files/unnamed-chunk-128-1.png b/docs/main_files/unnamed-chunk-128-1.png new file mode 100644 index 0000000..f6ebcd9 Binary files /dev/null and b/docs/main_files/unnamed-chunk-128-1.png differ diff --git a/docs/main_files/unnamed-chunk-129-1.png b/docs/main_files/unnamed-chunk-129-1.png new file mode 100644 index 0000000..5acc733 Binary files /dev/null and b/docs/main_files/unnamed-chunk-129-1.png differ diff --git a/docs/main_files/unnamed-chunk-130-1.png b/docs/main_files/unnamed-chunk-130-1.png new file mode 100644 index 0000000..41a201e Binary files /dev/null and b/docs/main_files/unnamed-chunk-130-1.png differ diff --git a/docs/main_files/unnamed-chunk-130-2.png b/docs/main_files/unnamed-chunk-130-2.png new file mode 100644 index 0000000..8e345e6 Binary files /dev/null and b/docs/main_files/unnamed-chunk-130-2.png differ diff --git a/docs/main_files/unnamed-chunk-131-1.png b/docs/main_files/unnamed-chunk-131-1.png new file mode 100644 index 0000000..d366494 Binary files /dev/null and b/docs/main_files/unnamed-chunk-131-1.png differ diff --git a/docs/main_files/unnamed-chunk-132-1.png b/docs/main_files/unnamed-chunk-132-1.png new file mode 100644 index 0000000..7a31065 Binary files /dev/null and b/docs/main_files/unnamed-chunk-132-1.png differ diff --git "a/docs/modu\305\202y-w-aplikacjach-shiny.html" "b/docs/modu\305\202y-w-aplikacjach-shiny.html" index 1d45bee..261f646 100644 --- "a/docs/modu\305\202y-w-aplikacjach-shiny.html" +++ "b/docs/modu\305\202y-w-aplikacjach-shiny.html" @@ -23,7 +23,7 @@ - + @@ -335,77 +335,77 @@

    13.2 Budowa modułu Shiny.

    13.2.1 Jak używać modułów Shiny?

    Rozważmy aplikację składającą się z dwóch paneli - każdy z wykresem i danymi dla dwóch rozkładów, otrzymaną za pomocą poniższego kodu:

    -
    library(shiny)
    -library(ggplot2)
    -
    -ui <- fluidPage(
    -  tabsetPanel(
    -    #generujemy panel dla rozkładu normalnego
    -    tabPanel(title = "Rozkład normalny",
    -             tabsetPanel(
    -               tabPanel(
    -                 title = "Wykres",
    -                 numericInput(inputId = "normal_n",
    -                              label = "Podaj wielkość próby", 
    -                              value = 1000),
    -                 plotOutput("normal_plot")
    -               ),
    -               tabPanel(
    -                 title = "Dane",
    -                 tableOutput("normal_data")
    -               )
    -             )
    -    ),
    -    #generujemy panel dla rozkładu wykładniczego
    -    tabPanel(title = "Rozkład wykładniczy",
    -             tabsetPanel(
    -               tabPanel(
    -                 title = "wykres",
    -                 numericInput(inputId = "exp_n",
    -                              label = "Podaj wielkość próby", 
    -                              value = 1000),
    -                 plotOutput("exp_plot")
    -               ),
    -               tabPanel(
    -                 title = "Dane",
    -                 tableOutput("exp_data")
    -               )
    -             )
    -    )
    -  )
    -)
    -
    -server <- function(input, output, session) {
    -  #generujemy dane
    -  normal_data <- reactive({
    -    set.seed(17)
    -    data.frame(id = 1:input[["normal_n"]],
    -               sample = rnorm(input[["normal_n"]]))
    -  })
    -  exp_data <- reactive({
    -    set.seed(17)
    -    data.frame(id = 1:input[["exp_n"]],
    -               sample = rnorm(input[["exp_n"]]))
    -  })
    -  #generujemy tabele
    -  output[["normal_data"]] <- renderTable({
    -    normal_data()
    -  })
    -  output[["exp_data"]] <- renderTable({
    -    exp_data()
    -  })
    -  #generuemy wykresy
    -  output[["normal_plot"]] <- renderPlot({
    -    ggplot(normal_data(), aes(x = sample)) +
    -      geom_density()
    -  })
    -  output[["exp_plot"]] <- renderPlot({
    -    ggplot(exp_data(), aes(x = sample)) +
    -      geom_density() +
    -      xlim(0, 5)
    -  })
    -}
    -shinyApp(ui, server)
    +
    library(shiny)
    +library(ggplot2)
    +
    +ui <- fluidPage(
    +  tabsetPanel(
    +    #generujemy panel dla rozkładu normalnego
    +    tabPanel(title = "Rozkład normalny",
    +             tabsetPanel(
    +               tabPanel(
    +                 title = "Wykres",
    +                 numericInput(inputId = "normal_n",
    +                              label = "Podaj wielkość próby", 
    +                              value = 1000),
    +                 plotOutput("normal_plot")
    +               ),
    +               tabPanel(
    +                 title = "Dane",
    +                 tableOutput("normal_data")
    +               )
    +             )
    +    ),
    +    #generujemy panel dla rozkładu wykładniczego
    +    tabPanel(title = "Rozkład wykładniczy",
    +             tabsetPanel(
    +               tabPanel(
    +                 title = "wykres",
    +                 numericInput(inputId = "exp_n",
    +                              label = "Podaj wielkość próby", 
    +                              value = 1000),
    +                 plotOutput("exp_plot")
    +               ),
    +               tabPanel(
    +                 title = "Dane",
    +                 tableOutput("exp_data")
    +               )
    +             )
    +    )
    +  )
    +)
    +
    +server <- function(input, output, session) {
    +  #generujemy dane
    +  normal_data <- reactive({
    +    set.seed(17)
    +    data.frame(id = 1:input[["normal_n"]],
    +               sample = rnorm(input[["normal_n"]]))
    +  })
    +  exp_data <- reactive({
    +    set.seed(17)
    +    data.frame(id = 1:input[["exp_n"]],
    +               sample = rnorm(input[["exp_n"]]))
    +  })
    +  #generujemy tabele
    +  output[["normal_data"]] <- renderTable({
    +    normal_data()
    +  })
    +  output[["exp_data"]] <- renderTable({
    +    exp_data()
    +  })
    +  #generuemy wykresy
    +  output[["normal_plot"]] <- renderPlot({
    +    ggplot(normal_data(), aes(x = sample)) +
    +      geom_density()
    +  })
    +  output[["exp_plot"]] <- renderPlot({
    +    ggplot(exp_data(), aes(x = sample)) +
    +      geom_density() +
    +      xlim(0, 5)
    +  })
    +}
    +shinyApp(ui, server)

    Aplikacja wygląda następująco:

    W naszej przestrzeni wykorzystaliśmy nazwy:

    @@ -415,68 +415,68 @@

    13.2.1 Jak używać modułów Shi

    Co daje razem 6 obiektów. W aplikacji UI zajmuje 36 linijek kodu, a server 29, razem 65 linijek.

    Zrefaktoryzuemy kod powyższej aplikacji przy użyciu modułów Shiny. Za powtarzające się elementy (tj. panele z wykresem i danymi) będą odpowiedzialne następujące funkcje module_UI oraz module_SERVER (odpowiedniki UI oraz servera dla odrębnego fragmentu aplikacji).

    -
    module_UI <- function(id) {
    -  ns <- NS(id)
    -  tagList(
    -    tabsetPanel(
    -      tabPanel(
    -        title = "Wykres",
    -        numericInput(inputId = ns("n"),
    -                     label = "Podaj wielkość próby", 
    -                     value = 1000),
    -        plotOutput(ns("plot"))
    -      ),
    -      tabPanel(title = "Dane",
    -               tableOutput(outputId = ns("data"))
    -      )
    -    )
    -  )
    -}
    +
    module_UI <- function(id) {
    +  ns <- NS(id)
    +  tagList(
    +    tabsetPanel(
    +      tabPanel(
    +        title = "Wykres",
    +        numericInput(inputId = ns("n"),
    +                     label = "Podaj wielkość próby", 
    +                     value = 1000),
    +        plotOutput(ns("plot"))
    +      ),
    +      tabPanel(title = "Dane",
    +               tableOutput(outputId = ns("data"))
    +      )
    +    )
    +  )
    +}

    Na szczególną uwagę w powyższym kodzie zasługuje linijka

    -
    ns <- NS(id)
    +
    ns <- NS(id)

    Za pomocą funkcji NS() tworzymy osobną przestrzeń nazw ID.

    -
    module_SERVER <- function(id) {
    -  moduleServer(id, function(input, output, session) {
    -    #generujemy dane
    -    data <- reactive({
    -      set.seed(17)
    -      data.frame(id = 1:input[["n"]],
    -                 sample = rnorm(input[["n"]]))
    -    })
    -    #generujemy wykres
    -    output[["plot"]] <- renderPlot({
    -      ggplot(data(), aes(x = sample)) +
    -        geom_density()
    -    })
    -    #generujemy tabelę
    -    output[["data"]] <- renderTable({
    -      data()
    -    })
    -  })
    -}
    +
    module_SERVER <- function(id) {
    +  moduleServer(id, function(input, output, session) {
    +    #generujemy dane
    +    data <- reactive({
    +      set.seed(17)
    +      data.frame(id = 1:input[["n"]],
    +                 sample = rnorm(input[["n"]]))
    +    })
    +    #generujemy wykres
    +    output[["plot"]] <- renderPlot({
    +      ggplot(data(), aes(x = sample)) +
    +        geom_density()
    +    })
    +    #generujemy tabelę
    +    output[["data"]] <- renderTable({
    +      data()
    +    })
    +  })
    +}

    Ostatecznie nasza aplikacja używająca pomocniczego modułu wygląda następująco

    -
    library(shiny)
    -
    -ui <- fluidPage(
    -  titlePanel("Przykładowe ciągłe rozkłady prawdopodobieństwa"),
    -  tabsetPanel(
    -    #generujemy panel dla rozkładu normalnego
    -    tabPanel(title = "Rozkład normalny",
    -             module_UI("norm") 
    -    ),
    -    #generujemy panel dla rozkładu wykładniczego
    -    tabPanel(title = "Rozkład wykładniczy",
    -             module_UI("exp") 
    -    )
    -  )
    -)
    -
    -server <- function(input, output, session) {
    -  module_SERVER("norm")
    -  module_SERVER("exp")
    -}
    -
    -shinyApp(ui, server)
    +
    library(shiny)
    +
    +ui <- fluidPage(
    +  titlePanel("Przykładowe ciągłe rozkłady prawdopodobieństwa"),
    +  tabsetPanel(
    +    #generujemy panel dla rozkładu normalnego
    +    tabPanel(title = "Rozkład normalny",
    +             module_UI("norm") 
    +    ),
    +    #generujemy panel dla rozkładu wykładniczego
    +    tabPanel(title = "Rozkład wykładniczy",
    +             module_UI("exp") 
    +    )
    +  )
    +)
    +
    +server <- function(input, output, session) {
    +  module_SERVER("norm")
    +  module_SERVER("exp")
    +}
    +
    +shinyApp(ui, server)

    Powyższy kod jest czytelniejszy, krótszy, a także rozwiązuje problem wielu zmiennych.

    diff --git "a/docs/podstawy-j\304\231zyka-r.html" "b/docs/podstawy-j\304\231zyka-r.html" index 37474c9..0954ec4 100644 --- "a/docs/podstawy-j\304\231zyka-r.html" +++ "b/docs/podstawy-j\304\231zyka-r.html" @@ -23,7 +23,7 @@ - + @@ -460,7 +460,8 @@

    2.5 Indeksowanie
    x_ind <- new_letters == "x"
     x_ind
    -
    ##  [1]  TRUE  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE
    +
    ##  [1]  TRUE  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
    +## [21] FALSE FALSE FALSE  TRUE FALSE FALSE
    new_letters[x_ind]
    ## [1] "x" "x" "x" "x" "x" "x"
    seq_vec <- seq(0, 1, length.out = 10)
    @@ -797,7 +798,7 @@ 

    2.6 Operacje na wektorach{ cat("Liczba ", x_norm, "jest dodatnia") }

    -
    ## Liczba -0.6045206 jest ujemna
    +
    ## Liczba -1.563892 jest ujemna

    Możemy chcieć wykonać różne operacje na tak wylosowanej liczbie. Przykładowo, jeśli będzie ujemna, to zmienić znak, zaokrąglić i zreplikować w wektorze

    if (x_norm < 0)
     {
    @@ -807,7 +808,7 @@ 

    2.6 Operacje na wektorach{ x_wek <- "X" }

    -

    i otrzymać 0.6, 0.6, 0.6, 0.6, 0.6 (X oznacza, że wylosowana liczba była dodatnia, a z nią nic nie robimy).

    +

    i otrzymać 1.56, 1.56, 1.56, 1.56, 1.56 (X oznacza, że wylosowana liczba była dodatnia, a z nią nic nie robimy).

    Pętla \(\verb+while+\) działa tak długo, dopóki warunek jest spełniony - tzn. do kiedy nie dostaniemy \(\verb+FALSE+\). Warunek należy opisać tak, żeby w pewnym momencie został spełniony - inaczej pętla będzie działać w “nieskończoność”. Często używa sie jej do szukania losowych liczb o pewnych właściwościach.

    Składnia tej pętli jest następująca:

    while(warunek)
    @@ -826,7 +827,7 @@ 

    2.6 Operacje na wektorach roznica <- los[i]-los[i-1] i = i+1 }

    -

    W ten sposób dostajemy wylosowane liczby: 0, 85, 75, z różnicą między ostatnimi równą -10.

    +

    W ten sposób dostajemy wylosowane liczby: 0, 5, 37, z różnicą między ostatnimi równą 32.

    Pętla \(\verb+for+\) wygląda następująco:

    for(iterator in warunek)
     {
    @@ -911,16 +912,17 @@ 

    2.6 Operacje na wektorachby(m1,x,mean)

    ## x: a
     ## [1] 4
    -## ------------------------------------------------------------------------------------------------------------------------------------------------------ 
    +## ----------------------------------------------------------------------------------------------- 
     ## x: b
     ## [1] 5
    -## ------------------------------------------------------------------------------------------------------------------------------------------------------ 
    +## ----------------------------------------------------------------------------------------------- 
     ## x: c
     ## [1] 6

    Z kolei \(\verb+mapply()+\) to wielowymiarowy odpowiednik funkcji \(\verb+sapply()+\). Argumentami tej funkcji jest funkcja \(\verb+fun+\) oraz kilka (dwa lub więcej) wektorów o tej samej długości. Wynikiem jest wektor, w którym na pozycji \(\verb+i+\)-tej jest wynik funkcji \(\verb+fun+\) wywołanej z \(\verb+i+\)-tych elementów wektorów będących argumentami.

    a=function(x,y){return(x**y)}
     mapply(a,x=seq(1,101,by=10),y=seq(1:11))
    -
    ##  [1] 1.000000e+00 1.210000e+02 9.261000e+03 9.235210e+05 1.158562e+08 1.759629e+10 3.142743e+12 6.457535e+14 1.500946e+17 3.894161e+19 1.115668e+22
    +
    ##  [1] 1.000000e+00 1.210000e+02 9.261000e+03 9.235210e+05 1.158562e+08 1.759629e+10 3.142743e+12 6.457535e+14 1.500946e+17
    +## [10] 3.894161e+19 1.115668e+22

    2.7 R - funkcje

    diff --git a/docs/podstawy-kontroli-wersji-przy-pomocy-gita.html b/docs/podstawy-kontroli-wersji-przy-pomocy-gita.html index 901e0b6..1f054e8 100644 --- a/docs/podstawy-kontroli-wersji-przy-pomocy-gita.html +++ b/docs/podstawy-kontroli-wersji-przy-pomocy-gita.html @@ -23,7 +23,7 @@ - + diff --git a/docs/programowanie-obiektowe-w-r-klasy-s3.html b/docs/programowanie-obiektowe-w-r-klasy-s3.html index 2fe8da1..ab062af 100644 --- a/docs/programowanie-obiektowe-w-r-klasy-s3.html +++ b/docs/programowanie-obiektowe-w-r-klasy-s3.html @@ -23,7 +23,7 @@ - + @@ -327,74 +327,74 @@

    12.1 Systemy programowania obiekt

    12.2 S3

    S3 to system, z którym stykamy się najczęśniej. Wszystkie wbudowane klasy obiektów zostały zbudowane właśnie przy pomocy systemu S3. By sprawdzić, do jakiej klasy S3 należy obiekt, używamy funkcji class. W codziennej pracy w R operujemy w wiekszości na obiektach zbudowanych w S3. Klasy S3 to m.in. factor,data.frame,matrix.

    -
    f <- factor(c("y","n","y","n","n"))
    -class(f)
    +
    f <- factor(c("y","n","y","n","n"))
    +class(f)
    ## [1] "factor"

    Warto w tym miejscu podkreślić, że klasa zmiennej nie jest równoważna typowi zmiennej, np. macierz liczb jest klasy matrix, ale typu double.

    -
    m <- matrix(c(1,2,3,4),2,2)
    -class(m)
    +
    m <- matrix(c(1,2,3,4),2,2)
    +class(m)
    ## [1] "matrix" "array"
    -
    typeof(m)
    +
    typeof(m)
    ## [1] "double"

    Każdemu obiektowi mogą być (ale nie muszą) przypisane atrybuty. Atrybuty mozna rozumieć jako cechy lub parametry obiektu. W przypadku macierzy są to jej wymiary.

    -
    attributes(m)
    +
    attributes(m)
    ## $dim
     ## [1] 2 2

    12.2.1 Klasy i atrybuty

    W systemie S3 nie tworzymy definicji klasy, nie określamy również, jakie atrybuty obiekt danej klasy ma. Obiektowi możemy nadać klasę przy jego utworzeniu, z użyciem funkcji structure:

    -
    kanapka <-structure(c("szynka", "margaryna", "chleb"), class = "jedzenie")
    -class(kanapka)
    +
    kanapka <-structure(c("szynka", "margaryna", "chleb"), class = "jedzenie")
    +class(kanapka)
    ## [1] "jedzenie"

    lub w dowolnym momencie po jego utworzeniu z użyciem class:

    -
    szarlotka <- c("jaja", "mąka", "masło", "cukier")
    -szarlotka <- c(szarlotka, "jabłka")
    -class(szarlotka) <- "jedzenie"
    -class(szarlotka)
    +
    szarlotka <- c("jaja", "mąka", "masło", "cukier")
    +szarlotka <- c(szarlotka, "jabłka")
    +class(szarlotka) <- "jedzenie"
    +class(szarlotka)
    ## [1] "jedzenie"

    Każdemu obiektowi możemy również indywidualnie przypisać atrybuty, również na kilka sposobów, przy jego utworzeniu z użyciem structure:

    -
    hot_dog <- structure(c("parówka", "bułka", "ketchup"), class="jedzenie", kalorie = 300)
    -attributes(hot_dog)
    +
    hot_dog <- structure(c("parówka", "bułka", "ketchup"), class="jedzenie", kalorie = 300)
    +attributes(hot_dog)
    ## $class
     ## [1] "jedzenie"
     ## 
     ## $kalorie
     ## [1] 300
    -
    class(hot_dog)
    +
    class(hot_dog)
    ## [1] "jedzenie"

    Lub w dowolnym momencie z użyciem funkcji attr:

    -
    attr(kanapka, "kalorie")=150
    -attr(szarlotka, "kalorie")=265
    -attributes(kanapka)
    +
    attr(kanapka, "kalorie")=150
    +attr(szarlotka, "kalorie")=265
    +attributes(kanapka)
    ## $class
     ## [1] "jedzenie"
     ## 
     ## $kalorie
     ## [1] 150
    -
    attributes(szarlotka)
    +
    attributes(szarlotka)
    ## $class
     ## [1] "jedzenie"
     ## 
     ## $kalorie
     ## [1] 265

    Z użyciem funkcji attributes i attr można również “dostać się” do wartości atrybutów obiektu:

    -
    attr(szarlotka, "kalorie")
    +
    attr(szarlotka, "kalorie")
    ## [1] 265
    -
    attributes(szarlotka)$kalorie
    +
    attributes(szarlotka)$kalorie
    ## [1] 265

    Tworzenie obiektów różnych klas S3 jest więc bardzo proste i nie wymaga (przynajmniej formalnie) predefiniowania klasy i atrybutów. System jest więc z jednej strony bardzo elastyczny, z drugiej - nieprecyzyjny. Niesie to za sobą pewne konsekwencje, np. formalnie nic nie stoi na przeszkodzie by zrobić coś takiego:

    -
    droga <- c("asfalt","pobocze", "lewy pas", "prawy pas")
    -class(droga) <- "jedzenie"
    -class(droga)
    +
    droga <- c("asfalt","pobocze", "lewy pas", "prawy pas")
    +class(droga) <- "jedzenie"
    +class(droga)
    ## [1] "jedzenie"

    lub takiego…

    -
    średnia_bez_na <- function(...) mean(na.rm=TRUE,...)
    -class(średnia_bez_na)<-"jedzenie"
    -class(średnia_bez_na)
    +
    średnia_bez_na <- function(...) mean(na.rm=TRUE,...)
    +class(średnia_bez_na)<-"jedzenie"
    +class(średnia_bez_na)
    ## [1] "jedzenie"

    lub takiego:

    -
    attr(hot_dog, "kalorie") <- "przecież to prawie nie ma kalorii!"
    -bilans_posilkow <- attr(hot_dog,"kalorie")+attr(szarlotka, "kalorie")+attr(kanapka, "kalorie")
    +
    attr(hot_dog, "kalorie") <- "przecież to prawie nie ma kalorii!"
    +bilans_posilkow <- attr(hot_dog,"kalorie")+attr(szarlotka, "kalorie")+attr(kanapka, "kalorie")
    ## Error in attr(hot_dog, "kalorie") + attr(szarlotka, "kalorie"): argument nieliczbowy przekazany do operatora dwuargumentowego

    Dlatego należy pamiętać, by klas i atrybutów nie przydzielać chaotycznie, zachować pewne reguły, mimo że formalnie nie są wymagane przy użyciu S3.

    @@ -406,38 +406,50 @@

    12.2.2.1 Funkcje generyczneBy lepiej zrozumieć logikę stojącą za funkcjami generycznymi, spróbujmy spojrzeć na klasy i ich metody z nieco mniej standardowej perspektywy. Dla różnych klas możemy mieć analogiczne metody, zachowujące się nieco inaczej w zależności od specyfiki klasy, np. inaczej rozumiemy różnicę między dwoma datami a różnicę między dwoma liczbami - liczby odejmujemy od siebie bezpośrednio, podczas gdy w przypadku dat oczekujemy różnicy w dniach pomiędzy nimi - w tym celu nie wystarczy bezpośrednie odjęcie od siebie dwóch dat.

    W systemie S3 metody nie są przypisane bezpośrednio klasie, są przypisane odpowiedniej funkcji generycznej. Funkcja generyczna określa nazwę metody wspólną dla wszystkich klas i umożliwia tworzenie wariantów metody dla różnych klas pod tą konkretną nazwą. Zanim przejdziemy do tworzenia funkcji generycznych oraz metod dla własnych klas przyjrzyjmy się działaniu już istniejących.

    Jedną z funkcji generycznych jest funkcja summary - funkcja podsumowująca obiekt (np. summary(lm(X~Y))).

    -
    summary
    +
    summary
    ## function (object, ...) 
     ## UseMethod("summary")
    -## <bytecode: 0x5612082d5ef8>
    +## <bytecode: 0x55f2bebc3790>
     ## <environment: namespace:base>

    Użyjemy funkcji methods, by wylistować wszystkie dostępne metody dla danej funkcji generycznej.

    -
    methods(summary)
    -
    ##  [1] summary,ANY-method                  summary,DBIObject-method            summary,diagonalMatrix-method       summary,sparseMatrix-method         summary.aov                        
    -##  [6] summary.aovlist*                    summary.aspell*                     summary.check_packages_in_dir*      summary.connection                  summary.corAR1*                    
    -## [11] summary.corARMA*                    summary.corCAR1*                    summary.corCompSymm*                summary.corExp*                     summary.corGaus*                   
    -## [16] summary.corIdent*                   summary.corLin*                     summary.corNatural*                 summary.corRatio*                   summary.corSpher*                  
    -## [21] summary.corStruct*                  summary.corSymm*                    summary.data.frame                  summary.Date                        summary.default                    
    -## [26] summary.Duration*                   summary.ecdf*                       summary.factor                      summary.gam*                        summary.ggplot*                    
    -## [31] summary.glm                         summary.gls*                        summary.haven_labelled*             summary.hcl_palettes*               summary.infl*                      
    -## [36] summary.Interval*                   summary.lm                          summary.lme*                        summary.lmList*                     summary.loess*                     
    -## [41] summary.manova                      summary.matrix                      summary.mlm*                        summary.modelStruct*                summary.nls*                       
    -## [46] summary.nlsList*                    summary.packageStatus*              summary.pdBlocked*                  summary.pdCompSymm*                 summary.pdDiag*                    
    -## [51] summary.pdIdent*                    summary.pdIdnot*                    summary.pdLogChol*                  summary.pdMat*                      summary.pdNatural*                 
    -## [56] summary.pdSymm*                     summary.pdTens*                     summary.Period*                     summary.POSIXct                     summary.POSIXlt                    
    -## [61] summary.ppr*                        summary.prcomp*                     summary.princomp*                   summary.proc_time                   summary.reStruct*                  
    -## [66] summary.rlang_error*                summary.rlang_message*              summary.rlang_trace*                summary.rlang_warning*              summary.rlang:::list_of_conditions*
    -## [71] summary.shingle*                    summary.srcfile                     summary.srcref                      summary.stepfun                     summary.stl*                       
    -## [76] summary.table                       summary.trellis*                    summary.tukeysmooth*                summary.varComb*                    summary.varConstPower*             
    -## [81] summary.varConstProp*               summary.varExp*                     summary.varFixed*                   summary.varFunc*                    summary.varIdent*                  
    -## [86] summary.varPower*                   summary.vctrs_sclr*                 summary.vctrs_vctr*                 summary.warnings                   
    +
    methods(summary)
    +
    ##  [1] summary,ANY-method                  summary,DBIObject-method            summary,diagonalMatrix-method      
    +##  [4] summary,sparseMatrix-method         summary.aov                         summary.aovlist*                   
    +##  [7] summary.aspell*                     summary.check_packages_in_dir*      summary.connection                 
    +## [10] summary.corAR1*                     summary.corARMA*                    summary.corCAR1*                   
    +## [13] summary.corCompSymm*                summary.corExp*                     summary.corGaus*                   
    +## [16] summary.corIdent*                   summary.corLin*                     summary.corNatural*                
    +## [19] summary.corRatio*                   summary.corSpher*                   summary.corStruct*                 
    +## [22] summary.corSymm*                    summary.data.frame                  summary.Date                       
    +## [25] summary.default                     summary.Duration*                   summary.ecdf*                      
    +## [28] summary.factor                      summary.gam*                        summary.ggplot*                    
    +## [31] summary.glm                         summary.gls*                        summary.haven_labelled*            
    +## [34] summary.hcl_palettes*               summary.infl*                       summary.Interval*                  
    +## [37] summary.lm                          summary.lme*                        summary.lmList*                    
    +## [40] summary.loess*                      summary.manova                      summary.matrix                     
    +## [43] summary.mlm*                        summary.modelStruct*                summary.nls*                       
    +## [46] summary.nlsList*                    summary.packageStatus*              summary.pdBlocked*                 
    +## [49] summary.pdCompSymm*                 summary.pdDiag*                     summary.pdIdent*                   
    +## [52] summary.pdIdnot*                    summary.pdLogChol*                  summary.pdMat*                     
    +## [55] summary.pdNatural*                  summary.pdSymm*                     summary.pdTens*                    
    +## [58] summary.Period*                     summary.POSIXct                     summary.POSIXlt                    
    +## [61] summary.ppr*                        summary.prcomp*                     summary.princomp*                  
    +## [64] summary.proc_time                   summary.reStruct*                   summary.rlang_error*               
    +## [67] summary.rlang_message*              summary.rlang_trace*                summary.rlang_warning*             
    +## [70] summary.rlang:::list_of_conditions* summary.shingle*                    summary.srcfile                    
    +## [73] summary.srcref                      summary.stepfun                     summary.stl*                       
    +## [76] summary.table                       summary.trellis*                    summary.tukeysmooth*               
    +## [79] summary.varComb*                    summary.varConstPower*              summary.varConstProp*              
    +## [82] summary.varExp*                     summary.varFixed*                   summary.varFunc*                   
    +## [85] summary.varIdent*                   summary.varPower*                   summary.vctrs_sclr*                
    +## [88] summary.vctrs_vctr*                 summary.warnings                   
     ## see '?methods' for accessing help and source code

    Każda z wypisanych nazw odpowiada wariantowi metody dla jednej klasy. Zwrócmy uwagę na specyficzną składnię nazw tych funkcji - człon po kropce odpowiada nazwie klasy, jakiej metoda dotyczy. Przyjrzyjmy się wariantom summary dla dwóch różnych klas: lm i matrix.

    -
    X <- matrix(rep(1,12), 6,2)
    -Y <- c(2,2,3,2,2,2)
    -model <- lm(Y~X)
    -
    -summary.lm(model)
    +
    X <- matrix(rep(1,12), 6,2)
    +Y <- c(2,2,3,2,2,2)
    +model <- lm(Y~X)
    +
    +summary.lm(model)
    ## 
     ## Call:
     ## lm(formula = Y ~ X)
    @@ -455,7 +467,7 @@ 

    12.2.2.1 Funkcje generyczne

    -
    summary.matrix(X)
    +
    summary.matrix(X)
    ##        V1          V2   
     ##  Min.   :1   Min.   :1  
     ##  1st Qu.:1   1st Qu.:1  
    @@ -464,7 +476,7 @@ 

    12.2.2.1 Funkcje generyczne

    Jednak, by użyć odpowiedniej funkcji dla obiektu, nie musimy specyfikować jego klasy - właśnie dzięki zdefiniowaniu funkcji generycznej. Bez względu na klasę obiektu uzywamy składni funkcja_generyczna(obiekt). Wywoływana jest wówczas funkcja generyczna, która na podstawie klasy lub typu obiektu dopasowuje wariant metody. Spójrzmy jak to wygląda na przykładzie summary:

    -
    summary(model)
    +
    summary(model)
    ## 
     ## Call:
     ## lm(formula = Y ~ X)
    @@ -482,7 +494,7 @@ 

    12.2.2.1 Funkcje generyczne

    -
    summary(X)
    +
    summary(X)
    ##        V1          V2   
     ##  Min.   :1   Min.   :1  
     ##  1st Qu.:1   1st Qu.:1  
    @@ -495,42 +507,42 @@ 

    12.2.2.1 Funkcje generyczne

    12.2.2.2 Tworzenie funkcji generycznych i metod

    Nowe funkcje generyczne tworzy się według następującego schematu:

    -
    nazwa_metody <- function (x) {
    -  UseMethod("nazwa_metody", x)
    -}
    +
    nazwa_metody <- function (x) {
    +  UseMethod("nazwa_metody", x)
    +}

    Po utworzeniu funkcji generycznej możemy przystąpić do napisania metod dla konkretnych klas. Metody tworzymy jako funkcje nazwane według konwencji nazwa_metody.klasa.

    Spróbujmy wg powyższego schematu utworzyć metodę dla utworzonej wcześniej klasy jedzenie. Niech nasza metoda nazywa się zjedz:

    -
    zjedz <- function(x){
    -  UseMethod("zjedz",x)
    -}
    +
    zjedz <- function(x){
    +  UseMethod("zjedz",x)
    +}

    Mając gotową funkcję generyczną możemy przejść do określenia zachowania metody dla naszej klasy:

    -
    zjedz.jedzenie <- function(x){
    -  cat("Mniam mniam\n")
    -}
    -zjedz(szarlotka)
    +
    zjedz.jedzenie <- function(x){
    +  cat("Mniam mniam\n")
    +}
    +zjedz(szarlotka)
    ## Mniam mniam
    -
    zjedz(kanapka)
    +
    zjedz(kanapka)
    ## Mniam mniam
    -
    zjedz(hot_dog)
    +
    zjedz(hot_dog)
    ## Mniam mniam

    Oraz, jeśli potrzebujemy, również dla innych istniejących klas, w tym również wbudowanych, np. matrix.

    -
    zjedz.matrix <- function(x){
    -  warning("Przeciez to macierz! Tego sie nie je!\n")
    -}
    -zjedz(matrix(c(1,2,2,3),2,2))
    +
    zjedz.matrix <- function(x){
    +  warning("Przeciez to macierz! Tego sie nie je!\n")
    +}
    +zjedz(matrix(c(1,2,2,3),2,2))
    ## Warning in zjedz.matrix(matrix(c(1, 2, 2, 3), 2, 2)): Przeciez to macierz! Tego sie nie je!

    W szczególności możemy określić zachowanie metody dla pseudoklasy default, czyli określić domyślne zachowanie metody. W kontekście naszego przykładu z klasą jedzenie moglibyśmy oczekiwać, że nasza metoda domyślnie nie będzie “jeść” żadnych obiektów, robiąc wyjątek wyłącznie dla jedzenia.

    -
    zjedz.default <- function(x,...){
    -  warning("Tego sie nie je!\n")
    -}
    -zjedz(mean)
    +
    zjedz.default <- function(x,...){
    +  warning("Tego sie nie je!\n")
    +}
    +zjedz(mean)
    ## Warning in zjedz.default(mean): Tego sie nie je!
    -
    zjedz(c(1,2,3,4))
    +
    zjedz(c(1,2,3,4))
    ## Warning in zjedz.default(c(1, 2, 3, 4)): Tego sie nie je!

    Zauważmy, że istnieje tu pewna hierarchia:

    -
    zjedz(matrix(c(1,2,3,4,4,5),2,3))
    +
    zjedz(matrix(c(1,2,3,4,4,5),2,3))
    ## Warning in zjedz.matrix(matrix(c(1, 2, 3, 4, 4, 5), 2, 3)): Przeciez to macierz! Tego sie nie je!
    -
    zjedz(szarlotka)
    +
    zjedz(szarlotka)
    ## Mniam mniam

    Funkcja generyczna wywołuje metodę dla pseudoklasy default wtedy, gdy dla danej klasy indywidualnie nie ma zdefiniowanej metody. W pierwszej kolejności szuka metody dedykowanej dla danej klasy, dlatego dla obiektów klas jedzenie i matrix zostały wywołane odpowiednio zjedz.jedzenie i zjedz.matrix, a nie zjedz.default.

    @@ -539,62 +551,62 @@

    12.2.2.2 Tworzenie funkcji genery

    12.2.3 Dziedziczenie

    O dziedziczeniu mówimy, gdy jedna z klas przejmuje (dziedziczy) właściwości i cechy innej. W S3 dziedziczenie wprowadza się w zaskakująco oczywisty i prosty sposób.

    Każdemu obiektowi można przypisać więcej niż jedną klasę, przypisując class(obiekt) nie jedną nazwę klasy, lecz ich wektor, np. dla utworzonego wcześniej obiektu szarlotka:

    -
    class(szarlotka) <- c("ciasto", "slodkosci", "jedzenie")
    -class(szarlotka)
    +
    class(szarlotka) <- c("ciasto", "slodkosci", "jedzenie")
    +class(szarlotka)
    ## [1] "ciasto"    "slodkosci" "jedzenie"

    Kolejność klas w wektorze nie jest przypadkowa: zaczynamy od klasy “najmłodszej” (i najważniejszej, najbardziej specyficznej), a kończymy na “najstarszej” (najogólniejszej). Kolejność ta jest również obowiązująca przy dopasowywaniu metod przez funkcję generyczną: funkcja generyczna najpierw szuka metody dla pierwszej z klas w wektorze, następnie dla drugiej itd. Jeśli nie uda jej się znaleźć metody dla żadnej z klas, wywołuje metodę dla pseudoklasy default.

    -
    zjedz.ciasto <- function(x){
    -  cat("Mniam mniam, pyszne ciacho!\n")
    -}
    -zjedz.slodkosci <- function(x){
    -  cat("Słodkie, dobre, mniam.. \n")
    -}
    -zjedz(szarlotka)
    +
    zjedz.ciasto <- function(x){
    +  cat("Mniam mniam, pyszne ciacho!\n")
    +}
    +zjedz.slodkosci <- function(x){
    +  cat("Słodkie, dobre, mniam.. \n")
    +}
    +zjedz(szarlotka)
    ## Mniam mniam, pyszne ciacho!

    Zgodnie z oczekiwaniami funkcja generyczna wywołała metodę dla pierwszej z klas w wektorze, czyli klasy ciasto, ignorując metody dla klas slodkosci i jedzenie.

    W większości przypadków jednak wprowadzamy nowe klasy nie po to, by zastępować istniejące metody dla klas ogólniejszych, lecz po to by wprowadzić pewne rozszerzenia, np. metody czy atrybuty, które nie mają sensu dla innych obiektów z klasy - rodzica.

    Wprowadźmy metodę pokroj. Dla każdego obiektu klasy jedzenie bedzie ona dzialac identycznie - dzielić atrybut kalorie przez wskazane n i zwracać n równych “porcji” :

    -
    pokroj<-function(x,...){
    -  UseMethod("pokroj",x)
    -}
    -pokroj.jedzenie <- function(x,n){
    -  porcja<-attr(x, "kalorie")/n
    -  rep(porcja,n)
    -}
    -pokroj(kanapka,4)
    +
    pokroj<-function(x,...){
    +  UseMethod("pokroj",x)
    +}
    +pokroj.jedzenie <- function(x,n){
    +  porcja<-attr(x, "kalorie")/n
    +  rep(porcja,n)
    +}
    +pokroj(kanapka,4)
    ## [1] 37.5 37.5 37.5 37.5

    Oprócz tego wprowadźmy metodę sensowną tylko dla klasy ciasto: posyp_cukrem_pudrem.

    -
    posyp_cukrem_pudrem<-function(x,...){
    -  UseMethod("posyp_cukrem_pudrem",x)
    -}
    -posyp_cukrem_pudrem.ciasto <- function(x){
    -  cat("Syp syp syp\n")
    -}
    +
    posyp_cukrem_pudrem<-function(x,...){
    +  UseMethod("posyp_cukrem_pudrem",x)
    +}
    +posyp_cukrem_pudrem.ciasto <- function(x){
    +  cat("Syp syp syp\n")
    +}

    Zauważmy, że przez wprowadzenie dodatkowych klas obiekt szarlotka nie traci funkcjonalności klasy jedzenie:

    -
    pokroj(szarlotka,5)
    +
    pokroj(szarlotka,5)
    ## [1] 53 53 53 53 53

    Podczas gdy równocześnie możliwym stało się zdefiniowanie dla niego indywidualnych metod:

    -
    posyp_cukrem_pudrem(szarlotka)
    +
    posyp_cukrem_pudrem(szarlotka)
    ## Syp syp syp
    -
    posyp_cukrem_pudrem(hot_dog)
    +
    posyp_cukrem_pudrem(hot_dog)
    ## Error in UseMethod("posyp_cukrem_pudrem", x): niestosowalna metoda dla 'posyp_cukrem_pudrem' zastosowana do obiektu klasy "jedzenie"

    Dziedziczenie może być bardzo użyteczne, ale należy je stosować z ostrożnością, mając w pamięci elastyczność klas i atrybutów w S3. Bardzo łatwo stracić orientację, w szczególności wprowadzając dziedziczenie po klasach wbudowanych lub po klasach zbudowanych przez kogoś innego - wówczas ciężko nam wziąć pod uwagę wszystkie istniejące dla danych klas metody. Posługując się dziedziczeniem i klasami w sposób rozrzutny i nieprzemyślany łatwo możemy doprowadzić do chaosu.

    12.2.3.1 NextMethod

    NextMethod jest używane w sytuacji, gdy wewnątrz metody klasy chcemy wywołać metodę klasy nadrzędnej (rodzica).

    -
    zjedz.ciasto<- function(x){
    -  cat("Mniam mniam, pyszne ciacho!\n")
    -  NextMethod()
    -}
    -
    -zjedz(szarlotka)
    +
    zjedz.ciasto<- function(x){
    +  cat("Mniam mniam, pyszne ciacho!\n")
    +  NextMethod()
    +}
    +
    +zjedz(szarlotka)
    ## Mniam mniam, pyszne ciacho!
     ## Słodkie, dobre, mniam..
    -
    zjedz.slodkosci <- function(x){
    -  cat("Słodkie, dobre, mniam.. \n")
    -  NextMethod()
    -}
    -zjedz(szarlotka)
    +
    zjedz.slodkosci <- function(x){
    +  cat("Słodkie, dobre, mniam.. \n")
    +  NextMethod()
    +}
    +zjedz(szarlotka)
    ## Mniam mniam, pyszne ciacho!
     ## Słodkie, dobre, mniam.. 
     ## Mniam mniam
    @@ -606,52 +618,52 @@

    12.3 S4
    # pierwszym argumentem funkcji jest nazwa klasy
    -setClass("nazwa_klasy",
    -  slots = c(
    -    # tutaj definiowane są sloty i ich typ
    -    slot_1 = "data.frame",
    -    slot_2 = "list"
    -  ),
    -  prototype = c(
    -    # tutaj definiowane są wartości domyślne (prototyp) dla slotów
    -    slot_1 = data.frame(),
    -    slot_2 = list()
    -  )
    -)

    +
    # pierwszym argumentem funkcji jest nazwa klasy
    +setClass("nazwa_klasy",
    +  slots = c(
    +    # tutaj definiowane są sloty i ich typ
    +    slot_1 = "data.frame",
    +    slot_2 = "list"
    +  ),
    +  prototype = c(
    +    # tutaj definiowane są wartości domyślne (prototyp) dla slotów
    +    slot_1 = data.frame(),
    +    slot_2 = list()
    +  )
    +)

    Funkcja setClass posiada również parametr contains, który odpowiada za dziedziczenie po innych klasach:

    -
    setClass("klasa_rodzic", 
    -         slots=c(
    -           macierz = "matrix"
    -         ))
    -setClass("klasa_dziecko", contains="klasa_rodzic")
    +
    setClass("klasa_rodzic", 
    +         slots=c(
    +           macierz = "matrix"
    +         ))
    +setClass("klasa_dziecko", contains="klasa_rodzic")

    Funkcje generyczne z użyciem których tworzymy metody S4 są rozróżniane od funkcji generycznych systemu S3. Mechanizm tworzenia metody jest więc bardzo podobny, ale używamy do tego celu dedykowanych dla S4 funkcji.

    -
    # tworzenie funkcji generycznej S4
    -setGeneric("nazwa_metody", function(x, ...) standardGeneric("nazwa_metody"))
    +
    # tworzenie funkcji generycznej S4
    +setGeneric("nazwa_metody", function(x, ...) standardGeneric("nazwa_metody"))
    ## [1] "nazwa_metody"
    -
    # tworzenie metody dla klasy
    -setMethod("nazwa_metody", "nazwa_klasy", function(x,...){
    -  # działanie metody na obiekcie klasy 
    -})
    +
    # tworzenie metody dla klasy
    +setMethod("nazwa_metody", "nazwa_klasy", function(x,...){
    +  # działanie metody na obiekcie klasy 
    +})

    12.3.1 Bonus: przykład wykorzystania systemu S4

    S4, choć bardziej restrykcyjny niż S3, nadal daje dużo swobody w przypisywaniu klas, dziedziczeniu, w szczególności w manewrowaniu wbudowanymi klasami R.

    Wykorzystaliśmy to w naszym raczkującym pakiecie autoeda do ominięcia problemu przypisania różnego zachowania funkcji w zależności od otrzymanego typu danych.

    Celem było obliczenie tej samej funkcji (np. średniej) dla wszystkich kolumn danych, przy założeniu, że nasz zbiór danych jest średniej wielkości (kilkadziesiąt kolumn - zmiennych). Jeśli niemożliwe jest obliczenie funkcji dla danej kolumny (np. próbujemy obliczyć średnią z kolumny stringów), chcieliśmy uniknąć przerywania pracy funkcji i zwracać NA.

    By osiągnąć powyższy rezultat zdecydowaliśmy się zdefiniować klasę funkcji - miar obliczanych na kolumnach jako klasę dziedziczącą po… klasie funkcji generycznych:

    -
    setClass("RankingMeasure",
    -  slots = c(
    -    name = "character",
    -    description = "character"
    -  ),
    -  prototype = list(
    -    name = NA_character_,
    -    description = NA_character_
    -  )
    -)
    -setClass("BuiltInMeasure",
    -  contains = c("standardGeneric", "RankingMeasure")
    -)
    +
    setClass("RankingMeasure",
    +  slots = c(
    +    name = "character",
    +    description = "character"
    +  ),
    +  prototype = list(
    +    name = NA_character_,
    +    description = NA_character_
    +  )
    +)
    +setClass("BuiltInMeasure",
    +  contains = c("standardGeneric", "RankingMeasure")
    +)

    Następnie dla każdej potrzebnej nam funkcji utworzyliśmy odpowiadający jej obiekt - funkcję generyczną klasy BuiltInMeasure i zdefiniowaliśmy metody tej funkcji generycznej dla możliwych typów zmiennych, zwracając NA domyślnie i wynik liczbowy, gdzie to możliwe.

    diff --git a/docs/przetwarzanie-danych-tabelarycznych.html b/docs/przetwarzanie-danych-tabelarycznych.html index 59c6cdf..3529947 100644 --- a/docs/przetwarzanie-danych-tabelarycznych.html +++ b/docs/przetwarzanie-danych-tabelarycznych.html @@ -23,7 +23,7 @@ - + diff --git a/docs/search_index.json b/docs/search_index.json index 3689c1c..0aef113 100644 --- a/docs/search_index.json +++ b/docs/search_index.json @@ -1 +1 @@ -[["index.html", "Notatki z laboratoriów ,,Programowanie i analiza danych w R’’ Instytut Matematyczny, Uniwersytet Wrocławski 1 Wstęp", " Notatki z laboratoriów ,,Programowanie i analiza danych w R’’ Instytut Matematyczny, Uniwersytet Wrocławski Mateusz Staniak 2023-10-12 1 Wstęp Zaliczenie przedmiotu opiera się głównie na projekcie grupowym. Przykładowy projekt z poprzednich edycji: SpotifyViz. Autorzy poszczególnych rozdziałów: Podstawy języka R: Michał Dylewicz, Marcela Kamchen, Anna Krasoń, Katarzyna Kulon, Arkadiusz Soból (z wyjątkiem podrozdziału Funkcje). Wczytywanie danych: Marta Kałużna, Sebastian Jachimek, Joanna Grunwald, Wojciech Wojnar. Eksploracyjna analiza danych: Magdalena Mazur, Agata Rogowska, Zuzanna Różak, Aleksandra Siepiela. [Także podrozdział Funkcje pierwszego rozdziału.] Podstawy kontroli wersji z Gitem: Magdalena Mazur, Agata Rogowska, Zuzanna Różak, Aleksandra Siepiela. Przetwarzanie danych tabelarycznych: Weronika Domaszewska, Ewelina Grzmocińska, Gracjan Hrynczyszyn, Dominik Jaźwiecki, Michał Ociepa. Czyste dane: Kacper Ambroży, Dominika Szewc, Radosław Szudra, Helena Wołoch. Wizualizacja danych z pakietem ggplot2: Katarzyna Frankiewicz, Maciej Grabias, Jakub Michałowski Czysty i wydajny kod w R: Paulina Bannert, Natalia Bercz, Piotr Mrozik, Dariusz Sudół, Monika Wyźnikiewicz Interaktywna wizualizacja danych z pakietem shiny: interfejs użytkownika: Stanisław Banaszek, Mateusz Drobina, Dominik Mika, Adrian Płoszczyca, Jakub Sobkowiak Interaktywna wizualizacja danych z pakietem shiny: strona serwerowa: Wojciech Leszkowicz, Małgorzata Stawińska, Tomasz Szmyd, Maciej Tadej. Dodatkowe rozdziały: Podstawy kontroli wersji przy pomocy Gita: Magdalena Mazur, Agata Rogowska, Zuzanna Różak, Aleksandra Siepiela. Programowanie obiektowe w R: klasy S3: Agata Cieślik. Moduły w aplikacjach shiny: Krystyna Grzesiak. "],["podstawy-języka-r.html", "2 Podstawy języka R 2.1 Liczby 2.2 Łańcuchy znaków 2.3 Wartości logiczne 2.4 Wektory 2.5 Indeksowanie 2.6 Operacje na wektorach 2.7 R - funkcje", " 2 Podstawy języka R Język R posiada kilka typów danych, które pokrótce postaramy sie omówić poniżej. Pokażemy ich budowe jak i operacje na nich, przytaczając stosowne przyklady. 2.1 Liczby Liczby całkowite i rzeczywiste (tutaj separator dziesiętny to kropka). Możemy używać również notacji naukowej. Operacje na liczbach to podstawowe działania matematyczne jak i trochę rozszerzone, ukazane niżej wraz z specjalnymi liczbami. 5; 5.5; 5.5e-2; ## [1] 5 ## [1] 5.5 ## [1] 0.055 Tutaj liczby specjalne, NaN # not a number ## [1] NaN Inf # nieskończoność ## [1] Inf -Inf # - nieskończoność ## [1] -Inf oraz kilka działań na liczbach 1 + 1 # podobnie '-' to odejmowanie ## [1] 2 4/2 # dzielenie, a '*' to mnożenie ## [1] 2 5 %/% 3 # dzielenie całkowite ## [1] 1 5 %% 3 # reszta z dzielenia ## [1] 2 2^3 # potęgowanie ## [1] 8 2**3 # też potęgowanie ## [1] 8 sqrt(4) #pierwiastkowanie ## [1] 2 abs(-1) # wartość bezwzględna ## [1] 1 2.2 Łańcuchy znaków Łańcuch znaków to po prostu napi. Napis jest otoczony przez ” lub ’. W napisie możemy umieszczać dowolne znaki, pamiętając że są też znaki specjalne (rozpoczynające się od \\ i mające specjalne funkcje). Na napisach istnieje wiele operacji (np. \\(\\verb+paste()+,\\) czyli sklejenie dwóch napisów), lecz je zobaczymy w notatce o napisach. "napis" ## [1] "napis" 'to też' ## [1] "to też" "'a tutaj nawet z bonusem'" ## [1] "'a tutaj nawet z bonusem'" # ""a"" to już wbrew intuicji nie jest napis cat("i znak \\n specjalny, wstawiający nową linie") # cat() wyświetla napis w sposób niesformatowany ## i znak ## specjalny, wstawiający nową linie 2.3 Wartości logiczne Logiczna Prawda (\\(\\verb+TRUE+\\) lub \\(\\verb+T+\\)) oraz logiczny Fałsz (\\(\\verb+FALSE+\\) lub \\(\\verb+F+\\)). Na tych obiektach możemy wykonywać operacje logiczne oraz algebraiczne. TRUE & TRUE # operator 'i' ## [1] TRUE TRUE | FALSE # operator 'lub' ## [1] TRUE 1 == 1 # testowanie równości ## [1] TRUE 1 != 2 # testowanie nierówności ## [1] TRUE 2*TRUE # TRUE ma wartość 1 ## [1] 2 2*FALSE # FALSE ma wartość 0 ## [1] 0 T ; `T` <- FALSE; T # używając `` możemy zmienić wartość logiczną wyrażenia ## [1] FALSE ## [1] FALSE 2.4 Wektory Wektor to w R uporządkowany zbiór elementów. Elementy te muszą mieć ten sam typ, także jeśli do wektora trafią elementy z różnym typem (poza NA), to nastąpi konwersja elementów do jednego typu. Proste wektory tworzymy przez polecenie \\(\\verb+c()+\\) i elementy wypisujemy w nawiasie po przecinku. Dodatkowo, element wektora jest traktowany jako jednoelementowy wektor. Wektory liczbowe jak i inne możemy tworzyć za pomocą wbudowanych funkcji do tego przeznaczonych. v <- c(1, 2, 3) #przypisanie wektora do zmiennej 0:10 # wektor liczbowy ## [1] 0 1 2 3 4 5 6 7 8 9 10 seq(from = 0, to = 10, by = 1) # to samo, ale za pomocą seq(), czyli sequance ## [1] 0 1 2 3 4 5 6 7 8 9 10 seq(0, 1, length.out = 4) # równe odstępy w 4 liczbowym wektorze ## [1] 0.0000000 0.3333333 0.6666667 1.0000000 length(v) # zwraca długość vectora ## [1] 3 # vector(mode, lenght) tworzy wektor dlugosci lenght, a wyrazy tego wektora maja klase mode vector("integer", 10) # wektor liczb calkowitych ## [1] 0 0 0 0 0 0 0 0 0 0 vector("numeric", 10) # wektor liczb rzeczywistych ## [1] 0 0 0 0 0 0 0 0 0 0 vector("character", 10) # wektor slów ## [1] "" "" "" "" "" "" "" "" "" "" rep(v, each = 2) # każdy element v zostanie powtórzony 2 razy ## [1] 1 1 2 2 3 3 rep(v, times = 2) # v zostanie powtórzony 2 razy ## [1] 1 2 3 1 2 3 # mały mix tj. tutaj element v traktujemy jako wektor jednoelementowy # i powtarzamy times razy rep(v, times = 1:3) ## [1] 1 2 2 3 3 3 x <- c("a", "A") # wektor napisowy v <- "a" # to też toupper(x) # zmieni stringi w argumencie na wielkie litery ## [1] "A" "A" tolower(x) # zmieni stringi w argumencie na male litery ## [1] "a" "a" 2.5 Indeksowanie W R wektory są indeksowane od 1 (a nie od 0 jak w wielu językach programowania!). Aby odwołać się do konkretnego elementu wektora korzystamy z nawiasów kwadratowych \\(\\verb+[]+.\\) letters[3] ## [1] "c" Można wybrać więcej niż jeden element, wpisując w nawiasach kwadratowych wektor indeksów. letters[1:10] ## [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" letters[c(1, 15)] ## [1] "a" "o" letters[seq(1, 20, by = 2)] ## [1] "a" "c" "e" "g" "i" "k" "m" "o" "q" "s" Jeśli przed wektorem indeksów widnieje znak minus, R zwróci wszystkie elementy wektora z wyjątkiem tych w nawiasie kwadratowym. letters[-(1:10)] # niezbędny nawias wokół 1:10 ## [1] "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" letters[-c(1, 15)] ## [1] "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" letters[-seq(1, 20, by = 2)] ## [1] "b" "d" "f" "h" "j" "l" "n" "p" "r" "t" "u" "v" "w" "x" "y" "z" Pod wybrane indeksy można przypisać nowe wartości. new_letters <- letters new_letters[1:5] <- LETTERS[1:5] new_letters ## [1] "A" "B" "C" "D" "E" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" Albo pod każdy wybrany indeks nową wspólną wartość. new_letters[1:5] <- "x" new_letters ## [1] "x" "x" "x" "x" "x" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" Tworząc wektor funkcją \\(\\verb+c()+,\\) możemy nazwać każdy z jego elementów. str_vec_nam <- c("a" = "A", "b" = "B", "c" = "C") str_vec_nam ## a b c ## "A" "B" "C" Może być to użyteczne przy odwoływaniu się do konkretnego elementu wektora, nie trzeba wtedy znać numeru jego indeksu. str_vec_nam["a"] ## a ## "A" str_vec_nam[c("a", "c")] ## a c ## "A" "C" str_vec_nam[c("c", "a")] ## c a ## "C" "A" Wektory możemy również indeksować za pomocą wektorów logicznych. Działa to wtedy jak wybieranie tych elementów wektora, które spełniają ustalony warunek. x_ind <- new_letters == "x" x_ind ## [1] TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE new_letters[x_ind] ## [1] "x" "x" "x" "x" "x" "x" seq_vec <- seq(0, 1, length.out = 10) seq_vec[seq_vec < 0.5] ## [1] 0.0000000 0.1111111 0.2222222 0.3333333 0.4444444 Można oczywiście rozbudowywać wyrażenia logiczne, np. następująco: seq_vec[seq_vec < 0.3 | seq_vec > 0.8] ## [1] 0.0000000 0.1111111 0.2222222 0.8888889 1.0000000 seq_vec[seq_vec > 0.3 & seq_vec < 0.8] ## [1] 0.3333333 0.4444444 0.5555556 0.6666667 0.7777778 2.6 Operacje na wektorach W R domyślnym i naturalnym zachowaniem funkcji na wektorach jest działanie element po elemencie 1:10 + seq(0, 1, length.out = 10) ## [1] 1.000000 2.111111 3.222222 4.333333 5.444444 6.555556 7.666667 8.777778 9.888889 11.000000 c(2,4,6,8)^(1:4) ## [1] 2 16 216 4096 W przypadku gdy wektory, na których wykonujemy obliczenia mają różne długości zachodzi recykling, tj. R samoistnie przedłuża krótszy wektor replikując go odpowiednią liczbę razy. Widzimy, że obie poniższe linie kodu dają taki sam efekt. 1:10 + 1:2 ## [1] 2 4 4 6 6 8 8 10 10 12 1:10 + rep(1:2, 5) ## [1] 2 4 4 6 6 8 8 10 10 12 Gdy długość dłuższego wektora nie jest wielokrotnością krótszego, recykling także zadziała, jednak R zgłosi warning. 1:10 + 1:3 ## Warning in 1:10 + 1:3: długość dłuszego obiektu nie jest wielokrotnością długości krótszego obiektu ## [1] 2 4 6 5 7 9 8 10 12 11 1:10 + 1:3 + 1:2 + 1:5 ## Warning in 1:10 + 1:3: długość dłuszego obiektu nie jest wielokrotnością długości krótszego obiektu ## [1] 4 8 10 11 13 12 11 15 17 18 Na wektorach możemy wykonywać oczywiście inne funkcje poza podstawowymi operacjami arytmetycznymi. Jedną z opcji jest posortowanie wektora. num_vec <- c(3,6,1,9,8,-3,0,102,-5) sort(num_vec) # sortowanie rosnące ## [1] -5 -3 0 1 3 6 8 9 102 sort(num_vec, decreasing = TRUE) # sortowanie malejące ## [1] 102 9 8 6 3 1 0 -3 -5 Odwrócić kolejnośc elementów wektora można następująco. rev(num_vec) ## [1] -5 102 0 -3 8 9 1 6 3 Oto kilka kolejnych funkcji. sum(num_vec) # suma elementów wektora ## [1] 121 prod(num_vec) # iloczyn elementów wektora ## [1] 0 mean(num_vec) # średnia elementów wektora ## [1] 13.44444 Przy operacjach jak powyższe należy jednak uważać na wektory zawierające “NA”. vec_with_NA <- c(3,6,1,NA) sum(vec_with_NA) ## [1] NA Aby zsumować wartości z pominięciem “NA” należy dopisać dodatkowy argument funkcji. sum(vec_with_NA, na.rm = TRUE) ## [1] 10 Analogicznie dla iloczynu i średniej elementów. prod(vec_with_NA) ## [1] NA prod(vec_with_NA, na.rm = TRUE) ## [1] 18 mean(vec_with_NA) ## [1] NA mean(vec_with_NA, na.rm = TRUE) ## [1] 3.333333 Lista jest podobna do wektora tj. jest pewnym ciągiem obiektów, tyle że jej elementy mogą mieć różne typy. l <- list(1:5) #lista z elementami bedacymi liczbami ## [[1]] ## [1] 1 2 3 4 5 l2 <- list(zwierze='dog', imie='Max',czyLubiInnePsy = TRUE) #lista z elementami bedacymi stringami lub wartosciami logicznymi ## $zwierze ## [1] "dog" ## ## $imie ## [1] "Max" ## ## $czyLubiInnePsy ## [1] TRUE Kolejnę różnica pomiedzy wektorem a listą jest możliwość odwoływania się do elementów listy za pomocą nazwy tego elementu i operatora $. Np: # odwolanie do elementu bedacego za pomoca [], # wynikiem takiej operacji jest lista zawierajaca wektor l[1] ## [[1]] ## [1] 1 2 3 4 5 # aby odwolac sie do konkretnego elementu uzwywamy [[]], na przyklad operacja l[[1]][2] # zwroci drugi element wektora z listy l[[1]][2] ## [1] 2 # nadpisywanie elementu listy wektorem l[[1]] <- c("a", "b", "c") # odwolanie do elementu za pomoca nazwy elementu l2$zwierze ## [1] "dog" l2$imie ## [1] "Max" l2$czyLubiInnePsy ## [1] TRUE Listy można łączyć oraz modyfikować. Funkcja \\(\\verb+lapply()+\\) to funkcja, która pozwala na wykonanie pewnego konkretnego działania na KAŻDYM elemencie z listy. Na przykład, możemy każdy element chcieć zapisać tylko dużymi literami: lapply(l2,toupper) ## $zwierze ## [1] "DOG" ## ## $imie ## [1] "MAX" ## ## $czyLubiInnePsy ## [1] "TRUE" Aby połączyć dwie listy, należy użyć \\(\\verb+c()+,\\) robiąc z dwóch list wektor i przypisując go do nowej zmiennej. l3 <- c(l,l2) ## [[1]] ## [1] "a" "b" "c" ## ## $zwierze ## [1] "dog" ## ## $imie ## [1] "Max" ## ## $czyLubiInnePsy ## [1] TRUE Macierz to obiekt dwuwymiarowy. Składa się z elementów tego samego typu. Tworzy się ją funkcją \\(\\verb+matrix()+,\\) do której podajemy wartości macierzy (zwykle w postaci wektora), liczbę wierszy i kolumn. matrix(data = 1:10, nrow = 2, ncol = 5) ## [,1] [,2] [,3] [,4] [,5] ## [1,] 1 3 5 7 9 ## [2,] 2 4 6 8 10 Widzimy, że R domyślnie wypełnia macierz po kolumnach. Aby wypełnić ją po wierszach ustalamy parametr \\(\\verb+byrow = TRUE+\\) m <- matrix(data = 1:10, nrow = 2, ncol = 5, byrow = TRUE) m ## [,1] [,2] [,3] [,4] [,5] ## [1,] 1 2 3 4 5 ## [2,] 6 7 8 9 10 Elementy macierzy wybiera się za pomocą dwóch indeksów - indeksu wiersza i indeksu kolumny umieszczonych w nawiasach kwadaratowych i rozdzielonych przecinkiem. m[2,3] ## [1] 8 Można również wybrać konkretne wiersze lub kolumny. m[1:2,3:4] # wybiera wiersze 1 i 2 oraz kolumny 3 i 4 ## [,1] [,2] ## [1,] 3 4 ## [2,] 8 9 m[2,c(1,4,5)] # wybiera wiersz 2 oraz kolumny 1,4 i 5 ## [1] 6 9 10 Nie podanie indeksu przed przecinkiem oznacza, że chcemy otrzymać wszystkie wiersze. Analogicznie nie podanie indeksu po przecinku oznacza, że chcemy otrzymać wszystkie kolumny. m[,c(1,3)] ## [,1] [,2] ## [1,] 1 3 ## [2,] 6 8 m[2,] ## [1] 6 7 8 9 10 Macierze, podobnie jak wektory, możemy także indeksować warunkami logicznymi. # zwraca elementy (w tym wypadku element) z pierwszej kolumny, # które są większe od 2 m[m[,1] > 2, 1] ## [1] 6 Można także indeksować macierz inną macierzą o dwóch kolumnach. Zwrócone zostaną wtedy elementy o indeksach będących wierszami tej macierzy. matrix_ind<- matrix(c(1, 2, 2, 3, 2, 4), byrow = TRUE, nrow = 3, ncol = 2) m[matrix_ind] ## [1] 2 8 9 Na macierzach o tych samych wymiarach możemy wykonywać operacje arytmetyczne. Trzeba zwrócić uwagę, że są one wykonywane element po elemencie (z matematycznego punktu widzenia jest to oczekiwane przy dodawaniu, ale nieoczekiwane przy mnożeniu macierzy). m1 <- matrix(1:4,2,2) m1 ## [,1] [,2] ## [1,] 1 3 ## [2,] 2 4 m2 <- matrix(2:5,2,2) m2 ## [,1] [,2] ## [1,] 2 4 ## [2,] 3 5 m1 + m2 ## [,1] [,2] ## [1,] 3 7 ## [2,] 5 9 m1 * m2 ## [,1] [,2] ## [1,] 2 12 ## [2,] 6 20 Aby wykonać matematyczne mnożenie macierzy należy użyć operatora \\(\\verb+%*%+.\\) m1 %*% m2 ## [,1] [,2] ## [1,] 11 19 ## [2,] 16 28 Jest to obiekt przechowujący dane w postaci tabeli dwuwymiarowej, którą tworzą wektory o dowolnym typie. Z ramki danych można korzystać jak z macierzy dwuwymiarowej (poprzez korzystanie z \\(\\verb+[,]+\\)), jak i z listy (poprzez korzystanie z $). imie <- c("Max", "Reksio","Rex","Luna") #utworzymy ramke z 2 wektorow wiek <- c(2,8,3,11) ramka <- data.frame(imie,wiek) #ramke tworzymy za pomoca polecenia data.frame() ## imie wiek ## 1 Max 2 ## 2 Reksio 8 ## 3 Rex 3 ## 4 Luna 11 #wyswietlanie nazw kolumn names(ramka) ## [1] "imie" "wiek" #odnoszenie sie do elementu znajdujacego sie w 2. rzedzie i 1. kolumnie ramka[2,1] ## [1] "Reksio" #pobieranie paru wierszy na raz za pomoca wektora ramka[c(1, 2), ] ## imie wiek ## 1 Max 2 ## 2 Reksio 8 #pobieranie wszystkich kolumn dla 1. wiersza ramka[1,] ## imie wiek ## 1 Max 2 #pobieranie wszystkich wierszy dla 1. kolumny ramka[,1] ## [1] "Max" "Reksio" "Rex" "Luna" # pierwsza kolumna bez drugiego wiersza ramka[-2, 1] ## [1] "Max" "Rex" "Luna" #pobieranie kolumn/wierszy po nazwie ramka$wiek ## [1] 2 8 3 11 # inny sposób indeksowanie po nazwie ramka[, "wiek"] ## [1] 2 8 3 11 Indeksowanie na podstawie zawartości ramki danych Dane z ramki mogą być przez nas “filtrowane” za pomocą []. Na przykład # psy poniżej 9 roku życia ramka[ramka$wiek < 9, ] ## imie wiek ## 1 Max 2 ## 2 Reksio 8 ## 3 Rex 3 #dane tylko dla Reksia ramka[ramka$imie == "Reksio", ] ## imie wiek ## 2 Reksio 8 # analogicznie dla wektorów wiek[wiek < 9] ## [1] 2 8 3 Tworząc ramkę danych należy pamiętać o tym, aby wektory danych służące za kolumny były tej samej długości. #zamiana nazw kolumn names(ramka) <- c("imie_psa", "wiek_psa") ## imie_psa wiek_psa ## 1 Max 2 ## 2 Reksio 8 ## 3 Rex 3 ## 4 Luna 11 Ramki danych możemy powiększać o dodatkowe wiersze i kolumny, ale typy (dla wierszy) i rozmiary muszą sie zgadzać z typami i rozmiarem ramki danych. Rozpatrzmy poniższy przykład, aby pokazać, jak dodać wiersz i kolumnę za pomocą funkcji \\(\\verb+cbind()+\\) oraz \\(\\verb+rbind()+\\). #dodawanie nowego wiersza dodajemy_wiersz <- data.frame(imie_psa ="Quentin", wiek_psa=9) #funkcja rbind "skleja" wierszowo argument pierwszy (u nas ramka) z drugim ramka <- rbind(ramka,dodajemy_wiersz) #dodawanie nowej kolumny czyLubiInnePsy <- c(TRUE,TRUE, FALSE, TRUE, FALSE) #funkcja cbind "skleja" kolumnowo argument pierwszy (u nas ramka) z drugim ramka <- cbind(ramka,czyLubiInnePsy) ## imie_psa wiek_psa czyLubiInnePsy ## 1 Max 2 TRUE ## 2 Reksio 8 TRUE ## 3 Rex 3 FALSE ## 4 Luna 11 TRUE ## 5 Quentin 9 FALSE Możemy rownież dodawać wiersze za pomocą indeksowania, to znaczy przypisywania wartości do konkretnych indeksów ramki: #jako 6. wiersz "wkladamy" nowy wektor ramka[6,] <- c("Fanta",0.5,TRUE) ## imie_psa wiek_psa czyLubiInnePsy ## 1 Max 2 TRUE ## 2 Reksio 8 TRUE ## 3 Rex 3 FALSE ## 4 Luna 11 TRUE ## 5 Quentin 9 FALSE ## 6 Fanta 0.5 TRUE # jako 4.kolumne "wkladamy" nowy wektor ramka[,4] <- c("Mateusz","Romek","Renata","Leon","Quennie","Filip") # nazywamy kolumne 4. names(ramka)[4] <- "opiekun_psa" ## imie_psa wiek_psa czyLubiInnePsy opiekun_psa ## 1 Max 2 TRUE Mateusz ## 2 Reksio 8 TRUE Romek ## 3 Rex 3 FALSE Renata ## 4 Luna 11 TRUE Leon ## 5 Quentin 9 FALSE Quennie ## 6 Fanta 0.5 TRUE Filip Analizując nową dla nas ramkę danych, użyteczne okazują się funkcje pozwalające na poznanie właściwości ramki danych. Oto pare z nich: # wymiary ramki (6 wierszy,4 kolumny) mozna sprawdzic za pomoca funkcji dim() dim(ramka) ## [1] 6 4 # aby zobaczyc skrocony opis typow danych zawartych w ramce uzywana jest funkcja str() str(ramka) ## 'data.frame': 6 obs. of 4 variables: ## $ imie_psa : chr "Max" "Reksio" "Rex" "Luna" ... ## $ wiek_psa : chr "2" "8" "3" "11" ... ## $ czyLubiInnePsy: chr "TRUE" "TRUE" "FALSE" "TRUE" ... ## $ opiekun_psa : chr "Mateusz" "Romek" "Renata" "Leon" ... # aby "podejrzec" pierwsze wiersze ramki danych, wraz naglowkami kolumn uzywana jest funkcja head() head(ramka) ## imie_psa wiek_psa czyLubiInnePsy opiekun_psa ## 1 Max 2 TRUE Mateusz ## 2 Reksio 8 TRUE Romek ## 3 Rex 3 FALSE Renata ## 4 Luna 11 TRUE Leon ## 5 Quentin 9 FALSE Quennie ## 6 Fanta 0.5 TRUE Filip # wysietlanie pierwszych n wierszy head(ramka,n=2) ## imie_psa wiek_psa czyLubiInnePsy opiekun_psa ## 1 Max 2 TRUE Mateusz ## 2 Reksio 8 TRUE Romek # wyswietlanie ostatnich n wierszy za pomoca funkcji tail() tail(ramka,n=2) ## imie_psa wiek_psa czyLubiInnePsy opiekun_psa ## 5 Quentin 9 FALSE Quennie ## 6 Fanta 0.5 TRUE Filip Pętli oraz instrukcji warunkowych używamy, kiedy chcemy uniknąć powielania kodu i chcemy zachować jego przejrzystość. Ułatwia to wprowadzanie potencjalnych zmian. Instrukcje opisujące co powinno się zdarzyć należy umieścić w nawiasach \\(\\verb+{ }+\\). Jeśli chcemy wykonać tylko jedną linijke kodu, możemy je opuścić. Umożliwia warunkowe wykonanie kawałka kodu - jeśli warunek zawarty w \\(\\verb+if+\\) jest spełniony, to R przejdzie do zawartej instrukcji. W przeciwnym wypadku wykona polecenie zawarte w \\(\\verb+else+\\), a jeśli go nie ma , to przejdzie do kolejnych pętli. Część \\(\\verb+else+\\) nie jest wymagana, w tym wypadku z góry wiadomo ile razy kod zostanie wykonany. Składnia wygląda następująco: if(warunek) { instrukcja_1 } i jest analogiczna do if(warunek) instrukcja_1 Możemy także zapisać if(warunek) { instrukcja_1 instrukcja_2 } else { instrukcja_3 } Powiedzmy, że rozpatrujemy liczbe z rozkładu normalnego i sprawdzamy jakiego jest znaku. x_norm <- rnorm(1) if (x_norm < 0) { cat("Liczba", x_norm, "jest ujemna") } else { cat("Liczba ", x_norm, "jest dodatnia") } ## Liczba -0.6045206 jest ujemna Możemy chcieć wykonać różne operacje na tak wylosowanej liczbie. Przykładowo, jeśli będzie ujemna, to zmienić znak, zaokrąglić i zreplikować w wektorze if (x_norm < 0) { x_norm <- abs(x_norm) x_wek <- rep(round(x_norm, 2), times = 5) } else { x_wek <- "X" } i otrzymać 0.6, 0.6, 0.6, 0.6, 0.6 (X oznacza, że wylosowana liczba była dodatnia, a z nią nic nie robimy). Pętla \\(\\verb+while+\\) działa tak długo, dopóki warunek jest spełniony - tzn. do kiedy nie dostaniemy \\(\\verb+FALSE+\\). Warunek należy opisać tak, żeby w pewnym momencie został spełniony - inaczej pętla będzie działać w “nieskończoność”. Często używa sie jej do szukania losowych liczb o pewnych właściwościach. Składnia tej pętli jest następująca: while(warunek) { instrukcja_1 instrukcja_2 } Tutaj przykład wykorzystania, gdy chcemy losować liczby z przedziału [1, 100], dopóki różnica między dwoma kolejnymi nie będzie parzysta i <- 2 los <- c() los[1] <- 0 roznica <- 1 while(roznica%%2 != 0) { los <- c(los, sample(1:100, 1, replace = TRUE)) roznica <- los[i]-los[i-1] i = i+1 } W ten sposób dostajemy wylosowane liczby: 0, 85, 75, z różnicą między ostatnimi równą -10. Pętla \\(\\verb+for+\\) wygląda następująco: for(iterator in warunek) { instrukcja_1 instrukcja_2 } Ta pętla wykonuje instrukcje określoną ilość razy - tyle ile elementów \\(\\verb+iterator+\\) w zbiorze \\(\\verb+warunek+\\). W warunku możemy mieć liste albo wektor. Po każdym wykonaniu pętli, zmienna \\(\\verb+iterator+\\) przeskakuje do kolejnego elementu warunku. Jeśli chcemy wykonać tylko 1 instrukcje, można zapisać for(iterator in warunek) instrukcja_1 Przykładowo, jeśli chcemy elementy ze zbioru [1, 10] podnieść do potęgi, możemy użyć pętli \\(\\verb+for+\\). wynik <-c() for (i in 1:10) wynik <- c(wynik, i*i) wynik ## [1] 1 4 9 16 25 36 49 64 81 100 Możemy także napisać pętle zagnieżdżone, przykładowo do obliczenia wartości w macierzach. W tym wypadku wartością każdego elementu macierzy (3x3) jest iloczyn jego indeksów, co daje następujący wynik macierz <- matrix(nrow=3, ncol=3) for(i in 1:dim(macierz)[1]) { for(j in 1:dim(macierz)[2]) { macierz[i,j] = i*j } } macierz ## [,1] [,2] [,3] ## [1,] 1 2 3 ## [2,] 2 4 6 ## [3,] 3 6 9 Teraz zajmiemy się rodziną funkcji \\(\\verb+apply+\\). Należą do niej takie funkcję jak \\(\\verb+apply, tapply, sapply, lapply, vapply+\\). Wszystkie one pozwalają na wykonanie pewnej operacji na szeregu podzbiorów danych. Operacja, która ma być wykonana określana jest przez argument \\(\\verb+FUN+\\). Funkcje z tej rodziny przyjmują elementy listy \\(\\verb+(lapply()+)\\), elementy wektora \\(\\verb+(sapply())+\\), macierze \\(\\verb+(apply())+\\) oraz podgrup wskazanych przez jedną lub kilka zmiennych \\(\\verb+(by()+\\) i \\(\\verb+tapply())+\\). Zacznijmy od funkcji \\(\\verb+lapply()+\\). Wykonuje funkcję \\(\\verb+FUN+\\) dla wszystkich elementów wektora \\(\\verb+x+\\). Przydatna funkcja zastępująca pętlę \\(\\verb+for+\\). Domyślnie wynikiem działania jest lista, lecz jeżeli w wyniku chcielibyśmy otrzymać wektor, to jednym z rozwiązań jest zamiana listy na wektor funkcją \\(\\verb+unlist()+\\). Oto przykładowe działanie funkcji \\(\\verb+lapply()+\\): x=c(1,2,3,4,5,6,7,8,9,10) func=function(x){return(x**3-3*x)} lapply(x,func) ## [[1]] ## [1] -2 ## ## [[2]] ## [1] 2 ## ## [[3]] ## [1] 18 ## ## [[4]] ## [1] 52 ## ## [[5]] ## [1] 110 ## ## [[6]] ## [1] 198 ## ## [[7]] ## [1] 322 ## ## [[8]] ## [1] 488 ## ## [[9]] ## [1] 702 ## ## [[10]] ## [1] 970 Funkcja \\(\\verb+sapply+\\) jest bardziej przyjazną użytkownikowi wersją \\(\\verb+lapply+\\) zwracającą wektor lub macierz i może przyjmować więcej argumentów, np. \\(\\verb+sapply(x, f, simplify = FALSE, USE.NAMES = FALSE)+\\) zwraca ten sam wynik co \\(\\verb+lapply(x, f)+\\). Funkcja \\(\\verb+vapply+\\) jest podobna do \\(\\verb+sapply+\\), ale ma z góry określony typ zwracanych wartości, a może być również bezpieczniejszy w użyciu, a czasem nawet szybszy. Teraz weźmiemy pod lupe \\(\\verb+tapply()+\\), która to wykonuje funkcję \\(\\verb+FUN+\\) dla podzbiorów wektora \\(\\verb+x+\\) określonego przez poziomy zmiennej czynnikowej \\(\\verb+index+\\). Przydatna funkcja, gdy chcemy policzyć pewną statystykę w podgrupach, np. odchylenie standardowe w z wagami. W tym przypadku \\(\\verb+x+\\) będzie wektorem z wagami, \\(\\verb+index+\\) wektorem z płcią a \\(\\verb+FUN+\\) będzie funkcją sd). x=c(98,67,65,82,55,60,72,81,48,88) index=c('M','M','K','M','K','M','M','M','K','M') tapply(x,index,sd) ## K M ## 8.544004 12.944938 A teraz bardziej zaawansowana werssa funkcji \\(\\verb+tapply()+\\) z tą różnicą, że \\(\\verb+x+\\) może być macierzą lub listą, \\(\\verb+index+\\) może być listą, a wynik tej funkcji jest specyficznie wyświetlany. Jeżeli \\(\\verb+index+\\) jest listą zmiennych czynnikowych, to wartość funkcji \\(\\verb+FUN+\\) będzie wyznaczona dla każdego przecięcia czynników tych zmiennych. Wynik funkcji \\(\\verb+by()+\\) jest klasy \\(\\verb+by+\\), ale po usunięciu informacji o klasie, np. poprzez użycie funkcji \\(\\verb+unclass()+\\) otrzymujemy zwykłą macierz. Argument \\(\\verb+x+\\) może być listą lub macierzą, dzięki czemu do funkcji \\(\\verb+FUN+\\) przekazać można kilka zmiennych – elementów/kolumn listy/macierzy \\(\\verb+x+\\). m1=seq(1:9) x=c('a','b','c','a','b','c','a','b','c') by(m1,x,mean) ## x: a ## [1] 4 ## ------------------------------------------------------------------------------------------------------------------------------------------------------ ## x: b ## [1] 5 ## ------------------------------------------------------------------------------------------------------------------------------------------------------ ## x: c ## [1] 6 Z kolei \\(\\verb+mapply()+\\) to wielowymiarowy odpowiednik funkcji \\(\\verb+sapply()+\\). Argumentami tej funkcji jest funkcja \\(\\verb+fun+\\) oraz kilka (dwa lub więcej) wektorów o tej samej długości. Wynikiem jest wektor, w którym na pozycji \\(\\verb+i+\\)-tej jest wynik funkcji \\(\\verb+fun+\\) wywołanej z \\(\\verb+i+\\)-tych elementów wektorów będących argumentami. a=function(x,y){return(x**y)} mapply(a,x=seq(1,101,by=10),y=seq(1:11)) ## [1] 1.000000e+00 1.210000e+02 9.261000e+03 9.235210e+05 1.158562e+08 1.759629e+10 3.142743e+12 6.457535e+14 1.500946e+17 3.894161e+19 1.115668e+22 2.7 R - funkcje Funkcje przydają się do zamknięcia w nich operacji, które się często powtarzają w naszym kodzie lub dla jego lepszej czytelności. Podstawowa składnia funkcji w R wygląda tak: nazwa_funkcja <- function(argument 1, argument 2, …){ ciało funkcji return(wartość lub obiekt zwracany) } Napiszmy funkcję, która będzie mnożyła dowolny wektor przez podaną liczbę, a następnie zsumuje elementy wektora: funkcja1 <- function(wektor, liczba){ rezultat <- wektor * liczba rezultat <- sum(rezultat) return(rezultat) } Możemy także pominąc \\(\\texttt{return}\\) i zdefiniować funkcje: funkcja2 <- function(wektor, liczba){ rezultat <- wektor * liczba rezultat <- sum(rezultat) rezultat } Obie funkcje \\(\\texttt{funkcja1}\\) i \\(\\texttt{funkcja2}\\) robią to samo. Wykonajmy nasze funkcje dla dwóch zdefiniowanych zmiennych: v <- 1:5 n <- 2 funkcja1(v, n) ## [1] 30 funkcja2(v, n) ## [1] 30 Oczywiście do wykonania funkcji potrzebne jest zdefiniowanie obu argumentów. Jak ich nie dodamy wyświetli się błąd, że argument drugi zaginął i nie mamy zdefiniowanej jego wartości domyślnej. Zdefiniujmy zatem domyślną wartość argumentu \\(\\texttt{liczba}\\) jako \\(\\texttt{NULL}\\) i dopiszmy do naszej funkcji kod, który gdy ten argument będzie miał wartość domyślną zwróci tylko sumę elementów wektora: funkcja3 <- function(wektor, liczba = NULL){ if(is.null(liczba)){ rezultat <- sum(wektor) } else{ rezultat <- wektor * liczba rezultat <- sum(rezultat) } rezultat } Wykonajmy funckję \\(\\texttt{funkcja3}\\) na wcześniej zdefiniowanym wektorze \\(\\texttt{v}\\): funkcja3(v) ## [1] 15 Oprócz zdefiniowania wartości domyślnej argumentu poprzez trzy kropki możemy również dopuścić parametry dodatkowe. Zdefiniujmy funkcję z parametrami dodatkowymi: funkcja4 <- function(wektor, liczba = NULL, ...){ if(is.null(liczba)){ rezultat <- sum(wektor, ...) } else{ rezultat <- wektor * liczba rezultat <- sum(rezultat, ...) } rezultat } Wykonajmy funckję \\(\\texttt{funkcja4}\\) usuwając wartości brakujące z nowo zdefiniowanego wektora: v <- c(NA, 1, NA, 2:4, NA, 5) v ## [1] NA 1 NA 2 3 4 NA 5 funkcja4(v, na.rm = TRUE) ## [1] 15 Funkcje są bardzo przydatne, gdy mamy do napisania długi skrypt. Pozwalają na podzielenie głównej części kodu na mniejsze kawałeczki, które kolejnemu użytkownikowi skryptu lub nam będzie łatwiej modyfikować. "],["wczytywanie-danych-w-r.html", "3 Wczytywanie danych w R 3.1 Formaty danych 3.2 Locale 3.3 Natywne formaty R", " 3 Wczytywanie danych w R 3.1 Formaty danych 3.1.1 CSV/DSV CSV (Comma Separated Values) to plik tekstowy, w którym wartości rozdzielane są przecinkami, a kolejne wiersze znakiem nowej linii. Plik CSV zazwyczaj przechowuje dane tabelaryczne. Nagłówki kolumn są często dołączane jako pierwszy wiersz (są to nazwy zmiennych), a każdy kolejny wiersz odpowiada jednej obserwacji (jednemu wierszowi w tabeli danych). CSV jest szczególnym przypadkiem formatu danych o nazwie Delimiter Seperated Values (DSV). Jest to plik tekstowy w którym pola w każdym wierszu oddzielone są dowolnym separatorem. Najczęściej spotykane separatory to: przecinek (CSV), tabulator (TSV), średnik. Przykładowy plik CSV 3.1.2 XML XML to skrót od nazwy Extensible Markup Language. Dane przechowywane w tym formacie mają zagnieżdżoną strukturę: znaczniki oznaczają nazwy zmiennych, a wewnątrz przechowywane są ich wartości. XML swoją strukturą przypomina plik HTML. Przykładowy plik XML 3.1.3 JSON JSON - JavaScript Object Notation - to format przydatny w przypadku pracy z danymi pochodzącymi z REST API, czyli pobieranymi z sieci. Niektóre bazy danych również komunikują się za pomocą tego formatu, np. MongoDB. Struktura: w pliku JSON obserwacje przechowywane są w słownikach, w których nazwy zmiennych są kluczami, a wartości zmiennych - wartościami. Obserwacje oddzielane są przecinkami, a dodatkowo, wszystkie dane spięte są nawiasami klamrowymi. Przykładowy plik JSON 3.1.4 Excel (XLSX) XLSX to format danych oparty na XML. Pliki tego typu są domyślnymi dokumentami wyjściowymi arkuszy kalkulacyjnych programu Microsoft Excel. Przedstawiają one głównie dane liczbowe i tekstowe w postaci tabel dwuwymiarowych. Przykładowy arkusz kalkulacyjny w Excelu 3.1.5 Otwarte wersje programu Excel Istnieją inne pakiety biurowe, np. LibreOffice, które - w przeciwieństwie do Excela - pozwalają na darmowe korzystanie z arkusza kalkulacyjnego. W przypadku LibreOffice, domyślnym formatem zapisu danych przez Calc (odpowiednik Excela) jest OpenDocument Format (.ods). Przykładowy arkusz kalkulacyjny w LibreOffice 3.1.6 Pliki tekstowe Jednym z najczęściej występujących i najbardziej uniwersalnych formatów przechowujących dane (np. w postaci tabeli) są pliki tekstowe. Mają one najczęściej rozszerzenie txt lub csv (comma separated values). Poniższą charakteryzację różnych metod wczytywania przedstawiamy na podstawie pliku listings.csv 3.1.6.1 Base Podstawową funkcją używaną do wczytywania tego typu plików w postaci tabeli jest funkcja read.table. Ze względu na specyfikację wewnętrzną plików, read.table posiada kilka wariantów, takie jak read.csv(), read.csv2() czy read.delim(). read.csv() używana jest w przypadku, gdy domyślnym separatorem dziesiętnym jest “.”, a wartości w wierszach oddzielone są poprzez “,”; read.csv2() używana jest w przypadku, gdy domyślnym separatorem dziesiętnym jest “,”, a wartości w wierszach oddzielone są poprzez “;”; read.delim() używana jest w przypadku, gdy domyślnym separatorem dziesiętnym jest “.”, a wartości w wierszach oddzielone są poprzez TAB Przykładowy sposób załadowania plików w formacie csv read.csv('./data/csv/listings.csv', header = TRUE, sep = ",") W przypadku read.table() dane zostają zaimportowane jako data.frame. Dla dużych plików wczytwanie za pomocą read.table() bywa jednak czasochłonne. Wówczas możemy użyć funkcji z paczki data.table lub readr. 3.1.6.2 readr readr jest częścią pakietu tidyverse. W tym przypadku import odbywa się za pomocą funkcji o podobnej nazwie, jak w przypadku read.table(), a mianowicie read_csv(). read_csv wczytuje dane oddzielone przecinkami, natomiast read_csv2() - dane oddzielone średnikami. read_csv('./data/csv/listings.csv') W przeciwieństwie do read.csv, funkcja read_csv na wyjściu daje dane w postaci tabeli w bardziej zwartej i przejrzystej formie. Oprócz tego podaje także specyfikację kolumn, tzn. informuje, jaka jest nazwa każdej kolumny oraz jej typ (np. col_double () oznaczają dane liczbowe). Typ danych jaki dostajemy na wyjściu to tbl_df (tzw. tibble), który jest w pewnym sensie zmodyfikowaną wersją tradycyjnej ramki danych data.frame, pozwalającą na łatwiejszą pracę w obrębie tidyverse. 3.1.6.3 data.table Do wczytywania danych z plików csv możemy także użyć funkcji fread z pakietu data.table. fread('./data/csv/listings.csv') Na wyjściu otrzymujemy ramkę danych, jednak wyświetloną w inny sposób niż w przypadku użycia read.csv. Różnica jest widoczna, gdyż po użyciu funkcji class() na fread() jako typ danych otrzymujemy \"data.table\" \"data.frame\". 3.1.6.4 Różnice Najważniejsze różnice pomiędzy wymienionymi sposobami wczytywania plików csv to: Typ danych Base: `data.frame readr: tibble data.table: `data.table data.frame Postać wyświetlania (co jest konsekwencją 1) Base: Wyświetla 62 początkowe wiersze każdej kolumny, wyświetlając informacje o liczbie pozostałych; readr: wyświetla 10 pierwszych wierszy z 10 pierwszych kolumn, z informacją o liczbie pozostałych wierszy i kolumn; automatycznie wyświetlane są też nazwy kolumn oraz skrót informujący o typie zmiennych data.table: wyświetla 5 początkowych i 5 końcowych wartości z każdej kolumny Czas i użycie pamięci przy dużych rozmiarach danych Zarówno czas wczytania danych, jak i wykorzystanie pamięci najkorzystniejsze jest w przypadku funkcji fread. Gdyby przez time oznaczyć czas potrzebny na wczytanie dużych plików, a przez memory zużycie pamięci, to time(fread) < time(read_csv) << time(read.csv) oraz memory(fread) < memory(read.csv) < memory(read_csv). 3.1.7 Arkusze kalkulacyjne i pliki JSON Do wczytywania arkusza kalkulacyjnego (np. pliku excela) używa się funkcji read_excel z pakietu readxl będącego częścią tidyverse. read_excel('./data/excel/listings.xlsx') Oprócz tego, można także użyć pakietu funkcji read.xlsx z pakietu xlsx. Wymaga ona jednak instalacji Javy. Do zaimportowania plików JSON możemy użyć funkcji z pakietu jsonlite listings_js <- jsonlite::fromJSON('./data/json/listings.json') listings_js <- mutate(listings_js, last_review = as_date(last_review)) 3.2 Locale Locale jest to uniksowe narzędzie powłokowe przechowujące ustawienia środowiskowe związane z ustawieniami regionalnymi. Sys.getlocale() ## [1] "LC_CTYPE=pl_PL.UTF-8;LC_NUMERIC=C;LC_TIME=pl_PL.UTF-8;LC_COLLATE=pl_PL.UTF-8;LC_MONETARY=pl_PL.UTF-8;LC_MESSAGES=pl_PL.UTF-8;LC_PAPER=pl_PL.UTF-8;LC_NAME=C;LC_ADDRESS=C;LC_TELEPHONE=C;LC_MEASUREMENT=pl_PL.UTF-8;LC_IDENTIFICATION=C" LC_COLLATE - odpowiada za porządek znaków, ważny przy sortowaniu LC_CTYPE - odpowiada za kodowanie znaków LC_MONETARY - odpowiada za system monetarny: znak waluty, separator tysięcy, liczba cyfr po przecinku itd. LC_NUMERIC - określa separator ułamkowy, separator tysięcy, grupowanie cyfr LC_TIME - odpowiada za system wyświetlania daty Sys.localeconv() ## decimal_point thousands_sep grouping int_curr_symbol currency_symbol mon_decimal_point mon_thousands_sep mon_grouping positive_sign negative_sign int_frac_digits ## "." "" "" "PLN " "zł" "," " " "\\003" "" "-" "2" ## frac_digits p_cs_precedes p_sep_by_space n_cs_precedes n_sep_by_space p_sign_posn n_sign_posn ## "2" "0" "1" "0" "1" "1" "1" Powyższa funkcja wyświetla szczegóły dotyczące systemu numerycznego i monetarnego. 3.2.1 Ustawienie locale przez użytkownika Sys.setlocale(category = "LC_ALL", locale = "polish") ## Warning in Sys.setlocale(category = "LC_ALL", locale = "polish"): Żądania raportów OS aby ustawić lokalizację na "polish" nie mogą zostać wykonane ## [1] "" W celu ustawienia innego locale niż domyślne systemowe należy użyć powyższej funkcji, przyjmowane przez nią argumenty to category i locale. category - odpowiada za określenie, które zmienne środowiskowe chcemy zmienić, gdzie opcje: “LC_ALL”, “LC_COLLATE”, “LC_CTYPE”, “LC_MONETARY”, “LC_NUMERIC” oraz “LC_TIME” są wspierane na każdym systemie operacyjnym, niektóre systemy wspierają również: “LC_MESSAGES”, “LC_PAPER” i “LC_MEASUREMENT” locale - odpowiada za region, który chcemy ustawić dla systemu windows podajemy angielską nazwę języka (regionu) np.: ‘polish’, w systemach UNIXowych podajemy np.: ‘pl_PL’ lub ‘pl_PL.UTF-8’. 3.2.2 Ustawienie locale poprzez readr Pakiet readr oferuje więcej możliwości dostosowywania locale, więcej informacji na ten temat można znaleźć w tym odnośniku. 3.3 Natywne formaty R R ma dwa natywne sposoby przechowywania danych, RDA(od RData) i RDS. Główną zaletą takiej obsługi danych jest szybkość przetwarzania ich. Zachowuje on także informacje z R o danych(np. typy zmiennych). 3.3.1 RDS W formacie RDS mogą być przechowywane jedynie pojedyncze pliki R. Mogą być one za to przypisywane do dowolnej nazwy. Aby załadować dane korzystamy z: listings_rds <- readRDS("./data/native/listings.rds") Do zapisania danych używamy: saveRDS(object = listings, file = "listings.rds") 3.3.2 RDA W plikach formatu RDA wczytane dane nie są przypisywane do zmiennej, tylko wywołujemy te funkcje i w efekcie plik pojawia się w środowisku. W RDA do załadowania danych służy load("./data/native/listings.rda") Natomiast, aby zapisać dane używamy: save(listings_rr, file = "listings_rr.rda") Korzystając z formatu RDA możemy jednocześnie zapisywać większą ilość plików save(iris, cars, file="data_frame.rda") "],["eksploracyjna-analiza-danych.html", "4 Eksploracyjna analiza danych 4.1 Dane tabelaryczne 4.2 Typy zmiennych 4.3 Miary 4.4 R - podsumowanie kolumn", " 4 Eksploracyjna analiza danych Badanie eksploracyjne danych (ang. exploratory data analysis) dotyczy opisu, wizualizacji i badania zebranych danych bez potrzeby zakładania z góry hipotez badawczych. Badania ekploracyjne obejmują również wstępne sprawdzenie danych w celu skontrolowania założeń modeli statystycznych lub występowania błędów w danych (np. braków odpowiedzi). 4.1 Dane tabelaryczne Dane tabelaryczne to dane, które mają postać tabeli. Tabela to struktura danych, która składa się z wierszy i kolumn. Każdy wiersz odpowiada pewnej obserwacji, której cechy zostały zapisane w kolejnych kolumnach. 4.2 Typy zmiennych Zmienne, które opisują kolejne obserwacje możemy podzielić na: zmienne jakościowe (niemierzalne) porządkowe - np. klasyfikacja wzrostu (niski, średni, wysoki) nominalne - np. kolor oczu, płeć, grupa krwi zmienne ilościowe (mierzalne) dyskretne - np. liczba dzieci, liczba gospodarstw domowych, wiek (w rozumieniu ilości skończonych lat) ciągłe - np. wzrost, masa, wiek (w rozumieniu ilości dni między datą urodzin a datą badania) proporcjonalne - np. masa, długość, temperatura wyrażona w Kelwinach lub stopniach Rankine’a (przyjmujemy istnienie zera i możemy twierdzić, że jedno ciało jest dwukrotnie gorętsze od drugiego) interwałowe - np. temperatura wyrażona w stopniach Celsjusza lub Fahrenheita (możemy twierdzić, że coś jest o 20 °C cieplejsze od czegoś innego, ale nie możemy stwierdzić ilokrotnie cieplejsze jest ciało o temperaturze 40 °C od ciała o temperaturze –10 °C), data kalendarzowa (możemy mówić o stałej różnicy pomiędzy kolejnymi dniami) 4.3 Miary Zapoznając się z danymi chcielibyśmy sprawdzić wokół jakiej wartości są skupione oraz jak bardzo są zmienne wartości danej cechy. Miary lokacji (miary tendencji centralnej) pomagają nam umiejscowić dane na osi. Przykładami takich miar są: średnia - najczęściej arytmetyczna określona jako \\(\\overline{x} = \\frac{1}{n}\\sum\\limits_{i=1}^n x_i\\). dominanta (moda) - ozn. \\(Mo\\) - dla zmiennych o rozkładzie dyskretnym, wartość o największym prawdopodobieństwie wystąpienia lub wartość najczęściej występująca w próbie. Dla zmiennej losowej o rozkładzie ciągłym jest to argument, dla którego funkcja gęstości prawdopodobieństwa ma wartość największą. mediana - ozn. \\(Me\\) - wartość cechy w szeregu uporządkowanym, powyżej i poniżej której znajduje się jednakowa liczba obserwacji. kwantyle rzędu \\(p\\) - wartość cechy w szeregu uporządkowanym, poniżej której znajduje się \\(p \\cdot 100\\%\\) liczby obserwacji, a powyżej której znajduje się \\((1 - p) \\cdot 100\\%\\) liczby obserwacji. Natomiast miary rozrzutu dostarczają informacji jak bardzo zróżnicowane są obserwacje pod względem badanej cechy. Przykładami takich miar są: wariancja - stopień rozrzutu badanej cechy wokół wartości oczekiwanej. Im większa wariancja, tym rozrzut zmiennej jest większy. Nieobciążony estymator wariancji wyraża się wzorem: \\(s^2 = \\frac{1}{n}\\sum\\limits_{i=1}^n\\left(x_i - \\overline{x}\\right)^2\\) odchylenie standardowe - mówi nam o przeciętnym odchyleniu wartości zmiennej losowej od jej wartości oczekiwanej. Im odchylenie standardowe jest większe, tym większe zróżnicowanie wartości badanej cechy. Odchylenie standardowe z próby obliczamy jako pierwiastek z wariancji z próby, tzn. \\(s = \\sqrt{s^2}\\). rozstęp międzykwartylowy - różnica między trzecim a pierwszym kwartylem. Ponieważ pomiędzy tymi kwartylami znajduje się z definicji 50% wszystkich obserwacji (położonych centralnie w rozkładzie), dlatego im większa szerokość tego rozstępu, tym większe zróżnicowanie cechy. Wyróżniamy także miary asymetrii. Miary asymetrii mówią nam, czy większa część populacji klasuje się powyżej, czy poniżej przeciętnego poziomu badanej cechy. Asymetrię rozkładu można zbadać porównując średnią, dominantę i medianę. W przypadku rozkładu symetrycznego wszystkie te parametry są równe. Jeśli zachodzi nierówność \\(Mo < Me < \\mathbb{E} X\\), to mamy do czynienia z prawostronną asymetrycznością rozkładu. Tzn. dużo małych wartości i bardzo mało dużych. Jeśli zachodzi nierówność \\(\\mathbb{E} X < Me < Mo\\), to mamy do czynienia z lewostronną asymetrycznością rozkładu. Tzn. mało małych i bardzo dużo dużych. 4.4 R - podsumowanie kolumn Podstawowymi funkcjami, które pomagają nam zapoznać się z danymi są funkcje: \\(\\texttt{head}\\) - zwraca pierwszą część wektora, macierzy, tabeli lub ramki danych. Domyślnie 6 pierwszych elementów. \\(\\texttt{nrow}\\) - zwraca liczbę wierszy macierzy, tabeli lub ramki danych. \\(\\texttt{ncol}\\) - zwraca liczbę kolumn macierzy, tabeli lub ramki danych. Natomiast podstawowymi funkcjami, które podsumowują kolejne kolumny są funkcje: \\(\\texttt{str}\\) - zwraca strukturę danego obiektu. Wyświetla np. klasę obiektu, liczbę wierszy i kolumn, a także nazwę danej kolumny, typ wartości w niej zawartych, jak i kilka początkowych wartości. \\(\\texttt{summary}\\) - zwraca podsumowanie każdej kolumny. Dla zmiennych ciagłych wyznacza wartości tj.: wartość najmniejsza i największa średnia i mediana 1 (0.25) i 3 (0.75) kwartyl liczba wartości brakujących (NA) Natomiast w przypadku zmiennych dyskretnych wyznacza liczbę obserwacji, które przyjmują daną wartość zmiennej. \\(\\texttt{glimpse}\\) - funkcja z pakietu \\(\\texttt{tidyverse}\\) podobna do \\(\\texttt{str}\\), ale stara się pokazać jak najwięcej danych. Wyświetla np. liczbę wierszy i kolumn, a także nazwę danej kolumny, typ wartości w niej zawartych oraz jak najwięcej wartości z tej kolumny. "],["przetwarzanie-danych-tabelarycznych.html", "5 Przetwarzanie danych tabelarycznych 5.1 Wybieranie kolumn 5.2 Zmiana nazw kolumn 5.3 Filtrowanie 5.4 Usuwanie kolumn 5.5 Manipulacje na kolumnach 5.6 Aplikowanie transformacji do każdej kolumny 5.7 Grupowanie i podsumowanie 5.8 Podsumowywanie wszystkich kolumn", " 5 Przetwarzanie danych tabelarycznych Operacje na danych w R są związane głównie z filtrowaniem, dodawaniem i modyfikowaniem kolumn, grupowaniem oraz podsumowywaniem danych. Można je wykonywać za pomocą funkcji bazowego R lub narzędzi z zaimportowanych pakietów: tidyverse, data.table. Załóżmy, że ramka danych jest przypisana do zmiennej \\(dane\\), a nazwy jej kolumn to: \\(kol.1, kol.2, kol.3,...\\) . 5.1 Wybieranie kolumn Poniżej przedstawione są instrukcje pozwalające na wybieranie konkretnych kolumn z ramki danych w~zależności od metody. Dla uproszczenia przyjmijmy, że wybieramy kolumny: \\(kol.1, kol.2, kol.3\\). base dane = dane[, c(“kol.1”, “kol.2”, “kol.3”)] tidyverse dane = select(dane, kol 1, kol 2, kol 3) dane = dane %>% select(kol 1, kol 2, kol 3) data.table Nazwy kolumn ramki danych zawierą znak “.” . Wprowadźmy zmienną pomocniczą \\(kolumny\\). Będzie ona zawierać nazwy kolumn, ale zastępując znak “.” znakiem ” “. kolumny = c("kol 1", "kol 2", "kol 3") dane = dane[, kolumny] dane = dane[, kolumny, with = FALSE] - dana metoda nie zadziała bez argumentu \\(with~=~FALSE\\), ponieważ szuka w ramce danych kolumn o nazwach zawartych w obiekcie \\(kolumny\\), a nie konkretnie podanych nazw dane = dane[, colnames(dane) %in% kolumny, with = FALSE] dane = dane[, ..kolumny] dane = dane[, list(kol 1, kol 2, kol 3)] dane = dane[, .(kol 1, kol 2, kol 3)] 5.2 Zmiana nazw kolumn Teraz zostaną zaprezentowane sposoby na zmianę nazw kolumn ramki danych. Przyjmijmy, że nowe nazwy kolumn są postaci \\(k1, k2, k3, ...\\) . base colnames(dane) = c(“k1”, “k2”, “k3”) tidyverse dane = dane %>% rename(k1 = kol.1, k2 = kol.2, k3 = kol.3) data.table setnames(dane, c(“kol.1”, “kol.2”, “kol.3”), c(“k1”, “k2”, “k3”)) - zaleta: nie kopiuje ramki danych 5.3 Filtrowanie Dany rozdział skupia się na sposobach filtrowania danych. Przydatne funkcje: unique(dane\\(\\$\\)k1) - zwraca unikalne wartości kolumny \\(k1\\) table(dane\\(\\$\\)k1) - zlicza ilość wystąpienia każdej wartości w kolumnie \\(k1\\) prop.table(table(dane\\(\\$\\)k1)) - pokazuje procentowo ilość wystąpienia każdej wartości w kolumnie \\(k1\\) w\\(~\\)stosunku do wszystkich wartości Przyjmnijmy, że wybieramy z kolumny \\(k1\\) określoną wartość \\(abc\\). base dane[dane\\(\\$\\)k1 == “abc”, ] tidyverse dane %>% filter(k1 == “abc”) można podać kilka warunków (po przecinku), będą one domyślnie rozdzielone spójnikiem \\(i\\) aby połączyć warunki spójnikiem \\(i\\) można również użyć operatora \\(\\&\\) aby połączyć warunki spójnikiem \\(lub\\) należy użyć operatora \\(|\\) data.table dane[k1 == “abc”] 5.4 Usuwanie kolumn Załóżmy, że usuwamy pierwszą kolumnę - \\(k1\\). base dane = dane[, -1] - gdzie \\(1\\) to numer usuwanej kolumny, a “-” oznacza usuwanie tidyverse dane = select(dane, -k1) - jak powyżej, “-” oznacza usuwanie, ale w tym przypadku stosujemy nazwę kolumny a nie jej numer data.table dane[, k1 := NULL] - operator \\(:=\\) (referencja) oznacza, że operacja jest wykonywana bez kopiowania ramki danych dane = dane[, -1, with = FALSE] 5.5 Manipulacje na kolumnach Przyjmijmy, że kolumna \\(k2\\) zawiera tylko liczby. Wartości ujemne zamieniamy na \\(0\\). W tym celu posłużymy się funkcją \\(ifelse\\): \\[ ifelse(warunek \\ logiczny,\\ wartość \\ jeśli \\ spełniony, \\ wartość \\ jeśli\\ niespełniony).\\] 1. base dane[[“k2”]] = ifelse(dane[[“k2”]] < 0, 0, dane[[“k2”]]) tidyverse dane = dane %>% mutate(k2 = ifelse(k2 < 0, 0, k2)) możemy modyfikować kilka kolumn jednocześnie, rozdzielając je przecinkiem data.table dane[, k2 := ifelse(k2 < 0, 0, k2)] - z użyciem referencji dane[[“k2”]] = ifelse(dane[[“k2”]] < 0, 0, dane[[“k2”]]) - bez użycia referencji 5.6 Aplikowanie transformacji do każdej kolumny W tym rozdziale będziemy operować na wszystkich kolumnach ramki danych. Wartości w nich zawarte mogą być typu \\(factor\\), które zamienimy na typ \\(character\\). base poprzez pętlę for (i in 1:ncol(dane)){ if (is.factor(dane[, i])){ dane[, i] = as.character(dane[, i]) } } poprzez funkcję \\(lapply\\) lapply(dane, fun(x){ if(is.factor(x)) x = as.character(x) }) tidyverse przy użyciu funkcji \\(mutate\\_all\\) dane = dane %>% mutate_all(function(x){ if (is.factor(x)){ as.character(x) } else{ x } }) data.table przy użyciu funkcji lapply dane = dane[, lapply(.SD, function(x){ if (is.factor(x)){ as.character(x) } else{ x } })] 5.7 Grupowanie i podsumowanie Załóżmy, że do wyznaczenia wszystkich unkialnych wartości ramki danych potrzebne są kolumny \\(k1\\), \\(k2\\) i \\(k3\\). Natomiast podsumowywana będzie kolumna \\(k4\\) - zostanie wyliczona średnia dla każdej unikalnej wartości. base przy użyciu funkcji \\(aggregate\\) - zastosowana zostanie formuła \\(k4\\) ~ \\(k1 + k2 + k3\\), która oznacza, że będzie podsumowywana zmienna \\(k4\\) w zależności od unikalnych zestawów wartości zmiennych \\(k1\\), \\(k2\\), \\(k3\\) aggregate(k4 ~ k1 + k2 + k3, data = dane, FUN = function(x) mean(x, na.rm = TRUE)) - poprzez zastosowanie własnej funkcji aggregate(k4 ~ k1 + k2 + k3, data = dane, FUN = mean, na.rm = TRUE) - poprzez zastosowanie istniejącej funkcji tidyverse dane %>% group_by(k1, k2, k3) %>% summarize(srednia = mean(k4, na.rm = TRUE), maksimum = max(k4, na.rm = TRUE)) \\(group\\_by\\) - grupuje po kolumnach \\(k1\\), \\(k2\\), \\(k3\\) \\(summarize\\) - podsumowuje według podanych elementów (w tym przypadku wylicza średnią i maksimum z kolumny \\(k4\\)) data.table dane[, list(średnia = mean(k4, na.rm = TRUE), maksimum = max(k4, na.rm = TRUE)), by = c(“k1”, “k2”, “k3”)] 5.8 Podsumowywanie wszystkich kolumn W celu podsumowania kolumn zdefiniujemy poniższą funkcję, która zwróci ilość niepustych wartości. num_unique_noNA = function(input_vector){ sum(!is.na(unique(input_vector))) } base apply(dane, 2, num_unique_noNA) - gdzie \\(2\\) oznacza, że wywołujemy podaną funkcję \\(num\\_unique\\_noNA\\) po kolumnach lapply(dane, num_unique_noNA) sapply(dane, num_unique_noNA) tidyverse summarise_all(dane, num_unique_noNA) data.table dane[, lapply(.SD, num_unique_noNA)] "],["czyste-dane.html", "6 Czyste dane 6.1 Dane w formacie wąskim i szerokim 6.2 Rozdzielanie na kolumny (wąska -> szeroka) 6.3 Scalanie kilku kolumn w jedną (szeroka -> wąska) 6.4 Łączenie tabel danych 6.5 Operacje na napisach i datach", " 6 Czyste dane Transformacja danych jest niezwykle ważnym elementem dobrze zrobionego raportu. Dane te powinny być prezentowane w sposób czytelny i ułatwiający ich porównywanie. To od potrzeby biznesowej zależy w jaki sposób powinniśmy przedstwiać dane. Np. dysponując wynikami finansowymi zbieranymi co miesiąc przez trzy lata bo planowania budżetu na następny rok przyda nam się prezentacja ich w formacie wąskim, czyli skupionym na wydatkach względem każdego roku. Jednakże, jeżeli chcielibyśmy kontrolować wydatki w tym następnym roku prezentacja danych w formacie szerokim będzie bardziej korzystna, gdyż będziemy mieli informację ile średnio wydajemy w danym miesiącu i na bieżąco będziemy mogli podejmować decyzję o inwestowaniu lub zaciskaniu pasa. Niekiedy jednak dane mają bardziej skomplikowaną formę i np. składają się z wielu tabel. Wówczas dla łatwiejszego uzyskania informacji biznesowej będzie połączenie tych tabel. Takie operacje w połączeniu z odpowiednią agregacją i grupowaniem zdecydowanie ułatwia wgląd w aktualną sytuację. Ostatnim tematem, na temat któtego ta notatka traktuje są operacje na napisach i datach. Bardzo łatwo uzmysłowić sobie przydatność w posługiwaniu się takimi operacjami. Ułatwia to konstruowanie prostych funkcji, które są kluczowe w każdym projekcie. Chociażby bazując na imionach i nazwiskach pewnych obywateli Polski łatwo wskazać z dużą pewnością kobiety w tym zbiorze sprawdzając ostatnią literę ich imienia (tj. czy dane imie kończy się na literę “a”). 6.1 Dane w formacie wąskim i szerokim Dane najczęściej są przedstawiane w postaci tabelarycznej. Jednak mogą być w tej tabeli różnie sformatowane. Wyróżnia się między innymi szeroką reprezentacje danych i wąską reprezentacje danych. W zależności od tego, co chcemy z nimi zrobić czasami trzeba przejść z jednej postaci do drugiej. Aby przetransformować dane korzysta się z funkcji z pakietów dplyr i tidyverse. O postaci szerokiej mówimy, gdy pojedyncza zmienna jest rozdzielona pomiędzy kilka kolumn. Różnicę najłatwiej jest pokazać na przykładzie. W tym celu wykorzystamy wbudowany zbiór danych sleep zawierający informacje o wpływie dwóch leków nasennych na ilość przespanych godzin. Kolumna extra zawiera informacje o ilości dodatkowo przespanych godzin. extra group ID 0.7 1 1 -1.6 1 2 -0.2 1 3 -1.2 1 4 -0.1 1 5 3.4 1 6 Dane są przedstawione w postaci wąskiej, każda zmienna jest przedstawiona w oddzielnej kolumnie. Teraz ‘rozbijmy’ kolumnę group na group 1 i group 2. ID group 1 group 2 1 0.7 1.9 2 -1.6 0.8 3 -0.2 1.1 4 -1.2 0.1 5 -0.1 -0.1 6 3.4 4.4 7 3.7 5.5 8 0.8 1.6 9 0.0 4.6 10 2.0 3.4 Można zaobserwować, że wartości z kolumny extra zostały wpisane w poszczególne komórki, a kolumna group została podzielona na dwie oddzielne kolumny group 1 i group 2. Tak sformatowane dane nazywamy szeroką reprezentacją danych. 6.2 Rozdzielanie na kolumny (wąska -> szeroka) Aby przejść z wąskiego formatu przedstawiania danych do szerokiego, można użyć funkcji spread() z pakietu dplyr. Funkcja spread(dataset,key,value) przyjmuje trzy agrumenty: dataset - zbiór danych w formacie wąskim, key - kolumna (klucz) odpowiadająca kolumnie, która ma zostać rozłożona, value - kolumna, w której znajdują się wartości wypełniające nowe kolumny. szeroka <- spread(sleep, group, extra) colnames(szeroka) = c("ID","group 1","group 2") kable_styling(kable(head(szeroka)), position = "center") ID group 1 group 2 1 0.7 1.9 2 -1.6 0.8 3 -0.2 1.1 4 -1.2 0.1 5 -0.1 -0.1 6 3.4 4.4 Drugą opcją na uzyskanie tego samego rezultatu jest użycie funkcji pivot_wider z pakietu tidyverse. Funkcja przyjmuje dwa argumenty pivot_wider(names_from = name, values_from = value): name - nazwa kolumny, która ma zostać rozłożona, value - nazwa kolumny, w której znajdują się wartości. sleep %>% pivot_wider(names_from = group, values_from = extra) 6.3 Scalanie kilku kolumn w jedną (szeroka -> wąska) Można wrócić z postaci szerokiej do wąskiej. W tym celu należy użyć funkcji gather() z pakietu tidyr. Funkcja gather(dataset, key, value, other) przyjmuje również trzy argumenty: dataset - zbiór danych w formacie szerokim, key - nazwy kolumn z kluczami, value - nazwy kolumn z wartościami, other - kolumny dataset, które mają być zawarte w nowej tabeli. Aby wrócić do postaci wąskiej nałóżmy funkcję gather na wygenerowaną wcześniej tabele szeroka. kable_styling(kable(head(szeroka %>% gather(group, extra, -ID))),position = "center") ID group extra 1 1 0.7 2 1 -1.6 3 1 -0.2 4 1 -1.2 5 1 -0.1 6 1 3.4 Drugą funkcją, która umożliwia przejście z szerokiej reprezentacji danych do wąskiej jest funkcja pivot_longer z pakietu tidyverse. Funkcja pivot_longer(col_names, names_to = name, values_to = value) przyjmuje trzy argumenty col_names - ciąg nazw kolumn, które chcemy złączyć, name - nazwa nowo powstałej kolumny, value - nazwa kolumny, w której pojawią się wartości. kable_styling(kable(head(szeroka %>% pivot_longer(c("1", "2"), names_to = "group", values_to = "extra"))), position = "center") 6.4 Łączenie tabel danych Mamy dwie tabele danych tab1 z małymi literami oraz tab2 z wielkimi literami: Table 6.1: tab1 = x indeks litery 1 a 2 b 3 c 4 d 5 e 6 f Table 6.1: tab2 = y indeks LITERY 4 E 5 F 6 G 7 H 8 I 9 J gdzie x = tab1, a y = tab2. Aby połączyć dwie tabele danych na podstawie wskazanych kolumn lub kolumn o wspólnej nazwie można użyć przykładowych funkcji. 6.4.1 merge() Dostępna w bazowym R. Domyślnie funkcja ta łączy tabele względem nazw kolumn, które są wspólne. tabela <- merge(x = tab1, y = tab2) kable(tabela) indeks litery LITERY 4 d E 5 e F 6 f G Jeśli chcemy być pewni, że tabele zostaną połączone po odpowiedniej kolumnie, możemy przekazać nazwę tej kolumny w argumencie. W tym przypadku: merge(tab1, tab2, by = "indeks") # INNER JOIN Jeśli jest więcej kolumn, po których chcemy połączyć tabele, wystarczy przekazać w argumencie by wektor z nazwami tych kolumn. Gdy nazwy kolumn po których chcemy złączyć tabele różnią się, należy wykorzystać argument by.*. Załóżmy, że kolumna tabeli tab1 - indeks zmieniła nazwę na index, zatem: merge(tab1, tab2, by.x = "index", by.y = "indeks") Wartości kolumn indeks w tab1 oraz tab2 różnią się. Dlatego korzystając z funkcji bez dodatkowych argumentów tracimy dane. Aby zapobiec traceniu danych z poszczególnych tabel należy skorzystać z argumentu all, brakujące wartości zostaną uzupełnione NA: merge(tab1, tab2, all.x = TRUE) # LEFT JOIN merge(tab1, tab2, all.y = TRUE) # RIGHT JOIN merge(tab1, tab2, all = TRUE) # OUTER JOIN Dostajemy wtedy kolejno: Table 6.2: all.x = TRUE indeks litery LITERY 1 a NA 2 b NA 3 c NA 4 d E 5 e F 6 f G Table 6.2: all.y = TRUE indeks litery LITERY 4 d E 5 e F 6 f G 7 NA H 8 NA I 9 NA J Table 6.2: all = TRUE indeks litery LITERY 1 a NA 2 b NA 3 c NA 4 d E 5 e F 6 f G 7 NA H 8 NA I 9 NA J Bez sprecyzowania argumentu sort wiersze wyniku merge() zostaną posortowane leksykograficznie po wspólnych kolumnach. Gdy sort = FALSE wiersze będą w nieokreślonej kolejności. Kolumny złączonej tabeli to najpierw kolumny wspólne, następnie pozostałe z x a na końcu pozostałe z y, co widać na przykładach. 6.4.2 join() Funkcja z paczki dplyr. Tabele x i y powinny zwykle pochodzić z tego samego źródła danych, ale jeśli copy = TRUE, y zostanie automatycznie skopiowany do tego samego źródła co x. Są cztery typy join zmieniających: left_join() - zwraca wszystkie wiersze z x i wszystkie kolumny z x i y. Wiersze w x bez dopasowania w y będą miały wartości NA w nowych kolumnach. Jeśli istnieje wiele dopasowań między x a y, zwracane są wszystkie kombinacje dopasowań tabela <- left_join(tab1, tab2) kable(tabela) indeks litery LITERY 1 a NA 2 b NA 3 c NA 4 d E 5 e F 6 f G 6 z G right_join() - analogicznie do left_join(), ale zwraca wszystkie wiersze z y, a wiersze bez dopasowania w x będą miały wartości NA inner_join() - zwraca wszystkie wiersze z x, w których znajdują się pasujące wartości w y, oraz wszystkie kolumny z x i y. Jeśli istnieje wiele dopasowań między x a y, zwracane są wszystkie kombinacje dopasowań. tabela <- inner_join(tab1, tab2) kable(tabela) indeks litery LITERY 4 d E 5 e F 6 f G 6 z G full_join() - zwraca wszystkie wiersze i wszystkie kolumny zarówno z x, jak i y. Jeśli nie ma pasujących wartości, zwraca NA dla brakujących. tabela <- full_join(tab1, tab2) kable(tabela) indeks litery LITERY 1 a NA 2 b NA 3 c NA 4 d E 5 e F 6 f G 6 z G 7 NA H 8 NA I 9 NA J Argument by przyjmuje wektor nazw zmiennych do połączenia. Jeśli by = NULL funkcja *_join() domyślnie połączy tabele dopasowując wartości ze wszystkich kolumn o wspólnych nazwach w obu tabelach. 6.5 Operacje na napisach i datach Większość poniższych funkcji pochodzi z pakietu stringi. 6.5.1 Operacje na napisach Wyznaczanie długości napisów. Funkcja stri_lenght() zwraca długości poszczególnych napisów w danym wektorze, a stri_isempty() sprawdza, które napisy są puste -> ’’. Łączenie i powielanie napisów. Funkcja używana do łączenia kilku wektorów napisów w inny wektor napisów lub nawet w jeden napis, jest stri_paste() i jej warianty. Przykład: x <- LETTERS[1:3] y <- letters[1:3] z <- '!' stri_paste(x, y, z) ## [1] "Aa!" "Bb!" "Cc!" Przycinanie i wypełnianie. Funkcja stri_wrap() wstawia znaki nowego wiersza (n), by napis po wyświetleniu np. przy funkcji cat() miał szerokość nie większą, niż podana, jeżeli to możliwe. W przypadku przetwarzania tekstów pochodzących np. z formularzy na stronach internetowych może zachodzić potrzeba usunięcia tzw. białych znaków, np. spacji z początku lub końca napisu. Możemy to zrobić przy użyciu funkcji stri_trim(). Operacja w pewnym sensie odwrotną do tej można wykonać przy użyciu funkcji stri_pad(). Przykład: stri_trim(' Mama i tata\\n') ## [1] "Mama i tata" Formatowanie napisów na podstawie innych obiektów. Najprostszym sposobem na uzyskanie napisowej reprezentacji danego obiektu jest użycie funkcji as.character(). Przykład: as.character(list(1L, mean, NULL, pi, FALSE)) ## [1] "1" "function (x, ...) \\nUseMethod(\\"mean\\")" "NULL" "3.14159265358979" ## [5] "FALSE" x <-data.frame(a=c(TRUE, FALSE, FALSE), b=as.integer(c(1, 2, 3))) as.character(x) ## [1] "c(TRUE, FALSE, FALSE)" "1:3" Zmiana pojedynczych znaków. Zmiana poszczególnych znaków na inne przydaje się między innymi na etapie wstępnego przygotowania danych w celu ujednolicenia tekstowych identyfikatorów obiektów, możemy np. zmieniać wielkości wszystkich liter w napisach. Przykład: stri_trans_toupper('chcemy duże litery') ## [1] "CHCEMY DUŻE LITERY" stri_trans_tolower('ChCemY MałE LiTErY') ## [1] "chcemy małe litery" stri_trans_char('zastępowanie znaków', 'ąćęłńóśżź', 'acelnoszz') ## [1] "zastepowanie znakow" stri_trans_general('żółć', 'Latin-ASCII') ## [1] "zolc" Wyznaczanie podnapisów. Funkcja stri_sub() zwraca podnapis składający się ze znaków leżących na określonych pozycjach danego napisu. Przykład: x <- 'Lasy, pola, pastwiska, koszą traktorem' stri_sub(x, 7) ## [1] "pola, pastwiska, koszą traktorem" 6.5.2 Operacje na datach Funkcją zwracającą aktualną datę systemową jest Sys.Date(), a Sys.time() aktualny czas systemowy wraz z datą. Przykład: (data <- Sys.Date()) ## [1] "2023-10-12" (czas <- Sys.time()) ## [1] "2023-10-12 16:27:08 CEST" Operacje arytmetyczne na datach – dodawanie, odejmowanie i porównywanie. Przykład: data ## [1] "2023-10-12" data-365 ## [1] "2022-10-12" data+365 ## [1] "2024-10-11" (d <- data-as.Date('2021-01-01')) ## Time difference of 1014 days Do konwersji do napisu może służyć przeciążona wersja metody format(), której wywołanie jest tożsame z wywołaniem funkcji strftime() (ang. string-format-time). Przykład: strftime(czas, '%Y-%m-%d %H:%M:%S %Z') ## [1] "2023-10-12 16:27:08 CEST" Do znajdowania “najstarszej” i “najmłodszej” daty używamy funkcji max() oraz min(). Do pracy ze strefami czasowymi możemy używać poniższych funkcji: force_tz() ustawienie strefy czasowej, with_tz() sprawdzenie daty w innej strefie czasowej. "],["wizualizacja-danych-z-pakietem-ggplot2.html", "7 Wizualizacja danych z pakietem ggplot2 7.1 Wprowadzenie 7.2 Gramatyka grafiki 7.3 Podstawy tworzenia wykresów w ggplot2 7.4 Mapowanie 7.5 Geometria wykresu 7.6 Funkcje pomagające poprawić czytelność wykresu 7.7 Panele", " 7 Wizualizacja danych z pakietem ggplot2 7.1 Wprowadzenie Jednym z ważnych elementów przekazywania ciekawych informacji oraz ich analizy jest przedstawienie graficzne interesujących nas danych. W R istnieje kilka sposobów na wizualizację danych. Jednym z nich jest korzytanie z narzędzi oferowanych przez pakiet ggplot2. Bibiloteka ggplot2 oprócz zwykłych funkcji plotowania, implementuje także gramatykę grafiki, co pozwala na wykonanie prawie każdego rodzaju (statystycznej) wizualizacji danych. 7.2 Gramatyka grafiki Powyżej wspomnieliśmy o gramatyce grafiki. Dla dokładniejszego uporządkowania wiedzy przypomnijmy, że gramatyka grafiki daje nam możliwość zadawania odpowiednich parametórw dla wszystkich linii, słów, strzałek, itp., które połączone tworzą wykres. Dodatkowo możemy m.in. zmieniać układ współrzędnych, czy korygować położenie każdego obiektu znajdującego się na wykresie. Możliwości jakie oferuje nam gramatyka grafiki będą przedstawione dokładniej w dalszej części notatki. 7.3 Podstawy tworzenia wykresów w ggplot2 Na początku, aby móc tworzyć wizualizacje, musimy załadować pakiet oraz bibilotekę ggplot2. Warto zwrócić uwagę, że ggplot2 posiada również szereg wbudowanych zestawów danych. Aby pokazać możliwości jakie oferuje nam ggplot, przeprowadzimy symulację danych mpg dostępnych w R. library(ggplot2) head(mpg) ## # A tibble: 6 × 11 ## manufacturer model displ year cyl trans drv cty hwy fl class ## <chr> <chr> <dbl> <int> <int> <chr> <chr> <int> <int> <chr> <chr> ## 1 audi a4 1.8 1999 4 auto(l5) f 18 29 p compact ## 2 audi a4 1.8 1999 4 manual(m5) f 21 29 p compact ## 3 audi a4 2 2008 4 manual(m6) f 20 31 p compact ## 4 audi a4 2 2008 4 auto(av) f 21 30 p compact ## 5 audi a4 2.8 1999 6 auto(l5) f 16 26 p compact ## 6 audi a4 2.8 1999 6 manual(m5) f 18 26 p compact Składnia wykresów w ggplot polega na warstwowym budowaniu wykresów. Zaczynamy od doboru danych, jakie chcielibyśmy zwizualizaować. Określamy sposób mapowania zmiennych do aspektów wizualnych. Definiujemy styl wykresu. Dodajemy dodatkowe warstwy personalizujące wykres, tzn. dodajemy tytuł, etykiety, itp. (opcjonalnie) Uwaga! Do dodawania nowych warstw do wykresu używamy symbolu “+” . # Budujemy podstawę wykresu, określając z jakich danych będziemy korzytsać ggplot(mpg) # Mapujemy dane ( na osiach pojawiły się odpowiednie podziałki) ggplot(mpg , aes( x = displ, y = hwy)) # Określamy styl wykresu, dzięki czemu dostemy wykres odpwiednich zależności ggplot(mpg , aes( x = displ, y = hwy))+ geom_point() # Personalizujemy wykres poprzez dodanie tytułu oraz określenie motywu wykresu ggplot(mpg , aes( x = displ, y = hwy))+ geom_point()+ ggtitle("disp vs hwy")+ theme_bw() 7.4 Mapowanie Mapowanie danych jest estetyką, która mówi ggplot’owi, jakie zmienne powinny znajdować się na osi x oraz y. Dodatkowo możemy wpływać na cechy wizualne danych, takie jak kolor ( color = ), kształt ( shape = ), czy przezroczystość ( alpha = ). Wszystkie estetyki dla podziałki wykresu są określone w wywołaniu funkcji aes(). Uwaga! Każda warstwa geom może mieć swoją własną specyfikację aes. Możemy zdecydować, czy na wykresie geom_point punkty mają być zaznaczone jako koła, czy kwadraty. # Punkty na wykresie będą kwadratami ggplot(mpg, aes(x = displ, y = hwy)) + geom_point(shape = 0)+ ggtitle("displ vs hwy")+ theme( axis.title = element_text(size = 1))+ theme_bw() # Punkty na wykresie to czerwone kropki ggplot(mpg, aes(x = displ, y = hwy)) + geom_point(color = "red")+ ggtitle("displ vs hwy")+ theme_bw() 7.5 Geometria wykresu Za pomocą ggplot2 możemy stworzyć prawie każdy rodzaj wykresu. W tym celu musimy zadać typ wykresu jaki nas interesuje. Ggplot2 daje możliwość stworzenia wykresu: punktowego ( geom_point) liniowego ( geom_line) słupkowego ( geom_bar) skrzypcowego ( geom_violin) histogramu ( geom_histogram) boxplota ( geom_boxplot) oraz wielu innych, które powstają m.in. przez zastosowanie funcji: geom_area, geom_density, geom_dotplot, geom_qq, geom_smooth. Uwaga! Wykresy słupkowe i histogramy grupują dane, a następnie prezentują liczbę elementów znajdujących się w poszczególnych grupach Na wykresach liniowych model jest dopasowywany do danych, a nastęonie wykreślane są przewidywania wyznaczone przez model Wykresy pudełkowe obliczają kompleksowe podsumowanie rozkładu wartości Poniżej przedstawione są dwa przykładowe typy wykresów. Pierwszy narysowany przy użyciu funkcji geom_smooth, która służy do rysowania wygładzonych linii, np. dla prostych trendów. Drugi wykres powstał przy zastosowaniu funkcji geom_histogram. W pakiecie ggplot2 bardzo prosto możemy łączyć ze sobą różne geometrie na jednym wykresie. Wystarczy wstawić znak “+” pomiędzy odpowiednimi funkcjami. ggplot(mpg , aes( x = displ, y = hwy))+ geom_point()+ geom_smooth()+ ggtitle("Multiple geomteries")+ theme_bw() 7.6 Funkcje pomagające poprawić czytelność wykresu 7.6.1 Systemy współrzędnych Domyślnym systemem współrzędnych dla ggplot2 jest kartezjański układ współrzędnych. W zależności od danych na jakich działamy, może się okazać, że pokazanie danych w innym układzie współrzędnych, wpłynie na lepszy odbiór informacji z wykresu. Funkcjami, które odpowiadają za przekształcenie układu współrzędnych są m.in. coord_flip która zamienia osie x i y coord_polar wykres jest pokazany we współrzędnych polarnych coord_fixed nadal jesteśmy w kartezjańskim układzie współrzędnych, ale możemy zmienić proporcję między jednostkami na osi x i y 7.6.2 Dopasowanie położenia Każda geometria w ggplot2 ma ustawione domyślne położenie różnych elementów na wykresie względem siebie. Różne opcje ustawienia położenia są dobrze widoczne na wykresach słupkowych. Zacznijmy od stworzenia zwykłego wykresu słupkowego, bez żadnych dodatkowych funkcji. Jeżeli teraz do mapowania dodamy opcję fill = dvr, to każdy prostokąt będzie reprezentował kombinację wartości class oraz dvr. Takie przedstawienie danych nie dla każdego może być czytelne, dlatego możemy skorzystać z opcji position, która przyjmuje m.in. argumenty: “dodge” i “fill”. 7.6.3 Zarządzanie osiami współrzędnych Jedną z możliwości jaką oferuje nam pakiet ggplot2 jest prosta zmiana skali na osiach wykresu. Podstawowymi funkcjami, które to umożliwiają są: scale_x_log10 (zamiast x możemy podać także y) wtedy skala x-ów będzie zlogarytmowana scale_x_reverse powoduje odwrotny kierunek na osi x scale_x_sqrt() skala x-ów będzie spierwiastkowana scale_fill_manual pozwala nam ręcznie wprowadzić oczekiwane przez nas wartości, m.in. możemy zmienić nazwy obiektów na skali, czy podać zakres wartości do uwzględnienia w mapowaniu xlim(5,40) powoduje ograniczenie podziałki na osi x od 5 do 40 (analogicznie z ylim) W ggplot2 z łatwością także dodamy etykiety tekstowe oraz adnotacje. Do wykresu możemy dodać tytuł oraz nazwy osi korzystając m.in. z funkcji labs(). ggplot(mpg, aes(x = displ, y = hwy, color = class)) + geom_point() + labs(title = "Fuel Efficiency by Engine Power", x = "Engine power", y = "Fuel Efficiency", color = "Car Type") 7.6.4 Motywy Theme to dobry sposób na dostosowanie odpowiedniego tytułu, etykiet, czcionek, tła, legendy, czy lini siatki na wykresie. Możemy skorzystać z jednego z dostępnych motywów, takich jak theme_bw(), czy theme_minimal(). Istnieje możliwość zastosowania wielu dostępnych opcji tak, aby odpowiednie elementy wykresu wyglądały tak, jak chcemy. Podstawowymi funkcjami, jakie warto znać są m.in. legend.position, dzięki której możemy ustalić pozycję legendy wykresu, axis.text, która umożliwia nam ustawienie czcionki na wykresie oraz ustalenie jej wielkości czy koloru. Przydatną funkcją pochodzącą z rodziny theme jest ‘theme(axis.text.x = element_text(angle = 90))’, która obraca nazwy znajdujące się na osi x, dzięki, czemu stają się one czytelniejsze. 7.7 Panele Ostatnim z podstawowych funkcji jakie oferuje pakiet ggplot2 jest facets. Panele to sposoby grupowania wykresu danych w wiele różnych części ze względu na zadaną zmienną. Możemy korzystać z funkcji: facet_wrap(), która ustawia panele w prostokątnym układzie facet_grid(), która ustawia panele w kolumny lub w wiersze (zależnie jaką opcję wybierzemy) ggplot(mpg, aes(x = displ, y = hwy)) + geom_point() + facet_grid(~ class) Uwaga! Aby zadać względem, której zmiennej chcemy grupować, w funkcji ‘facet_’ po znaku “~”, podajemy nazwę tej zmiennej. Kiedy korzystamy z funkcji tworzącej panele, automatycznie wszytskie wykresy będą pokazane w układzie współrzędnych dopasowanym do wszytkich okienek. Istnieje jednak możliwość dopasowania układu współrzędnych do każdego panelu osobno. W tym celu możemy wykorzystać funcję ‘scale = “free”’. "],["czysty-i-wydajny-kod-w-r.html", "8 Czysty i wydajny kod w R 8.1 Czysty kod 8.2 Styl kodu i narzędzia pomagające w utrzymaniu czystego kodu", " 8 Czysty i wydajny kod w R 8.1 Czysty kod Na początku zajmiemy się szeroko pojętą czystością kodu. Aby dany kod mógł aspirować do takiego miana, musi przede wszystkim spełniać dwa podstawowe warunki: Być łatwym do zrozumienia Aby kod był łatwy do zrozumienia musi być przede wszystkim czytelny. Niewątpliwie pomoże w\\(~\\)tym odpowiednie nazwanie zmiennych, zadbanie o to, żeby wszystkie użyte funkcje i obiekty miały swoją określoną rolę oraz by relacje między nimi były zrozumiałe. Być łatwym do zmiany Tworząc kod powinniśmy myśleć o tym, że będzie on w przyszłości wykorzystywany. Aby to ułatwić, musi być napisany w taki sposób, żeby można było nanieść drobne poprawki lub zmienić dane bez konieczności zmieniania całego kodu. Jeśli te dwa warunki nie są spełnione, istnieje obawa, że wprowadzenie nawet najmniejszych zmian całkowicie zniszczy kod. 8.1.1 Co jeśli w kodzie jest ,,bałagan’’? Nieuporządkowany i nieklarowny kod może sprawić w przyszłości wiele kłopotów, takich jak na przykład: Zmarnowanie czasu Jeśli my lub ktokolwiek inny będzie chciał w przyszłości wykorzystać taki kod z pewnością straci mnóstwo czasu na próby jego przeczytania i zrozumienia. Gdy już mu się to uda, może napotkać kolejny problem w postaci trudności z wprowadzeniem jakichkolwiek zmian. Ograniczenie lub nawet brak możliwości rozwoju Złe napisanie kodu może spowodować, że po jego jedynym użyciu stanie się bezwartościowy. Nie będzie sensu wprowadzać w nim jakichkolwiek zmian (gdyż będzie to zbyt pracochłonne), ani w żaden sposób rozwinąć by mógł posłużyć do przyszłych projektów (gdyż nawet najmniejsze zmiany mogą ,,zepsuć’’ istniejący kod). Podatność na wystąpienie błędów W nieczytelnym i napisanym w sposób niezrozumiały kodzie, łatwo przemycić błędy, które na pierwszy rzut oka są niewidoczne, ale wychodzą na jaw później. 8.1.2 Opis zmiennych 8.1.3 Opis intencji Aby tworzyć czysty kod musimy pamiętać o kilku zasadach. Jedną z nich jest odpowiednie nazywanie zmiennych. Nie powinniśmy używać do tego skrótów, czy przypadkowych znaków. Idealna nazwa od razu wskazuje na to, czym jest dany obiekt oraz co oznacza. Przedstawia zamiary, jakie mamy do nazywanego obiektu. 8.1.4 Unikanie błędnych informacji Równie ważne jest, aby w nazwach nie znajdywały się błędy lub informacje, które mogą wprowadzić potencjalnego czytelnika w błąd. Mówimy tu np. o: - nazwaniu kilku obiektów zbyt podobnie, - użyciu do nazwania listy (np. osób) słowa \\(\\mathtt{List}\\), choć w rzeczywistości ta ,,lista’’ osób może być wektorem, - użyciu trudno rozróżnialnych znaków (takich jak np. 0 i O), - nazwaniu wszystkich obiektów za pomocą jednej litery i cyfry (np. \\(x_1,x_2,...,x_n\\)). 8.1.4.1 Kilka wskazówek Jakie powinny być idealne nazwy obiektów w R? Oto kilka wskazówek: - zrozumiałe dla osób, dla których jest przeznaczony kod, - utrzymane w jednym stylu, - łatwe do zrozumienia i napisania, - nazwa obiektu powinna być rzeczownikiem, który wskazuje na to, z czym mamy do czynienia, - nazwa funkcji powinna być czasownikiem wskazującym na to, co robi dana funkcja. 8.1.5 Funkcje W tym rozdziale dowiemy się jak pisać ,,dobre’’ funkcje. Tutaj również musimy pamiętać o kilku zasadach. Funkcje powinny: - być możliwie jak najkrótsze, - odpowiadać za jedno pojedyncze zadanie, - być na jednym poziomie abstrakcji, - mieć maksymalnie 3 parametry. To znaczy, że nie jest wskazane, aby tworzyć jedną wielką funkcję, która np. wylicza kilkanaście rzeczy, aby na końcu wygenerować jeden wynik. Zamiast tego lepiej stworzyć kilka mniejszych funkcji, które będą się odwoływały do poprzednich. Dzięki temu nasz kod będzie bardziej przejrzysty oraz w prosty sposób będzie można sprawdzić, czy pojedyncze funkcje działają poprawnie. Co więcej, nie ma sensu tworzyć funkcji, która zwraca nam już oprawioną tabelę z wynikami. Lepiej, gdy zwraca surowe wyniki, a tworzeniem tabeli zajmuje się kolejna funkcja. Przykładowa, poprawnie napisana funkcja: calculate_conf_interval = function(sample, alpha) { len = length(sample) successes = length(sample[sample == 1]) mi = successes / n se = sqrt(mi * (1 - mi) / len) quantile = qt(1 - alpha / 2, len - 1) left = mi - quantile * se right = mi + quantile * se return(c(left, right)) } Przykładowa funkcja, napisana w ,,nieładny’’ sposób: func= function(x,y,temp1,temp2){ n =length(x) s <-length(x[x==1]) m = s/n sgm = sqrt(mi *(1- m)/n) q<-qt(1 - y /2,len-1) tmp = (s + 0.5 * q ^ 2) /(n + q ^ 2) se = sqrt(tmp *(1 - tmp)/ (n+ q^2)) l<- tmp- q* se r = tmp + q*se return(c(l,r))} Główne problemy: - czasem przypisanie jest za pomocą =, czasem <-, - brak spacji po przecinkach, - brak spacji pomiędzy +, -, *, /, itd, - niepoprawnie umiejscowione nawiasy {, }. - nazwa funkcji nie opisuje, co robi ta funkcja, - zmienne mają nic nieznaczące i jednoliterowe nazwy, - nazwa zmiennej tmp także nie mówi, czym ona jest, - dwa nieużywane parametry funkcji. 8.1.6 Komentarze Zazwyczaj komentarze do kodu nie są potrzebne, a wręcz zbędne. Dzieje się tak, ponieważ dobrze napisany kod powinien sam się tłumaczyć, tzn. być na tyle zrozumiałym, żeby dodatkowe komentarze nie były potrzebne. Jeśli jednak w kodzie jest bałagan, dodatkowe komentarze mogą wręcz wprowadzić dodatkowy chaos. Od tej reguły są jednak pewne wyjątki. Jeśli używamy niezbyt oczywistych implementacji lub ,,sztuczek programistycznych’’ warto wspomnieć w komentarzu, co się w danej chwili dzieje. Wyjątkiem są też komentarze informujące o tym, co trzeba jeszcze zrobić lub o potrzebie poprawienia jakiejś części kodu. 8.1.7 Obiekt a struktura danych W kontekście pisania czystego i wydajnego kodu, należy wziąć pod uwagę rozróżnienie pomiędzy klasami a strukturami danych. Te pierwsze zawierają atrybuty i funkcje, a instancje klasy nazywamy obiektem. Zastosowanie klas pozwala na stworzenie interfejsu definującego pewne dane. Struktury danych służą natomiast do reprezentacji danych dowolnego typu a nie ich opisu. 8.2 Styl kodu i narzędzia pomagające w utrzymaniu czystego kodu Dobry styl kodowania jest porównywany do prawidłowego stosowania interpunkcji. Jest możliwe nie stosowanie się do jej zasad, jednak przestrzeganie ich pozwala, aby w zapisie panował ład i porządek. W R dominują dwa style, które pomagają utrzymać dobry układ kodu. Jednym jest tidyverse style, a\\(~\\)drugim, wywodzącym się z poprzedniego, Google style. Istnieją przewodniki, które ułatwiają stosowanie się do zasad panujących w tych stylach. Style ustosunkowują się m.in. do stawiania spacji po przecinkach, przed operatorami matematycznymi oraz po nich, a także podkreślników w nazwach. Dodatkowo można zainstalować pakiety, które będą pomagać w utrzymaniu schludnego kodu: cleanr, stylerr, lintr. "],["interaktywna-wizualizacja-danych-z-pakietem-shiny-interfejs-użytkownika.html", "9 Interaktywna wizualizacja danych z pakietem shiny: interfejs użytkownika 9.1 Wstęp 9.2 Tworzenie UI 9.3 Układ strony 9.4 Elementy wejścia i wyjścia 9.5 Przykład użycia 9.6 Wygląd aplikacji", " 9 Interaktywna wizualizacja danych z pakietem shiny: interfejs użytkownika 9.1 Wstęp Shiny jest pakietem R pozwalającym na tworzenie interaktywnych aplikacji webowych w łatwy i przystępny sposób. Aplikacja w shiny zbudowana jest z dwóch następujcych elementów: ui - user interface, czyli obiekt, w którym zawarty jest wygląd aplikacji, server - funkcja organizująca działanie aplikacji. Do uruchomienia aplikacji służy funkcja shinyApp(ui, server). Stworzenie dobrej i czytelnej aplikacji może znacznie ułatwić analizowanie danych. W tej notatce zajmiemy się omówieniem elementów oraz podstawowych schematów budowy UI. library(shiny) library(shinyWidgets) library(shinydashboard) 9.2 Tworzenie UI Do budowania podstawowego interfejsu w shiny będziemy korzystać z funkcji fluidPage, w której tworzymy cały UI. Wszystkie informacje o rodzajach wprowadznych danych, strukturze wyświetlanych danych oraz szeroko rozumianej estetyce aplikacji będą zawarte wewnątrz tej funkcji. ui <- fluidPage( # coś ) 9.3 Układ strony Tym co jest bardzo ważne w UI jest oczywiście wygląd, a dokładniej mówiąc przejrzystość i czytelność, dlatego chcielibyśmy uporządkować wyświetlane elementy tak, aby umożliwić użytkownikowi intuicyjne korzystanie z aplikacji. Pakiet shiny oferuje wiele narzędzi pozwalających na zorganizowanie układu interfejsu zgodnie z naszymi oczekiwaniami. Przydadzą nam się do tego następujące funkcje: titlePanel - funkcja tworząca panel tytułowy, w której podajemy tytuł aplikacji, sidebarLayout - funkcja organizująca wygląd strony jako mniejszy panel boczny po lewej stronie oraz większy panel po prawej stronie, sidebarPanel - funkcja, którą możemy umieścić w poprzedniej funkcji, aby uporządkować panel, w którym będziemy np. wprowadzać dane, mainPanel - funkcja, w której umieszczamy treści, które chcemy, aby znalazły się w panelu głównym, tabsetPanel - funkcja umożliwiająca organizowanie paska zakładek. Aby utworzyć zakładki w jej ciele używamy funkcji tabPanel, w której umieszczamy dowolne treści, np. wykresy lub tabele. Oprócz tego możemy bardziej modyfikować wygląd aplikacji dzięki funkcjom fluidRow i column pozwalającym na uporządkowanie obiektów odpowiednio w wierszach oraz kolumnach. 9.4 Elementy wejścia i wyjścia Układ strony należy oczywiście podporządkować temu jaką funkcję ma pełnić aplikacja, a także temu jaki rodzaj interakcji ma mieć z nią docelowo użytkownik. Interakcje użytkownika z aplikacją można intuicyjnie podzielić na to co zostaje do aplikacji wprowadzone (input) oraz to co ostatecznie w związku z tym aplikacja zwraca (output). Każdy input i output jest w kodzie identyfikowany dzięki nadanej mu przez nas nazwie. Wewnątrz fluidPage możemy zawrzeć różne rodzaje inputów i outputów w zależności od rodzaju wprowadzanych/wyświetlanych danych. 9.4.0.1 Przykładowe elementy wejścia textInput - funkcja tworząca pole, w którym użytkownik może wprowadzić dowolny tekst, ui <- fluidPage( # Okienko do wpisywania tekstu textInput("nazwa_inputu_1", "Tekst wyświetlany w aplikacji") ) numericInput - funkcja tworząca pole, w którym użytkownik może wprowadzić wartość liczbową, ui <- fluidPage( # Okienko do wpisywania liczb numericInput("nazwa_inputu_2", "Tekst wyświetlany w aplikacji", # Wartość domyślna value = 10) ) selectInput - funkcja tworząca listę, z której użytkownik może dokonać wyboru - domyślnie parametr multiple umożliwia wybór jednej pozycji z listy, ui <- fluidPage( # Możliwość wybrania z listy selectInput("nazwa_inputu_3", "Tekst wyświetlany w aplikacji", # Lista możliwości do wyboru choices = c("Wybór_1", "Wybór_2")) ) sliderInput - funkcja tworząca suwak umożliwiający użytkownikowi wybór zakresu interesujących go wartości, ui <- fluidPage( # Suwak do wyboru wartości sliderInput("nazwa_inputu_4", "Tekst wyświetlany w aplikacji", # Wartość domyślna value = 1, # Wartość minimalna min = 0, # Wartość maksymalna max = 10) ) dateRangeInput - funkcja tworząca pole wyboru zakresu interesujących dat. ui <- fluidPage( # Pole wyboru zakresu dat dateRangeInput("nazwa_inputu_5", "Tekst wyświetlany w aplikacji", # Data początkowa start = "2001-01-01", # Data końcowa end = "2010-12-31") ) 9.4.0.2 Przykładowe elementy wyjścia Używanie funkcji wyświetlających outputy jest bardzo proste, ponieważ w UI decydujemy jedynie gdzie i jak wyswietlić output, który jest obiektem utworzonym wewnątrz funkcji server na podstawie wprowadzonego przez użytkownika inputu. textOutput - funkcja wyświetlająca tekst, ui <- fluidPage( # Wyświetla tekst, który stworzyliśmy w serwerze pod daną nazwą textOutput("nazwa_outputu_1") ) tableOutput - podstawowa funkcja wyświetlająca tabelę, ui <- fluidPage( # Wyświetla tabelę stworzoną w serwerze pod daną nazwą tableOutput("nazwa_outputu_2") ) DTOutput - funkcja wyświetlająca interaktywną ramkę danych z użyciem pakietu data.table, ui <- fluidPage( # Interaktywna ramka danych z użyciem data.table DT::DTOutput("nazwa_outputu_3") ) plotOutput - funkcja wyświetlająca wykres. ui <- fluidPage( # Wyświetla wykres stworzony w serwerze plotOutput("nazwa_outputu_4", # Szerokość wykresu width = "100%", # Wysokość wykresu height = "400px") ) 9.5 Przykład użycia Oczywiście powyższe kody były jedynie fragmentami większej całości. Poniżej możemy zobaczyć przykładowy kod obrazujący strukturę budowy interfejsu. Rzeczą, o której należy pamiętać jest oddzielanie funkcji przecinkami. ui <- fluidPage( # Tytuł titlePanel("Tytuł"), # To co będzie wyświetlone z boku interfejsu sidebarLayout( # Panel boczny sidebarPanel( # Pierwszy input - wybór selectInput("nazwa_inputu_1", "Tekst wyświetlany w aplikacji", choices = c("Wybór_1", "Wybór_2")), # Drugi input - suwak sliderInput("nazwa_inputu_2", "Tekst wyświetlany w aplikacji", value = 1, min = 0, max = 10) ), # Główny panel mainPanel( # Tworzymy zakładki tabsetPanel( # Pierwsza zakładka - wykres tabPanel("Tytuł wykresu", plotOutput("nazwa_outputu_1")), # Druga zakładka - ramka danych tabPanel("Tytuł ramki", DT::DTOutput("nazwa_outputu_2")) ) ) ) ) Dodatkowo warto zdawać sobie sprawę, że po wprowadzeniu danych przez użytkownika outputy aktualizują się automatycznie, dlatego często przydatne jest programowanie reaktywne z funkcją observeEvent oraz użycie actionButton, który pozwala na wykonanie danego działania dopiero po kliknięciu odpowiedniego przycisku przez użytkownika. 9.6 Wygląd aplikacji Ostatecznie chcielibyśmy, aby aplikacja wyglądała bardziej estetycznie. Możemy do tego użyć kilku narzędzi. Po pierwsze możemy zmienić motyw naszej aplikacji. Z pomocą przychodzi nam funkcja shinythemes::themeSelector(), którą musimy umieścić w naszym UI. Wtedy w naszej aplikacji pojawia się pole z możliwością wyboru motywu. Gdy już wybierzemy ulubiony motyw zamieniamy poprzednią funkcję w UI na theme = shinythemes::shinytheme('NASZ_MOTYW') i gotowe! Poza tym Shiny umożliwia całkowitą customizację wyglądu aplikacji przy użyciu HTML, CSS oraz JavaScript. Ostatnim narzędziem, o którym warto pamiętać, jest shinyWidgetsGallery(). Jest to bardzo użyteczna aplikacja stworzona w bibliotece shinyWidgets, dzięki której możemy między innymi zobaczyć w praktyce działanie różnego typu inputów oraz kod umożliwiający użycie ich w aplikacji. 9.6.1 Uwaga W tej notatce omówiliśmy podstawowe elementy pozwalające na zbudowanie interfejsu w shiny ale chcielibyśmy też dodać, że w poszukiwaniu bardziej zaawansowanych rozwiązań warto odwiedzić stronę https://shiny.rstudio.com/, gdzie można znaleźć dokumentację pakietu shiny, wiele przykładów oraz nieomówionych tu funkcji. "],["interaktywna-wizualizacja-danych-z-pakietem-shiny-strona-serwerowa.html", "10 Interaktywna wizualizacja danych z pakietem shiny: strona serwerowa 10.1 Wstęp 10.2 Serwer Shiny", " 10 Interaktywna wizualizacja danych z pakietem shiny: strona serwerowa 10.1 Wstęp Shiny to biblioteka w R pozwalająca na budowanie interaktywnych aplikacji w prosty i szybki sposób. Aplikacja Shiny składa się z dwóch części, opisywanych w dwóch osobnych plikach: interfejs użytkownika (UI), czyli jak aplikacja będzie wyglądać u użytkownika oraz sposób przetwarzania danych (serwer). W tej pracy zajmiemy się stroną serwerową Shiny. 10.2 Serwer Shiny Aplikacje Shiny zazwyczaj budujemy w sytuacjach, w których mamy dane, chcemy obliczyć pewne rzeczy i narysować odpowiednie wykresy. Użytkownik widzi efekt końcowy, czyli to jak zaprogramowaliśmy gdzie ma się wyświetlać wynik, natomiast w części serwerowej opisujemy jak ten wynik ma być obliczony. Jest to więc część zależna od pliku UI. Musimy więc w kodzie serwera zamieścić obiekty opisane w UI. Zauważmy, że tworzymy kod serwera jako funkcję od dwóch parametrów: input, output. W środku serwera definiujemy zależności pomiędzy inputami i outputami. Jedną z zalet Shiny jest interaktywność. Dzięki temu użytkownik może na bieżąco zmieniać parametry i generować nowe wykresy. Jednak generowanie kodu na nowo przy każdej zmianie danych nie zawsze jest pożądane. Ważnym pojęciem przy pisaniu strony serwerowej jest reaktywność (żródło infografiki: Shiny Cheat Sheet). reaktywnosc Jeśli zmienna jest reaktywna, to znaczy że jakakolwiek jej zmiana powoduje ponowne uruchomienie funkcji z nią powiązanych. Do budowania reaktywnych wyrażeń używamy funkcji reactive(). Taka zmienna jest liczona tylko raz i wyrażenia z nią związane używają tej wartości aż do momentu aktualizacji wybranego przez użytkownika. Z pojęciem reaktywności wiąże się kilka ważnych funkcji: reactiveValues(...), które tworzy listą reaktywnych zmiennych, isolate(expr) - zapobiega zależności od reaktywnych zmiennych, render*() - funkcje tworzące obiekty do wyświetlenia, które zmieniają się wraz z reaktywnymi zmiennymi, observeEvent(...) - gdy nie chcemy aby model od razu się zaktualizował przy zmianie danych, a przy jakiejś określonej akcji, reactive() - tworzy reaktywne wyrażenia eventReactive - tworzy reaktywne wyrażenia, które nie zależą od wszystkich reaktywnych zmiennych, a zależą jedynie od akcji wymienionych w pierwszym argumencie. "],["podstawy-kontroli-wersji-przy-pomocy-gita.html", "11 Podstawy kontroli wersji przy pomocy Gita 11.1 Podstawowe informacje 11.2 Podstawowe komendy 11.3 Repozytoria 11.4 Podstawowe komendy 11.5 Cofanie zmian 11.6 Gałęzie 11.7 Tworzenie własnych repozytoriów", " 11 Podstawy kontroli wersji przy pomocy Gita 11.1 Podstawowe informacje System kontroli wersji to narzędzie, które zarządza zmianami wprowadzanymi w plikach i katalogach w projekcie. Istnieje wiele systemów kontroli wersji. Przykładem takiego systemu jest Git. Jego mocne strony to: Nic, co jest zapisane w Git, nigdy nie jest tracone, więc zawsze możesz wrócić, aby zobaczyć, które wyniki zostały wygenerowane przez które wersje twoich programów. Git automatycznie powiadamia Cię, gdy Twoja praca koliduje z pracą innej osoby, więc jest trudniej (choć nie jest to niemożliwe) o przypadkowe nadpisanie pracy. Git może synchronizować pracę wykonywaną przez różne osoby na różnych komputerach. Kontrola wersji nie dotyczy tylko oprogramowania: książki, artykuły, zestawy parametrów i wszystko, co zmienia się w czasie lub wymaga udostępnienia, może i powinno być przechowywane i udostępniane za pomocą czegoś takiego jak Git. Każdy z projektów Git składa się z dwóch części: plików i katalogów, które tworzysz i edytujesz bezpośrednio, oraz dodatkowych informacji, które Git rejestruje o historii projektu. Połączenie tych dwóch rzeczy nazywa się repozytorium. Git przechowuje wszystkie dodatkowe informacje w katalogu o nazwie \\(\\texttt{.git}\\) znajdującym się w katalogu głównym repozytorium. 11.2 Podstawowe komendy Używając Gita zapewne często będziemy chcieli sprawdzić stan swojego repozytorium. Aby to zrobić, użyjemy polecenie \\(\\texttt{git status}\\). \\(\\texttt{git status}\\) - wyświetla listę plików, które zostały zmodyfikowane od czasu ostatniego zapisania zmian Git ma obszar przejściowy, w którym przechowuje pliki ze zmianami, które chcemy zapisać, a które nie zostały jeszcze zapisane. \\(\\texttt{git status}\\) - pokazuje, które pliki znajdują się w tym obszarze przejściowy i które mają zmiany, które nie zostały jeszcze zatwierdzone \\(\\texttt{git diff}\\) - pokaże wszystkie zmiany w twoim repozytorium (porównując obecną postać plików z ostatnio zapisaną) \\(\\texttt{git diff directory}\\) - pokaże zmiany w plikach w jakimś katalogu (porównując obecną postać plików z ostatnio zapisaną) \\(\\texttt{git diff filename}\\) - pokaże zmiany w danym pliku (porównując obecną postać z ostatnio zapisaną) Git różnice między dwiema wersjami pliku wyświetla w poniższy sposób: diff --git a/report.txt b/report.txt index e713b17..4c0742a 100644 --- a/report.txt +++ b/report.txt @@ -1,4 +1,5 @@ -# Seasonal Dental Surgeries 2017-18 +# Seasonal Dental Surgeries (2017) 2017-18 +# TODO: write new summary gdzie: \\(\\texttt{a/report.txt, b/report.txt}\\) to pierwsza i druga wersja pliku, linia druga wypisuje klucze do wewnętrznej bazy danych zmian Gita, \\(\\texttt{--- a/report.txt, +++ b/report.txt}\\) oznacza, że usuwane linie oznaczone są przedrostkiem \\(\\texttt{-}\\), dodawane linie oznaczone są przedrostkiem \\(\\texttt{+}\\), linia zaczynająca się od \\(\\texttt{@@}\\) mówi, gdzie wprowadzane są zmiany. Pary liczb to numer lini ,,startowej’’ i liczba linii, kolejne linie są listą zmian, które zostały wprowadzone. \\(\\texttt{git add filename}\\) - dodaje plik do obszaru przejściowego \\(\\texttt{git diff -r HEAD}\\) - porówna pliki z repozytorium z plikami z obszaru przejściowego \\(\\texttt{git diff -r HEAD path/to/file}\\) - porówna konkretny plik z repozytorium z plikiem z obszaru przejściowego \\(\\texttt{nano filename}\\) - otwiera plik w edytorze tekstowym \\(\\texttt{nano}\\) poruszanie się strzałkami \\(\\texttt{Backspace}\\) - usuń znak \\(\\texttt{Ctrl-K}\\): usuń linię \\(\\texttt{Ctrl-U}\\): cofnij usunięcie linii \\(\\texttt{Ctrl-O}\\): zapisz plik \\(\\texttt{Ctrl-X}\\): wyjdź z edytora \\(\\texttt{git commit -m "comment"}\\) - zapisuje zmiany w obszarze przejściowym z jednowierszowym komunikatem o wprowadzonych zmianach \\(\\texttt{git commit --amend - m "new message"}\\) - zmienia ostatni komunikat \\(\\texttt{git log}\\) - wyświetlenie historii projektu (od najnowszych zmian). Wyświetlany zostaje unikatowy identyfikator dla zatwierdzenia oraz informacje na temat tego kto dokonał zmiany, kiedy i jaki komunikat napisał dokonując zmiany. \\(\\texttt{spacja}\\) - przejcie w dół o stronę \\(\\texttt{q}\\) - wyjście \\(\\texttt{git log path}\\) - wyświetlenie historii danego pliku lub katalogu 11.3 Repozytoria    Informacje dotyczące zatwiedzonych zmian przechowywane są poprzez trzypoziomową strukturę. Każde zatwierdzenie (tzw. commit) zwiera komunikat o zatwierdzeniu i informacje o autorze i czasie, w którym zatwierdzenie zmian zostało wykonane. Każdy commit ma również swoje drzewo, które śledzi, gdzie w repozytorium dokonano zmian. Dla każdego pliku w drzewie istnieje tzw. blob (binary large object). Każdy blob zawiera skompresowaną migawkę zawartości pliku, z chwili w której nastąpił commit. Czym jest hash? Każde zatwierdzenie zmian w repozytorium ma unikalny identyfikator zwany hashem. Jest on zapisywany jako 40-znakowy ciąg szesnastkowy. Zazwyczaj jednak wystarczy podać pierwsze 6 lub 8 znaków hasha, by odnaleźć konkretne zatwierdzenie (commit). Identyfikatory jakimi są hashe umożliwiają Gitowi wydajne udostępnianie danych pomiędzy repozytoriami. Jak wyświetlić konkretny commit? By wyświetlić szczegóły dotyczące konkretnego commitu należy użyć komendy git show z pierwszymi 6 znakami hasha danego commmitu np.: git show Oda2f7. Czym jest odpowiednik ścieżki względnej w Git? Innym sposobem identyfikacji zatwierdzenia jest użycie odpowiednika ściezki względnej. By wyświetlić zatem ostatni commit możemy użyć komendy git show z etykietą HEAD. Jeśli natomiast zamiast HEAD wpiszemy HEAD~1 wyświetlony zostanie przedostatni commit, polecenie git show HEAD~2 zwróci nam natomiast jeszcze wcześniejszy commit itp. 11.4 Podstawowe komendy git log - wyświetla całą historię danego pliku lub projektu. W Gicie możemy jednak sprawdzić bardziej szczegółowe informacje. Dzięki poleceniu git annotate file możemy sprawdzić kto i kiedy dokonał ostatniej zmiany w każdej linijce pliku. git diff ID1..ID2 - umożliwia sprawdzenie zmian pomiędzy dwoma commitami, których identyfikatory to odpowiednio ID1 i ID2. git add - polecenie umożliwiające dodanie nowego pliku. Po wykonaniu tego polecenia Git zaczyna śledzić dodany plik. git clean -n - pokazuje listę plików, które są w repozytorium, ale których historia nie jest śledzona przez Gita. git clean -f - usuwa pliki, które są w repozytorium i których historii nie śledzi Git. Z używaniem tego polecenia należy uważać, ponieważ usuwa ono pliki z pamięci na stałe i nie da się ich już odzyskać. git config - -list - wyświetla ustawienia Gita. git config - -system - wyświetla ustawienia każdego użytkownika na danym komputerze. git config - -global - wyświetla ustawienia każdego projektu. git config - -local - wyświetla ustawienia poszczególnego projektu. Każdy poziom zastępuje poziom nad nim, więc ustawienia lokalne (na projekt) mają pierwszeństwo przed ustawieniami globalnymi (na użytkownika), które z kolei mają pierwszeństwo przed ustawieniami systemowymi (dla wszystkich użytkowników na komputerze). git config - -global setting value - zmienia konfigurację odpowiedniej wartości dla wszystkich projektów na danym komputerze. Jako setting należy wpisać to co chcemy zmienić (np. user.name, user.email itp.), a jako value to co chcemy ustawić. 11.5 Cofanie zmian Teraz dowiemy się jak cofnąć wprowadzone zmiany. \\(\\texttt{git reset HEAD}\\)- usuwa ostatnio dodany plik ze śledzenia, \\(\\texttt{git checkout -- filename}\\) - odrzuci zmiany, które nie zostały jeszcze dodane do śledzenia, \\(\\texttt{git reset HEAD path/to/file}\\) - odrzuci ostatnie zmiany w pliku, który został juz dodany do śledzenia, \\(\\texttt{git checkout 2242bd filename}\\)- zamienia aktualna wersje pliku, na tę o hashu ‘2242bd’. Do ostatniej komendy przydatne może być wykonanie poniższzego polecenia, aby sprawdzić hashe plików. \\(\\texttt{git log - 3 filename}\\)- pokaże 3 ostatnie commity dotyczące wskazanego pliku. Poniższe dwie komendy pokazują, jak cofać zmiany na więcej niż jednym pliku. \\(\\texttt{git reset HEAD data}\\)- usuwa ze śledzenia wszystkie pliki z katalogu data. Jeżeli nie podamy nazwy katalogu( wtedy wystarczy samo \\(\\texttt{git reset}\\)) wszystkie pliki zostaną usunięte. \\(\\texttt{git checkout -- data}\\)- wszystkie pliki w katalagu data zostaną cofnięte do poprzednich wersji. 11.6 Gałęzie Jeśli nie używasz kontroli wersji, typowym przepływem pracy jest tworzenie różnych podkatalogów do przechowywania różnych wersji projektu w różnych stanach, na przykład deweloperskich i końcowych. Oczywiście zawsze kończy się to ostateczną aktualizacją i ostateczną aktualizacją-poprawioną. Problem polega na tym, że trudno jest to rozwiązać, jeśli masz odpowiednią wersję każdego pliku w odpowiednim podkatalogu i ryzykujesz utratę pracy.     Jednym z powodów, dla których Git jest popularny, jest jego obsługa tworzenia gałęzi (branchy), co pozwala na posiadanie wielu wersji Twojej pracy i pozwala na systematyczne śledzenie każdej wersji.     Każda gałąź jest jak wszechświat równoległy: zmiany, które wprowadzasz w jednej gałęzi, nie wpływają na inne gałęzie (dopóki nie połączysz ich z powrotem).     Domyślnie kazde repozytorium Gita ma branch zwany master.     Podstawowe komendy związanie z działaniem na branchach (gałęziach):   \\(\\texttt{git branch}\\) - pokazuje wszystkie branche w repozytorium (branch, w którym obecnie się znajdujesz będziesz wylistowany z \\(*\\)).   \\(\\texttt{git diff branch1..branch2}\\) - wyświetla różnice między dwoma branchami   Ciekawostka: \\(\\texttt{git diff branch1..branch2}\\) - -\\(\\texttt{shortstat}\\) - wyświetla konkretną liczbę plików które się różnią między dwoma branchami  \\(\\texttt{git checkout branch1}\\) - pozwala przełączyć się na branch1  \\(\\texttt{git checkout -b branch-name}\\) - pozwala utworzyć nowego brancha o nazwie branch-name    Rozgałęzianie pozwala tworzyć równoległe wszechświaty. Scalanie (merging) to sposób, w jaki łączysz je z powrotem. Kiedy łączysz jedną gałąź (nazwijmy ją źródłową) z inną (nazwijmy ją docelową), Git włącza zmiany wprowadzone w gałęzi źródłowej do gałęzi docelowej. Jeśli te zmiany nie nakładają się, wynikiem jest nowe zatwierdzenie w gałęzi docelowej, które zawiera wszystko z gałęzi źródłowej. Do mergowania dwóch gałęzi używamy polecenia:   \\(\\texttt{git merge source destination}\\) - mergowanie dwóch branchy w jeden  Czasami zmiany w dwóch gałęziach będą ze sobą kolidować: na przykład poprawki błędów mogą dotyczyć tych samych wierszy kodu lub analizy w dwóch różnych gałęziach mogą dołączać nowe (i różne) rekordy do pliku danych podsumowania. W takim przypadku ty decydujesz o sprzeczności zmian.    Jeżeli podczas mergowania występuje konflikt Git informuje Cię, że wystapił problem a \\(\\texttt{git status}\\) poinformuje Cię, które pliki wmagają rozwiązania konfliktów.    Git pozostawia na danym pliku znaczniki, aby poinformować Cię o konkretnym miejscu konfliktu. Znaczniki te wyglądają następująco: <<<<<<< destination-branch-name ...changes from the destination branch... ======= ...changes from the source branch... >>>>>>> source-branch-name   Aby rozwiązać konflikt edytuj plik, usuwając znaczniki i wprowadź wszelkie zmiany potrzbne do rozwiązania kofilktu, a następnie zrób commit tych zmian.    11.7 Tworzenie własnych repozytoriów Przejdźmy do kolejnego zagadnienia związanego z pracą w Gicie.     Do tej pory wszystkie poznane funkcje Gita dotyczyły działań na repozytoriach już istniejących. Aby stworzyć własne repozytorium w bieżącym katalogu roboczym wystarczy komenda:  \\(\\texttt{git init project-name}\\)   Warto wspomnieć, że chociaż Git pozwala tworzyć zagnieżdżone repozytoria nie powinieneś tego robić. Aktualizacja takich repozytoriów bardzo szybko staje się bardzo skomplikowana, ponieważ musisz powiedzieć Gitowi, w którym z dwóch katalogów .git ma być przechowywana aktualizacja.    Nie tworzymy repozytorium w innym już istniejącym!     Poniżej kilka ważnych komend:  \\(\\texttt{git init}\\) - inicjalizacja repozytorium w bieżącym katalogu   \\(\\texttt{git init /path/to/project}\\) - inicjalizacja repozytorium we wskazanym ścieżką katalogu   \\(\\texttt{git clone URL}\\) - tworzenie kopii istniejącego pod wskazanym adresem URL repozytorium \\(\\texttt{git clone /existing/project newprojectname}\\) - tworzenie kopii istniejącego repozytroium o zadanej nazwie - newprojectname   \\(\\texttt{git remote}\\) - wyświetla informację o fizycznej lokalizacji na serwerze Gita, z której zostało sklonowane repo   \\(\\texttt{git remote -v}\\) - wyświetla informację o URL serwerze Gita, z którego zostało sklonowane repo   \\(\\texttt{git remote add remote-name URL}\\) - pozawala na dodanie własnego remota z podanego URL \\(\\texttt{git remote rm remote-name}\\) - usuwanie istniejącego remota   \\(\\texttt{git pull remote branch}\\) - pobieranie zmian w branchu w lokalnym repozytorium i mergowanie ich z bieżacym brnachem w lokalnym repozytorium   Uwaga!   Git powstrzymuje Cię przed pobieraniem ze zdalnego repozytorium zmian, które mogą nadpisać niezapisane lokalnie zmiany. Wystarczy zrobić commit tych zmian lub cofnąć je, a następnie spullować repo ponownie.   \\(\\texttt{git push remote-name branch-name}\\) - pushuje zmiany wprowadzone lokalnie na danym branchu do zdalnego repozytorium   "],["programowanie-obiektowe-w-r-klasy-s3.html", "12 Programowanie obiektowe w R: klasy S3 12.1 Systemy programowania obiektowego w R 12.2 S3 12.3 S4", " 12 Programowanie obiektowe w R: klasy S3 Programując w R jesteśmy oswojeni z myśleniem kategoriami funkcji - przekształceń nakładanych na macierze lub ramki danych. Jest to naturalne ze względu na zastosowanie R głównie w statystyce i pochodnych jej dziedzin. Tymczasem programowanie obiektowe, choć często niepotrzebne do przeprowadzenia analiz lub symulacji, może się okazać użyteczne przy tworzeniu większego projektu, w szczególności projektu współtworzonego przez więcej osób. Zdefiniowanie klas i metod nadaje projektowi strukturę, co sprawia, że jego rozbudowa przebiega mniej chaotycznie. Ponadto, znajomość podstaw systemów programowania obiektowego w R umożliwia nam lepsze zrozumienie działania bazowych funkcji i obiektów R oraz ewentualne ich rozbudowywanie. 12.1 Systemy programowania obiektowego w R W przeciwieństwie do wielu popularnych języków programowania, R nie ma jednego ujednoliconego systemu programowania obiektowego - jest ich wiele, przy czym różnią się nie tylko składnią, ale też funkcjonalnościami. Pierwsze wersje pierwowzoru języka R - języka S nie posiadały żadnego systemu obiektowego. Wraz z trzecią wersją S wprowadzono pierwszy z nich: S3. Następnie, kiedy ten okazał się niewystarczający dla potrzeb użytkowników - S4. Oba systemy finalnie znalazły się w base języka R. Z czasem, w miarę wzrastania potrzeb, powstawały kolejne alternatywne systemy klas, które funkcjonowały równolegle i równoprawnie. Do dzisiaj nie wyróżniamy systemu “oficjalnego” czy preferowanego - każdy z kilku pozostałych w powszechnym użyciu ma swoje zastosowania, w których niekorzystnym lub niewygodnym jest zastąpienie go innym. W tej notatce przyjrzymy się przede wszystkim S3. 12.2 S3 S3 to system, z którym stykamy się najczęśniej. Wszystkie wbudowane klasy obiektów zostały zbudowane właśnie przy pomocy systemu S3. By sprawdzić, do jakiej klasy S3 należy obiekt, używamy funkcji class. W codziennej pracy w R operujemy w wiekszości na obiektach zbudowanych w S3. Klasy S3 to m.in. factor,data.frame,matrix. f <- factor(c("y","n","y","n","n")) class(f) ## [1] "factor" Warto w tym miejscu podkreślić, że klasa zmiennej nie jest równoważna typowi zmiennej, np. macierz liczb jest klasy matrix, ale typu double. m <- matrix(c(1,2,3,4),2,2) class(m) ## [1] "matrix" "array" typeof(m) ## [1] "double" Każdemu obiektowi mogą być (ale nie muszą) przypisane atrybuty. Atrybuty mozna rozumieć jako cechy lub parametry obiektu. W przypadku macierzy są to jej wymiary. attributes(m) ## $dim ## [1] 2 2 12.2.1 Klasy i atrybuty W systemie S3 nie tworzymy definicji klasy, nie określamy również, jakie atrybuty obiekt danej klasy ma. Obiektowi możemy nadać klasę przy jego utworzeniu, z użyciem funkcji structure: kanapka <-structure(c("szynka", "margaryna", "chleb"), class = "jedzenie") class(kanapka) ## [1] "jedzenie" lub w dowolnym momencie po jego utworzeniu z użyciem class: szarlotka <- c("jaja", "mąka", "masło", "cukier") szarlotka <- c(szarlotka, "jabłka") class(szarlotka) <- "jedzenie" class(szarlotka) ## [1] "jedzenie" Każdemu obiektowi możemy również indywidualnie przypisać atrybuty, również na kilka sposobów, przy jego utworzeniu z użyciem structure: hot_dog <- structure(c("parówka", "bułka", "ketchup"), class="jedzenie", kalorie = 300) attributes(hot_dog) ## $class ## [1] "jedzenie" ## ## $kalorie ## [1] 300 class(hot_dog) ## [1] "jedzenie" Lub w dowolnym momencie z użyciem funkcji attr: attr(kanapka, "kalorie")=150 attr(szarlotka, "kalorie")=265 attributes(kanapka) ## $class ## [1] "jedzenie" ## ## $kalorie ## [1] 150 attributes(szarlotka) ## $class ## [1] "jedzenie" ## ## $kalorie ## [1] 265 Z użyciem funkcji attributes i attr można również “dostać się” do wartości atrybutów obiektu: attr(szarlotka, "kalorie") ## [1] 265 attributes(szarlotka)$kalorie ## [1] 265 Tworzenie obiektów różnych klas S3 jest więc bardzo proste i nie wymaga (przynajmniej formalnie) predefiniowania klasy i atrybutów. System jest więc z jednej strony bardzo elastyczny, z drugiej - nieprecyzyjny. Niesie to za sobą pewne konsekwencje, np. formalnie nic nie stoi na przeszkodzie by zrobić coś takiego: droga <- c("asfalt","pobocze", "lewy pas", "prawy pas") class(droga) <- "jedzenie" class(droga) ## [1] "jedzenie" lub takiego… średnia_bez_na <- function(...) mean(na.rm=TRUE,...) class(średnia_bez_na)<-"jedzenie" class(średnia_bez_na) ## [1] "jedzenie" lub takiego: attr(hot_dog, "kalorie") <- "przecież to prawie nie ma kalorii!" bilans_posilkow <- attr(hot_dog,"kalorie")+attr(szarlotka, "kalorie")+attr(kanapka, "kalorie") ## Error in attr(hot_dog, "kalorie") + attr(szarlotka, "kalorie"): argument nieliczbowy przekazany do operatora dwuargumentowego Dlatego należy pamiętać, by klas i atrybutów nie przydzielać chaotycznie, zachować pewne reguły, mimo że formalnie nie są wymagane przy użyciu S3. 12.2.2 Funkcje generyczne i metody Metody to funkcje działające na obiektach danej klasy. Z reguły są predefiniowane przy utworzeniu klasy wraz z polami. Inaczej jednak jest z systemem S3 w R. Nie definiujemy klasy - klasa jest tworzona przy pierwszym przypisaniu jej jakiemuś obiektowi. Metody tworzy się przy pomocy funkcji generycznych (generics). 12.2.2.1 Funkcje generyczne By lepiej zrozumieć logikę stojącą za funkcjami generycznymi, spróbujmy spojrzeć na klasy i ich metody z nieco mniej standardowej perspektywy. Dla różnych klas możemy mieć analogiczne metody, zachowujące się nieco inaczej w zależności od specyfiki klasy, np. inaczej rozumiemy różnicę między dwoma datami a różnicę między dwoma liczbami - liczby odejmujemy od siebie bezpośrednio, podczas gdy w przypadku dat oczekujemy różnicy w dniach pomiędzy nimi - w tym celu nie wystarczy bezpośrednie odjęcie od siebie dwóch dat. W systemie S3 metody nie są przypisane bezpośrednio klasie, są przypisane odpowiedniej funkcji generycznej. Funkcja generyczna określa nazwę metody wspólną dla wszystkich klas i umożliwia tworzenie wariantów metody dla różnych klas pod tą konkretną nazwą. Zanim przejdziemy do tworzenia funkcji generycznych oraz metod dla własnych klas przyjrzyjmy się działaniu już istniejących. Jedną z funkcji generycznych jest funkcja summary - funkcja podsumowująca obiekt (np. summary(lm(X~Y))). summary ## function (object, ...) ## UseMethod("summary") ## <bytecode: 0x5612082d5ef8> ## <environment: namespace:base> Użyjemy funkcji methods, by wylistować wszystkie dostępne metody dla danej funkcji generycznej. methods(summary) ## [1] summary,ANY-method summary,DBIObject-method summary,diagonalMatrix-method summary,sparseMatrix-method summary.aov ## [6] summary.aovlist* summary.aspell* summary.check_packages_in_dir* summary.connection summary.corAR1* ## [11] summary.corARMA* summary.corCAR1* summary.corCompSymm* summary.corExp* summary.corGaus* ## [16] summary.corIdent* summary.corLin* summary.corNatural* summary.corRatio* summary.corSpher* ## [21] summary.corStruct* summary.corSymm* summary.data.frame summary.Date summary.default ## [26] summary.Duration* summary.ecdf* summary.factor summary.gam* summary.ggplot* ## [31] summary.glm summary.gls* summary.haven_labelled* summary.hcl_palettes* summary.infl* ## [36] summary.Interval* summary.lm summary.lme* summary.lmList* summary.loess* ## [41] summary.manova summary.matrix summary.mlm* summary.modelStruct* summary.nls* ## [46] summary.nlsList* summary.packageStatus* summary.pdBlocked* summary.pdCompSymm* summary.pdDiag* ## [51] summary.pdIdent* summary.pdIdnot* summary.pdLogChol* summary.pdMat* summary.pdNatural* ## [56] summary.pdSymm* summary.pdTens* summary.Period* summary.POSIXct summary.POSIXlt ## [61] summary.ppr* summary.prcomp* summary.princomp* summary.proc_time summary.reStruct* ## [66] summary.rlang_error* summary.rlang_message* summary.rlang_trace* summary.rlang_warning* summary.rlang:::list_of_conditions* ## [71] summary.shingle* summary.srcfile summary.srcref summary.stepfun summary.stl* ## [76] summary.table summary.trellis* summary.tukeysmooth* summary.varComb* summary.varConstPower* ## [81] summary.varConstProp* summary.varExp* summary.varFixed* summary.varFunc* summary.varIdent* ## [86] summary.varPower* summary.vctrs_sclr* summary.vctrs_vctr* summary.warnings ## see '?methods' for accessing help and source code Każda z wypisanych nazw odpowiada wariantowi metody dla jednej klasy. Zwrócmy uwagę na specyficzną składnię nazw tych funkcji - człon po kropce odpowiada nazwie klasy, jakiej metoda dotyczy. Przyjrzyjmy się wariantom summary dla dwóch różnych klas: lm i matrix. X <- matrix(rep(1,12), 6,2) Y <- c(2,2,3,2,2,2) model <- lm(Y~X) summary.lm(model) ## ## Call: ## lm(formula = Y ~ X) ## ## Residuals: ## 1 2 3 4 5 6 ## -0.1667 -0.1667 0.8333 -0.1667 -0.1667 -0.1667 ## ## Coefficients: (2 not defined because of singularities) ## Estimate Std. Error t value Pr(>|t|) ## (Intercept) 2.1667 0.1667 13 4.8e-05 *** ## X1 NA NA NA NA ## X2 NA NA NA NA ## --- ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 ## ## Residual standard error: 0.4082 on 5 degrees of freedom summary.matrix(X) ## V1 V2 ## Min. :1 Min. :1 ## 1st Qu.:1 1st Qu.:1 ## Median :1 Median :1 ## Mean :1 Mean :1 ## 3rd Qu.:1 3rd Qu.:1 ## Max. :1 Max. :1 Jednak, by użyć odpowiedniej funkcji dla obiektu, nie musimy specyfikować jego klasy - właśnie dzięki zdefiniowaniu funkcji generycznej. Bez względu na klasę obiektu uzywamy składni funkcja_generyczna(obiekt). Wywoływana jest wówczas funkcja generyczna, która na podstawie klasy lub typu obiektu dopasowuje wariant metody. Spójrzmy jak to wygląda na przykładzie summary: summary(model) ## ## Call: ## lm(formula = Y ~ X) ## ## Residuals: ## 1 2 3 4 5 6 ## -0.1667 -0.1667 0.8333 -0.1667 -0.1667 -0.1667 ## ## Coefficients: (2 not defined because of singularities) ## Estimate Std. Error t value Pr(>|t|) ## (Intercept) 2.1667 0.1667 13 4.8e-05 *** ## X1 NA NA NA NA ## X2 NA NA NA NA ## --- ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 ## ## Residual standard error: 0.4082 on 5 degrees of freedom summary(X) ## V1 V2 ## Min. :1 Min. :1 ## 1st Qu.:1 1st Qu.:1 ## Median :1 Median :1 ## Mean :1 Mean :1 ## 3rd Qu.:1 3rd Qu.:1 ## Max. :1 Max. :1 Jak widać na przykładzie powyżej wywołanie funkcji generycznej na obiekcie spowodowało dopasowanie odpowiedniej dla klasy obiektu metody i dało identyczny efekt jak wywołanie bezpośrednio dedykowanej funkcji. 12.2.2.2 Tworzenie funkcji generycznych i metod Nowe funkcje generyczne tworzy się według następującego schematu: nazwa_metody <- function (x) { UseMethod("nazwa_metody", x) } Po utworzeniu funkcji generycznej możemy przystąpić do napisania metod dla konkretnych klas. Metody tworzymy jako funkcje nazwane według konwencji nazwa_metody.klasa. Spróbujmy wg powyższego schematu utworzyć metodę dla utworzonej wcześniej klasy jedzenie. Niech nasza metoda nazywa się zjedz: zjedz <- function(x){ UseMethod("zjedz",x) } Mając gotową funkcję generyczną możemy przejść do określenia zachowania metody dla naszej klasy: zjedz.jedzenie <- function(x){ cat("Mniam mniam\\n") } zjedz(szarlotka) ## Mniam mniam zjedz(kanapka) ## Mniam mniam zjedz(hot_dog) ## Mniam mniam Oraz, jeśli potrzebujemy, również dla innych istniejących klas, w tym również wbudowanych, np. matrix. zjedz.matrix <- function(x){ warning("Przeciez to macierz! Tego sie nie je!\\n") } zjedz(matrix(c(1,2,2,3),2,2)) ## Warning in zjedz.matrix(matrix(c(1, 2, 2, 3), 2, 2)): Przeciez to macierz! Tego sie nie je! W szczególności możemy określić zachowanie metody dla pseudoklasy default, czyli określić domyślne zachowanie metody. W kontekście naszego przykładu z klasą jedzenie moglibyśmy oczekiwać, że nasza metoda domyślnie nie będzie “jeść” żadnych obiektów, robiąc wyjątek wyłącznie dla jedzenia. zjedz.default <- function(x,...){ warning("Tego sie nie je!\\n") } zjedz(mean) ## Warning in zjedz.default(mean): Tego sie nie je! zjedz(c(1,2,3,4)) ## Warning in zjedz.default(c(1, 2, 3, 4)): Tego sie nie je! Zauważmy, że istnieje tu pewna hierarchia: zjedz(matrix(c(1,2,3,4,4,5),2,3)) ## Warning in zjedz.matrix(matrix(c(1, 2, 3, 4, 4, 5), 2, 3)): Przeciez to macierz! Tego sie nie je! zjedz(szarlotka) ## Mniam mniam Funkcja generyczna wywołuje metodę dla pseudoklasy default wtedy, gdy dla danej klasy indywidualnie nie ma zdefiniowanej metody. W pierwszej kolejności szuka metody dedykowanej dla danej klasy, dlatego dla obiektów klas jedzenie i matrix zostały wywołane odpowiednio zjedz.jedzenie i zjedz.matrix, a nie zjedz.default. 12.2.3 Dziedziczenie O dziedziczeniu mówimy, gdy jedna z klas przejmuje (dziedziczy) właściwości i cechy innej. W S3 dziedziczenie wprowadza się w zaskakująco oczywisty i prosty sposób. Każdemu obiektowi można przypisać więcej niż jedną klasę, przypisując class(obiekt) nie jedną nazwę klasy, lecz ich wektor, np. dla utworzonego wcześniej obiektu szarlotka: class(szarlotka) <- c("ciasto", "slodkosci", "jedzenie") class(szarlotka) ## [1] "ciasto" "slodkosci" "jedzenie" Kolejność klas w wektorze nie jest przypadkowa: zaczynamy od klasy “najmłodszej” (i najważniejszej, najbardziej specyficznej), a kończymy na “najstarszej” (najogólniejszej). Kolejność ta jest również obowiązująca przy dopasowywaniu metod przez funkcję generyczną: funkcja generyczna najpierw szuka metody dla pierwszej z klas w wektorze, następnie dla drugiej itd. Jeśli nie uda jej się znaleźć metody dla żadnej z klas, wywołuje metodę dla pseudoklasy default. zjedz.ciasto <- function(x){ cat("Mniam mniam, pyszne ciacho!\\n") } zjedz.slodkosci <- function(x){ cat("Słodkie, dobre, mniam.. \\n") } zjedz(szarlotka) ## Mniam mniam, pyszne ciacho! Zgodnie z oczekiwaniami funkcja generyczna wywołała metodę dla pierwszej z klas w wektorze, czyli klasy ciasto, ignorując metody dla klas slodkosci i jedzenie. W większości przypadków jednak wprowadzamy nowe klasy nie po to, by zastępować istniejące metody dla klas ogólniejszych, lecz po to by wprowadzić pewne rozszerzenia, np. metody czy atrybuty, które nie mają sensu dla innych obiektów z klasy - rodzica. Wprowadźmy metodę pokroj. Dla każdego obiektu klasy jedzenie bedzie ona dzialac identycznie - dzielić atrybut kalorie przez wskazane n i zwracać n równych “porcji” : pokroj<-function(x,...){ UseMethod("pokroj",x) } pokroj.jedzenie <- function(x,n){ porcja<-attr(x, "kalorie")/n rep(porcja,n) } pokroj(kanapka,4) ## [1] 37.5 37.5 37.5 37.5 Oprócz tego wprowadźmy metodę sensowną tylko dla klasy ciasto: posyp_cukrem_pudrem. posyp_cukrem_pudrem<-function(x,...){ UseMethod("posyp_cukrem_pudrem",x) } posyp_cukrem_pudrem.ciasto <- function(x){ cat("Syp syp syp\\n") } Zauważmy, że przez wprowadzenie dodatkowych klas obiekt szarlotka nie traci funkcjonalności klasy jedzenie: pokroj(szarlotka,5) ## [1] 53 53 53 53 53 Podczas gdy równocześnie możliwym stało się zdefiniowanie dla niego indywidualnych metod: posyp_cukrem_pudrem(szarlotka) ## Syp syp syp posyp_cukrem_pudrem(hot_dog) ## Error in UseMethod("posyp_cukrem_pudrem", x): niestosowalna metoda dla 'posyp_cukrem_pudrem' zastosowana do obiektu klasy "jedzenie" Dziedziczenie może być bardzo użyteczne, ale należy je stosować z ostrożnością, mając w pamięci elastyczność klas i atrybutów w S3. Bardzo łatwo stracić orientację, w szczególności wprowadzając dziedziczenie po klasach wbudowanych lub po klasach zbudowanych przez kogoś innego - wówczas ciężko nam wziąć pod uwagę wszystkie istniejące dla danych klas metody. Posługując się dziedziczeniem i klasami w sposób rozrzutny i nieprzemyślany łatwo możemy doprowadzić do chaosu. 12.2.3.1 NextMethod NextMethod jest używane w sytuacji, gdy wewnątrz metody klasy chcemy wywołać metodę klasy nadrzędnej (rodzica). zjedz.ciasto<- function(x){ cat("Mniam mniam, pyszne ciacho!\\n") NextMethod() } zjedz(szarlotka) ## Mniam mniam, pyszne ciacho! ## Słodkie, dobre, mniam.. zjedz.slodkosci <- function(x){ cat("Słodkie, dobre, mniam.. \\n") NextMethod() } zjedz(szarlotka) ## Mniam mniam, pyszne ciacho! ## Słodkie, dobre, mniam.. ## Mniam mniam 12.3 S4 S3 jest użyteczny i do niektórych zastosowań wystarczający, ale nie posiada wielu własności znanych z systemów programowania w innych językach. Ponadto, ze względu na dużą swobodę w tworzeniu klas i metod, utrzymanie bardziej złożonych struktur i hierarchii może być uciążliwe i mało przejrzyste z użyciem S3. Pierwszą alternatywą dla S3 był system S4. W S4 metody tworzone z użyciem tej samej logiki - przez funckje generyczne. W przeciwieństie do S3, system S4 wymaga zdefiniowania klasy, w szczególności jej pól (slotów) i dziedziczenia po innych klasach. Klasę definiuje się z użyciem funkcji setClass: # pierwszym argumentem funkcji jest nazwa klasy setClass("nazwa_klasy", slots = c( # tutaj definiowane są sloty i ich typ slot_1 = "data.frame", slot_2 = "list" ), prototype = c( # tutaj definiowane są wartości domyślne (prototyp) dla slotów slot_1 = data.frame(), slot_2 = list() ) ) Funkcja setClass posiada również parametr contains, który odpowiada za dziedziczenie po innych klasach: setClass("klasa_rodzic", slots=c( macierz = "matrix" )) setClass("klasa_dziecko", contains="klasa_rodzic") Funkcje generyczne z użyciem których tworzymy metody S4 są rozróżniane od funkcji generycznych systemu S3. Mechanizm tworzenia metody jest więc bardzo podobny, ale używamy do tego celu dedykowanych dla S4 funkcji. # tworzenie funkcji generycznej S4 setGeneric("nazwa_metody", function(x, ...) standardGeneric("nazwa_metody")) ## [1] "nazwa_metody" # tworzenie metody dla klasy setMethod("nazwa_metody", "nazwa_klasy", function(x,...){ # działanie metody na obiekcie klasy }) 12.3.1 Bonus: przykład wykorzystania systemu S4 S4, choć bardziej restrykcyjny niż S3, nadal daje dużo swobody w przypisywaniu klas, dziedziczeniu, w szczególności w manewrowaniu wbudowanymi klasami R. Wykorzystaliśmy to w naszym raczkującym pakiecie autoeda do ominięcia problemu przypisania różnego zachowania funkcji w zależności od otrzymanego typu danych. Celem było obliczenie tej samej funkcji (np. średniej) dla wszystkich kolumn danych, przy założeniu, że nasz zbiór danych jest średniej wielkości (kilkadziesiąt kolumn - zmiennych). Jeśli niemożliwe jest obliczenie funkcji dla danej kolumny (np. próbujemy obliczyć średnią z kolumny stringów), chcieliśmy uniknąć przerywania pracy funkcji i zwracać NA. By osiągnąć powyższy rezultat zdecydowaliśmy się zdefiniować klasę funkcji - miar obliczanych na kolumnach jako klasę dziedziczącą po… klasie funkcji generycznych: setClass("RankingMeasure", slots = c( name = "character", description = "character" ), prototype = list( name = NA_character_, description = NA_character_ ) ) setClass("BuiltInMeasure", contains = c("standardGeneric", "RankingMeasure") ) Następnie dla każdej potrzebnej nam funkcji utworzyliśmy odpowiadający jej obiekt - funkcję generyczną klasy BuiltInMeasure i zdefiniowaliśmy metody tej funkcji generycznej dla możliwych typów zmiennych, zwracając NA domyślnie i wynik liczbowy, gdzie to możliwe. "],["moduły-w-aplikacjach-shiny.html", "13 Moduły w aplikacjach shiny 13.1 Czym jest moduł Shiny 13.2 Budowa modułu Shiny.", " 13 Moduły w aplikacjach shiny 13.1 Czym jest moduł Shiny Modułem Shiny nazywamy odrębny kawałek aplikacji Shiny. Moduł nie może być wywołany niezależnie od reszty aplikacji. Traktuje się go jako część większej aplikacji lub większego modułu Shiny (moduł może składać się z modułów). 13.1.1 Dlaczego warto używać modułów Shiny? Uproszczenie kodu - moduły pozwalają nam na uporządkowanie złożonego kodu w przypadku dużych i skomplikowanych aplikacji Własna przestrzeń nazw - w aplikacjach shiny ID obiektów z inputów i outputów pochodzą ze wspólnej przestrzeni nazw. To znaczy, że ID każdego z obiektów w całej aplikacji musi być unikalne. Jako że moduł jest osobną funkcją wywołaną w aplikacji, posiada własną przestrzeń nazw. Wystarczy zatem, że ID obiektów są unikalne wewnątrz modułu. Recykling - ponieważ moduł Shiny jest niezależną funkcją, może być użyty zarówno wiele razy w jednej aplikacji, jak i w wielu różnych aplikacjach. Dzięki temu można z łatwością przechowywać gotowe fragmenty aplikacji w eRowych pakietach i wykorzystywać je w razie potrzeby. 13.2 Budowa modułu Shiny. kawałek UI - funkcja odpowiadająca za User Interface w module Shiny kawałek serwera - funkcja zawierająca fragment serwera, który jest wykorzystywany w UI 13.2.1 Jak używać modułów Shiny? Rozważmy aplikację składającą się z dwóch paneli - każdy z wykresem i danymi dla dwóch rozkładów, otrzymaną za pomocą poniższego kodu: library(shiny) library(ggplot2) ui <- fluidPage( tabsetPanel( #generujemy panel dla rozkładu normalnego tabPanel(title = "Rozkład normalny", tabsetPanel( tabPanel( title = "Wykres", numericInput(inputId = "normal_n", label = "Podaj wielkość próby", value = 1000), plotOutput("normal_plot") ), tabPanel( title = "Dane", tableOutput("normal_data") ) ) ), #generujemy panel dla rozkładu wykładniczego tabPanel(title = "Rozkład wykładniczy", tabsetPanel( tabPanel( title = "wykres", numericInput(inputId = "exp_n", label = "Podaj wielkość próby", value = 1000), plotOutput("exp_plot") ), tabPanel( title = "Dane", tableOutput("exp_data") ) ) ) ) ) server <- function(input, output, session) { #generujemy dane normal_data <- reactive({ set.seed(17) data.frame(id = 1:input[["normal_n"]], sample = rnorm(input[["normal_n"]])) }) exp_data <- reactive({ set.seed(17) data.frame(id = 1:input[["exp_n"]], sample = rnorm(input[["exp_n"]])) }) #generujemy tabele output[["normal_data"]] <- renderTable({ normal_data() }) output[["exp_data"]] <- renderTable({ exp_data() }) #generuemy wykresy output[["normal_plot"]] <- renderPlot({ ggplot(normal_data(), aes(x = sample)) + geom_density() }) output[["exp_plot"]] <- renderPlot({ ggplot(exp_data(), aes(x = sample)) + geom_density() + xlim(0, 5) }) } shinyApp(ui, server) Aplikacja wygląda następująco: W naszej przestrzeni wykorzystaliśmy nazwy: inputy - normal_n, exp_n outputy - normal_plot, normal_data, exp_plot, exp_data Co daje razem 6 obiektów. W aplikacji UI zajmuje 36 linijek kodu, a server 29, razem 65 linijek. Zrefaktoryzuemy kod powyższej aplikacji przy użyciu modułów Shiny. Za powtarzające się elementy (tj. panele z wykresem i danymi) będą odpowiedzialne następujące funkcje module_UI oraz module_SERVER (odpowiedniki UI oraz servera dla odrębnego fragmentu aplikacji). module_UI <- function(id) { ns <- NS(id) tagList( tabsetPanel( tabPanel( title = "Wykres", numericInput(inputId = ns("n"), label = "Podaj wielkość próby", value = 1000), plotOutput(ns("plot")) ), tabPanel(title = "Dane", tableOutput(outputId = ns("data")) ) ) ) } Na szczególną uwagę w powyższym kodzie zasługuje linijka ns <- NS(id) Za pomocą funkcji NS() tworzymy osobną przestrzeń nazw ID. module_SERVER <- function(id) { moduleServer(id, function(input, output, session) { #generujemy dane data <- reactive({ set.seed(17) data.frame(id = 1:input[["n"]], sample = rnorm(input[["n"]])) }) #generujemy wykres output[["plot"]] <- renderPlot({ ggplot(data(), aes(x = sample)) + geom_density() }) #generujemy tabelę output[["data"]] <- renderTable({ data() }) }) } Ostatecznie nasza aplikacja używająca pomocniczego modułu wygląda następująco library(shiny) ui <- fluidPage( titlePanel("Przykładowe ciągłe rozkłady prawdopodobieństwa"), tabsetPanel( #generujemy panel dla rozkładu normalnego tabPanel(title = "Rozkład normalny", module_UI("norm") ), #generujemy panel dla rozkładu wykładniczego tabPanel(title = "Rozkład wykładniczy", module_UI("exp") ) ) ) server <- function(input, output, session) { module_SERVER("norm") module_SERVER("exp") } shinyApp(ui, server) Powyższy kod jest czytelniejszy, krótszy, a także rozwiązuje problem wielu zmiennych. "]] +[["index.html", "Notatki z laboratoriów ,,Programowanie i analiza danych w R’’ Instytut Matematyczny, Uniwersytet Wrocławski 1 Wstęp", " Notatki z laboratoriów ,,Programowanie i analiza danych w R’’ Instytut Matematyczny, Uniwersytet Wrocławski Mateusz Staniak 2023-11-14 1 Wstęp Zaliczenie przedmiotu opiera się głównie na projekcie grupowym. Przykładowy projekt z poprzednich edycji: SpotifyViz. Autorzy poszczególnych rozdziałów: Podstawy języka R: Michał Dylewicz, Marcela Kamchen, Anna Krasoń, Katarzyna Kulon, Arkadiusz Soból (z wyjątkiem podrozdziału Funkcje). Wczytywanie danych: Marta Kałużna, Sebastian Jachimek, Joanna Grunwald, Wojciech Wojnar. Eksploracyjna analiza danych: Magdalena Mazur, Agata Rogowska, Zuzanna Różak, Aleksandra Siepiela. [Także podrozdział Funkcje pierwszego rozdziału.] Podstawy kontroli wersji z Gitem: Magdalena Mazur, Agata Rogowska, Zuzanna Różak, Aleksandra Siepiela. Przetwarzanie danych tabelarycznych: Weronika Domaszewska, Ewelina Grzmocińska, Gracjan Hrynczyszyn, Dominik Jaźwiecki, Michał Ociepa. Czyste dane: Kacper Ambroży, Dominika Szewc, Radosław Szudra, Helena Wołoch. Wizualizacja danych z pakietem ggplot2: Katarzyna Frankiewicz, Maciej Grabias, Jakub Michałowski Czysty i wydajny kod w R: Paulina Bannert, Natalia Bercz, Piotr Mrozik, Dariusz Sudół, Monika Wyźnikiewicz Interaktywna wizualizacja danych z pakietem shiny: interfejs użytkownika: Stanisław Banaszek, Mateusz Drobina, Dominik Mika, Adrian Płoszczyca, Jakub Sobkowiak Interaktywna wizualizacja danych z pakietem shiny: strona serwerowa: Wojciech Leszkowicz, Małgorzata Stawińska, Tomasz Szmyd, Maciej Tadej. Dodatkowe rozdziały: Podstawy kontroli wersji przy pomocy Gita: Magdalena Mazur, Agata Rogowska, Zuzanna Różak, Aleksandra Siepiela. Programowanie obiektowe w R: klasy S3: Agata Cieślik. Moduły w aplikacjach shiny: Krystyna Grzesiak. "],["podstawy-języka-r.html", "2 Podstawy języka R 2.1 Liczby 2.2 Łańcuchy znaków 2.3 Wartości logiczne 2.4 Wektory 2.5 Indeksowanie 2.6 Operacje na wektorach 2.7 R - funkcje", " 2 Podstawy języka R Język R posiada kilka typów danych, które pokrótce postaramy sie omówić poniżej. Pokażemy ich budowe jak i operacje na nich, przytaczając stosowne przyklady. 2.1 Liczby Liczby całkowite i rzeczywiste (tutaj separator dziesiętny to kropka). Możemy używać również notacji naukowej. Operacje na liczbach to podstawowe działania matematyczne jak i trochę rozszerzone, ukazane niżej wraz z specjalnymi liczbami. 5; 5.5; 5.5e-2; ## [1] 5 ## [1] 5.5 ## [1] 0.055 Tutaj liczby specjalne, NaN # not a number ## [1] NaN Inf # nieskończoność ## [1] Inf -Inf # - nieskończoność ## [1] -Inf oraz kilka działań na liczbach 1 + 1 # podobnie '-' to odejmowanie ## [1] 2 4/2 # dzielenie, a '*' to mnożenie ## [1] 2 5 %/% 3 # dzielenie całkowite ## [1] 1 5 %% 3 # reszta z dzielenia ## [1] 2 2^3 # potęgowanie ## [1] 8 2**3 # też potęgowanie ## [1] 8 sqrt(4) #pierwiastkowanie ## [1] 2 abs(-1) # wartość bezwzględna ## [1] 1 2.2 Łańcuchy znaków Łańcuch znaków to po prostu napi. Napis jest otoczony przez ” lub ’. W napisie możemy umieszczać dowolne znaki, pamiętając że są też znaki specjalne (rozpoczynające się od \\ i mające specjalne funkcje). Na napisach istnieje wiele operacji (np. \\(\\verb+paste()+,\\) czyli sklejenie dwóch napisów), lecz je zobaczymy w notatce o napisach. "napis" ## [1] "napis" 'to też' ## [1] "to też" "'a tutaj nawet z bonusem'" ## [1] "'a tutaj nawet z bonusem'" # ""a"" to już wbrew intuicji nie jest napis cat("i znak \\n specjalny, wstawiający nową linie") # cat() wyświetla napis w sposób niesformatowany ## i znak ## specjalny, wstawiający nową linie 2.3 Wartości logiczne Logiczna Prawda (\\(\\verb+TRUE+\\) lub \\(\\verb+T+\\)) oraz logiczny Fałsz (\\(\\verb+FALSE+\\) lub \\(\\verb+F+\\)). Na tych obiektach możemy wykonywać operacje logiczne oraz algebraiczne. TRUE & TRUE # operator 'i' ## [1] TRUE TRUE | FALSE # operator 'lub' ## [1] TRUE 1 == 1 # testowanie równości ## [1] TRUE 1 != 2 # testowanie nierówności ## [1] TRUE 2*TRUE # TRUE ma wartość 1 ## [1] 2 2*FALSE # FALSE ma wartość 0 ## [1] 0 T ; `T` <- FALSE; T # używając `` możemy zmienić wartość logiczną wyrażenia ## [1] FALSE ## [1] FALSE 2.4 Wektory Wektor to w R uporządkowany zbiór elementów. Elementy te muszą mieć ten sam typ, także jeśli do wektora trafią elementy z różnym typem (poza NA), to nastąpi konwersja elementów do jednego typu. Proste wektory tworzymy przez polecenie \\(\\verb+c()+\\) i elementy wypisujemy w nawiasie po przecinku. Dodatkowo, element wektora jest traktowany jako jednoelementowy wektor. Wektory liczbowe jak i inne możemy tworzyć za pomocą wbudowanych funkcji do tego przeznaczonych. v <- c(1, 2, 3) #przypisanie wektora do zmiennej 0:10 # wektor liczbowy ## [1] 0 1 2 3 4 5 6 7 8 9 10 seq(from = 0, to = 10, by = 1) # to samo, ale za pomocą seq(), czyli sequance ## [1] 0 1 2 3 4 5 6 7 8 9 10 seq(0, 1, length.out = 4) # równe odstępy w 4 liczbowym wektorze ## [1] 0.0000000 0.3333333 0.6666667 1.0000000 length(v) # zwraca długość vectora ## [1] 3 # vector(mode, lenght) tworzy wektor dlugosci lenght, a wyrazy tego wektora maja klase mode vector("integer", 10) # wektor liczb calkowitych ## [1] 0 0 0 0 0 0 0 0 0 0 vector("numeric", 10) # wektor liczb rzeczywistych ## [1] 0 0 0 0 0 0 0 0 0 0 vector("character", 10) # wektor slów ## [1] "" "" "" "" "" "" "" "" "" "" rep(v, each = 2) # każdy element v zostanie powtórzony 2 razy ## [1] 1 1 2 2 3 3 rep(v, times = 2) # v zostanie powtórzony 2 razy ## [1] 1 2 3 1 2 3 # mały mix tj. tutaj element v traktujemy jako wektor jednoelementowy # i powtarzamy times razy rep(v, times = 1:3) ## [1] 1 2 2 3 3 3 x <- c("a", "A") # wektor napisowy v <- "a" # to też toupper(x) # zmieni stringi w argumencie na wielkie litery ## [1] "A" "A" tolower(x) # zmieni stringi w argumencie na male litery ## [1] "a" "a" 2.5 Indeksowanie W R wektory są indeksowane od 1 (a nie od 0 jak w wielu językach programowania!). Aby odwołać się do konkretnego elementu wektora korzystamy z nawiasów kwadratowych \\(\\verb+[]+.\\) letters[3] ## [1] "c" Można wybrać więcej niż jeden element, wpisując w nawiasach kwadratowych wektor indeksów. letters[1:10] ## [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" letters[c(1, 15)] ## [1] "a" "o" letters[seq(1, 20, by = 2)] ## [1] "a" "c" "e" "g" "i" "k" "m" "o" "q" "s" Jeśli przed wektorem indeksów widnieje znak minus, R zwróci wszystkie elementy wektora z wyjątkiem tych w nawiasie kwadratowym. letters[-(1:10)] # niezbędny nawias wokół 1:10 ## [1] "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" letters[-c(1, 15)] ## [1] "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" letters[-seq(1, 20, by = 2)] ## [1] "b" "d" "f" "h" "j" "l" "n" "p" "r" "t" "u" "v" "w" "x" "y" "z" Pod wybrane indeksy można przypisać nowe wartości. new_letters <- letters new_letters[1:5] <- LETTERS[1:5] new_letters ## [1] "A" "B" "C" "D" "E" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" Albo pod każdy wybrany indeks nową wspólną wartość. new_letters[1:5] <- "x" new_letters ## [1] "x" "x" "x" "x" "x" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" Tworząc wektor funkcją \\(\\verb+c()+,\\) możemy nazwać każdy z jego elementów. str_vec_nam <- c("a" = "A", "b" = "B", "c" = "C") str_vec_nam ## a b c ## "A" "B" "C" Może być to użyteczne przy odwoływaniu się do konkretnego elementu wektora, nie trzeba wtedy znać numeru jego indeksu. str_vec_nam["a"] ## a ## "A" str_vec_nam[c("a", "c")] ## a c ## "A" "C" str_vec_nam[c("c", "a")] ## c a ## "C" "A" Wektory możemy również indeksować za pomocą wektorów logicznych. Działa to wtedy jak wybieranie tych elementów wektora, które spełniają ustalony warunek. x_ind <- new_letters == "x" x_ind ## [1] TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE ## [21] FALSE FALSE FALSE TRUE FALSE FALSE new_letters[x_ind] ## [1] "x" "x" "x" "x" "x" "x" seq_vec <- seq(0, 1, length.out = 10) seq_vec[seq_vec < 0.5] ## [1] 0.0000000 0.1111111 0.2222222 0.3333333 0.4444444 Można oczywiście rozbudowywać wyrażenia logiczne, np. następująco: seq_vec[seq_vec < 0.3 | seq_vec > 0.8] ## [1] 0.0000000 0.1111111 0.2222222 0.8888889 1.0000000 seq_vec[seq_vec > 0.3 & seq_vec < 0.8] ## [1] 0.3333333 0.4444444 0.5555556 0.6666667 0.7777778 2.6 Operacje na wektorach W R domyślnym i naturalnym zachowaniem funkcji na wektorach jest działanie element po elemencie 1:10 + seq(0, 1, length.out = 10) ## [1] 1.000000 2.111111 3.222222 4.333333 5.444444 6.555556 7.666667 8.777778 9.888889 11.000000 c(2,4,6,8)^(1:4) ## [1] 2 16 216 4096 W przypadku gdy wektory, na których wykonujemy obliczenia mają różne długości zachodzi recykling, tj. R samoistnie przedłuża krótszy wektor replikując go odpowiednią liczbę razy. Widzimy, że obie poniższe linie kodu dają taki sam efekt. 1:10 + 1:2 ## [1] 2 4 4 6 6 8 8 10 10 12 1:10 + rep(1:2, 5) ## [1] 2 4 4 6 6 8 8 10 10 12 Gdy długość dłuższego wektora nie jest wielokrotnością krótszego, recykling także zadziała, jednak R zgłosi warning. 1:10 + 1:3 ## Warning in 1:10 + 1:3: długość dłuszego obiektu nie jest wielokrotnością długości krótszego obiektu ## [1] 2 4 6 5 7 9 8 10 12 11 1:10 + 1:3 + 1:2 + 1:5 ## Warning in 1:10 + 1:3: długość dłuszego obiektu nie jest wielokrotnością długości krótszego obiektu ## [1] 4 8 10 11 13 12 11 15 17 18 Na wektorach możemy wykonywać oczywiście inne funkcje poza podstawowymi operacjami arytmetycznymi. Jedną z opcji jest posortowanie wektora. num_vec <- c(3,6,1,9,8,-3,0,102,-5) sort(num_vec) # sortowanie rosnące ## [1] -5 -3 0 1 3 6 8 9 102 sort(num_vec, decreasing = TRUE) # sortowanie malejące ## [1] 102 9 8 6 3 1 0 -3 -5 Odwrócić kolejnośc elementów wektora można następująco. rev(num_vec) ## [1] -5 102 0 -3 8 9 1 6 3 Oto kilka kolejnych funkcji. sum(num_vec) # suma elementów wektora ## [1] 121 prod(num_vec) # iloczyn elementów wektora ## [1] 0 mean(num_vec) # średnia elementów wektora ## [1] 13.44444 Przy operacjach jak powyższe należy jednak uważać na wektory zawierające “NA”. vec_with_NA <- c(3,6,1,NA) sum(vec_with_NA) ## [1] NA Aby zsumować wartości z pominięciem “NA” należy dopisać dodatkowy argument funkcji. sum(vec_with_NA, na.rm = TRUE) ## [1] 10 Analogicznie dla iloczynu i średniej elementów. prod(vec_with_NA) ## [1] NA prod(vec_with_NA, na.rm = TRUE) ## [1] 18 mean(vec_with_NA) ## [1] NA mean(vec_with_NA, na.rm = TRUE) ## [1] 3.333333 Lista jest podobna do wektora tj. jest pewnym ciągiem obiektów, tyle że jej elementy mogą mieć różne typy. l <- list(1:5) #lista z elementami bedacymi liczbami ## [[1]] ## [1] 1 2 3 4 5 l2 <- list(zwierze='dog', imie='Max',czyLubiInnePsy = TRUE) #lista z elementami bedacymi stringami lub wartosciami logicznymi ## $zwierze ## [1] "dog" ## ## $imie ## [1] "Max" ## ## $czyLubiInnePsy ## [1] TRUE Kolejnę różnica pomiedzy wektorem a listą jest możliwość odwoływania się do elementów listy za pomocą nazwy tego elementu i operatora $. Np: # odwolanie do elementu bedacego za pomoca [], # wynikiem takiej operacji jest lista zawierajaca wektor l[1] ## [[1]] ## [1] 1 2 3 4 5 # aby odwolac sie do konkretnego elementu uzwywamy [[]], na przyklad operacja l[[1]][2] # zwroci drugi element wektora z listy l[[1]][2] ## [1] 2 # nadpisywanie elementu listy wektorem l[[1]] <- c("a", "b", "c") # odwolanie do elementu za pomoca nazwy elementu l2$zwierze ## [1] "dog" l2$imie ## [1] "Max" l2$czyLubiInnePsy ## [1] TRUE Listy można łączyć oraz modyfikować. Funkcja \\(\\verb+lapply()+\\) to funkcja, która pozwala na wykonanie pewnego konkretnego działania na KAŻDYM elemencie z listy. Na przykład, możemy każdy element chcieć zapisać tylko dużymi literami: lapply(l2,toupper) ## $zwierze ## [1] "DOG" ## ## $imie ## [1] "MAX" ## ## $czyLubiInnePsy ## [1] "TRUE" Aby połączyć dwie listy, należy użyć \\(\\verb+c()+,\\) robiąc z dwóch list wektor i przypisując go do nowej zmiennej. l3 <- c(l,l2) ## [[1]] ## [1] "a" "b" "c" ## ## $zwierze ## [1] "dog" ## ## $imie ## [1] "Max" ## ## $czyLubiInnePsy ## [1] TRUE Macierz to obiekt dwuwymiarowy. Składa się z elementów tego samego typu. Tworzy się ją funkcją \\(\\verb+matrix()+,\\) do której podajemy wartości macierzy (zwykle w postaci wektora), liczbę wierszy i kolumn. matrix(data = 1:10, nrow = 2, ncol = 5) ## [,1] [,2] [,3] [,4] [,5] ## [1,] 1 3 5 7 9 ## [2,] 2 4 6 8 10 Widzimy, że R domyślnie wypełnia macierz po kolumnach. Aby wypełnić ją po wierszach ustalamy parametr \\(\\verb+byrow = TRUE+\\) m <- matrix(data = 1:10, nrow = 2, ncol = 5, byrow = TRUE) m ## [,1] [,2] [,3] [,4] [,5] ## [1,] 1 2 3 4 5 ## [2,] 6 7 8 9 10 Elementy macierzy wybiera się za pomocą dwóch indeksów - indeksu wiersza i indeksu kolumny umieszczonych w nawiasach kwadaratowych i rozdzielonych przecinkiem. m[2,3] ## [1] 8 Można również wybrać konkretne wiersze lub kolumny. m[1:2,3:4] # wybiera wiersze 1 i 2 oraz kolumny 3 i 4 ## [,1] [,2] ## [1,] 3 4 ## [2,] 8 9 m[2,c(1,4,5)] # wybiera wiersz 2 oraz kolumny 1,4 i 5 ## [1] 6 9 10 Nie podanie indeksu przed przecinkiem oznacza, że chcemy otrzymać wszystkie wiersze. Analogicznie nie podanie indeksu po przecinku oznacza, że chcemy otrzymać wszystkie kolumny. m[,c(1,3)] ## [,1] [,2] ## [1,] 1 3 ## [2,] 6 8 m[2,] ## [1] 6 7 8 9 10 Macierze, podobnie jak wektory, możemy także indeksować warunkami logicznymi. # zwraca elementy (w tym wypadku element) z pierwszej kolumny, # które są większe od 2 m[m[,1] > 2, 1] ## [1] 6 Można także indeksować macierz inną macierzą o dwóch kolumnach. Zwrócone zostaną wtedy elementy o indeksach będących wierszami tej macierzy. matrix_ind<- matrix(c(1, 2, 2, 3, 2, 4), byrow = TRUE, nrow = 3, ncol = 2) m[matrix_ind] ## [1] 2 8 9 Na macierzach o tych samych wymiarach możemy wykonywać operacje arytmetyczne. Trzeba zwrócić uwagę, że są one wykonywane element po elemencie (z matematycznego punktu widzenia jest to oczekiwane przy dodawaniu, ale nieoczekiwane przy mnożeniu macierzy). m1 <- matrix(1:4,2,2) m1 ## [,1] [,2] ## [1,] 1 3 ## [2,] 2 4 m2 <- matrix(2:5,2,2) m2 ## [,1] [,2] ## [1,] 2 4 ## [2,] 3 5 m1 + m2 ## [,1] [,2] ## [1,] 3 7 ## [2,] 5 9 m1 * m2 ## [,1] [,2] ## [1,] 2 12 ## [2,] 6 20 Aby wykonać matematyczne mnożenie macierzy należy użyć operatora \\(\\verb+%*%+.\\) m1 %*% m2 ## [,1] [,2] ## [1,] 11 19 ## [2,] 16 28 Jest to obiekt przechowujący dane w postaci tabeli dwuwymiarowej, którą tworzą wektory o dowolnym typie. Z ramki danych można korzystać jak z macierzy dwuwymiarowej (poprzez korzystanie z \\(\\verb+[,]+\\)), jak i z listy (poprzez korzystanie z $). imie <- c("Max", "Reksio","Rex","Luna") #utworzymy ramke z 2 wektorow wiek <- c(2,8,3,11) ramka <- data.frame(imie,wiek) #ramke tworzymy za pomoca polecenia data.frame() ## imie wiek ## 1 Max 2 ## 2 Reksio 8 ## 3 Rex 3 ## 4 Luna 11 #wyswietlanie nazw kolumn names(ramka) ## [1] "imie" "wiek" #odnoszenie sie do elementu znajdujacego sie w 2. rzedzie i 1. kolumnie ramka[2,1] ## [1] "Reksio" #pobieranie paru wierszy na raz za pomoca wektora ramka[c(1, 2), ] ## imie wiek ## 1 Max 2 ## 2 Reksio 8 #pobieranie wszystkich kolumn dla 1. wiersza ramka[1,] ## imie wiek ## 1 Max 2 #pobieranie wszystkich wierszy dla 1. kolumny ramka[,1] ## [1] "Max" "Reksio" "Rex" "Luna" # pierwsza kolumna bez drugiego wiersza ramka[-2, 1] ## [1] "Max" "Rex" "Luna" #pobieranie kolumn/wierszy po nazwie ramka$wiek ## [1] 2 8 3 11 # inny sposób indeksowanie po nazwie ramka[, "wiek"] ## [1] 2 8 3 11 Indeksowanie na podstawie zawartości ramki danych Dane z ramki mogą być przez nas “filtrowane” za pomocą []. Na przykład # psy poniżej 9 roku życia ramka[ramka$wiek < 9, ] ## imie wiek ## 1 Max 2 ## 2 Reksio 8 ## 3 Rex 3 #dane tylko dla Reksia ramka[ramka$imie == "Reksio", ] ## imie wiek ## 2 Reksio 8 # analogicznie dla wektorów wiek[wiek < 9] ## [1] 2 8 3 Tworząc ramkę danych należy pamiętać o tym, aby wektory danych służące za kolumny były tej samej długości. #zamiana nazw kolumn names(ramka) <- c("imie_psa", "wiek_psa") ## imie_psa wiek_psa ## 1 Max 2 ## 2 Reksio 8 ## 3 Rex 3 ## 4 Luna 11 Ramki danych możemy powiększać o dodatkowe wiersze i kolumny, ale typy (dla wierszy) i rozmiary muszą sie zgadzać z typami i rozmiarem ramki danych. Rozpatrzmy poniższy przykład, aby pokazać, jak dodać wiersz i kolumnę za pomocą funkcji \\(\\verb+cbind()+\\) oraz \\(\\verb+rbind()+\\). #dodawanie nowego wiersza dodajemy_wiersz <- data.frame(imie_psa ="Quentin", wiek_psa=9) #funkcja rbind "skleja" wierszowo argument pierwszy (u nas ramka) z drugim ramka <- rbind(ramka,dodajemy_wiersz) #dodawanie nowej kolumny czyLubiInnePsy <- c(TRUE,TRUE, FALSE, TRUE, FALSE) #funkcja cbind "skleja" kolumnowo argument pierwszy (u nas ramka) z drugim ramka <- cbind(ramka,czyLubiInnePsy) ## imie_psa wiek_psa czyLubiInnePsy ## 1 Max 2 TRUE ## 2 Reksio 8 TRUE ## 3 Rex 3 FALSE ## 4 Luna 11 TRUE ## 5 Quentin 9 FALSE Możemy rownież dodawać wiersze za pomocą indeksowania, to znaczy przypisywania wartości do konkretnych indeksów ramki: #jako 6. wiersz "wkladamy" nowy wektor ramka[6,] <- c("Fanta",0.5,TRUE) ## imie_psa wiek_psa czyLubiInnePsy ## 1 Max 2 TRUE ## 2 Reksio 8 TRUE ## 3 Rex 3 FALSE ## 4 Luna 11 TRUE ## 5 Quentin 9 FALSE ## 6 Fanta 0.5 TRUE # jako 4.kolumne "wkladamy" nowy wektor ramka[,4] <- c("Mateusz","Romek","Renata","Leon","Quennie","Filip") # nazywamy kolumne 4. names(ramka)[4] <- "opiekun_psa" ## imie_psa wiek_psa czyLubiInnePsy opiekun_psa ## 1 Max 2 TRUE Mateusz ## 2 Reksio 8 TRUE Romek ## 3 Rex 3 FALSE Renata ## 4 Luna 11 TRUE Leon ## 5 Quentin 9 FALSE Quennie ## 6 Fanta 0.5 TRUE Filip Analizując nową dla nas ramkę danych, użyteczne okazują się funkcje pozwalające na poznanie właściwości ramki danych. Oto pare z nich: # wymiary ramki (6 wierszy,4 kolumny) mozna sprawdzic za pomoca funkcji dim() dim(ramka) ## [1] 6 4 # aby zobaczyc skrocony opis typow danych zawartych w ramce uzywana jest funkcja str() str(ramka) ## 'data.frame': 6 obs. of 4 variables: ## $ imie_psa : chr "Max" "Reksio" "Rex" "Luna" ... ## $ wiek_psa : chr "2" "8" "3" "11" ... ## $ czyLubiInnePsy: chr "TRUE" "TRUE" "FALSE" "TRUE" ... ## $ opiekun_psa : chr "Mateusz" "Romek" "Renata" "Leon" ... # aby "podejrzec" pierwsze wiersze ramki danych, wraz naglowkami kolumn uzywana jest funkcja head() head(ramka) ## imie_psa wiek_psa czyLubiInnePsy opiekun_psa ## 1 Max 2 TRUE Mateusz ## 2 Reksio 8 TRUE Romek ## 3 Rex 3 FALSE Renata ## 4 Luna 11 TRUE Leon ## 5 Quentin 9 FALSE Quennie ## 6 Fanta 0.5 TRUE Filip # wysietlanie pierwszych n wierszy head(ramka,n=2) ## imie_psa wiek_psa czyLubiInnePsy opiekun_psa ## 1 Max 2 TRUE Mateusz ## 2 Reksio 8 TRUE Romek # wyswietlanie ostatnich n wierszy za pomoca funkcji tail() tail(ramka,n=2) ## imie_psa wiek_psa czyLubiInnePsy opiekun_psa ## 5 Quentin 9 FALSE Quennie ## 6 Fanta 0.5 TRUE Filip Pętli oraz instrukcji warunkowych używamy, kiedy chcemy uniknąć powielania kodu i chcemy zachować jego przejrzystość. Ułatwia to wprowadzanie potencjalnych zmian. Instrukcje opisujące co powinno się zdarzyć należy umieścić w nawiasach \\(\\verb+{ }+\\). Jeśli chcemy wykonać tylko jedną linijke kodu, możemy je opuścić. Umożliwia warunkowe wykonanie kawałka kodu - jeśli warunek zawarty w \\(\\verb+if+\\) jest spełniony, to R przejdzie do zawartej instrukcji. W przeciwnym wypadku wykona polecenie zawarte w \\(\\verb+else+\\), a jeśli go nie ma , to przejdzie do kolejnych pętli. Część \\(\\verb+else+\\) nie jest wymagana, w tym wypadku z góry wiadomo ile razy kod zostanie wykonany. Składnia wygląda następująco: if(warunek) { instrukcja_1 } i jest analogiczna do if(warunek) instrukcja_1 Możemy także zapisać if(warunek) { instrukcja_1 instrukcja_2 } else { instrukcja_3 } Powiedzmy, że rozpatrujemy liczbe z rozkładu normalnego i sprawdzamy jakiego jest znaku. x_norm <- rnorm(1) if (x_norm < 0) { cat("Liczba", x_norm, "jest ujemna") } else { cat("Liczba ", x_norm, "jest dodatnia") } ## Liczba -1.563892 jest ujemna Możemy chcieć wykonać różne operacje na tak wylosowanej liczbie. Przykładowo, jeśli będzie ujemna, to zmienić znak, zaokrąglić i zreplikować w wektorze if (x_norm < 0) { x_norm <- abs(x_norm) x_wek <- rep(round(x_norm, 2), times = 5) } else { x_wek <- "X" } i otrzymać 1.56, 1.56, 1.56, 1.56, 1.56 (X oznacza, że wylosowana liczba była dodatnia, a z nią nic nie robimy). Pętla \\(\\verb+while+\\) działa tak długo, dopóki warunek jest spełniony - tzn. do kiedy nie dostaniemy \\(\\verb+FALSE+\\). Warunek należy opisać tak, żeby w pewnym momencie został spełniony - inaczej pętla będzie działać w “nieskończoność”. Często używa sie jej do szukania losowych liczb o pewnych właściwościach. Składnia tej pętli jest następująca: while(warunek) { instrukcja_1 instrukcja_2 } Tutaj przykład wykorzystania, gdy chcemy losować liczby z przedziału [1, 100], dopóki różnica między dwoma kolejnymi nie będzie parzysta i <- 2 los <- c() los[1] <- 0 roznica <- 1 while(roznica%%2 != 0) { los <- c(los, sample(1:100, 1, replace = TRUE)) roznica <- los[i]-los[i-1] i = i+1 } W ten sposób dostajemy wylosowane liczby: 0, 5, 37, z różnicą między ostatnimi równą 32. Pętla \\(\\verb+for+\\) wygląda następująco: for(iterator in warunek) { instrukcja_1 instrukcja_2 } Ta pętla wykonuje instrukcje określoną ilość razy - tyle ile elementów \\(\\verb+iterator+\\) w zbiorze \\(\\verb+warunek+\\). W warunku możemy mieć liste albo wektor. Po każdym wykonaniu pętli, zmienna \\(\\verb+iterator+\\) przeskakuje do kolejnego elementu warunku. Jeśli chcemy wykonać tylko 1 instrukcje, można zapisać for(iterator in warunek) instrukcja_1 Przykładowo, jeśli chcemy elementy ze zbioru [1, 10] podnieść do potęgi, możemy użyć pętli \\(\\verb+for+\\). wynik <-c() for (i in 1:10) wynik <- c(wynik, i*i) wynik ## [1] 1 4 9 16 25 36 49 64 81 100 Możemy także napisać pętle zagnieżdżone, przykładowo do obliczenia wartości w macierzach. W tym wypadku wartością każdego elementu macierzy (3x3) jest iloczyn jego indeksów, co daje następujący wynik macierz <- matrix(nrow=3, ncol=3) for(i in 1:dim(macierz)[1]) { for(j in 1:dim(macierz)[2]) { macierz[i,j] = i*j } } macierz ## [,1] [,2] [,3] ## [1,] 1 2 3 ## [2,] 2 4 6 ## [3,] 3 6 9 Teraz zajmiemy się rodziną funkcji \\(\\verb+apply+\\). Należą do niej takie funkcję jak \\(\\verb+apply, tapply, sapply, lapply, vapply+\\). Wszystkie one pozwalają na wykonanie pewnej operacji na szeregu podzbiorów danych. Operacja, która ma być wykonana określana jest przez argument \\(\\verb+FUN+\\). Funkcje z tej rodziny przyjmują elementy listy \\(\\verb+(lapply()+)\\), elementy wektora \\(\\verb+(sapply())+\\), macierze \\(\\verb+(apply())+\\) oraz podgrup wskazanych przez jedną lub kilka zmiennych \\(\\verb+(by()+\\) i \\(\\verb+tapply())+\\). Zacznijmy od funkcji \\(\\verb+lapply()+\\). Wykonuje funkcję \\(\\verb+FUN+\\) dla wszystkich elementów wektora \\(\\verb+x+\\). Przydatna funkcja zastępująca pętlę \\(\\verb+for+\\). Domyślnie wynikiem działania jest lista, lecz jeżeli w wyniku chcielibyśmy otrzymać wektor, to jednym z rozwiązań jest zamiana listy na wektor funkcją \\(\\verb+unlist()+\\). Oto przykładowe działanie funkcji \\(\\verb+lapply()+\\): x=c(1,2,3,4,5,6,7,8,9,10) func=function(x){return(x**3-3*x)} lapply(x,func) ## [[1]] ## [1] -2 ## ## [[2]] ## [1] 2 ## ## [[3]] ## [1] 18 ## ## [[4]] ## [1] 52 ## ## [[5]] ## [1] 110 ## ## [[6]] ## [1] 198 ## ## [[7]] ## [1] 322 ## ## [[8]] ## [1] 488 ## ## [[9]] ## [1] 702 ## ## [[10]] ## [1] 970 Funkcja \\(\\verb+sapply+\\) jest bardziej przyjazną użytkownikowi wersją \\(\\verb+lapply+\\) zwracającą wektor lub macierz i może przyjmować więcej argumentów, np. \\(\\verb+sapply(x, f, simplify = FALSE, USE.NAMES = FALSE)+\\) zwraca ten sam wynik co \\(\\verb+lapply(x, f)+\\). Funkcja \\(\\verb+vapply+\\) jest podobna do \\(\\verb+sapply+\\), ale ma z góry określony typ zwracanych wartości, a może być również bezpieczniejszy w użyciu, a czasem nawet szybszy. Teraz weźmiemy pod lupe \\(\\verb+tapply()+\\), która to wykonuje funkcję \\(\\verb+FUN+\\) dla podzbiorów wektora \\(\\verb+x+\\) określonego przez poziomy zmiennej czynnikowej \\(\\verb+index+\\). Przydatna funkcja, gdy chcemy policzyć pewną statystykę w podgrupach, np. odchylenie standardowe w z wagami. W tym przypadku \\(\\verb+x+\\) będzie wektorem z wagami, \\(\\verb+index+\\) wektorem z płcią a \\(\\verb+FUN+\\) będzie funkcją sd). x=c(98,67,65,82,55,60,72,81,48,88) index=c('M','M','K','M','K','M','M','M','K','M') tapply(x,index,sd) ## K M ## 8.544004 12.944938 A teraz bardziej zaawansowana werssa funkcji \\(\\verb+tapply()+\\) z tą różnicą, że \\(\\verb+x+\\) może być macierzą lub listą, \\(\\verb+index+\\) może być listą, a wynik tej funkcji jest specyficznie wyświetlany. Jeżeli \\(\\verb+index+\\) jest listą zmiennych czynnikowych, to wartość funkcji \\(\\verb+FUN+\\) będzie wyznaczona dla każdego przecięcia czynników tych zmiennych. Wynik funkcji \\(\\verb+by()+\\) jest klasy \\(\\verb+by+\\), ale po usunięciu informacji o klasie, np. poprzez użycie funkcji \\(\\verb+unclass()+\\) otrzymujemy zwykłą macierz. Argument \\(\\verb+x+\\) może być listą lub macierzą, dzięki czemu do funkcji \\(\\verb+FUN+\\) przekazać można kilka zmiennych – elementów/kolumn listy/macierzy \\(\\verb+x+\\). m1=seq(1:9) x=c('a','b','c','a','b','c','a','b','c') by(m1,x,mean) ## x: a ## [1] 4 ## ----------------------------------------------------------------------------------------------- ## x: b ## [1] 5 ## ----------------------------------------------------------------------------------------------- ## x: c ## [1] 6 Z kolei \\(\\verb+mapply()+\\) to wielowymiarowy odpowiednik funkcji \\(\\verb+sapply()+\\). Argumentami tej funkcji jest funkcja \\(\\verb+fun+\\) oraz kilka (dwa lub więcej) wektorów o tej samej długości. Wynikiem jest wektor, w którym na pozycji \\(\\verb+i+\\)-tej jest wynik funkcji \\(\\verb+fun+\\) wywołanej z \\(\\verb+i+\\)-tych elementów wektorów będących argumentami. a=function(x,y){return(x**y)} mapply(a,x=seq(1,101,by=10),y=seq(1:11)) ## [1] 1.000000e+00 1.210000e+02 9.261000e+03 9.235210e+05 1.158562e+08 1.759629e+10 3.142743e+12 6.457535e+14 1.500946e+17 ## [10] 3.894161e+19 1.115668e+22 2.7 R - funkcje Funkcje przydają się do zamknięcia w nich operacji, które się często powtarzają w naszym kodzie lub dla jego lepszej czytelności. Podstawowa składnia funkcji w R wygląda tak: nazwa_funkcja <- function(argument 1, argument 2, …){ ciało funkcji return(wartość lub obiekt zwracany) } Napiszmy funkcję, która będzie mnożyła dowolny wektor przez podaną liczbę, a następnie zsumuje elementy wektora: funkcja1 <- function(wektor, liczba){ rezultat <- wektor * liczba rezultat <- sum(rezultat) return(rezultat) } Możemy także pominąc \\(\\texttt{return}\\) i zdefiniować funkcje: funkcja2 <- function(wektor, liczba){ rezultat <- wektor * liczba rezultat <- sum(rezultat) rezultat } Obie funkcje \\(\\texttt{funkcja1}\\) i \\(\\texttt{funkcja2}\\) robią to samo. Wykonajmy nasze funkcje dla dwóch zdefiniowanych zmiennych: v <- 1:5 n <- 2 funkcja1(v, n) ## [1] 30 funkcja2(v, n) ## [1] 30 Oczywiście do wykonania funkcji potrzebne jest zdefiniowanie obu argumentów. Jak ich nie dodamy wyświetli się błąd, że argument drugi zaginął i nie mamy zdefiniowanej jego wartości domyślnej. Zdefiniujmy zatem domyślną wartość argumentu \\(\\texttt{liczba}\\) jako \\(\\texttt{NULL}\\) i dopiszmy do naszej funkcji kod, który gdy ten argument będzie miał wartość domyślną zwróci tylko sumę elementów wektora: funkcja3 <- function(wektor, liczba = NULL){ if(is.null(liczba)){ rezultat <- sum(wektor) } else{ rezultat <- wektor * liczba rezultat <- sum(rezultat) } rezultat } Wykonajmy funckję \\(\\texttt{funkcja3}\\) na wcześniej zdefiniowanym wektorze \\(\\texttt{v}\\): funkcja3(v) ## [1] 15 Oprócz zdefiniowania wartości domyślnej argumentu poprzez trzy kropki możemy również dopuścić parametry dodatkowe. Zdefiniujmy funkcję z parametrami dodatkowymi: funkcja4 <- function(wektor, liczba = NULL, ...){ if(is.null(liczba)){ rezultat <- sum(wektor, ...) } else{ rezultat <- wektor * liczba rezultat <- sum(rezultat, ...) } rezultat } Wykonajmy funckję \\(\\texttt{funkcja4}\\) usuwając wartości brakujące z nowo zdefiniowanego wektora: v <- c(NA, 1, NA, 2:4, NA, 5) v ## [1] NA 1 NA 2 3 4 NA 5 funkcja4(v, na.rm = TRUE) ## [1] 15 Funkcje są bardzo przydatne, gdy mamy do napisania długi skrypt. Pozwalają na podzielenie głównej części kodu na mniejsze kawałeczki, które kolejnemu użytkownikowi skryptu lub nam będzie łatwiej modyfikować. "],["wczytywanie-danych-w-r.html", "3 Wczytywanie danych w R 3.1 Formaty danych 3.2 Locale 3.3 Natywne formaty R", " 3 Wczytywanie danych w R 3.1 Formaty danych 3.1.1 CSV/DSV CSV (Comma Separated Values) to plik tekstowy, w którym wartości rozdzielane są przecinkami, a kolejne wiersze znakiem nowej linii. Plik CSV zazwyczaj przechowuje dane tabelaryczne. Nagłówki kolumn są często dołączane jako pierwszy wiersz (są to nazwy zmiennych), a każdy kolejny wiersz odpowiada jednej obserwacji (jednemu wierszowi w tabeli danych). CSV jest szczególnym przypadkiem formatu danych o nazwie Delimiter Seperated Values (DSV). Jest to plik tekstowy w którym pola w każdym wierszu oddzielone są dowolnym separatorem. Najczęściej spotykane separatory to: przecinek (CSV), tabulator (TSV), średnik. Przykładowy plik CSV 3.1.2 XML XML to skrót od nazwy Extensible Markup Language. Dane przechowywane w tym formacie mają zagnieżdżoną strukturę: znaczniki oznaczają nazwy zmiennych, a wewnątrz przechowywane są ich wartości. XML swoją strukturą przypomina plik HTML. Przykładowy plik XML 3.1.3 JSON JSON - JavaScript Object Notation - to format przydatny w przypadku pracy z danymi pochodzącymi z REST API, czyli pobieranymi z sieci. Niektóre bazy danych również komunikują się za pomocą tego formatu, np. MongoDB. Struktura: w pliku JSON obserwacje przechowywane są w słownikach, w których nazwy zmiennych są kluczami, a wartości zmiennych - wartościami. Obserwacje oddzielane są przecinkami, a dodatkowo, wszystkie dane spięte są nawiasami klamrowymi. Przykładowy plik JSON 3.1.4 Excel (XLSX) XLSX to format danych oparty na XML. Pliki tego typu są domyślnymi dokumentami wyjściowymi arkuszy kalkulacyjnych programu Microsoft Excel. Przedstawiają one głównie dane liczbowe i tekstowe w postaci tabel dwuwymiarowych. Przykładowy arkusz kalkulacyjny w Excelu 3.1.5 Otwarte wersje programu Excel Istnieją inne pakiety biurowe, np. LibreOffice, które - w przeciwieństwie do Excela - pozwalają na darmowe korzystanie z arkusza kalkulacyjnego. W przypadku LibreOffice, domyślnym formatem zapisu danych przez Calc (odpowiednik Excela) jest OpenDocument Format (.ods). Przykładowy arkusz kalkulacyjny w LibreOffice 3.1.6 Pliki tekstowe Jednym z najczęściej występujących i najbardziej uniwersalnych formatów przechowujących dane (np. w postaci tabeli) są pliki tekstowe. Mają one najczęściej rozszerzenie txt lub csv (comma separated values). Poniższą charakteryzację różnych metod wczytywania przedstawiamy na podstawie pliku listings.csv 3.1.6.1 Base Podstawową funkcją używaną do wczytywania tego typu plików w postaci tabeli jest funkcja read.table. Ze względu na specyfikację wewnętrzną plików, read.table posiada kilka wariantów, takie jak read.csv(), read.csv2() czy read.delim(). read.csv() używana jest w przypadku, gdy domyślnym separatorem dziesiętnym jest “.”, a wartości w wierszach oddzielone są poprzez “,”; read.csv2() używana jest w przypadku, gdy domyślnym separatorem dziesiętnym jest “,”, a wartości w wierszach oddzielone są poprzez “;”; read.delim() używana jest w przypadku, gdy domyślnym separatorem dziesiętnym jest “.”, a wartości w wierszach oddzielone są poprzez TAB Przykładowy sposób załadowania plików w formacie csv read.csv('./data/csv/listings.csv', header = TRUE, sep = ",") W przypadku read.table() dane zostają zaimportowane jako data.frame. Dla dużych plików wczytwanie za pomocą read.table() bywa jednak czasochłonne. Wówczas możemy użyć funkcji z paczki data.table lub readr. 3.1.6.2 readr readr jest częścią pakietu tidyverse. W tym przypadku import odbywa się za pomocą funkcji o podobnej nazwie, jak w przypadku read.table(), a mianowicie read_csv(). read_csv wczytuje dane oddzielone przecinkami, natomiast read_csv2() - dane oddzielone średnikami. read_csv('./data/csv/listings.csv') W przeciwieństwie do read.csv, funkcja read_csv na wyjściu daje dane w postaci tabeli w bardziej zwartej i przejrzystej formie. Oprócz tego podaje także specyfikację kolumn, tzn. informuje, jaka jest nazwa każdej kolumny oraz jej typ (np. col_double () oznaczają dane liczbowe). Typ danych jaki dostajemy na wyjściu to tbl_df (tzw. tibble), który jest w pewnym sensie zmodyfikowaną wersją tradycyjnej ramki danych data.frame, pozwalającą na łatwiejszą pracę w obrębie tidyverse. 3.1.6.3 data.table Do wczytywania danych z plików csv możemy także użyć funkcji fread z pakietu data.table. fread('./data/csv/listings.csv') Na wyjściu otrzymujemy ramkę danych, jednak wyświetloną w inny sposób niż w przypadku użycia read.csv. Różnica jest widoczna, gdyż po użyciu funkcji class() na fread() jako typ danych otrzymujemy \"data.table\" \"data.frame\". 3.1.6.4 Różnice Najważniejsze różnice pomiędzy wymienionymi sposobami wczytywania plików csv to: Typ danych Base: `data.frame readr: tibble data.table: `data.table data.frame Postać wyświetlania (co jest konsekwencją 1) Base: Wyświetla 62 początkowe wiersze każdej kolumny, wyświetlając informacje o liczbie pozostałych; readr: wyświetla 10 pierwszych wierszy z 10 pierwszych kolumn, z informacją o liczbie pozostałych wierszy i kolumn; automatycznie wyświetlane są też nazwy kolumn oraz skrót informujący o typie zmiennych data.table: wyświetla 5 początkowych i 5 końcowych wartości z każdej kolumny Czas i użycie pamięci przy dużych rozmiarach danych Zarówno czas wczytania danych, jak i wykorzystanie pamięci najkorzystniejsze jest w przypadku funkcji fread. Gdyby przez time oznaczyć czas potrzebny na wczytanie dużych plików, a przez memory zużycie pamięci, to time(fread) < time(read_csv) << time(read.csv) oraz memory(fread) < memory(read.csv) < memory(read_csv). 3.1.7 Arkusze kalkulacyjne i pliki JSON Do wczytywania arkusza kalkulacyjnego (np. pliku excela) używa się funkcji read_excel z pakietu readxl będącego częścią tidyverse. read_excel('./data/excel/listings.xlsx') Oprócz tego, można także użyć pakietu funkcji read.xlsx z pakietu xlsx. Wymaga ona jednak instalacji Javy. Do zaimportowania plików JSON możemy użyć funkcji z pakietu jsonlite listings_js <- jsonlite::fromJSON('./data/json/listings.json') listings_js <- mutate(listings_js, last_review = as_date(last_review)) 3.2 Locale Locale jest to uniksowe narzędzie powłokowe przechowujące ustawienia środowiskowe związane z ustawieniami regionalnymi. Sys.getlocale() ## [1] "LC_CTYPE=pl_PL.UTF-8;LC_NUMERIC=C;LC_TIME=pl_PL.UTF-8;LC_COLLATE=pl_PL.UTF-8;LC_MONETARY=pl_PL.UTF-8;LC_MESSAGES=pl_PL.UTF-8;LC_PAPER=pl_PL.UTF-8;LC_NAME=C;LC_ADDRESS=C;LC_TELEPHONE=C;LC_MEASUREMENT=pl_PL.UTF-8;LC_IDENTIFICATION=C" LC_COLLATE - odpowiada za porządek znaków, ważny przy sortowaniu LC_CTYPE - odpowiada za kodowanie znaków LC_MONETARY - odpowiada za system monetarny: znak waluty, separator tysięcy, liczba cyfr po przecinku itd. LC_NUMERIC - określa separator ułamkowy, separator tysięcy, grupowanie cyfr LC_TIME - odpowiada za system wyświetlania daty Sys.localeconv() ## decimal_point thousands_sep grouping int_curr_symbol currency_symbol mon_decimal_point mon_thousands_sep ## "." "" "" "PLN " "zł" "," " " ## mon_grouping positive_sign negative_sign int_frac_digits frac_digits p_cs_precedes p_sep_by_space ## "\\003" "" "-" "2" "2" "0" "1" ## n_cs_precedes n_sep_by_space p_sign_posn n_sign_posn ## "0" "1" "1" "1" Powyższa funkcja wyświetla szczegóły dotyczące systemu numerycznego i monetarnego. 3.2.1 Ustawienie locale przez użytkownika Sys.setlocale(category = "LC_ALL", locale = "polish") ## Warning in Sys.setlocale(category = "LC_ALL", locale = "polish"): Żądania raportów OS aby ustawić lokalizację na "polish" nie ## mogą zostać wykonane ## [1] "" W celu ustawienia innego locale niż domyślne systemowe należy użyć powyższej funkcji, przyjmowane przez nią argumenty to category i locale. category - odpowiada za określenie, które zmienne środowiskowe chcemy zmienić, gdzie opcje: “LC_ALL”, “LC_COLLATE”, “LC_CTYPE”, “LC_MONETARY”, “LC_NUMERIC” oraz “LC_TIME” są wspierane na każdym systemie operacyjnym, niektóre systemy wspierają również: “LC_MESSAGES”, “LC_PAPER” i “LC_MEASUREMENT” locale - odpowiada za region, który chcemy ustawić dla systemu windows podajemy angielską nazwę języka (regionu) np.: ‘polish’, w systemach UNIXowych podajemy np.: ‘pl_PL’ lub ‘pl_PL.UTF-8’. 3.2.2 Ustawienie locale poprzez readr Pakiet readr oferuje więcej możliwości dostosowywania locale, więcej informacji na ten temat można znaleźć w tym odnośniku. 3.3 Natywne formaty R R ma dwa natywne sposoby przechowywania danych, RDA(od RData) i RDS. Główną zaletą takiej obsługi danych jest szybkość przetwarzania ich. Zachowuje on także informacje z R o danych(np. typy zmiennych). 3.3.1 RDS W formacie RDS mogą być przechowywane jedynie pojedyncze pliki R. Mogą być one za to przypisywane do dowolnej nazwy. Aby załadować dane korzystamy z: listings_rds <- readRDS("./data/native/listings.rds") Do zapisania danych używamy: saveRDS(object = listings, file = "listings.rds") 3.3.2 RDA W plikach formatu RDA wczytane dane nie są przypisywane do zmiennej, tylko wywołujemy te funkcje i w efekcie plik pojawia się w środowisku. W RDA do załadowania danych służy load("./data/native/listings.rda") Natomiast, aby zapisać dane używamy: save(listings_rr, file = "listings_rr.rda") Korzystając z formatu RDA możemy jednocześnie zapisywać większą ilość plików save(iris, cars, file="data_frame.rda") "],["eksploracyjna-analiza-danych.html", "4 Eksploracyjna analiza danych 4.1 Dane tabelaryczne 4.2 Typy zmiennych 4.3 Miary 4.4 R - podsumowanie kolumn", " 4 Eksploracyjna analiza danych Badanie eksploracyjne danych (ang. exploratory data analysis) dotyczy opisu, wizualizacji i badania zebranych danych bez potrzeby zakładania z góry hipotez badawczych. Badania ekploracyjne obejmują również wstępne sprawdzenie danych w celu skontrolowania założeń modeli statystycznych lub występowania błędów w danych (np. braków odpowiedzi). 4.1 Dane tabelaryczne Dane tabelaryczne to dane, które mają postać tabeli. Tabela to struktura danych, która składa się z wierszy i kolumn. Każdy wiersz odpowiada pewnej obserwacji, której cechy zostały zapisane w kolejnych kolumnach. 4.2 Typy zmiennych Zmienne, które opisują kolejne obserwacje możemy podzielić na: zmienne jakościowe (niemierzalne) porządkowe - np. klasyfikacja wzrostu (niski, średni, wysoki) nominalne - np. kolor oczu, płeć, grupa krwi zmienne ilościowe (mierzalne) dyskretne - np. liczba dzieci, liczba gospodarstw domowych, wiek (w rozumieniu ilości skończonych lat) ciągłe - np. wzrost, masa, wiek (w rozumieniu ilości dni między datą urodzin a datą badania) proporcjonalne - np. masa, długość, temperatura wyrażona w Kelwinach lub stopniach Rankine’a (przyjmujemy istnienie zera i możemy twierdzić, że jedno ciało jest dwukrotnie gorętsze od drugiego) interwałowe - np. temperatura wyrażona w stopniach Celsjusza lub Fahrenheita (możemy twierdzić, że coś jest o 20 °C cieplejsze od czegoś innego, ale nie możemy stwierdzić ilokrotnie cieplejsze jest ciało o temperaturze 40 °C od ciała o temperaturze –10 °C), data kalendarzowa (możemy mówić o stałej różnicy pomiędzy kolejnymi dniami) 4.3 Miary Zapoznając się z danymi chcielibyśmy sprawdzić wokół jakiej wartości są skupione oraz jak bardzo są zmienne wartości danej cechy. Miary lokacji (miary tendencji centralnej) pomagają nam umiejscowić dane na osi. Przykładami takich miar są: średnia - najczęściej arytmetyczna określona jako \\(\\overline{x} = \\frac{1}{n}\\sum\\limits_{i=1}^n x_i\\). dominanta (moda) - ozn. \\(Mo\\) - dla zmiennych o rozkładzie dyskretnym, wartość o największym prawdopodobieństwie wystąpienia lub wartość najczęściej występująca w próbie. Dla zmiennej losowej o rozkładzie ciągłym jest to argument, dla którego funkcja gęstości prawdopodobieństwa ma wartość największą. mediana - ozn. \\(Me\\) - wartość cechy w szeregu uporządkowanym, powyżej i poniżej której znajduje się jednakowa liczba obserwacji. kwantyle rzędu \\(p\\) - wartość cechy w szeregu uporządkowanym, poniżej której znajduje się \\(p \\cdot 100\\%\\) liczby obserwacji, a powyżej której znajduje się \\((1 - p) \\cdot 100\\%\\) liczby obserwacji. Natomiast miary rozrzutu dostarczają informacji jak bardzo zróżnicowane są obserwacje pod względem badanej cechy. Przykładami takich miar są: wariancja - stopień rozrzutu badanej cechy wokół wartości oczekiwanej. Im większa wariancja, tym rozrzut zmiennej jest większy. Nieobciążony estymator wariancji wyraża się wzorem: \\(s^2 = \\frac{1}{n}\\sum\\limits_{i=1}^n\\left(x_i - \\overline{x}\\right)^2\\) odchylenie standardowe - mówi nam o przeciętnym odchyleniu wartości zmiennej losowej od jej wartości oczekiwanej. Im odchylenie standardowe jest większe, tym większe zróżnicowanie wartości badanej cechy. Odchylenie standardowe z próby obliczamy jako pierwiastek z wariancji z próby, tzn. \\(s = \\sqrt{s^2}\\). rozstęp międzykwartylowy - różnica między trzecim a pierwszym kwartylem. Ponieważ pomiędzy tymi kwartylami znajduje się z definicji 50% wszystkich obserwacji (położonych centralnie w rozkładzie), dlatego im większa szerokość tego rozstępu, tym większe zróżnicowanie cechy. Wyróżniamy także miary asymetrii. Miary asymetrii mówią nam, czy większa część populacji klasuje się powyżej, czy poniżej przeciętnego poziomu badanej cechy. Asymetrię rozkładu można zbadać porównując średnią, dominantę i medianę. W przypadku rozkładu symetrycznego wszystkie te parametry są równe. Jeśli zachodzi nierówność \\(Mo < Me < \\mathbb{E} X\\), to mamy do czynienia z prawostronną asymetrycznością rozkładu. Tzn. dużo małych wartości i bardzo mało dużych. Jeśli zachodzi nierówność \\(\\mathbb{E} X < Me < Mo\\), to mamy do czynienia z lewostronną asymetrycznością rozkładu. Tzn. mało małych i bardzo dużo dużych. 4.4 R - podsumowanie kolumn Podstawowymi funkcjami, które pomagają nam zapoznać się z danymi są funkcje: \\(\\texttt{head}\\) - zwraca pierwszą część wektora, macierzy, tabeli lub ramki danych. Domyślnie 6 pierwszych elementów. \\(\\texttt{nrow}\\) - zwraca liczbę wierszy macierzy, tabeli lub ramki danych. \\(\\texttt{ncol}\\) - zwraca liczbę kolumn macierzy, tabeli lub ramki danych. Natomiast podstawowymi funkcjami, które podsumowują kolejne kolumny są funkcje: \\(\\texttt{str}\\) - zwraca strukturę danego obiektu. Wyświetla np. klasę obiektu, liczbę wierszy i kolumn, a także nazwę danej kolumny, typ wartości w niej zawartych, jak i kilka początkowych wartości. \\(\\texttt{summary}\\) - zwraca podsumowanie każdej kolumny. Dla zmiennych ciagłych wyznacza wartości tj.: wartość najmniejsza i największa średnia i mediana 1 (0.25) i 3 (0.75) kwartyl liczba wartości brakujących (NA) Natomiast w przypadku zmiennych dyskretnych wyznacza liczbę obserwacji, które przyjmują daną wartość zmiennej. \\(\\texttt{glimpse}\\) - funkcja z pakietu \\(\\texttt{tidyverse}\\) podobna do \\(\\texttt{str}\\), ale stara się pokazać jak najwięcej danych. Wyświetla np. liczbę wierszy i kolumn, a także nazwę danej kolumny, typ wartości w niej zawartych oraz jak najwięcej wartości z tej kolumny. "],["przetwarzanie-danych-tabelarycznych.html", "5 Przetwarzanie danych tabelarycznych 5.1 Wybieranie kolumn 5.2 Zmiana nazw kolumn 5.3 Filtrowanie 5.4 Usuwanie kolumn 5.5 Manipulacje na kolumnach 5.6 Aplikowanie transformacji do każdej kolumny 5.7 Grupowanie i podsumowanie 5.8 Podsumowywanie wszystkich kolumn", " 5 Przetwarzanie danych tabelarycznych Operacje na danych w R są związane głównie z filtrowaniem, dodawaniem i modyfikowaniem kolumn, grupowaniem oraz podsumowywaniem danych. Można je wykonywać za pomocą funkcji bazowego R lub narzędzi z zaimportowanych pakietów: tidyverse, data.table. Załóżmy, że ramka danych jest przypisana do zmiennej \\(dane\\), a nazwy jej kolumn to: \\(kol.1, kol.2, kol.3,...\\) . 5.1 Wybieranie kolumn Poniżej przedstawione są instrukcje pozwalające na wybieranie konkretnych kolumn z ramki danych w~zależności od metody. Dla uproszczenia przyjmijmy, że wybieramy kolumny: \\(kol.1, kol.2, kol.3\\). base dane = dane[, c(“kol.1”, “kol.2”, “kol.3”)] tidyverse dane = select(dane, kol 1, kol 2, kol 3) dane = dane %>% select(kol 1, kol 2, kol 3) data.table Nazwy kolumn ramki danych zawierą znak “.” . Wprowadźmy zmienną pomocniczą \\(kolumny\\). Będzie ona zawierać nazwy kolumn, ale zastępując znak “.” znakiem ” “. kolumny = c("kol 1", "kol 2", "kol 3") dane = dane[, kolumny] dane = dane[, kolumny, with = FALSE] - dana metoda nie zadziała bez argumentu \\(with~=~FALSE\\), ponieważ szuka w ramce danych kolumn o nazwach zawartych w obiekcie \\(kolumny\\), a nie konkretnie podanych nazw dane = dane[, colnames(dane) %in% kolumny, with = FALSE] dane = dane[, ..kolumny] dane = dane[, list(kol 1, kol 2, kol 3)] dane = dane[, .(kol 1, kol 2, kol 3)] 5.2 Zmiana nazw kolumn Teraz zostaną zaprezentowane sposoby na zmianę nazw kolumn ramki danych. Przyjmijmy, że nowe nazwy kolumn są postaci \\(k1, k2, k3, ...\\) . base colnames(dane) = c(“k1”, “k2”, “k3”) tidyverse dane = dane %>% rename(k1 = kol.1, k2 = kol.2, k3 = kol.3) data.table setnames(dane, c(“kol.1”, “kol.2”, “kol.3”), c(“k1”, “k2”, “k3”)) - zaleta: nie kopiuje ramki danych 5.3 Filtrowanie Dany rozdział skupia się na sposobach filtrowania danych. Przydatne funkcje: unique(dane\\(\\$\\)k1) - zwraca unikalne wartości kolumny \\(k1\\) table(dane\\(\\$\\)k1) - zlicza ilość wystąpienia każdej wartości w kolumnie \\(k1\\) prop.table(table(dane\\(\\$\\)k1)) - pokazuje procentowo ilość wystąpienia każdej wartości w kolumnie \\(k1\\) w\\(~\\)stosunku do wszystkich wartości Przyjmnijmy, że wybieramy z kolumny \\(k1\\) określoną wartość \\(abc\\). base dane[dane\\(\\$\\)k1 == “abc”, ] tidyverse dane %>% filter(k1 == “abc”) można podać kilka warunków (po przecinku), będą one domyślnie rozdzielone spójnikiem \\(i\\) aby połączyć warunki spójnikiem \\(i\\) można również użyć operatora \\(\\&\\) aby połączyć warunki spójnikiem \\(lub\\) należy użyć operatora \\(|\\) data.table dane[k1 == “abc”] 5.4 Usuwanie kolumn Załóżmy, że usuwamy pierwszą kolumnę - \\(k1\\). base dane = dane[, -1] - gdzie \\(1\\) to numer usuwanej kolumny, a “-” oznacza usuwanie tidyverse dane = select(dane, -k1) - jak powyżej, “-” oznacza usuwanie, ale w tym przypadku stosujemy nazwę kolumny a nie jej numer data.table dane[, k1 := NULL] - operator \\(:=\\) (referencja) oznacza, że operacja jest wykonywana bez kopiowania ramki danych dane = dane[, -1, with = FALSE] 5.5 Manipulacje na kolumnach Przyjmijmy, że kolumna \\(k2\\) zawiera tylko liczby. Wartości ujemne zamieniamy na \\(0\\). W tym celu posłużymy się funkcją \\(ifelse\\): \\[ ifelse(warunek \\ logiczny,\\ wartość \\ jeśli \\ spełniony, \\ wartość \\ jeśli\\ niespełniony).\\] 1. base dane[[“k2”]] = ifelse(dane[[“k2”]] < 0, 0, dane[[“k2”]]) tidyverse dane = dane %>% mutate(k2 = ifelse(k2 < 0, 0, k2)) możemy modyfikować kilka kolumn jednocześnie, rozdzielając je przecinkiem data.table dane[, k2 := ifelse(k2 < 0, 0, k2)] - z użyciem referencji dane[[“k2”]] = ifelse(dane[[“k2”]] < 0, 0, dane[[“k2”]]) - bez użycia referencji 5.6 Aplikowanie transformacji do każdej kolumny W tym rozdziale będziemy operować na wszystkich kolumnach ramki danych. Wartości w nich zawarte mogą być typu \\(factor\\), które zamienimy na typ \\(character\\). base poprzez pętlę for (i in 1:ncol(dane)){ if (is.factor(dane[, i])){ dane[, i] = as.character(dane[, i]) } } poprzez funkcję \\(lapply\\) lapply(dane, fun(x){ if(is.factor(x)) x = as.character(x) }) tidyverse przy użyciu funkcji \\(mutate\\_all\\) dane = dane %>% mutate_all(function(x){ if (is.factor(x)){ as.character(x) } else{ x } }) data.table przy użyciu funkcji lapply dane = dane[, lapply(.SD, function(x){ if (is.factor(x)){ as.character(x) } else{ x } })] 5.7 Grupowanie i podsumowanie Załóżmy, że do wyznaczenia wszystkich unkialnych wartości ramki danych potrzebne są kolumny \\(k1\\), \\(k2\\) i \\(k3\\). Natomiast podsumowywana będzie kolumna \\(k4\\) - zostanie wyliczona średnia dla każdej unikalnej wartości. base przy użyciu funkcji \\(aggregate\\) - zastosowana zostanie formuła \\(k4\\) ~ \\(k1 + k2 + k3\\), która oznacza, że będzie podsumowywana zmienna \\(k4\\) w zależności od unikalnych zestawów wartości zmiennych \\(k1\\), \\(k2\\), \\(k3\\) aggregate(k4 ~ k1 + k2 + k3, data = dane, FUN = function(x) mean(x, na.rm = TRUE)) - poprzez zastosowanie własnej funkcji aggregate(k4 ~ k1 + k2 + k3, data = dane, FUN = mean, na.rm = TRUE) - poprzez zastosowanie istniejącej funkcji tidyverse dane %>% group_by(k1, k2, k3) %>% summarize(srednia = mean(k4, na.rm = TRUE), maksimum = max(k4, na.rm = TRUE)) \\(group\\_by\\) - grupuje po kolumnach \\(k1\\), \\(k2\\), \\(k3\\) \\(summarize\\) - podsumowuje według podanych elementów (w tym przypadku wylicza średnią i maksimum z kolumny \\(k4\\)) data.table dane[, list(średnia = mean(k4, na.rm = TRUE), maksimum = max(k4, na.rm = TRUE)), by = c(“k1”, “k2”, “k3”)] 5.8 Podsumowywanie wszystkich kolumn W celu podsumowania kolumn zdefiniujemy poniższą funkcję, która zwróci ilość niepustych wartości. num_unique_noNA = function(input_vector){ sum(!is.na(unique(input_vector))) } base apply(dane, 2, num_unique_noNA) - gdzie \\(2\\) oznacza, że wywołujemy podaną funkcję \\(num\\_unique\\_noNA\\) po kolumnach lapply(dane, num_unique_noNA) sapply(dane, num_unique_noNA) tidyverse summarise_all(dane, num_unique_noNA) data.table dane[, lapply(.SD, num_unique_noNA)] "],["czyste-dane.html", "6 Czyste dane 6.1 Dane w formacie wąskim i szerokim 6.2 Rozdzielanie na kolumny (wąska -> szeroka) 6.3 Scalanie kilku kolumn w jedną (szeroka -> wąska) 6.4 Łączenie tabel danych 6.5 Operacje na napisach i datach", " 6 Czyste dane Transformacja danych jest niezwykle ważnym elementem dobrze zrobionego raportu. Dane te powinny być prezentowane w sposób czytelny i ułatwiający ich porównywanie. To od potrzeby biznesowej zależy w jaki sposób powinniśmy przedstwiać dane. Np. dysponując wynikami finansowymi zbieranymi co miesiąc przez trzy lata bo planowania budżetu na następny rok przyda nam się prezentacja ich w formacie wąskim, czyli skupionym na wydatkach względem każdego roku. Jednakże, jeżeli chcielibyśmy kontrolować wydatki w tym następnym roku prezentacja danych w formacie szerokim będzie bardziej korzystna, gdyż będziemy mieli informację ile średnio wydajemy w danym miesiącu i na bieżąco będziemy mogli podejmować decyzję o inwestowaniu lub zaciskaniu pasa. Niekiedy jednak dane mają bardziej skomplikowaną formę i np. składają się z wielu tabel. Wówczas dla łatwiejszego uzyskania informacji biznesowej będzie połączenie tych tabel. Takie operacje w połączeniu z odpowiednią agregacją i grupowaniem zdecydowanie ułatwia wgląd w aktualną sytuację. Ostatnim tematem, na temat któtego ta notatka traktuje są operacje na napisach i datach. Bardzo łatwo uzmysłowić sobie przydatność w posługiwaniu się takimi operacjami. Ułatwia to konstruowanie prostych funkcji, które są kluczowe w każdym projekcie. Chociażby bazując na imionach i nazwiskach pewnych obywateli Polski łatwo wskazać z dużą pewnością kobiety w tym zbiorze sprawdzając ostatnią literę ich imienia (tj. czy dane imie kończy się na literę “a”). 6.1 Dane w formacie wąskim i szerokim Dane najczęściej są przedstawiane w postaci tabelarycznej. Jednak mogą być w tej tabeli różnie sformatowane. Wyróżnia się między innymi szeroką reprezentacje danych i wąską reprezentacje danych. W zależności od tego, co chcemy z nimi zrobić czasami trzeba przejść z jednej postaci do drugiej. Aby przetransformować dane korzysta się z funkcji z pakietów dplyr i tidyverse. O postaci szerokiej mówimy, gdy pojedyncza zmienna jest rozdzielona pomiędzy kilka kolumn. Różnicę najłatwiej jest pokazać na przykładzie. W tym celu wykorzystamy wbudowany zbiór danych sleep zawierający informacje o wpływie dwóch leków nasennych na ilość przespanych godzin. Kolumna extra zawiera informacje o ilości dodatkowo przespanych godzin. extra group ID 0.7 1 1 -1.6 1 2 -0.2 1 3 -1.2 1 4 -0.1 1 5 3.4 1 6 Dane są przedstawione w postaci wąskiej, każda zmienna jest przedstawiona w oddzielnej kolumnie. Teraz ‘rozbijmy’ kolumnę group na group 1 i group 2. ID group 1 group 2 1 0.7 1.9 2 -1.6 0.8 3 -0.2 1.1 4 -1.2 0.1 5 -0.1 -0.1 6 3.4 4.4 7 3.7 5.5 8 0.8 1.6 9 0.0 4.6 10 2.0 3.4 Można zaobserwować, że wartości z kolumny extra zostały wpisane w poszczególne komórki, a kolumna group została podzielona na dwie oddzielne kolumny group 1 i group 2. Tak sformatowane dane nazywamy szeroką reprezentacją danych. 6.2 Rozdzielanie na kolumny (wąska -> szeroka) Aby przejść z wąskiego formatu przedstawiania danych do szerokiego, można użyć funkcji spread() z pakietu dplyr. Funkcja spread(dataset,key,value) przyjmuje trzy agrumenty: dataset - zbiór danych w formacie wąskim, key - kolumna (klucz) odpowiadająca kolumnie, która ma zostać rozłożona, value - kolumna, w której znajdują się wartości wypełniające nowe kolumny. szeroka <- spread(sleep, group, extra) colnames(szeroka) = c("ID","group 1","group 2") kable_styling(kable(head(szeroka)), position = "center") ID group 1 group 2 1 0.7 1.9 2 -1.6 0.8 3 -0.2 1.1 4 -1.2 0.1 5 -0.1 -0.1 6 3.4 4.4 Drugą opcją na uzyskanie tego samego rezultatu jest użycie funkcji pivot_wider z pakietu tidyverse. Funkcja przyjmuje dwa argumenty pivot_wider(names_from = name, values_from = value): name - nazwa kolumny, która ma zostać rozłożona, value - nazwa kolumny, w której znajdują się wartości. sleep %>% pivot_wider(names_from = group, values_from = extra) 6.3 Scalanie kilku kolumn w jedną (szeroka -> wąska) Można wrócić z postaci szerokiej do wąskiej. W tym celu należy użyć funkcji gather() z pakietu tidyr. Funkcja gather(dataset, key, value, other) przyjmuje również trzy argumenty: dataset - zbiór danych w formacie szerokim, key - nazwy kolumn z kluczami, value - nazwy kolumn z wartościami, other - kolumny dataset, które mają być zawarte w nowej tabeli. Aby wrócić do postaci wąskiej nałóżmy funkcję gather na wygenerowaną wcześniej tabele szeroka. kable_styling(kable(head(szeroka %>% gather(group, extra, -ID))),position = "center") ID group extra 1 1 0.7 2 1 -1.6 3 1 -0.2 4 1 -1.2 5 1 -0.1 6 1 3.4 Drugą funkcją, która umożliwia przejście z szerokiej reprezentacji danych do wąskiej jest funkcja pivot_longer z pakietu tidyverse. Funkcja pivot_longer(col_names, names_to = name, values_to = value) przyjmuje trzy argumenty col_names - ciąg nazw kolumn, które chcemy złączyć, name - nazwa nowo powstałej kolumny, value - nazwa kolumny, w której pojawią się wartości. kable_styling(kable(head(szeroka %>% pivot_longer(c("1", "2"), names_to = "group", values_to = "extra"))), position = "center") 6.4 Łączenie tabel danych Mamy dwie tabele danych tab1 z małymi literami oraz tab2 z wielkimi literami: Table 6.1: tab1 = x indeks litery 1 a 2 b 3 c 4 d 5 e 6 f Table 6.1: tab2 = y indeks LITERY 4 E 5 F 6 G 7 H 8 I 9 J gdzie x = tab1, a y = tab2. Aby połączyć dwie tabele danych na podstawie wskazanych kolumn lub kolumn o wspólnej nazwie można użyć przykładowych funkcji. 6.4.1 merge() Dostępna w bazowym R. Domyślnie funkcja ta łączy tabele względem nazw kolumn, które są wspólne. tabela <- merge(x = tab1, y = tab2) kable(tabela) indeks litery LITERY 4 d E 5 e F 6 f G Jeśli chcemy być pewni, że tabele zostaną połączone po odpowiedniej kolumnie, możemy przekazać nazwę tej kolumny w argumencie. W tym przypadku: merge(tab1, tab2, by = "indeks") # INNER JOIN Jeśli jest więcej kolumn, po których chcemy połączyć tabele, wystarczy przekazać w argumencie by wektor z nazwami tych kolumn. Gdy nazwy kolumn po których chcemy złączyć tabele różnią się, należy wykorzystać argument by.*. Załóżmy, że kolumna tabeli tab1 - indeks zmieniła nazwę na index, zatem: merge(tab1, tab2, by.x = "index", by.y = "indeks") Wartości kolumn indeks w tab1 oraz tab2 różnią się. Dlatego korzystając z funkcji bez dodatkowych argumentów tracimy dane. Aby zapobiec traceniu danych z poszczególnych tabel należy skorzystać z argumentu all, brakujące wartości zostaną uzupełnione NA: merge(tab1, tab2, all.x = TRUE) # LEFT JOIN merge(tab1, tab2, all.y = TRUE) # RIGHT JOIN merge(tab1, tab2, all = TRUE) # OUTER JOIN Dostajemy wtedy kolejno: Table 6.2: all.x = TRUE indeks litery LITERY 1 a NA 2 b NA 3 c NA 4 d E 5 e F 6 f G Table 6.2: all.y = TRUE indeks litery LITERY 4 d E 5 e F 6 f G 7 NA H 8 NA I 9 NA J Table 6.2: all = TRUE indeks litery LITERY 1 a NA 2 b NA 3 c NA 4 d E 5 e F 6 f G 7 NA H 8 NA I 9 NA J Bez sprecyzowania argumentu sort wiersze wyniku merge() zostaną posortowane leksykograficznie po wspólnych kolumnach. Gdy sort = FALSE wiersze będą w nieokreślonej kolejności. Kolumny złączonej tabeli to najpierw kolumny wspólne, następnie pozostałe z x a na końcu pozostałe z y, co widać na przykładach. 6.4.2 join() Funkcja z paczki dplyr. Tabele x i y powinny zwykle pochodzić z tego samego źródła danych, ale jeśli copy = TRUE, y zostanie automatycznie skopiowany do tego samego źródła co x. Są cztery typy join zmieniających: left_join() - zwraca wszystkie wiersze z x i wszystkie kolumny z x i y. Wiersze w x bez dopasowania w y będą miały wartości NA w nowych kolumnach. Jeśli istnieje wiele dopasowań między x a y, zwracane są wszystkie kombinacje dopasowań tabela <- left_join(tab1, tab2) kable(tabela) indeks litery LITERY 1 a NA 2 b NA 3 c NA 4 d E 5 e F 6 f G 6 z G right_join() - analogicznie do left_join(), ale zwraca wszystkie wiersze z y, a wiersze bez dopasowania w x będą miały wartości NA inner_join() - zwraca wszystkie wiersze z x, w których znajdują się pasujące wartości w y, oraz wszystkie kolumny z x i y. Jeśli istnieje wiele dopasowań między x a y, zwracane są wszystkie kombinacje dopasowań. tabela <- inner_join(tab1, tab2) kable(tabela) indeks litery LITERY 4 d E 5 e F 6 f G 6 z G full_join() - zwraca wszystkie wiersze i wszystkie kolumny zarówno z x, jak i y. Jeśli nie ma pasujących wartości, zwraca NA dla brakujących. tabela <- full_join(tab1, tab2) kable(tabela) indeks litery LITERY 1 a NA 2 b NA 3 c NA 4 d E 5 e F 6 f G 6 z G 7 NA H 8 NA I 9 NA J Argument by przyjmuje wektor nazw zmiennych do połączenia. Jeśli by = NULL funkcja *_join() domyślnie połączy tabele dopasowując wartości ze wszystkich kolumn o wspólnych nazwach w obu tabelach. 6.5 Operacje na napisach i datach Większość poniższych funkcji pochodzi z pakietu stringi. 6.5.1 Operacje na napisach Wyznaczanie długości napisów. Funkcja stri_lenght() zwraca długości poszczególnych napisów w danym wektorze, a stri_isempty() sprawdza, które napisy są puste -> ’’. Łączenie i powielanie napisów. Funkcja używana do łączenia kilku wektorów napisów w inny wektor napisów lub nawet w jeden napis, jest stri_paste() i jej warianty. Przykład: x <- LETTERS[1:3] y <- letters[1:3] z <- '!' stri_paste(x, y, z) ## [1] "Aa!" "Bb!" "Cc!" Przycinanie i wypełnianie. Funkcja stri_wrap() wstawia znaki nowego wiersza (n), by napis po wyświetleniu np. przy funkcji cat() miał szerokość nie większą, niż podana, jeżeli to możliwe. W przypadku przetwarzania tekstów pochodzących np. z formularzy na stronach internetowych może zachodzić potrzeba usunięcia tzw. białych znaków, np. spacji z początku lub końca napisu. Możemy to zrobić przy użyciu funkcji stri_trim(). Operacja w pewnym sensie odwrotną do tej można wykonać przy użyciu funkcji stri_pad(). Przykład: stri_trim(' Mama i tata\\n') ## [1] "Mama i tata" Formatowanie napisów na podstawie innych obiektów. Najprostszym sposobem na uzyskanie napisowej reprezentacji danego obiektu jest użycie funkcji as.character(). Przykład: as.character(list(1L, mean, NULL, pi, FALSE)) ## [1] "1" "function (x, ...) \\nUseMethod(\\"mean\\")" ## [3] "NULL" "3.14159265358979" ## [5] "FALSE" x <-data.frame(a=c(TRUE, FALSE, FALSE), b=as.integer(c(1, 2, 3))) as.character(x) ## [1] "c(TRUE, FALSE, FALSE)" "1:3" Zmiana pojedynczych znaków. Zmiana poszczególnych znaków na inne przydaje się między innymi na etapie wstępnego przygotowania danych w celu ujednolicenia tekstowych identyfikatorów obiektów, możemy np. zmieniać wielkości wszystkich liter w napisach. Przykład: stri_trans_toupper('chcemy duże litery') ## [1] "CHCEMY DUŻE LITERY" stri_trans_tolower('ChCemY MałE LiTErY') ## [1] "chcemy małe litery" stri_trans_char('zastępowanie znaków', 'ąćęłńóśżź', 'acelnoszz') ## [1] "zastepowanie znakow" stri_trans_general('żółć', 'Latin-ASCII') ## [1] "zolc" Wyznaczanie podnapisów. Funkcja stri_sub() zwraca podnapis składający się ze znaków leżących na określonych pozycjach danego napisu. Przykład: x <- 'Lasy, pola, pastwiska, koszą traktorem' stri_sub(x, 7) ## [1] "pola, pastwiska, koszą traktorem" 6.5.2 Operacje na datach Funkcją zwracającą aktualną datę systemową jest Sys.Date(), a Sys.time() aktualny czas systemowy wraz z datą. Przykład: (data <- Sys.Date()) ## [1] "2023-11-14" (czas <- Sys.time()) ## [1] "2023-11-14 13:44:20 CET" Operacje arytmetyczne na datach – dodawanie, odejmowanie i porównywanie. Przykład: data ## [1] "2023-11-14" data-365 ## [1] "2022-11-14" data+365 ## [1] "2024-11-13" (d <- data-as.Date('2021-01-01')) ## Time difference of 1047 days Do konwersji do napisu może służyć przeciążona wersja metody format(), której wywołanie jest tożsame z wywołaniem funkcji strftime() (ang. string-format-time). Przykład: strftime(czas, '%Y-%m-%d %H:%M:%S %Z') ## [1] "2023-11-14 13:44:20 CET" Do znajdowania “najstarszej” i “najmłodszej” daty używamy funkcji max() oraz min(). Do pracy ze strefami czasowymi możemy używać poniższych funkcji: force_tz() ustawienie strefy czasowej, with_tz() sprawdzenie daty w innej strefie czasowej. "],["wizualizacja-danych-z-pakietem-ggplot2.html", "7 Wizualizacja danych z pakietem ggplot2 7.1 Wprowadzenie 7.2 Gramatyka grafiki 7.3 Podstawy tworzenia wykresów w ggplot2 7.4 Mapowanie 7.5 Geometria wykresu 7.6 Funkcje pomagające poprawić czytelność wykresu 7.7 Panele", " 7 Wizualizacja danych z pakietem ggplot2 7.1 Wprowadzenie Jednym z ważnych elementów przekazywania ciekawych informacji oraz ich analizy jest przedstawienie graficzne interesujących nas danych. W R istnieje kilka sposobów na wizualizację danych. Jednym z nich jest korzytanie z narzędzi oferowanych przez pakiet ggplot2. Bibiloteka ggplot2 oprócz zwykłych funkcji plotowania, implementuje także gramatykę grafiki, co pozwala na wykonanie prawie każdego rodzaju (statystycznej) wizualizacji danych. 7.2 Gramatyka grafiki Powyżej wspomnieliśmy o gramatyce grafiki. Dla dokładniejszego uporządkowania wiedzy przypomnijmy, że gramatyka grafiki daje nam możliwość zadawania odpowiednich parametórw dla wszystkich linii, słów, strzałek, itp., które połączone tworzą wykres. Dodatkowo możemy m.in. zmieniać układ współrzędnych, czy korygować położenie każdego obiektu znajdującego się na wykresie. Możliwości jakie oferuje nam gramatyka grafiki będą przedstawione dokładniej w dalszej części notatki. 7.3 Podstawy tworzenia wykresów w ggplot2 Na początku, aby móc tworzyć wizualizacje, musimy załadować pakiet oraz bibilotekę ggplot2. Warto zwrócić uwagę, że ggplot2 posiada również szereg wbudowanych zestawów danych. Aby pokazać możliwości jakie oferuje nam ggplot, przeprowadzimy symulację danych mpg dostępnych w R. library(ggplot2) head(mpg) ## # A tibble: 6 × 11 ## manufacturer model displ year cyl trans drv cty hwy fl class ## <chr> <chr> <dbl> <int> <int> <chr> <chr> <int> <int> <chr> <chr> ## 1 audi a4 1.8 1999 4 auto(l5) f 18 29 p compact ## 2 audi a4 1.8 1999 4 manual(m5) f 21 29 p compact ## 3 audi a4 2 2008 4 manual(m6) f 20 31 p compact ## 4 audi a4 2 2008 4 auto(av) f 21 30 p compact ## 5 audi a4 2.8 1999 6 auto(l5) f 16 26 p compact ## 6 audi a4 2.8 1999 6 manual(m5) f 18 26 p compact Składnia wykresów w ggplot polega na warstwowym budowaniu wykresów. Zaczynamy od doboru danych, jakie chcielibyśmy zwizualizaować. Określamy sposób mapowania zmiennych do aspektów wizualnych. Definiujemy styl wykresu. Dodajemy dodatkowe warstwy personalizujące wykres, tzn. dodajemy tytuł, etykiety, itp. (opcjonalnie) Uwaga! Do dodawania nowych warstw do wykresu używamy symbolu “+” . # Budujemy podstawę wykresu, określając z jakich danych będziemy korzytsać ggplot(mpg) # Mapujemy dane ( na osiach pojawiły się odpowiednie podziałki) ggplot(mpg , aes( x = displ, y = hwy)) # Określamy styl wykresu, dzięki czemu dostemy wykres odpwiednich zależności ggplot(mpg , aes( x = displ, y = hwy))+ geom_point() # Personalizujemy wykres poprzez dodanie tytułu oraz określenie motywu wykresu ggplot(mpg , aes( x = displ, y = hwy))+ geom_point()+ ggtitle("disp vs hwy")+ theme_bw() 7.4 Mapowanie Mapowanie danych jest estetyką, która mówi ggplot’owi, jakie zmienne powinny znajdować się na osi x oraz y. Dodatkowo możemy wpływać na cechy wizualne danych, takie jak kolor ( color = ), kształt ( shape = ), czy przezroczystość ( alpha = ). Wszystkie estetyki dla podziałki wykresu są określone w wywołaniu funkcji aes(). Uwaga! Każda warstwa geom może mieć swoją własną specyfikację aes. Możemy zdecydować, czy na wykresie geom_point punkty mają być zaznaczone jako koła, czy kwadraty. # Punkty na wykresie będą kwadratami ggplot(mpg, aes(x = displ, y = hwy)) + geom_point(shape = 0)+ ggtitle("displ vs hwy")+ theme( axis.title = element_text(size = 1))+ theme_bw() # Punkty na wykresie to czerwone kropki ggplot(mpg, aes(x = displ, y = hwy)) + geom_point(color = "red")+ ggtitle("displ vs hwy")+ theme_bw() 7.5 Geometria wykresu Za pomocą ggplot2 możemy stworzyć prawie każdy rodzaj wykresu. W tym celu musimy zadać typ wykresu jaki nas interesuje. Ggplot2 daje możliwość stworzenia wykresu: punktowego ( geom_point) liniowego ( geom_line) słupkowego ( geom_bar) skrzypcowego ( geom_violin) histogramu ( geom_histogram) boxplota ( geom_boxplot) oraz wielu innych, które powstają m.in. przez zastosowanie funcji: geom_area, geom_density, geom_dotplot, geom_qq, geom_smooth. Uwaga! Wykresy słupkowe i histogramy grupują dane, a następnie prezentują liczbę elementów znajdujących się w poszczególnych grupach Na wykresach liniowych model jest dopasowywany do danych, a nastęonie wykreślane są przewidywania wyznaczone przez model Wykresy pudełkowe obliczają kompleksowe podsumowanie rozkładu wartości Poniżej przedstawione są dwa przykładowe typy wykresów. Pierwszy narysowany przy użyciu funkcji geom_smooth, która służy do rysowania wygładzonych linii, np. dla prostych trendów. Drugi wykres powstał przy zastosowaniu funkcji geom_histogram. ggplot(mpg, aes(x = displ, y = hwy)) + geom_smooth() ## `geom_smooth()` using method = 'loess' and formula = 'y ~ x' ggplot(data = mpg, aes(x = hwy)) + geom_histogram() ## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`. W pakiecie ggplot2 bardzo prosto możemy łączyć ze sobą różne geometrie na jednym wykresie. Wystarczy wstawić znak “+” pomiędzy odpowiednimi funkcjami. ggplot(mpg , aes( x = displ, y = hwy))+ geom_point()+ geom_smooth()+ ggtitle("Multiple geomteries")+ theme_bw() ## `geom_smooth()` using method = 'loess' and formula = 'y ~ x' 7.6 Funkcje pomagające poprawić czytelność wykresu 7.6.1 Systemy współrzędnych Domyślnym systemem współrzędnych dla ggplot2 jest kartezjański układ współrzędnych. W zależności od danych na jakich działamy, może się okazać, że pokazanie danych w innym układzie współrzędnych, wpłynie na lepszy odbiór informacji z wykresu. Funkcjami, które odpowiadają za przekształcenie układu współrzędnych są m.in. coord_flip która zamienia osie x i y coord_polar wykres jest pokazany we współrzędnych polarnych coord_fixed nadal jesteśmy w kartezjańskim układzie współrzędnych, ale możemy zmienić proporcję między jednostkami na osi x i y 7.6.2 Dopasowanie położenia Każda geometria w ggplot2 ma ustawione domyślne położenie różnych elementów na wykresie względem siebie. Różne opcje ustawienia położenia są dobrze widoczne na wykresach słupkowych. Zacznijmy od stworzenia zwykłego wykresu słupkowego, bez żadnych dodatkowych funkcji. ggplot(mpg, aes(x = class)) + geom_bar() Jeżeli teraz do mapowania dodamy opcję fill = dvr, to każdy prostokąt będzie reprezentował kombinację wartości class oraz dvr. ggplot(mpg, aes(x = class, fill = drv)) + geom_bar()+ theme(axis.text.x = element_text(angle = 90))+ theme_bw() Takie przedstawienie danych nie dla każdego może być czytelne, dlatego możemy skorzystać z opcji position, która przyjmuje m.in. argumenty: “dodge” i “fill”. ggplot(mpg, aes(x = class, fill = drv)) + geom_bar(position = "dodge")+ ggtitle(" Plot for dodge position")+ theme_bw() ggplot(mpg, aes(x = class, fill = drv)) + geom_bar(position = "fill")+ ggtitle(" Plot for fill position")+ theme_bw() 7.6.3 Zarządzanie osiami współrzędnych Jedną z możliwości jaką oferuje nam pakiet ggplot2 jest prosta zmiana skali na osiach wykresu. Podstawowymi funkcjami, które to umożliwiają są: scale_x_log10 (zamiast x możemy podać także y) wtedy skala x-ów będzie zlogarytmowana scale_x_reverse powoduje odwrotny kierunek na osi x scale_x_sqrt() skala x-ów będzie spierwiastkowana scale_fill_manual pozwala nam ręcznie wprowadzić oczekiwane przez nas wartości, m.in. możemy zmienić nazwy obiektów na skali, czy podać zakres wartości do uwzględnienia w mapowaniu xlim(5,40) powoduje ograniczenie podziałki na osi x od 5 do 40 (analogicznie z ylim) W ggplot2 z łatwością także dodamy etykiety tekstowe oraz adnotacje. Do wykresu możemy dodać tytuł oraz nazwy osi korzystając m.in. z funkcji labs(). ggplot(mpg, aes(x = displ, y = hwy, color = class)) + geom_point() + labs(title = "Fuel Efficiency by Engine Power", x = "Engine power", y = "Fuel Efficiency", color = "Car Type") 7.6.4 Motywy Theme to dobry sposób na dostosowanie odpowiedniego tytułu, etykiet, czcionek, tła, legendy, czy lini siatki na wykresie. Możemy skorzystać z jednego z dostępnych motywów, takich jak theme_bw(), czy theme_minimal(). Istnieje możliwość zastosowania wielu dostępnych opcji tak, aby odpowiednie elementy wykresu wyglądały tak, jak chcemy. Podstawowymi funkcjami, jakie warto znać są m.in. legend.position, dzięki której możemy ustalić pozycję legendy wykresu, axis.text, która umożliwia nam ustawienie czcionki na wykresie oraz ustalenie jej wielkości czy koloru. Przydatną funkcją pochodzącą z rodziny theme jest ‘theme(axis.text.x = element_text(angle = 90))’, która obraca nazwy znajdujące się na osi x, dzięki, czemu stają się one czytelniejsze. 7.7 Panele Ostatnim z podstawowych funkcji jakie oferuje pakiet ggplot2 jest facets. Panele to sposoby grupowania wykresu danych w wiele różnych części ze względu na zadaną zmienną. Możemy korzystać z funkcji: facet_wrap(), która ustawia panele w prostokątnym układzie facet_grid(), która ustawia panele w kolumny lub w wiersze (zależnie jaką opcję wybierzemy) ggplot(mpg, aes(x = displ, y = hwy)) + geom_point() + facet_grid(~ class) Uwaga! Aby zadać względem, której zmiennej chcemy grupować, w funkcji ‘facet_’ po znaku “~”, podajemy nazwę tej zmiennej. Kiedy korzystamy z funkcji tworzącej panele, automatycznie wszytskie wykresy będą pokazane w układzie współrzędnych dopasowanym do wszytkich okienek. Istnieje jednak możliwość dopasowania układu współrzędnych do każdego panelu osobno. W tym celu możemy wykorzystać funcję ‘scale = “free”’. "],["czysty-i-wydajny-kod-w-r.html", "8 Czysty i wydajny kod w R 8.1 Czysty kod 8.2 Styl kodu i narzędzia pomagające w utrzymaniu czystego kodu", " 8 Czysty i wydajny kod w R 8.1 Czysty kod Na początku zajmiemy się szeroko pojętą czystością kodu. Aby dany kod mógł aspirować do takiego miana, musi przede wszystkim spełniać dwa podstawowe warunki: Być łatwym do zrozumienia Aby kod był łatwy do zrozumienia musi być przede wszystkim czytelny. Niewątpliwie pomoże w\\(~\\)tym odpowiednie nazwanie zmiennych, zadbanie o to, żeby wszystkie użyte funkcje i obiekty miały swoją określoną rolę oraz by relacje między nimi były zrozumiałe. Być łatwym do zmiany Tworząc kod powinniśmy myśleć o tym, że będzie on w przyszłości wykorzystywany. Aby to ułatwić, musi być napisany w taki sposób, żeby można było nanieść drobne poprawki lub zmienić dane bez konieczności zmieniania całego kodu. Jeśli te dwa warunki nie są spełnione, istnieje obawa, że wprowadzenie nawet najmniejszych zmian całkowicie zniszczy kod. 8.1.1 Co jeśli w kodzie jest ,,bałagan’’? Nieuporządkowany i nieklarowny kod może sprawić w przyszłości wiele kłopotów, takich jak na przykład: Zmarnowanie czasu Jeśli my lub ktokolwiek inny będzie chciał w przyszłości wykorzystać taki kod z pewnością straci mnóstwo czasu na próby jego przeczytania i zrozumienia. Gdy już mu się to uda, może napotkać kolejny problem w postaci trudności z wprowadzeniem jakichkolwiek zmian. Ograniczenie lub nawet brak możliwości rozwoju Złe napisanie kodu może spowodować, że po jego jedynym użyciu stanie się bezwartościowy. Nie będzie sensu wprowadzać w nim jakichkolwiek zmian (gdyż będzie to zbyt pracochłonne), ani w żaden sposób rozwinąć by mógł posłużyć do przyszłych projektów (gdyż nawet najmniejsze zmiany mogą ,,zepsuć’’ istniejący kod). Podatność na wystąpienie błędów W nieczytelnym i napisanym w sposób niezrozumiały kodzie, łatwo przemycić błędy, które na pierwszy rzut oka są niewidoczne, ale wychodzą na jaw później. 8.1.2 Opis zmiennych 8.1.3 Opis intencji Aby tworzyć czysty kod musimy pamiętać o kilku zasadach. Jedną z nich jest odpowiednie nazywanie zmiennych. Nie powinniśmy używać do tego skrótów, czy przypadkowych znaków. Idealna nazwa od razu wskazuje na to, czym jest dany obiekt oraz co oznacza. Przedstawia zamiary, jakie mamy do nazywanego obiektu. 8.1.4 Unikanie błędnych informacji Równie ważne jest, aby w nazwach nie znajdywały się błędy lub informacje, które mogą wprowadzić potencjalnego czytelnika w błąd. Mówimy tu np. o: - nazwaniu kilku obiektów zbyt podobnie, - użyciu do nazwania listy (np. osób) słowa \\(\\mathtt{List}\\), choć w rzeczywistości ta ,,lista’’ osób może być wektorem, - użyciu trudno rozróżnialnych znaków (takich jak np. 0 i O), - nazwaniu wszystkich obiektów za pomocą jednej litery i cyfry (np. \\(x_1,x_2,...,x_n\\)). 8.1.4.1 Kilka wskazówek Jakie powinny być idealne nazwy obiektów w R? Oto kilka wskazówek: - zrozumiałe dla osób, dla których jest przeznaczony kod, - utrzymane w jednym stylu, - łatwe do zrozumienia i napisania, - nazwa obiektu powinna być rzeczownikiem, który wskazuje na to, z czym mamy do czynienia, - nazwa funkcji powinna być czasownikiem wskazującym na to, co robi dana funkcja. 8.1.5 Funkcje W tym rozdziale dowiemy się jak pisać ,,dobre’’ funkcje. Tutaj również musimy pamiętać o kilku zasadach. Funkcje powinny: - być możliwie jak najkrótsze, - odpowiadać za jedno pojedyncze zadanie, - być na jednym poziomie abstrakcji, - mieć maksymalnie 3 parametry. To znaczy, że nie jest wskazane, aby tworzyć jedną wielką funkcję, która np. wylicza kilkanaście rzeczy, aby na końcu wygenerować jeden wynik. Zamiast tego lepiej stworzyć kilka mniejszych funkcji, które będą się odwoływały do poprzednich. Dzięki temu nasz kod będzie bardziej przejrzysty oraz w prosty sposób będzie można sprawdzić, czy pojedyncze funkcje działają poprawnie. Co więcej, nie ma sensu tworzyć funkcji, która zwraca nam już oprawioną tabelę z wynikami. Lepiej, gdy zwraca surowe wyniki, a tworzeniem tabeli zajmuje się kolejna funkcja. Przykładowa, poprawnie napisana funkcja: calculate_conf_interval = function(sample, alpha) { len = length(sample) successes = length(sample[sample == 1]) mi = successes / n se = sqrt(mi * (1 - mi) / len) quantile = qt(1 - alpha / 2, len - 1) left = mi - quantile * se right = mi + quantile * se return(c(left, right)) } Przykładowa funkcja, napisana w ,,nieładny’’ sposób: func= function(x,y,temp1,temp2){ n =length(x) s <-length(x[x==1]) m = s/n sgm = sqrt(mi *(1- m)/n) q<-qt(1 - y /2,len-1) tmp = (s + 0.5 * q ^ 2) /(n + q ^ 2) se = sqrt(tmp *(1 - tmp)/ (n+ q^2)) l<- tmp- q* se r = tmp + q*se return(c(l,r))} Główne problemy: - czasem przypisanie jest za pomocą =, czasem <-, - brak spacji po przecinkach, - brak spacji pomiędzy +, -, *, /, itd, - niepoprawnie umiejscowione nawiasy {, }. - nazwa funkcji nie opisuje, co robi ta funkcja, - zmienne mają nic nieznaczące i jednoliterowe nazwy, - nazwa zmiennej tmp także nie mówi, czym ona jest, - dwa nieużywane parametry funkcji. 8.1.6 Komentarze Zazwyczaj komentarze do kodu nie są potrzebne, a wręcz zbędne. Dzieje się tak, ponieważ dobrze napisany kod powinien sam się tłumaczyć, tzn. być na tyle zrozumiałym, żeby dodatkowe komentarze nie były potrzebne. Jeśli jednak w kodzie jest bałagan, dodatkowe komentarze mogą wręcz wprowadzić dodatkowy chaos. Od tej reguły są jednak pewne wyjątki. Jeśli używamy niezbyt oczywistych implementacji lub ,,sztuczek programistycznych’’ warto wspomnieć w komentarzu, co się w danej chwili dzieje. Wyjątkiem są też komentarze informujące o tym, co trzeba jeszcze zrobić lub o potrzebie poprawienia jakiejś części kodu. 8.1.7 Obiekt a struktura danych W kontekście pisania czystego i wydajnego kodu, należy wziąć pod uwagę rozróżnienie pomiędzy klasami a strukturami danych. Te pierwsze zawierają atrybuty i funkcje, a instancje klasy nazywamy obiektem. Zastosowanie klas pozwala na stworzenie interfejsu definującego pewne dane. Struktury danych służą natomiast do reprezentacji danych dowolnego typu a nie ich opisu. 8.2 Styl kodu i narzędzia pomagające w utrzymaniu czystego kodu Dobry styl kodowania jest porównywany do prawidłowego stosowania interpunkcji. Jest możliwe nie stosowanie się do jej zasad, jednak przestrzeganie ich pozwala, aby w zapisie panował ład i porządek. W R dominują dwa style, które pomagają utrzymać dobry układ kodu. Jednym jest tidyverse style, a\\(~\\)drugim, wywodzącym się z poprzedniego, Google style. Istnieją przewodniki, które ułatwiają stosowanie się do zasad panujących w tych stylach. Style ustosunkowują się m.in. do stawiania spacji po przecinkach, przed operatorami matematycznymi oraz po nich, a także podkreślników w nazwach. Dodatkowo można zainstalować pakiety, które będą pomagać w utrzymaniu schludnego kodu: cleanr, stylerr, lintr. "],["interaktywna-wizualizacja-danych-z-pakietem-shiny-interfejs-użytkownika.html", "9 Interaktywna wizualizacja danych z pakietem shiny: interfejs użytkownika 9.1 Wstęp 9.2 Tworzenie UI 9.3 Układ strony 9.4 Elementy wejścia i wyjścia 9.5 Przykład użycia 9.6 Wygląd aplikacji", " 9 Interaktywna wizualizacja danych z pakietem shiny: interfejs użytkownika 9.1 Wstęp Shiny jest pakietem R pozwalającym na tworzenie interaktywnych aplikacji webowych w łatwy i przystępny sposób. Aplikacja w shiny zbudowana jest z dwóch następujcych elementów: ui - user interface, czyli obiekt, w którym zawarty jest wygląd aplikacji, server - funkcja organizująca działanie aplikacji. Do uruchomienia aplikacji służy funkcja shinyApp(ui, server). Stworzenie dobrej i czytelnej aplikacji może znacznie ułatwić analizowanie danych. W tej notatce zajmiemy się omówieniem elementów oraz podstawowych schematów budowy UI. library(shiny) library(shinyWidgets) library(shinydashboard) 9.2 Tworzenie UI Do budowania podstawowego interfejsu w shiny będziemy korzystać z funkcji fluidPage, w której tworzymy cały UI. Wszystkie informacje o rodzajach wprowadznych danych, strukturze wyświetlanych danych oraz szeroko rozumianej estetyce aplikacji będą zawarte wewnątrz tej funkcji. ui <- fluidPage( # coś ) 9.3 Układ strony Tym co jest bardzo ważne w UI jest oczywiście wygląd, a dokładniej mówiąc przejrzystość i czytelność, dlatego chcielibyśmy uporządkować wyświetlane elementy tak, aby umożliwić użytkownikowi intuicyjne korzystanie z aplikacji. Pakiet shiny oferuje wiele narzędzi pozwalających na zorganizowanie układu interfejsu zgodnie z naszymi oczekiwaniami. Przydadzą nam się do tego następujące funkcje: titlePanel - funkcja tworząca panel tytułowy, w której podajemy tytuł aplikacji, sidebarLayout - funkcja organizująca wygląd strony jako mniejszy panel boczny po lewej stronie oraz większy panel po prawej stronie, sidebarPanel - funkcja, którą możemy umieścić w poprzedniej funkcji, aby uporządkować panel, w którym będziemy np. wprowadzać dane, mainPanel - funkcja, w której umieszczamy treści, które chcemy, aby znalazły się w panelu głównym, tabsetPanel - funkcja umożliwiająca organizowanie paska zakładek. Aby utworzyć zakładki w jej ciele używamy funkcji tabPanel, w której umieszczamy dowolne treści, np. wykresy lub tabele. Oprócz tego możemy bardziej modyfikować wygląd aplikacji dzięki funkcjom fluidRow i column pozwalającym na uporządkowanie obiektów odpowiednio w wierszach oraz kolumnach. 9.4 Elementy wejścia i wyjścia Układ strony należy oczywiście podporządkować temu jaką funkcję ma pełnić aplikacja, a także temu jaki rodzaj interakcji ma mieć z nią docelowo użytkownik. Interakcje użytkownika z aplikacją można intuicyjnie podzielić na to co zostaje do aplikacji wprowadzone (input) oraz to co ostatecznie w związku z tym aplikacja zwraca (output). Każdy input i output jest w kodzie identyfikowany dzięki nadanej mu przez nas nazwie. Wewnątrz fluidPage możemy zawrzeć różne rodzaje inputów i outputów w zależności od rodzaju wprowadzanych/wyświetlanych danych. 9.4.0.1 Przykładowe elementy wejścia textInput - funkcja tworząca pole, w którym użytkownik może wprowadzić dowolny tekst, ui <- fluidPage( # Okienko do wpisywania tekstu textInput("nazwa_inputu_1", "Tekst wyświetlany w aplikacji") ) numericInput - funkcja tworząca pole, w którym użytkownik może wprowadzić wartość liczbową, ui <- fluidPage( # Okienko do wpisywania liczb numericInput("nazwa_inputu_2", "Tekst wyświetlany w aplikacji", # Wartość domyślna value = 10) ) selectInput - funkcja tworząca listę, z której użytkownik może dokonać wyboru - domyślnie parametr multiple umożliwia wybór jednej pozycji z listy, ui <- fluidPage( # Możliwość wybrania z listy selectInput("nazwa_inputu_3", "Tekst wyświetlany w aplikacji", # Lista możliwości do wyboru choices = c("Wybór_1", "Wybór_2")) ) sliderInput - funkcja tworząca suwak umożliwiający użytkownikowi wybór zakresu interesujących go wartości, ui <- fluidPage( # Suwak do wyboru wartości sliderInput("nazwa_inputu_4", "Tekst wyświetlany w aplikacji", # Wartość domyślna value = 1, # Wartość minimalna min = 0, # Wartość maksymalna max = 10) ) dateRangeInput - funkcja tworząca pole wyboru zakresu interesujących dat. ui <- fluidPage( # Pole wyboru zakresu dat dateRangeInput("nazwa_inputu_5", "Tekst wyświetlany w aplikacji", # Data początkowa start = "2001-01-01", # Data końcowa end = "2010-12-31") ) 9.4.0.2 Przykładowe elementy wyjścia Używanie funkcji wyświetlających outputy jest bardzo proste, ponieważ w UI decydujemy jedynie gdzie i jak wyswietlić output, który jest obiektem utworzonym wewnątrz funkcji server na podstawie wprowadzonego przez użytkownika inputu. textOutput - funkcja wyświetlająca tekst, ui <- fluidPage( # Wyświetla tekst, który stworzyliśmy w serwerze pod daną nazwą textOutput("nazwa_outputu_1") ) tableOutput - podstawowa funkcja wyświetlająca tabelę, ui <- fluidPage( # Wyświetla tabelę stworzoną w serwerze pod daną nazwą tableOutput("nazwa_outputu_2") ) DTOutput - funkcja wyświetlająca interaktywną ramkę danych z użyciem pakietu data.table, ui <- fluidPage( # Interaktywna ramka danych z użyciem data.table DT::DTOutput("nazwa_outputu_3") ) plotOutput - funkcja wyświetlająca wykres. ui <- fluidPage( # Wyświetla wykres stworzony w serwerze plotOutput("nazwa_outputu_4", # Szerokość wykresu width = "100%", # Wysokość wykresu height = "400px") ) 9.5 Przykład użycia Oczywiście powyższe kody były jedynie fragmentami większej całości. Poniżej możemy zobaczyć przykładowy kod obrazujący strukturę budowy interfejsu. Rzeczą, o której należy pamiętać jest oddzielanie funkcji przecinkami. ui <- fluidPage( # Tytuł titlePanel("Tytuł"), # To co będzie wyświetlone z boku interfejsu sidebarLayout( # Panel boczny sidebarPanel( # Pierwszy input - wybór selectInput("nazwa_inputu_1", "Tekst wyświetlany w aplikacji", choices = c("Wybór_1", "Wybór_2")), # Drugi input - suwak sliderInput("nazwa_inputu_2", "Tekst wyświetlany w aplikacji", value = 1, min = 0, max = 10) ), # Główny panel mainPanel( # Tworzymy zakładki tabsetPanel( # Pierwsza zakładka - wykres tabPanel("Tytuł wykresu", plotOutput("nazwa_outputu_1")), # Druga zakładka - ramka danych tabPanel("Tytuł ramki", DT::DTOutput("nazwa_outputu_2")) ) ) ) ) Dodatkowo warto zdawać sobie sprawę, że po wprowadzeniu danych przez użytkownika outputy aktualizują się automatycznie, dlatego często przydatne jest programowanie reaktywne z funkcją observeEvent oraz użycie actionButton, który pozwala na wykonanie danego działania dopiero po kliknięciu odpowiedniego przycisku przez użytkownika. 9.6 Wygląd aplikacji Ostatecznie chcielibyśmy, aby aplikacja wyglądała bardziej estetycznie. Możemy do tego użyć kilku narzędzi. Po pierwsze możemy zmienić motyw naszej aplikacji. Z pomocą przychodzi nam funkcja shinythemes::themeSelector(), którą musimy umieścić w naszym UI. Wtedy w naszej aplikacji pojawia się pole z możliwością wyboru motywu. Gdy już wybierzemy ulubiony motyw zamieniamy poprzednią funkcję w UI na theme = shinythemes::shinytheme('NASZ_MOTYW') i gotowe! Poza tym Shiny umożliwia całkowitą customizację wyglądu aplikacji przy użyciu HTML, CSS oraz JavaScript. Ostatnim narzędziem, o którym warto pamiętać, jest shinyWidgetsGallery(). Jest to bardzo użyteczna aplikacja stworzona w bibliotece shinyWidgets, dzięki której możemy między innymi zobaczyć w praktyce działanie różnego typu inputów oraz kod umożliwiający użycie ich w aplikacji. 9.6.1 Uwaga W tej notatce omówiliśmy podstawowe elementy pozwalające na zbudowanie interfejsu w shiny ale chcielibyśmy też dodać, że w poszukiwaniu bardziej zaawansowanych rozwiązań warto odwiedzić stronę https://shiny.rstudio.com/, gdzie można znaleźć dokumentację pakietu shiny, wiele przykładów oraz nieomówionych tu funkcji. "],["interaktywna-wizualizacja-danych-z-pakietem-shiny-strona-serwerowa.html", "10 Interaktywna wizualizacja danych z pakietem shiny: strona serwerowa 10.1 Wstęp 10.2 Serwer Shiny", " 10 Interaktywna wizualizacja danych z pakietem shiny: strona serwerowa 10.1 Wstęp Shiny to biblioteka w R pozwalająca na budowanie interaktywnych aplikacji w prosty i szybki sposób. Aplikacja Shiny składa się z dwóch części, opisywanych w dwóch osobnych plikach: interfejs użytkownika (UI), czyli jak aplikacja będzie wyglądać u użytkownika oraz sposób przetwarzania danych (serwer). W tej pracy zajmiemy się stroną serwerową Shiny. 10.2 Serwer Shiny Aplikacje Shiny zazwyczaj budujemy w sytuacjach, w których mamy dane, chcemy obliczyć pewne rzeczy i narysować odpowiednie wykresy. Użytkownik widzi efekt końcowy, czyli to jak zaprogramowaliśmy gdzie ma się wyświetlać wynik, natomiast w części serwerowej opisujemy jak ten wynik ma być obliczony. Jest to więc część zależna od pliku UI. Musimy więc w kodzie serwera zamieścić obiekty opisane w UI. Zauważmy, że tworzymy kod serwera jako funkcję od dwóch parametrów: input, output. W środku serwera definiujemy zależności pomiędzy inputami i outputami. Jedną z zalet Shiny jest interaktywność. Dzięki temu użytkownik może na bieżąco zmieniać parametry i generować nowe wykresy. Jednak generowanie kodu na nowo przy każdej zmianie danych nie zawsze jest pożądane. Ważnym pojęciem przy pisaniu strony serwerowej jest reaktywność (żródło infografiki: Shiny Cheat Sheet). reaktywnosc Jeśli zmienna jest reaktywna, to znaczy że jakakolwiek jej zmiana powoduje ponowne uruchomienie funkcji z nią powiązanych. Do budowania reaktywnych wyrażeń używamy funkcji reactive(). Taka zmienna jest liczona tylko raz i wyrażenia z nią związane używają tej wartości aż do momentu aktualizacji wybranego przez użytkownika. Z pojęciem reaktywności wiąże się kilka ważnych funkcji: reactiveValues(...), które tworzy listą reaktywnych zmiennych, isolate(expr) - zapobiega zależności od reaktywnych zmiennych, render*() - funkcje tworzące obiekty do wyświetlenia, które zmieniają się wraz z reaktywnymi zmiennymi, observeEvent(...) - gdy nie chcemy aby model od razu się zaktualizował przy zmianie danych, a przy jakiejś określonej akcji, reactive() - tworzy reaktywne wyrażenia eventReactive - tworzy reaktywne wyrażenia, które nie zależą od wszystkich reaktywnych zmiennych, a zależą jedynie od akcji wymienionych w pierwszym argumencie. "],["podstawy-kontroli-wersji-przy-pomocy-gita.html", "11 Podstawy kontroli wersji przy pomocy Gita 11.1 Podstawowe informacje 11.2 Podstawowe komendy 11.3 Repozytoria 11.4 Podstawowe komendy 11.5 Cofanie zmian 11.6 Gałęzie 11.7 Tworzenie własnych repozytoriów", " 11 Podstawy kontroli wersji przy pomocy Gita 11.1 Podstawowe informacje System kontroli wersji to narzędzie, które zarządza zmianami wprowadzanymi w plikach i katalogach w projekcie. Istnieje wiele systemów kontroli wersji. Przykładem takiego systemu jest Git. Jego mocne strony to: Nic, co jest zapisane w Git, nigdy nie jest tracone, więc zawsze możesz wrócić, aby zobaczyć, które wyniki zostały wygenerowane przez które wersje twoich programów. Git automatycznie powiadamia Cię, gdy Twoja praca koliduje z pracą innej osoby, więc jest trudniej (choć nie jest to niemożliwe) o przypadkowe nadpisanie pracy. Git może synchronizować pracę wykonywaną przez różne osoby na różnych komputerach. Kontrola wersji nie dotyczy tylko oprogramowania: książki, artykuły, zestawy parametrów i wszystko, co zmienia się w czasie lub wymaga udostępnienia, może i powinno być przechowywane i udostępniane za pomocą czegoś takiego jak Git. Każdy z projektów Git składa się z dwóch części: plików i katalogów, które tworzysz i edytujesz bezpośrednio, oraz dodatkowych informacji, które Git rejestruje o historii projektu. Połączenie tych dwóch rzeczy nazywa się repozytorium. Git przechowuje wszystkie dodatkowe informacje w katalogu o nazwie \\(\\texttt{.git}\\) znajdującym się w katalogu głównym repozytorium. 11.2 Podstawowe komendy Używając Gita zapewne często będziemy chcieli sprawdzić stan swojego repozytorium. Aby to zrobić, użyjemy polecenie \\(\\texttt{git status}\\). \\(\\texttt{git status}\\) - wyświetla listę plików, które zostały zmodyfikowane od czasu ostatniego zapisania zmian Git ma obszar przejściowy, w którym przechowuje pliki ze zmianami, które chcemy zapisać, a które nie zostały jeszcze zapisane. \\(\\texttt{git status}\\) - pokazuje, które pliki znajdują się w tym obszarze przejściowy i które mają zmiany, które nie zostały jeszcze zatwierdzone \\(\\texttt{git diff}\\) - pokaże wszystkie zmiany w twoim repozytorium (porównując obecną postać plików z ostatnio zapisaną) \\(\\texttt{git diff directory}\\) - pokaże zmiany w plikach w jakimś katalogu (porównując obecną postać plików z ostatnio zapisaną) \\(\\texttt{git diff filename}\\) - pokaże zmiany w danym pliku (porównując obecną postać z ostatnio zapisaną) Git różnice między dwiema wersjami pliku wyświetla w poniższy sposób: diff --git a/report.txt b/report.txt index e713b17..4c0742a 100644 --- a/report.txt +++ b/report.txt @@ -1,4 +1,5 @@ -# Seasonal Dental Surgeries 2017-18 +# Seasonal Dental Surgeries (2017) 2017-18 +# TODO: write new summary gdzie: \\(\\texttt{a/report.txt, b/report.txt}\\) to pierwsza i druga wersja pliku, linia druga wypisuje klucze do wewnętrznej bazy danych zmian Gita, \\(\\texttt{--- a/report.txt, +++ b/report.txt}\\) oznacza, że usuwane linie oznaczone są przedrostkiem \\(\\texttt{-}\\), dodawane linie oznaczone są przedrostkiem \\(\\texttt{+}\\), linia zaczynająca się od \\(\\texttt{@@}\\) mówi, gdzie wprowadzane są zmiany. Pary liczb to numer lini ,,startowej’’ i liczba linii, kolejne linie są listą zmian, które zostały wprowadzone. \\(\\texttt{git add filename}\\) - dodaje plik do obszaru przejściowego \\(\\texttt{git diff -r HEAD}\\) - porówna pliki z repozytorium z plikami z obszaru przejściowego \\(\\texttt{git diff -r HEAD path/to/file}\\) - porówna konkretny plik z repozytorium z plikiem z obszaru przejściowego \\(\\texttt{nano filename}\\) - otwiera plik w edytorze tekstowym \\(\\texttt{nano}\\) poruszanie się strzałkami \\(\\texttt{Backspace}\\) - usuń znak \\(\\texttt{Ctrl-K}\\): usuń linię \\(\\texttt{Ctrl-U}\\): cofnij usunięcie linii \\(\\texttt{Ctrl-O}\\): zapisz plik \\(\\texttt{Ctrl-X}\\): wyjdź z edytora \\(\\texttt{git commit -m "comment"}\\) - zapisuje zmiany w obszarze przejściowym z jednowierszowym komunikatem o wprowadzonych zmianach \\(\\texttt{git commit --amend - m "new message"}\\) - zmienia ostatni komunikat \\(\\texttt{git log}\\) - wyświetlenie historii projektu (od najnowszych zmian). Wyświetlany zostaje unikatowy identyfikator dla zatwierdzenia oraz informacje na temat tego kto dokonał zmiany, kiedy i jaki komunikat napisał dokonując zmiany. \\(\\texttt{spacja}\\) - przejcie w dół o stronę \\(\\texttt{q}\\) - wyjście \\(\\texttt{git log path}\\) - wyświetlenie historii danego pliku lub katalogu 11.3 Repozytoria    Informacje dotyczące zatwiedzonych zmian przechowywane są poprzez trzypoziomową strukturę. Każde zatwierdzenie (tzw. commit) zwiera komunikat o zatwierdzeniu i informacje o autorze i czasie, w którym zatwierdzenie zmian zostało wykonane. Każdy commit ma również swoje drzewo, które śledzi, gdzie w repozytorium dokonano zmian. Dla każdego pliku w drzewie istnieje tzw. blob (binary large object). Każdy blob zawiera skompresowaną migawkę zawartości pliku, z chwili w której nastąpił commit. Czym jest hash? Każde zatwierdzenie zmian w repozytorium ma unikalny identyfikator zwany hashem. Jest on zapisywany jako 40-znakowy ciąg szesnastkowy. Zazwyczaj jednak wystarczy podać pierwsze 6 lub 8 znaków hasha, by odnaleźć konkretne zatwierdzenie (commit). Identyfikatory jakimi są hashe umożliwiają Gitowi wydajne udostępnianie danych pomiędzy repozytoriami. Jak wyświetlić konkretny commit? By wyświetlić szczegóły dotyczące konkretnego commitu należy użyć komendy git show z pierwszymi 6 znakami hasha danego commmitu np.: git show Oda2f7. Czym jest odpowiednik ścieżki względnej w Git? Innym sposobem identyfikacji zatwierdzenia jest użycie odpowiednika ściezki względnej. By wyświetlić zatem ostatni commit możemy użyć komendy git show z etykietą HEAD. Jeśli natomiast zamiast HEAD wpiszemy HEAD~1 wyświetlony zostanie przedostatni commit, polecenie git show HEAD~2 zwróci nam natomiast jeszcze wcześniejszy commit itp. 11.4 Podstawowe komendy git log - wyświetla całą historię danego pliku lub projektu. W Gicie możemy jednak sprawdzić bardziej szczegółowe informacje. Dzięki poleceniu git annotate file możemy sprawdzić kto i kiedy dokonał ostatniej zmiany w każdej linijce pliku. git diff ID1..ID2 - umożliwia sprawdzenie zmian pomiędzy dwoma commitami, których identyfikatory to odpowiednio ID1 i ID2. git add - polecenie umożliwiające dodanie nowego pliku. Po wykonaniu tego polecenia Git zaczyna śledzić dodany plik. git clean -n - pokazuje listę plików, które są w repozytorium, ale których historia nie jest śledzona przez Gita. git clean -f - usuwa pliki, które są w repozytorium i których historii nie śledzi Git. Z używaniem tego polecenia należy uważać, ponieważ usuwa ono pliki z pamięci na stałe i nie da się ich już odzyskać. git config - -list - wyświetla ustawienia Gita. git config - -system - wyświetla ustawienia każdego użytkownika na danym komputerze. git config - -global - wyświetla ustawienia każdego projektu. git config - -local - wyświetla ustawienia poszczególnego projektu. Każdy poziom zastępuje poziom nad nim, więc ustawienia lokalne (na projekt) mają pierwszeństwo przed ustawieniami globalnymi (na użytkownika), które z kolei mają pierwszeństwo przed ustawieniami systemowymi (dla wszystkich użytkowników na komputerze). git config - -global setting value - zmienia konfigurację odpowiedniej wartości dla wszystkich projektów na danym komputerze. Jako setting należy wpisać to co chcemy zmienić (np. user.name, user.email itp.), a jako value to co chcemy ustawić. 11.5 Cofanie zmian Teraz dowiemy się jak cofnąć wprowadzone zmiany. \\(\\texttt{git reset HEAD}\\)- usuwa ostatnio dodany plik ze śledzenia, \\(\\texttt{git checkout -- filename}\\) - odrzuci zmiany, które nie zostały jeszcze dodane do śledzenia, \\(\\texttt{git reset HEAD path/to/file}\\) - odrzuci ostatnie zmiany w pliku, który został juz dodany do śledzenia, \\(\\texttt{git checkout 2242bd filename}\\)- zamienia aktualna wersje pliku, na tę o hashu ‘2242bd’. Do ostatniej komendy przydatne może być wykonanie poniższzego polecenia, aby sprawdzić hashe plików. \\(\\texttt{git log - 3 filename}\\)- pokaże 3 ostatnie commity dotyczące wskazanego pliku. Poniższe dwie komendy pokazują, jak cofać zmiany na więcej niż jednym pliku. \\(\\texttt{git reset HEAD data}\\)- usuwa ze śledzenia wszystkie pliki z katalogu data. Jeżeli nie podamy nazwy katalogu( wtedy wystarczy samo \\(\\texttt{git reset}\\)) wszystkie pliki zostaną usunięte. \\(\\texttt{git checkout -- data}\\)- wszystkie pliki w katalagu data zostaną cofnięte do poprzednich wersji. 11.6 Gałęzie Jeśli nie używasz kontroli wersji, typowym przepływem pracy jest tworzenie różnych podkatalogów do przechowywania różnych wersji projektu w różnych stanach, na przykład deweloperskich i końcowych. Oczywiście zawsze kończy się to ostateczną aktualizacją i ostateczną aktualizacją-poprawioną. Problem polega na tym, że trudno jest to rozwiązać, jeśli masz odpowiednią wersję każdego pliku w odpowiednim podkatalogu i ryzykujesz utratę pracy.     Jednym z powodów, dla których Git jest popularny, jest jego obsługa tworzenia gałęzi (branchy), co pozwala na posiadanie wielu wersji Twojej pracy i pozwala na systematyczne śledzenie każdej wersji.     Każda gałąź jest jak wszechświat równoległy: zmiany, które wprowadzasz w jednej gałęzi, nie wpływają na inne gałęzie (dopóki nie połączysz ich z powrotem).     Domyślnie kazde repozytorium Gita ma branch zwany master.     Podstawowe komendy związanie z działaniem na branchach (gałęziach):   \\(\\texttt{git branch}\\) - pokazuje wszystkie branche w repozytorium (branch, w którym obecnie się znajdujesz będziesz wylistowany z \\(*\\)).   \\(\\texttt{git diff branch1..branch2}\\) - wyświetla różnice między dwoma branchami   Ciekawostka: \\(\\texttt{git diff branch1..branch2}\\) - -\\(\\texttt{shortstat}\\) - wyświetla konkretną liczbę plików które się różnią między dwoma branchami  \\(\\texttt{git checkout branch1}\\) - pozwala przełączyć się na branch1  \\(\\texttt{git checkout -b branch-name}\\) - pozwala utworzyć nowego brancha o nazwie branch-name    Rozgałęzianie pozwala tworzyć równoległe wszechświaty. Scalanie (merging) to sposób, w jaki łączysz je z powrotem. Kiedy łączysz jedną gałąź (nazwijmy ją źródłową) z inną (nazwijmy ją docelową), Git włącza zmiany wprowadzone w gałęzi źródłowej do gałęzi docelowej. Jeśli te zmiany nie nakładają się, wynikiem jest nowe zatwierdzenie w gałęzi docelowej, które zawiera wszystko z gałęzi źródłowej. Do mergowania dwóch gałęzi używamy polecenia:   \\(\\texttt{git merge source destination}\\) - mergowanie dwóch branchy w jeden  Czasami zmiany w dwóch gałęziach będą ze sobą kolidować: na przykład poprawki błędów mogą dotyczyć tych samych wierszy kodu lub analizy w dwóch różnych gałęziach mogą dołączać nowe (i różne) rekordy do pliku danych podsumowania. W takim przypadku ty decydujesz o sprzeczności zmian.    Jeżeli podczas mergowania występuje konflikt Git informuje Cię, że wystapił problem a \\(\\texttt{git status}\\) poinformuje Cię, które pliki wmagają rozwiązania konfliktów.    Git pozostawia na danym pliku znaczniki, aby poinformować Cię o konkretnym miejscu konfliktu. Znaczniki te wyglądają następująco: <<<<<<< destination-branch-name ...changes from the destination branch... ======= ...changes from the source branch... >>>>>>> source-branch-name   Aby rozwiązać konflikt edytuj plik, usuwając znaczniki i wprowadź wszelkie zmiany potrzbne do rozwiązania kofilktu, a następnie zrób commit tych zmian.    11.7 Tworzenie własnych repozytoriów Przejdźmy do kolejnego zagadnienia związanego z pracą w Gicie.     Do tej pory wszystkie poznane funkcje Gita dotyczyły działań na repozytoriach już istniejących. Aby stworzyć własne repozytorium w bieżącym katalogu roboczym wystarczy komenda:  \\(\\texttt{git init project-name}\\)   Warto wspomnieć, że chociaż Git pozwala tworzyć zagnieżdżone repozytoria nie powinieneś tego robić. Aktualizacja takich repozytoriów bardzo szybko staje się bardzo skomplikowana, ponieważ musisz powiedzieć Gitowi, w którym z dwóch katalogów .git ma być przechowywana aktualizacja.    Nie tworzymy repozytorium w innym już istniejącym!     Poniżej kilka ważnych komend:  \\(\\texttt{git init}\\) - inicjalizacja repozytorium w bieżącym katalogu   \\(\\texttt{git init /path/to/project}\\) - inicjalizacja repozytorium we wskazanym ścieżką katalogu   \\(\\texttt{git clone URL}\\) - tworzenie kopii istniejącego pod wskazanym adresem URL repozytorium \\(\\texttt{git clone /existing/project newprojectname}\\) - tworzenie kopii istniejącego repozytroium o zadanej nazwie - newprojectname   \\(\\texttt{git remote}\\) - wyświetla informację o fizycznej lokalizacji na serwerze Gita, z której zostało sklonowane repo   \\(\\texttt{git remote -v}\\) - wyświetla informację o URL serwerze Gita, z którego zostało sklonowane repo   \\(\\texttt{git remote add remote-name URL}\\) - pozawala na dodanie własnego remota z podanego URL \\(\\texttt{git remote rm remote-name}\\) - usuwanie istniejącego remota   \\(\\texttt{git pull remote branch}\\) - pobieranie zmian w branchu w lokalnym repozytorium i mergowanie ich z bieżacym brnachem w lokalnym repozytorium   Uwaga!   Git powstrzymuje Cię przed pobieraniem ze zdalnego repozytorium zmian, które mogą nadpisać niezapisane lokalnie zmiany. Wystarczy zrobić commit tych zmian lub cofnąć je, a następnie spullować repo ponownie.   \\(\\texttt{git push remote-name branch-name}\\) - pushuje zmiany wprowadzone lokalnie na danym branchu do zdalnego repozytorium   "],["programowanie-obiektowe-w-r-klasy-s3.html", "12 Programowanie obiektowe w R: klasy S3 12.1 Systemy programowania obiektowego w R 12.2 S3 12.3 S4", " 12 Programowanie obiektowe w R: klasy S3 Programując w R jesteśmy oswojeni z myśleniem kategoriami funkcji - przekształceń nakładanych na macierze lub ramki danych. Jest to naturalne ze względu na zastosowanie R głównie w statystyce i pochodnych jej dziedzin. Tymczasem programowanie obiektowe, choć często niepotrzebne do przeprowadzenia analiz lub symulacji, może się okazać użyteczne przy tworzeniu większego projektu, w szczególności projektu współtworzonego przez więcej osób. Zdefiniowanie klas i metod nadaje projektowi strukturę, co sprawia, że jego rozbudowa przebiega mniej chaotycznie. Ponadto, znajomość podstaw systemów programowania obiektowego w R umożliwia nam lepsze zrozumienie działania bazowych funkcji i obiektów R oraz ewentualne ich rozbudowywanie. 12.1 Systemy programowania obiektowego w R W przeciwieństwie do wielu popularnych języków programowania, R nie ma jednego ujednoliconego systemu programowania obiektowego - jest ich wiele, przy czym różnią się nie tylko składnią, ale też funkcjonalnościami. Pierwsze wersje pierwowzoru języka R - języka S nie posiadały żadnego systemu obiektowego. Wraz z trzecią wersją S wprowadzono pierwszy z nich: S3. Następnie, kiedy ten okazał się niewystarczający dla potrzeb użytkowników - S4. Oba systemy finalnie znalazły się w base języka R. Z czasem, w miarę wzrastania potrzeb, powstawały kolejne alternatywne systemy klas, które funkcjonowały równolegle i równoprawnie. Do dzisiaj nie wyróżniamy systemu “oficjalnego” czy preferowanego - każdy z kilku pozostałych w powszechnym użyciu ma swoje zastosowania, w których niekorzystnym lub niewygodnym jest zastąpienie go innym. W tej notatce przyjrzymy się przede wszystkim S3. 12.2 S3 S3 to system, z którym stykamy się najczęśniej. Wszystkie wbudowane klasy obiektów zostały zbudowane właśnie przy pomocy systemu S3. By sprawdzić, do jakiej klasy S3 należy obiekt, używamy funkcji class. W codziennej pracy w R operujemy w wiekszości na obiektach zbudowanych w S3. Klasy S3 to m.in. factor,data.frame,matrix. f <- factor(c("y","n","y","n","n")) class(f) ## [1] "factor" Warto w tym miejscu podkreślić, że klasa zmiennej nie jest równoważna typowi zmiennej, np. macierz liczb jest klasy matrix, ale typu double. m <- matrix(c(1,2,3,4),2,2) class(m) ## [1] "matrix" "array" typeof(m) ## [1] "double" Każdemu obiektowi mogą być (ale nie muszą) przypisane atrybuty. Atrybuty mozna rozumieć jako cechy lub parametry obiektu. W przypadku macierzy są to jej wymiary. attributes(m) ## $dim ## [1] 2 2 12.2.1 Klasy i atrybuty W systemie S3 nie tworzymy definicji klasy, nie określamy również, jakie atrybuty obiekt danej klasy ma. Obiektowi możemy nadać klasę przy jego utworzeniu, z użyciem funkcji structure: kanapka <-structure(c("szynka", "margaryna", "chleb"), class = "jedzenie") class(kanapka) ## [1] "jedzenie" lub w dowolnym momencie po jego utworzeniu z użyciem class: szarlotka <- c("jaja", "mąka", "masło", "cukier") szarlotka <- c(szarlotka, "jabłka") class(szarlotka) <- "jedzenie" class(szarlotka) ## [1] "jedzenie" Każdemu obiektowi możemy również indywidualnie przypisać atrybuty, również na kilka sposobów, przy jego utworzeniu z użyciem structure: hot_dog <- structure(c("parówka", "bułka", "ketchup"), class="jedzenie", kalorie = 300) attributes(hot_dog) ## $class ## [1] "jedzenie" ## ## $kalorie ## [1] 300 class(hot_dog) ## [1] "jedzenie" Lub w dowolnym momencie z użyciem funkcji attr: attr(kanapka, "kalorie")=150 attr(szarlotka, "kalorie")=265 attributes(kanapka) ## $class ## [1] "jedzenie" ## ## $kalorie ## [1] 150 attributes(szarlotka) ## $class ## [1] "jedzenie" ## ## $kalorie ## [1] 265 Z użyciem funkcji attributes i attr można również “dostać się” do wartości atrybutów obiektu: attr(szarlotka, "kalorie") ## [1] 265 attributes(szarlotka)$kalorie ## [1] 265 Tworzenie obiektów różnych klas S3 jest więc bardzo proste i nie wymaga (przynajmniej formalnie) predefiniowania klasy i atrybutów. System jest więc z jednej strony bardzo elastyczny, z drugiej - nieprecyzyjny. Niesie to za sobą pewne konsekwencje, np. formalnie nic nie stoi na przeszkodzie by zrobić coś takiego: droga <- c("asfalt","pobocze", "lewy pas", "prawy pas") class(droga) <- "jedzenie" class(droga) ## [1] "jedzenie" lub takiego… średnia_bez_na <- function(...) mean(na.rm=TRUE,...) class(średnia_bez_na)<-"jedzenie" class(średnia_bez_na) ## [1] "jedzenie" lub takiego: attr(hot_dog, "kalorie") <- "przecież to prawie nie ma kalorii!" bilans_posilkow <- attr(hot_dog,"kalorie")+attr(szarlotka, "kalorie")+attr(kanapka, "kalorie") ## Error in attr(hot_dog, "kalorie") + attr(szarlotka, "kalorie"): argument nieliczbowy przekazany do operatora dwuargumentowego Dlatego należy pamiętać, by klas i atrybutów nie przydzielać chaotycznie, zachować pewne reguły, mimo że formalnie nie są wymagane przy użyciu S3. 12.2.2 Funkcje generyczne i metody Metody to funkcje działające na obiektach danej klasy. Z reguły są predefiniowane przy utworzeniu klasy wraz z polami. Inaczej jednak jest z systemem S3 w R. Nie definiujemy klasy - klasa jest tworzona przy pierwszym przypisaniu jej jakiemuś obiektowi. Metody tworzy się przy pomocy funkcji generycznych (generics). 12.2.2.1 Funkcje generyczne By lepiej zrozumieć logikę stojącą za funkcjami generycznymi, spróbujmy spojrzeć na klasy i ich metody z nieco mniej standardowej perspektywy. Dla różnych klas możemy mieć analogiczne metody, zachowujące się nieco inaczej w zależności od specyfiki klasy, np. inaczej rozumiemy różnicę między dwoma datami a różnicę między dwoma liczbami - liczby odejmujemy od siebie bezpośrednio, podczas gdy w przypadku dat oczekujemy różnicy w dniach pomiędzy nimi - w tym celu nie wystarczy bezpośrednie odjęcie od siebie dwóch dat. W systemie S3 metody nie są przypisane bezpośrednio klasie, są przypisane odpowiedniej funkcji generycznej. Funkcja generyczna określa nazwę metody wspólną dla wszystkich klas i umożliwia tworzenie wariantów metody dla różnych klas pod tą konkretną nazwą. Zanim przejdziemy do tworzenia funkcji generycznych oraz metod dla własnych klas przyjrzyjmy się działaniu już istniejących. Jedną z funkcji generycznych jest funkcja summary - funkcja podsumowująca obiekt (np. summary(lm(X~Y))). summary ## function (object, ...) ## UseMethod("summary") ## <bytecode: 0x55f2bebc3790> ## <environment: namespace:base> Użyjemy funkcji methods, by wylistować wszystkie dostępne metody dla danej funkcji generycznej. methods(summary) ## [1] summary,ANY-method summary,DBIObject-method summary,diagonalMatrix-method ## [4] summary,sparseMatrix-method summary.aov summary.aovlist* ## [7] summary.aspell* summary.check_packages_in_dir* summary.connection ## [10] summary.corAR1* summary.corARMA* summary.corCAR1* ## [13] summary.corCompSymm* summary.corExp* summary.corGaus* ## [16] summary.corIdent* summary.corLin* summary.corNatural* ## [19] summary.corRatio* summary.corSpher* summary.corStruct* ## [22] summary.corSymm* summary.data.frame summary.Date ## [25] summary.default summary.Duration* summary.ecdf* ## [28] summary.factor summary.gam* summary.ggplot* ## [31] summary.glm summary.gls* summary.haven_labelled* ## [34] summary.hcl_palettes* summary.infl* summary.Interval* ## [37] summary.lm summary.lme* summary.lmList* ## [40] summary.loess* summary.manova summary.matrix ## [43] summary.mlm* summary.modelStruct* summary.nls* ## [46] summary.nlsList* summary.packageStatus* summary.pdBlocked* ## [49] summary.pdCompSymm* summary.pdDiag* summary.pdIdent* ## [52] summary.pdIdnot* summary.pdLogChol* summary.pdMat* ## [55] summary.pdNatural* summary.pdSymm* summary.pdTens* ## [58] summary.Period* summary.POSIXct summary.POSIXlt ## [61] summary.ppr* summary.prcomp* summary.princomp* ## [64] summary.proc_time summary.reStruct* summary.rlang_error* ## [67] summary.rlang_message* summary.rlang_trace* summary.rlang_warning* ## [70] summary.rlang:::list_of_conditions* summary.shingle* summary.srcfile ## [73] summary.srcref summary.stepfun summary.stl* ## [76] summary.table summary.trellis* summary.tukeysmooth* ## [79] summary.varComb* summary.varConstPower* summary.varConstProp* ## [82] summary.varExp* summary.varFixed* summary.varFunc* ## [85] summary.varIdent* summary.varPower* summary.vctrs_sclr* ## [88] summary.vctrs_vctr* summary.warnings ## see '?methods' for accessing help and source code Każda z wypisanych nazw odpowiada wariantowi metody dla jednej klasy. Zwrócmy uwagę na specyficzną składnię nazw tych funkcji - człon po kropce odpowiada nazwie klasy, jakiej metoda dotyczy. Przyjrzyjmy się wariantom summary dla dwóch różnych klas: lm i matrix. X <- matrix(rep(1,12), 6,2) Y <- c(2,2,3,2,2,2) model <- lm(Y~X) summary.lm(model) ## ## Call: ## lm(formula = Y ~ X) ## ## Residuals: ## 1 2 3 4 5 6 ## -0.1667 -0.1667 0.8333 -0.1667 -0.1667 -0.1667 ## ## Coefficients: (2 not defined because of singularities) ## Estimate Std. Error t value Pr(>|t|) ## (Intercept) 2.1667 0.1667 13 4.8e-05 *** ## X1 NA NA NA NA ## X2 NA NA NA NA ## --- ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 ## ## Residual standard error: 0.4082 on 5 degrees of freedom summary.matrix(X) ## V1 V2 ## Min. :1 Min. :1 ## 1st Qu.:1 1st Qu.:1 ## Median :1 Median :1 ## Mean :1 Mean :1 ## 3rd Qu.:1 3rd Qu.:1 ## Max. :1 Max. :1 Jednak, by użyć odpowiedniej funkcji dla obiektu, nie musimy specyfikować jego klasy - właśnie dzięki zdefiniowaniu funkcji generycznej. Bez względu na klasę obiektu uzywamy składni funkcja_generyczna(obiekt). Wywoływana jest wówczas funkcja generyczna, która na podstawie klasy lub typu obiektu dopasowuje wariant metody. Spójrzmy jak to wygląda na przykładzie summary: summary(model) ## ## Call: ## lm(formula = Y ~ X) ## ## Residuals: ## 1 2 3 4 5 6 ## -0.1667 -0.1667 0.8333 -0.1667 -0.1667 -0.1667 ## ## Coefficients: (2 not defined because of singularities) ## Estimate Std. Error t value Pr(>|t|) ## (Intercept) 2.1667 0.1667 13 4.8e-05 *** ## X1 NA NA NA NA ## X2 NA NA NA NA ## --- ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 ## ## Residual standard error: 0.4082 on 5 degrees of freedom summary(X) ## V1 V2 ## Min. :1 Min. :1 ## 1st Qu.:1 1st Qu.:1 ## Median :1 Median :1 ## Mean :1 Mean :1 ## 3rd Qu.:1 3rd Qu.:1 ## Max. :1 Max. :1 Jak widać na przykładzie powyżej wywołanie funkcji generycznej na obiekcie spowodowało dopasowanie odpowiedniej dla klasy obiektu metody i dało identyczny efekt jak wywołanie bezpośrednio dedykowanej funkcji. 12.2.2.2 Tworzenie funkcji generycznych i metod Nowe funkcje generyczne tworzy się według następującego schematu: nazwa_metody <- function (x) { UseMethod("nazwa_metody", x) } Po utworzeniu funkcji generycznej możemy przystąpić do napisania metod dla konkretnych klas. Metody tworzymy jako funkcje nazwane według konwencji nazwa_metody.klasa. Spróbujmy wg powyższego schematu utworzyć metodę dla utworzonej wcześniej klasy jedzenie. Niech nasza metoda nazywa się zjedz: zjedz <- function(x){ UseMethod("zjedz",x) } Mając gotową funkcję generyczną możemy przejść do określenia zachowania metody dla naszej klasy: zjedz.jedzenie <- function(x){ cat("Mniam mniam\\n") } zjedz(szarlotka) ## Mniam mniam zjedz(kanapka) ## Mniam mniam zjedz(hot_dog) ## Mniam mniam Oraz, jeśli potrzebujemy, również dla innych istniejących klas, w tym również wbudowanych, np. matrix. zjedz.matrix <- function(x){ warning("Przeciez to macierz! Tego sie nie je!\\n") } zjedz(matrix(c(1,2,2,3),2,2)) ## Warning in zjedz.matrix(matrix(c(1, 2, 2, 3), 2, 2)): Przeciez to macierz! Tego sie nie je! W szczególności możemy określić zachowanie metody dla pseudoklasy default, czyli określić domyślne zachowanie metody. W kontekście naszego przykładu z klasą jedzenie moglibyśmy oczekiwać, że nasza metoda domyślnie nie będzie “jeść” żadnych obiektów, robiąc wyjątek wyłącznie dla jedzenia. zjedz.default <- function(x,...){ warning("Tego sie nie je!\\n") } zjedz(mean) ## Warning in zjedz.default(mean): Tego sie nie je! zjedz(c(1,2,3,4)) ## Warning in zjedz.default(c(1, 2, 3, 4)): Tego sie nie je! Zauważmy, że istnieje tu pewna hierarchia: zjedz(matrix(c(1,2,3,4,4,5),2,3)) ## Warning in zjedz.matrix(matrix(c(1, 2, 3, 4, 4, 5), 2, 3)): Przeciez to macierz! Tego sie nie je! zjedz(szarlotka) ## Mniam mniam Funkcja generyczna wywołuje metodę dla pseudoklasy default wtedy, gdy dla danej klasy indywidualnie nie ma zdefiniowanej metody. W pierwszej kolejności szuka metody dedykowanej dla danej klasy, dlatego dla obiektów klas jedzenie i matrix zostały wywołane odpowiednio zjedz.jedzenie i zjedz.matrix, a nie zjedz.default. 12.2.3 Dziedziczenie O dziedziczeniu mówimy, gdy jedna z klas przejmuje (dziedziczy) właściwości i cechy innej. W S3 dziedziczenie wprowadza się w zaskakująco oczywisty i prosty sposób. Każdemu obiektowi można przypisać więcej niż jedną klasę, przypisując class(obiekt) nie jedną nazwę klasy, lecz ich wektor, np. dla utworzonego wcześniej obiektu szarlotka: class(szarlotka) <- c("ciasto", "slodkosci", "jedzenie") class(szarlotka) ## [1] "ciasto" "slodkosci" "jedzenie" Kolejność klas w wektorze nie jest przypadkowa: zaczynamy od klasy “najmłodszej” (i najważniejszej, najbardziej specyficznej), a kończymy na “najstarszej” (najogólniejszej). Kolejność ta jest również obowiązująca przy dopasowywaniu metod przez funkcję generyczną: funkcja generyczna najpierw szuka metody dla pierwszej z klas w wektorze, następnie dla drugiej itd. Jeśli nie uda jej się znaleźć metody dla żadnej z klas, wywołuje metodę dla pseudoklasy default. zjedz.ciasto <- function(x){ cat("Mniam mniam, pyszne ciacho!\\n") } zjedz.slodkosci <- function(x){ cat("Słodkie, dobre, mniam.. \\n") } zjedz(szarlotka) ## Mniam mniam, pyszne ciacho! Zgodnie z oczekiwaniami funkcja generyczna wywołała metodę dla pierwszej z klas w wektorze, czyli klasy ciasto, ignorując metody dla klas slodkosci i jedzenie. W większości przypadków jednak wprowadzamy nowe klasy nie po to, by zastępować istniejące metody dla klas ogólniejszych, lecz po to by wprowadzić pewne rozszerzenia, np. metody czy atrybuty, które nie mają sensu dla innych obiektów z klasy - rodzica. Wprowadźmy metodę pokroj. Dla każdego obiektu klasy jedzenie bedzie ona dzialac identycznie - dzielić atrybut kalorie przez wskazane n i zwracać n równych “porcji” : pokroj<-function(x,...){ UseMethod("pokroj",x) } pokroj.jedzenie <- function(x,n){ porcja<-attr(x, "kalorie")/n rep(porcja,n) } pokroj(kanapka,4) ## [1] 37.5 37.5 37.5 37.5 Oprócz tego wprowadźmy metodę sensowną tylko dla klasy ciasto: posyp_cukrem_pudrem. posyp_cukrem_pudrem<-function(x,...){ UseMethod("posyp_cukrem_pudrem",x) } posyp_cukrem_pudrem.ciasto <- function(x){ cat("Syp syp syp\\n") } Zauważmy, że przez wprowadzenie dodatkowych klas obiekt szarlotka nie traci funkcjonalności klasy jedzenie: pokroj(szarlotka,5) ## [1] 53 53 53 53 53 Podczas gdy równocześnie możliwym stało się zdefiniowanie dla niego indywidualnych metod: posyp_cukrem_pudrem(szarlotka) ## Syp syp syp posyp_cukrem_pudrem(hot_dog) ## Error in UseMethod("posyp_cukrem_pudrem", x): niestosowalna metoda dla 'posyp_cukrem_pudrem' zastosowana do obiektu klasy "jedzenie" Dziedziczenie może być bardzo użyteczne, ale należy je stosować z ostrożnością, mając w pamięci elastyczność klas i atrybutów w S3. Bardzo łatwo stracić orientację, w szczególności wprowadzając dziedziczenie po klasach wbudowanych lub po klasach zbudowanych przez kogoś innego - wówczas ciężko nam wziąć pod uwagę wszystkie istniejące dla danych klas metody. Posługując się dziedziczeniem i klasami w sposób rozrzutny i nieprzemyślany łatwo możemy doprowadzić do chaosu. 12.2.3.1 NextMethod NextMethod jest używane w sytuacji, gdy wewnątrz metody klasy chcemy wywołać metodę klasy nadrzędnej (rodzica). zjedz.ciasto<- function(x){ cat("Mniam mniam, pyszne ciacho!\\n") NextMethod() } zjedz(szarlotka) ## Mniam mniam, pyszne ciacho! ## Słodkie, dobre, mniam.. zjedz.slodkosci <- function(x){ cat("Słodkie, dobre, mniam.. \\n") NextMethod() } zjedz(szarlotka) ## Mniam mniam, pyszne ciacho! ## Słodkie, dobre, mniam.. ## Mniam mniam 12.3 S4 S3 jest użyteczny i do niektórych zastosowań wystarczający, ale nie posiada wielu własności znanych z systemów programowania w innych językach. Ponadto, ze względu na dużą swobodę w tworzeniu klas i metod, utrzymanie bardziej złożonych struktur i hierarchii może być uciążliwe i mało przejrzyste z użyciem S3. Pierwszą alternatywą dla S3 był system S4. W S4 metody tworzone z użyciem tej samej logiki - przez funckje generyczne. W przeciwieństie do S3, system S4 wymaga zdefiniowania klasy, w szczególności jej pól (slotów) i dziedziczenia po innych klasach. Klasę definiuje się z użyciem funkcji setClass: # pierwszym argumentem funkcji jest nazwa klasy setClass("nazwa_klasy", slots = c( # tutaj definiowane są sloty i ich typ slot_1 = "data.frame", slot_2 = "list" ), prototype = c( # tutaj definiowane są wartości domyślne (prototyp) dla slotów slot_1 = data.frame(), slot_2 = list() ) ) Funkcja setClass posiada również parametr contains, który odpowiada za dziedziczenie po innych klasach: setClass("klasa_rodzic", slots=c( macierz = "matrix" )) setClass("klasa_dziecko", contains="klasa_rodzic") Funkcje generyczne z użyciem których tworzymy metody S4 są rozróżniane od funkcji generycznych systemu S3. Mechanizm tworzenia metody jest więc bardzo podobny, ale używamy do tego celu dedykowanych dla S4 funkcji. # tworzenie funkcji generycznej S4 setGeneric("nazwa_metody", function(x, ...) standardGeneric("nazwa_metody")) ## [1] "nazwa_metody" # tworzenie metody dla klasy setMethod("nazwa_metody", "nazwa_klasy", function(x,...){ # działanie metody na obiekcie klasy }) 12.3.1 Bonus: przykład wykorzystania systemu S4 S4, choć bardziej restrykcyjny niż S3, nadal daje dużo swobody w przypisywaniu klas, dziedziczeniu, w szczególności w manewrowaniu wbudowanymi klasami R. Wykorzystaliśmy to w naszym raczkującym pakiecie autoeda do ominięcia problemu przypisania różnego zachowania funkcji w zależności od otrzymanego typu danych. Celem było obliczenie tej samej funkcji (np. średniej) dla wszystkich kolumn danych, przy założeniu, że nasz zbiór danych jest średniej wielkości (kilkadziesiąt kolumn - zmiennych). Jeśli niemożliwe jest obliczenie funkcji dla danej kolumny (np. próbujemy obliczyć średnią z kolumny stringów), chcieliśmy uniknąć przerywania pracy funkcji i zwracać NA. By osiągnąć powyższy rezultat zdecydowaliśmy się zdefiniować klasę funkcji - miar obliczanych na kolumnach jako klasę dziedziczącą po… klasie funkcji generycznych: setClass("RankingMeasure", slots = c( name = "character", description = "character" ), prototype = list( name = NA_character_, description = NA_character_ ) ) setClass("BuiltInMeasure", contains = c("standardGeneric", "RankingMeasure") ) Następnie dla każdej potrzebnej nam funkcji utworzyliśmy odpowiadający jej obiekt - funkcję generyczną klasy BuiltInMeasure i zdefiniowaliśmy metody tej funkcji generycznej dla możliwych typów zmiennych, zwracając NA domyślnie i wynik liczbowy, gdzie to możliwe. "],["moduły-w-aplikacjach-shiny.html", "13 Moduły w aplikacjach shiny 13.1 Czym jest moduł Shiny 13.2 Budowa modułu Shiny.", " 13 Moduły w aplikacjach shiny 13.1 Czym jest moduł Shiny Modułem Shiny nazywamy odrębny kawałek aplikacji Shiny. Moduł nie może być wywołany niezależnie od reszty aplikacji. Traktuje się go jako część większej aplikacji lub większego modułu Shiny (moduł może składać się z modułów). 13.1.1 Dlaczego warto używać modułów Shiny? Uproszczenie kodu - moduły pozwalają nam na uporządkowanie złożonego kodu w przypadku dużych i skomplikowanych aplikacji Własna przestrzeń nazw - w aplikacjach shiny ID obiektów z inputów i outputów pochodzą ze wspólnej przestrzeni nazw. To znaczy, że ID każdego z obiektów w całej aplikacji musi być unikalne. Jako że moduł jest osobną funkcją wywołaną w aplikacji, posiada własną przestrzeń nazw. Wystarczy zatem, że ID obiektów są unikalne wewnątrz modułu. Recykling - ponieważ moduł Shiny jest niezależną funkcją, może być użyty zarówno wiele razy w jednej aplikacji, jak i w wielu różnych aplikacjach. Dzięki temu można z łatwością przechowywać gotowe fragmenty aplikacji w eRowych pakietach i wykorzystywać je w razie potrzeby. 13.2 Budowa modułu Shiny. kawałek UI - funkcja odpowiadająca za User Interface w module Shiny kawałek serwera - funkcja zawierająca fragment serwera, który jest wykorzystywany w UI 13.2.1 Jak używać modułów Shiny? Rozważmy aplikację składającą się z dwóch paneli - każdy z wykresem i danymi dla dwóch rozkładów, otrzymaną za pomocą poniższego kodu: library(shiny) library(ggplot2) ui <- fluidPage( tabsetPanel( #generujemy panel dla rozkładu normalnego tabPanel(title = "Rozkład normalny", tabsetPanel( tabPanel( title = "Wykres", numericInput(inputId = "normal_n", label = "Podaj wielkość próby", value = 1000), plotOutput("normal_plot") ), tabPanel( title = "Dane", tableOutput("normal_data") ) ) ), #generujemy panel dla rozkładu wykładniczego tabPanel(title = "Rozkład wykładniczy", tabsetPanel( tabPanel( title = "wykres", numericInput(inputId = "exp_n", label = "Podaj wielkość próby", value = 1000), plotOutput("exp_plot") ), tabPanel( title = "Dane", tableOutput("exp_data") ) ) ) ) ) server <- function(input, output, session) { #generujemy dane normal_data <- reactive({ set.seed(17) data.frame(id = 1:input[["normal_n"]], sample = rnorm(input[["normal_n"]])) }) exp_data <- reactive({ set.seed(17) data.frame(id = 1:input[["exp_n"]], sample = rnorm(input[["exp_n"]])) }) #generujemy tabele output[["normal_data"]] <- renderTable({ normal_data() }) output[["exp_data"]] <- renderTable({ exp_data() }) #generuemy wykresy output[["normal_plot"]] <- renderPlot({ ggplot(normal_data(), aes(x = sample)) + geom_density() }) output[["exp_plot"]] <- renderPlot({ ggplot(exp_data(), aes(x = sample)) + geom_density() + xlim(0, 5) }) } shinyApp(ui, server) Aplikacja wygląda następująco: W naszej przestrzeni wykorzystaliśmy nazwy: inputy - normal_n, exp_n outputy - normal_plot, normal_data, exp_plot, exp_data Co daje razem 6 obiektów. W aplikacji UI zajmuje 36 linijek kodu, a server 29, razem 65 linijek. Zrefaktoryzuemy kod powyższej aplikacji przy użyciu modułów Shiny. Za powtarzające się elementy (tj. panele z wykresem i danymi) będą odpowiedzialne następujące funkcje module_UI oraz module_SERVER (odpowiedniki UI oraz servera dla odrębnego fragmentu aplikacji). module_UI <- function(id) { ns <- NS(id) tagList( tabsetPanel( tabPanel( title = "Wykres", numericInput(inputId = ns("n"), label = "Podaj wielkość próby", value = 1000), plotOutput(ns("plot")) ), tabPanel(title = "Dane", tableOutput(outputId = ns("data")) ) ) ) } Na szczególną uwagę w powyższym kodzie zasługuje linijka ns <- NS(id) Za pomocą funkcji NS() tworzymy osobną przestrzeń nazw ID. module_SERVER <- function(id) { moduleServer(id, function(input, output, session) { #generujemy dane data <- reactive({ set.seed(17) data.frame(id = 1:input[["n"]], sample = rnorm(input[["n"]])) }) #generujemy wykres output[["plot"]] <- renderPlot({ ggplot(data(), aes(x = sample)) + geom_density() }) #generujemy tabelę output[["data"]] <- renderTable({ data() }) }) } Ostatecznie nasza aplikacja używająca pomocniczego modułu wygląda następująco library(shiny) ui <- fluidPage( titlePanel("Przykładowe ciągłe rozkłady prawdopodobieństwa"), tabsetPanel( #generujemy panel dla rozkładu normalnego tabPanel(title = "Rozkład normalny", module_UI("norm") ), #generujemy panel dla rozkładu wykładniczego tabPanel(title = "Rozkład wykładniczy", module_UI("exp") ) ) ) server <- function(input, output, session) { module_SERVER("norm") module_SERVER("exp") } shinyApp(ui, server) Powyższy kod jest czytelniejszy, krótszy, a także rozwiązuje problem wielu zmiennych. "]] diff --git a/docs/wczytywanie-danych-w-r.html b/docs/wczytywanie-danych-w-r.html index 087099b..458b75f 100644 --- a/docs/wczytywanie-danych-w-r.html +++ b/docs/wczytywanie-danych-w-r.html @@ -23,7 +23,7 @@ - + @@ -425,15 +425,18 @@

    3.2 Locale
    Sys.localeconv()
    -
    ##     decimal_point     thousands_sep          grouping   int_curr_symbol   currency_symbol mon_decimal_point mon_thousands_sep      mon_grouping     positive_sign     negative_sign   int_frac_digits 
    -##               "."                ""                ""            "PLN "              "zł"               ","               " "            "\003"                ""               "-"               "2" 
    -##       frac_digits     p_cs_precedes    p_sep_by_space     n_cs_precedes    n_sep_by_space       p_sign_posn       n_sign_posn 
    -##               "2"               "0"               "1"               "0"               "1"               "1"               "1"
    +
    ##     decimal_point     thousands_sep          grouping   int_curr_symbol   currency_symbol mon_decimal_point mon_thousands_sep 
    +##               "."                ""                ""            "PLN "              "zł"               ","               " " 
    +##      mon_grouping     positive_sign     negative_sign   int_frac_digits       frac_digits     p_cs_precedes    p_sep_by_space 
    +##            "\003"                ""               "-"               "2"               "2"               "0"               "1" 
    +##     n_cs_precedes    n_sep_by_space       p_sign_posn       n_sign_posn 
    +##               "0"               "1"               "1"               "1"

    Powyższa funkcja wyświetla szczegóły dotyczące systemu numerycznego i monetarnego.

    3.2.1 Ustawienie locale przez użytkownika

    Sys.setlocale(category = "LC_ALL", locale = "polish")
    -
    ## Warning in Sys.setlocale(category = "LC_ALL", locale = "polish"): Żądania raportów OS aby ustawić lokalizację na "polish" nie mogą zostać wykonane
    +
    ## Warning in Sys.setlocale(category = "LC_ALL", locale = "polish"): Żądania raportów OS aby ustawić lokalizację na "polish" nie
    +## mogą zostać wykonane
    ## [1] ""

    W celu ustawienia innego locale niż domyślne systemowe należy użyć powyższej funkcji, przyjmowane przez nią argumenty to category i locale.

    category - odpowiada za określenie, które zmienne środowiskowe chcemy zmienić, gdzie opcje: “LC_ALL”, “LC_COLLATE”, “LC_CTYPE”, “LC_MONETARY”, “LC_NUMERIC” oraz “LC_TIME” są wspierane na każdym systemie operacyjnym, niektóre systemy wspierają również: “LC_MESSAGES”, “LC_PAPER” i “LC_MEASUREMENT”

    diff --git a/docs/wizualizacja-danych-z-pakietem-ggplot2.html b/docs/wizualizacja-danych-z-pakietem-ggplot2.html index f6c7a12..9dc6b89 100644 --- a/docs/wizualizacja-danych-z-pakietem-ggplot2.html +++ b/docs/wizualizacja-danych-z-pakietem-ggplot2.html @@ -23,7 +23,7 @@ - + @@ -347,20 +347,20 @@

    7.3 Podstawy tworzenia wykresów

    Do dodawania nowych warstw do wykresu używamy symbolu “+” .

    # Budujemy podstawę wykresu, określając z jakich danych będziemy korzytsać
     ggplot(mpg)
    -

    +

    # Mapujemy dane ( na osiach pojawiły się odpowiednie podziałki)
     ggplot(mpg , aes( x = displ, y = hwy))
    -

    +

    # Określamy styl wykresu, dzięki czemu dostemy wykres odpwiednich zależności
     ggplot(mpg , aes( x = displ, y = hwy))+
       geom_point()
    -

    +

    # Personalizujemy wykres poprzez dodanie tytułu oraz określenie motywu wykresu
     ggplot(mpg , aes( x = displ, y = hwy))+
       geom_point()+
       ggtitle("disp vs hwy")+
       theme_bw()
    -

    +

    7.4 Mapowanie

    @@ -373,13 +373,13 @@

    7.4 Mapowanie ggtitle("displ vs hwy")+ theme( axis.title = element_text(size = 1))+ theme_bw()

-

+

# Punkty na wykresie to czerwone kropki
 ggplot(mpg, aes(x = displ, y = hwy)) +
   geom_point(color = "red")+
   ggtitle("displ vs hwy")+
   theme_bw()
-

+

7.5 Geometria wykresu

@@ -399,15 +399,23 @@

7.5 Geometria wykresu

+

Poniżej przedstawione są dwa przykładowe typy wykresów. Pierwszy narysowany przy użyciu funkcji geom_smooth, która służy do rysowania wygładzonych linii, np. dla prostych trendów. Drugi wykres powstał przy zastosowaniu funkcji geom_histogram.

+
ggplot(mpg, aes(x = displ, y = hwy)) +
+  geom_smooth()
+
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
+

+
ggplot(data = mpg, aes(x = hwy)) +
+  geom_histogram()
+
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
+

W pakiecie ggplot2 bardzo prosto możemy łączyć ze sobą różne geometrie na jednym wykresie. Wystarczy wstawić znak “+” pomiędzy odpowiednimi funkcjami.

-
ggplot(mpg , aes( x = displ, y = hwy))+
-  geom_point()+
-  geom_smooth()+
-  ggtitle("Multiple geomteries")+
-  theme_bw()
-

+
ggplot(mpg , aes( x = displ, y = hwy))+
+  geom_point()+
+  geom_smooth()+
+  ggtitle("Multiple geomteries")+
+  theme_bw()
+
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
+

7.6 Funkcje pomagające poprawić czytelność wykresu

@@ -424,11 +432,26 @@

7.6.1 Systemy współrzędnych

7.6.2 Dopasowanie położenia

Każda geometria w ggplot2 ma ustawione domyślne położenie różnych elementów na wykresie względem siebie. Różne opcje ustawienia położenia są dobrze widoczne na wykresach słupkowych. Zacznijmy od stworzenia zwykłego wykresu słupkowego, bez żadnych dodatkowych funkcji.

-

+
ggplot(mpg, aes(x = class)) + 
+  geom_bar()
+

Jeżeli teraz do mapowania dodamy opcję fill = dvr, to każdy prostokąt będzie reprezentował kombinację wartości class oraz dvr.

-

+
ggplot(mpg, aes(x = class, fill = drv)) + 
+  geom_bar()+
+  theme(axis.text.x = element_text(angle = 90))+
+  theme_bw()
+

Takie przedstawienie danych nie dla każdego może być czytelne, dlatego możemy skorzystać z opcji position, która przyjmuje m.in. argumenty: “dodge” i “fill”.

-

+
ggplot(mpg, aes(x = class, fill = drv)) + 
+  geom_bar(position = "dodge")+
+  ggtitle(" Plot for dodge position")+
+  theme_bw()
+

+
ggplot(mpg, aes(x = class, fill = drv)) + 
+  geom_bar(position = "fill")+
+  ggtitle(" Plot for fill position")+
+  theme_bw()
+

7.6.3 Zarządzanie osiami współrzędnych

@@ -441,13 +464,13 @@

7.6.3 Zarządzanie osiami współ
  • xlim(5,40) powoduje ograniczenie podziałki na osi x od 5 do 40 (analogicznie z ylim)
  • W ggplot2 z łatwością także dodamy etykiety tekstowe oraz adnotacje. Do wykresu możemy dodać tytuł oraz nazwy osi korzystając m.in. z funkcji labs().

    -
    ggplot(mpg, aes(x = displ, y = hwy, color = class)) +
    -  geom_point() +
    -  labs(title = "Fuel Efficiency by Engine Power",
    -       x = "Engine power",
    -       y = "Fuel Efficiency",
    -       color = "Car Type")
    -

    +
    ggplot(mpg, aes(x = displ, y = hwy, color = class)) +
    +  geom_point() +
    +  labs(title = "Fuel Efficiency by Engine Power",
    +       x = "Engine power",
    +       y = "Fuel Efficiency",
    +       color = "Car Type")
    +

    7.6.4 Motywy

    @@ -462,10 +485,10 @@

    7.7 Panele
    ggplot(mpg, aes(x = displ, y = hwy)) +
    -  geom_point() +
    -  facet_grid(~ class)

    -

    +
    ggplot(mpg, aes(x = displ, y = hwy)) +
    +  geom_point() +
    +  facet_grid(~ class)
    +

    Uwaga!

    Aby zadać względem, której zmiennej chcemy grupować, w funkcji ‘facet_’ po znaku “~”, podajemy nazwę tej zmiennej.

    Kiedy korzystamy z funkcji tworzącej panele, automatycznie wszytskie wykresy będą pokazane w układzie współrzędnych dopasowanym do wszytkich okienek. Istnieje jednak możliwość dopasowania układu współrzędnych do każdego panelu osobno. W tym celu możemy wykorzystać funcję ‘scale = “free”’.