Cel spotkania


  • zrozumieć podstawowe typy danych w R oraz różnicę między typem a klasą obiektu (typeof() vs class()),

  • oswoić się z automatyczną konwersją typów (koercją) oraz wartościami specjalnymi NA, NaN, Inf, NULL,

  • zrozumieć, skąd wynika problem z porównywaniem liczb zmiennoprzecinkowych i nauczyć się robić to bezpiecznie,

  • nauczyć się wywoływać funkcje z argumentami nazwanymi i domyślnymi oraz oswoić się z korzystaniem z dokumentacji (?nazwa_funkcji) R,

  • zacząć swobodnie pracować z wektorami – tworzyć je, indeksować i modyfikować – oraz zrozumieć wektoryzację i recykling,

  • poznać i rozróżniać podstawowe struktury danych w R oraz zrozumieć, kiedy warto wykorzystać każdą z nich,

  • nauczyć się obliczać podstawowe statystyki opisowe oraz zrozumieć, kiedy średnia może prowadzić do mylnych wniosków,

  • zacząć tworzyć proste, powtarzalne symulacje z wykorzystaniem set.seed() i sample().




Jak mądrze korzystać z modeli językowych?


Modele językowe (typu Claude, ChatGPT , Gemini, Copilot i inne) mogą bardzo pomóc w nauce R (czy innego języka programowania), ale tylko wtedy, gdy uczą, a nie wyręczają. Kilka zasad, które warto zinternalizować:

  • Proś o wyjaśnienie, nie tylko o kod. Zamiast „napisz kod, który…” spróbuj: „wyjaśnij krok po kroku, co robi x[x > mean(x)]”.

  • Każ się odpytywać. np.: „nie podawaj rozwiązania – zadaj mi pytanie naprowadzające”.

  • Uruchamiaj i sprawdzaj. Modele potrafią z pełnym przekonaniem wymyślić nieistniejący argument albo całą funkcję. Ostatecznym arbitrem jest dokumentacja (?funkcja) i wynik w konsoli.

  • Najpierw spróbuj sam. Dziesięć minut walki z błędem uczy więcej niż gotowa odpowiedź po dziesięciu sekundach.

  • Nie wklejaj danych osobowych – np. surowych danych uczestników badań.

  • Zadawaj pytania generyczne – uogólniaj i upraszczaj przypadki.


📖 Strategie projektowania promptów




Przewidywanie wyników

Najwięcej uczymy się tam, gdzie nasze przewidywanie rozjeżdża się z rzeczywistością.

— parafraza idei uczenia opartego na błędzie przewidywania (prediction/error-driven learning)


######################
TRUE + TRUE

######################
"5" == 5

######################
"10" < "9"

######################
0.1 + 0.2 == 0.3

######################
round(2.5)

######################
round(3.5)

######################
as.integer(-1.7)

######################
NA^0

######################
NA > 1

######################
length(NULL)




Wyrażenie Wynik Krótkie wyjaśnienie
TRUE + TRUE 2 TRUE zamienia się na 1, FALSE na 0
"5" == 5 TRUE liczba 5 zostaje zamieniona na tekst "5" (koercja)
"10" < "9" TRUE teksty porównywane są jak w słowniku, znak po znaku: "1" < "9"
0.1 + 0.2 == 0.3 FALSE komputer nie potrafi dokładnie zapisać 0.1 w systemie dwójkowym
round(2.5) 2 R zaokrągla „do parzystej” (standard IEC 60559)
round(3.5) 4 j.w. – najbliższa parzysta to 4
as.integer(-1.7) -1 obcięcie części ułamkowej w stronę zera, a nie zaokrąglenie
NA^0 1 cokolwiek do potęgi zero daje 1, nawet wartość nieznana
NA > 1 NA nie wiemy, ile wynosi NA, więc nie wiemy, czy jest większe od 1
length(NULL) 0 NULL to „nic” – pusty obiekt




Istnieją w R specjalne słowa/znaki przeznaczone do operowania wartościami prawda (TRUE & T) i fałsz (FALSE & F)

class(TRUE); typeof(TRUE); class(T); typeof(T)

T == TRUE
T <- FALSE
T == TRUE
rm(T)
TRUE <- FALSE



T == TRUE
## [1] TRUE
T <- FALSE      # nikt nam nie zabroni...
T == TRUE       # FALSE
## [1] FALSE
rm(T)           # przywracamy porządek: T znów wskazuje na wbudowane TRUE
TRUE <- FALSE   # a tu R się postawi, bo TRUE to słowo zastrzeżone
## Error in `TRUE <- FALSE`:
## ! invalid (do_set) left-hand side to assignment


T i F to tylko skróty, które da się nadpisać. TRUE i FALSE są “nietykalne”.

SUGESTIA Piszmy raczej TRUE/FALSE w całości, skoro T i F to modyfikowalne obiekty.




Podstawowe typy danych


W języku R wyróżniamy kilka podstawowych typów danych, które są kluczowe do pracy z danymi.


Typ / klasa Co przechowuje Przykład class() typeof()
character tekst (ciąg znaków) "Hello, World!" character character
numeric liczby zmiennoprzecinkowe 3.14 numeric double
integer liczby całkowite (z literą L) 42L integer integer
logical TRUE (T) lub FALSE (F) TRUE logical logical
complex liczby zespolone 1 + 2i complex complex
factor zmienne kategorialne factor(c("male", "female", "female", "male")) factor integer
Date daty as.Date("2024-09-05") Date double
POSIXct daty i czasy as.POSIXct("2024-09-05 09:25:01") POSIXct POSIXt double


Pięć pierwszych wierszy to typy podstawowe (atomowe). Trzy ostatnie to klasy zbudowane na typach podstawowych – stąd różnica między kolumnami class() i typeof().

Każdy z tych typów danych ma swoje specyficzne zastosowania i jest używany w różnych kontekstach analizy danych. Warto znać te podstawowe typy, aby efektywnie pracować w R.


moja_dokladna_liczba <- 54.7
class(moja_dokladna_liczba) #  numeric
## [1] "numeric"
moja_liczba <- 53 
class(moja_liczba) # teoretycznie integer
## [1] "numeric"
moja_liczba <- 53L
class(moja_liczba) # integer
## [1] "integer"

WNIOSEK Jeśli chcemy, aby wektor numeryczny przechowywał wartości całkowite (klasy integer), należy podawać wartości liczbowe z dopisaną literą L

moje_slowo <- "histogram" # character (string), dygresja: możliwe też pojedyncze nawiasy '...'
class(moje_slowo)
## [1] "character"
moja_wart_logiczna <- T # Boolean data type (logical)
class(moja_wart_logiczna)
## [1] "logical"
class(moja_wart_logiczna) == "logical" # do sprawdzenia np. w jakiejś pętli, czy jakaś transformacja nie spowodowała zmiany typu na niechciany
## [1] TRUE
moja_wart_logiczna2 <- TRUE

str(moja_wart_logiczna2) # nowa funkcji ?str ;jej output będzie wyglądał nieco inaczej na różnych klasach/ typach obiektów
##  logi TRUE


Jeśli nie podobają nam się typy danych zastosowane przez R’a, możemy próbować je zmienić funkcjami as. ...()


# czasem przydaje się trzymać wartość numeryczną jako string (kiedy?)
moja_dokladna_liczba <- as.character(moja_dokladna_liczba)
class(moja_dokladna_liczba)
## [1] "character"
moja_dokladna_liczba
## [1] "54.7"
# wybuchnie?
moje_slowo <- as.numeric(moje_slowo)
class(moje_slowo)
## [1] "numeric"
moje_slowo # buuuuu
## [1] NA
# wtf?
moja_wart_logiczna2 <- as.integer(moja_wart_logiczna2)
class(moja_wart_logiczna2)
## [1] "integer"
# ale...
moja_wart_logiczna2
## [1] 1
1 + as.logical(moja_wart_logiczna2) # serio?
## [1] 2
as.integer(c(-5.9, -3.8, 5.1, 3.2, 7.9, 1.4)) # a tu?
## [1] -5 -3  5  3  7  1

as.integer() obcina część ułamkową w stronę zera, a nie zaokrągla ani nie bierze podłogi.

Dlatego:

  • 7.9 daje 7, a nie 8, więc to nie jest zaokrąglanie.
  • -5.9 daje -5, a nie -6, więc to nie jest floor(). Dla liczb ujemnych zachowuje się jak ceiling().

R robi to bez żadnego ostrzeżenia, więc łatwo przeoczyć błąd.


x <- c(-5.9, -3.8, 5.1, 3.2, 7.9, 1.4)

Funkcja Wynik
x # -5.9 -3.8 5.1 3.2 7.9 1.4
trunc(x) # -5 -3 5 3 7 1
floor(x) # -6 -4 5 3 7 1
ceiling(x) # -5 -3 6 4 8 2
round(x) # -6 -4 5 3 8 1

Dla zaokrąglenia do liczby całkowitej typu integer, można użyć as.integer(round(x)).




Hierarchia typów


Ogólnie hierarchia typów wygląda następująco:

character > complex > numeric (double) > integer > logical.

Przy łączeniu różnych typów R zawsze „awansuje” wszystko do typu najwyższego. Ponad nimi stoi jeszcze lista, która może przechowywać wszystko naraz.




Typ a klasa


Klasa (class()) to „kostium”, w którym obiekt się pokazuje. Typ (typeof()) to sposób, w jaki obiekt jest naprawdę przechowywany w pamięci. Ta różnica wyjaśni nam później kilka pułapek z factorami.


(x <- factor(c("niski", "wysoki", "niski")))
class(x)
typeof(x)

(d <- as.Date("2000-02-29")) # R 1.0.0 !
class(d)
typeof(d)
as.numeric(d)



(x <- factor(c("niski", "wysoki", "niski")))
## [1] niski  wysoki niski 
## Levels: niski wysoki
class(x)    # "factor"
## [1] "factor"
typeof(x)   # "integer": factor to liczby całkowite w przebraniu, z etykietami
## [1] "integer"
unclass(x)  # zdejmujemy kostium
## [1] 1 2 1
## attr(,"levels")
## [1] "niski"  "wysoki"
c(x, 67)
## [1]  1  2  1 67
(d <- as.Date("1985-02-14"))
## [1] "1985-02-14"
class(d)    # "Date"
## [1] "Date"
typeof(d)   # "double"
## [1] "double"
as.numeric(d)  # liczba dni od 1970-01-01, "urodzin" Uniksa
## [1] 5523


Ile żyjesz dni?

# Ile żyjesz dni?
Sys.Date() - as.Date("1985-02-14")
## Time difference of 15224 days

Ile żyjesz lat?

# Ile żyjesz lat?
as.numeric(
  Sys.Date() - as.Date("1985-02-14")
  ) / 365.25 
## [1] 41.68104


Funkcje is.*() zadają pytanie “czy to jest…?”


is.numeric(42L)     # TRUE – integer też jest liczbą
## [1] TRUE
is.character("42")  # TRUE
## [1] TRUE
is.logical(NA)      # TRUE – samotne NA jest typu logical
## [1] TRUE




Koercja w praktyce

c(1, "dwa", TRUE)     # wszystko staje się tekstem
## [1] "1"    "dwa"  "TRUE"
c(1, TRUE, NA)        # wszystko staje się liczbą
## [1]  1  1 NA
"10" < "9"            # porównanie słownikowe!
## [1] TRUE
sort(c("10", "9", "100", "2"))               # sortowanie "po słownikowemu"
## [1] "10"  "100" "2"   "9"
sort(as.numeric(c("10", "9", "100", "2")))   # sortowanie liczbowe
## [1]   2   9  10 100
# Liczenie TRUE to codzienność analizy danych
poprawne <- c(TRUE, FALSE, TRUE, TRUE, FALSE)   # czy uczestnik odpowiedział poprawnie?
sum(poprawne)    # ile poprawnych odpowiedzi?
## [1] 3
mean(poprawne)   # jaki odsetek poprawnych odpowiedzi?
## [1] 0.6


Przewidywanie wyników


Bez uruchamiania poniższych linijek przwidźmy klasę obiektów:

class(c(1L, 2.5))
class(c(TRUE, "FALSE"))
class(c(1L, TRUE))




Wartości specjalne

Wartość Znaczenie Jak powstaje? Jak sprawdzić? length()
NA brak danych (Not Available) brak odpowiedzi w ankiecie is.na() 1
NaN nie-liczba (Not a Number) 0/0, sqrt(-1) is.nan() 1
Inf, -Inf nieskończoność 1/0, exp(1000) is.infinite() 1
NULL brak obiektu, „pustka” x <- NULL is.null() 0

Analogia: NA to puste pole w ankiecie (osoba istnieje, odpowiedzi brak), a NULL to ankieta, której w ogóle nie ma.



NA - Not Available (brakujące dane)

wektor_z_brakami <- c(1, 2, NA, 4, 5)
wektor_z_brakami
## [1]  1  2 NA  4  5
# Większość funkcji zwróci NA, jeśli są braki
mean(wektor_z_brakami)
## [1] NA
# Musimy pominąć NA
mean(wektor_z_brakami, na.rm = TRUE)
## [1] 3
# Sprawdzanie NA
is.na(wektor_z_brakami)
## [1] FALSE FALSE  TRUE FALSE FALSE
# Ile jest NA?
sum(is.na(wektor_z_brakami))
## [1] 1


NULL - Brak

# NULL - pusty obiekt
x <- NULL
length(x)
## [1] 0


NaN - Not a Number (nie-liczba)

# NaN - Not a Number (nieokreślona operacja matematyczna)
0/0
## [1] NaN


Inf - Infinity (nieskończoność)

1/0 # Inf (nieskończoność)
## [1] Inf
-1/0 # -Inf (-nieskończoność)
## [1] -Inf


A teraz kilka zachowań, które warto znać

NA + 1          # NA "zaraża" wynik
## [1] NA
NA == NA        # czy dwie nieznane wartości są równe? Nie wiadomo!
## [1] NA
sqrt(-1)        # NaN + ostrzeżenie
## [1] NaN
is.na(NaN)      # TRUE – NaN to szczególny przypadek braku
## [1] TRUE
is.nan(NA)      # FALSE
## [1] FALSE
c(1, NA, 3)     # NA zajmuje miejsce w wektorze
## [1]  1 NA  3
c(1, NULL, 3)   # NULL po prostu znika
## [1] 1 3
Inf - Inf       # nieskończoność minus nieskończoność to... NaN
## [1] NaN
NA & FALSE      # FALSE! Niezależnie od NA koniunkcja z FALSE jest fałszem
## [1] FALSE
NA | TRUE       # TRUE – z tego samego powodu
## [1] TRUE

PRZYPOMNIENIE

exp(x) to funkcja wykładnicza, czyli e do potęgi x, gdzie e ≈ 2,71828 (liczba Eulera).

\[ \exp(x) = e^{x} \]

exp(-1)   # 0.3678794
exp(0)    # 1
exp(1)    # 2.718282  (samo e)
exp(2)    # 7.389056

# Działa wektorowo, jak większość funkcji w R
exp(c(-1, 0, 1, 2))
# [1] 0.3678794 1.0000000 2.7182818 7.3890561


PRZYPOMNIENIE

\[ \exp(\ln x) = x, \qquad \ln(e^{x}) = x \]

Jest odwrotnością logarytmu naturalnego log()

log(exp(3))   # 3
exp(log(10))  # 10



Liczby w komputerze

0.1 + 0.2 == 0.3

print(0.1 + 0.2, digits = 17)
print(0.3, digits = 17)
all.equal(0.1 + 0.2, 0.3)

###################

sqrt(2)^2 == 2
print(sqrt(2)^2, digits = 16)
print(sqrt(2)^2, digits = 17)

###################

seq(0, 10, by = 1) == 3 
seq(0, 1, by = 0.1) == 0.3



0.1 + 0.2 == 0.3
## [1] FALSE
print(0.1 + 0.2, digits = 17)   # 0.30000000000000004: hmmm...
## [1] 0.30000000000000004
print(0.3, digits = 17)         # 0.29999999999999999 wtf?
## [1] 0.29999999999999999
isTRUE(all.equal(0.1 + 0.2, 0.3))       # porównanie "z tolerancją": TRUE ; ?all.equal
## [1] TRUE
###################

sqrt(2)^2 == 2                   # FALSE
## [1] FALSE
print(sqrt(2)^2, digits = 16)     # 2
## [1] 2
print(sqrt(2)^2, digits = 17)     # 2.0000000000000004
## [1] 2.0000000000000004
###################

seq(0, 10, by = 1) == 3       # jest na czwartym indeksie
##  [1] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
seq(0, 1, by = 0.1) == 0.3    # hmmm... gdzie się podziało 0.3?!
##  [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

Analogia. Ułamek 1/3 w systemie dziesiętnym to 0,333…, czyli rozwinięcie nieskończone – gdzieś musimy je uciąć. Komputer liczy w systemie dwójkowym, a tam właśnie 0,1 jest takim „nieskończonym” ułamkiem. Komputer zapisuje więc liczbę bardzo bliską 0,1, ale nie dokładnie 0,1.


Wniosek praktyczny: liczb niecałkowitych nie porównujemy przez ==, tylko przez isTRUE(all.equal(a, b)).


all.equal() nie zwraca FALSE, gdy wartości się różnią. Zwraca wtedy tekst opisujący różnicę. Porównuje z tolerancją (domyślnie około 1.5e-8), co rozwiązuje problem błędów zmiennoprzecinkowych

all.equal(0.1 + 0.2, 0.3)
# [1] TRUE

all.equal(1, 1.5)
# [1] "Mean relative difference: 0.5"

isTRUE()rozwiązuje ten problem: zwraca TRUE tylko wtedy, gdy argument jest dokładnie pojedynczym TRUE, a w każdym innym przypadku (tekst, NA, wektor) zwraca FALSE.


Swoją drogą:

1.5e-8 == 1.5*10^-8 
## [1] FALSE
isTRUE(all.equal(1.5e-8, 1.5*10^-8)) 
## [1] TRUE


Zaokrąglanie

x round(x) floor(x) ceiling(x) trunc(x) as.integer(x)
2.7 3 2 3 2 2
2.5 2 2 3 2 2
3.5 4 3 4 3 3
-1.7 -2 -2 -1 -1 -1
-2.5 -2 -3 -2 -2 -2


round()


round(x, digits = 0) zaokrągla wartości x do podanej liczby miejsc po przecinku. Działa wektorowo i zwraca typ double, nie integer.

# btw

x <- 123.456789

round(x, digits = 2)   # 123.46  (2 miejsca po przecinku)
## [1] 123.46
round(x, digits = 1)   # 123.5   (1 miejsce po przecinku)
## [1] 123.5
round(x, digits = 0)   # 123     (liczba całkowita)
## [1] 123
round(x, digits = -1)  # 120     (zaokrąglenie do dziesiątek, do wielokrotności 10)  # [poprawka: było "10**2"]
## [1] 120
round(x, digits = -2)  # 100     (zaokrąglenie do setek)
## [1] 100
round(x, digits = -3)  # 0       (zaokrąglenie do tysięcy)
## [1] 0


Dlaczego round(2.5) daje 2? R stosuje regułę „zaokrąglaj połówki do parzystej” (tzw. zaokrąglanie bankierskie). Przy sumowaniu wielu zaokrągleń błędy w górę i w dół się znoszą, zamiast systematycznie zawyżać wynik.

round(c(0.5, 1.5, 2.5, 3.5, 4.5))       # 0 2 2 4 4
## [1] 0 2 2 4 4


floor(x)


Funkcje floor(x) i ceiling(x) zaokrąglają do najbliższej wartości całkowitej odpowiednio w dół lub w górę.

floor(1.23456789)
## [1] 1
floor(-1.23456789)
## [1] -2
floor(c(0.5, 1.5, 2.5, 3.5, 4.5))
## [1] 0 1 2 3 4


ceiling(x)


ceiling(1.23456789)
## [1] 2
ceiling(-1.23456789)
## [1] -1
ceiling(c(0.5, 1.5, 2.5, 3.5, 4.5))
## [1] 1 2 3 4 5


trunc()

Funkcja trunc() w R służy do obcinania części dziesiętnej liczby, czyli usuwania wszystkiego, co znajduje się po przecinku, i zwracania liczby całkowitej najbliższej zeru.

Na przykład, liczba dodatnia zostanie zaokrąglona w dół, a liczba ujemna w górę.

# Obcinanie części dziesiętnej liczby dodatniej
trunc(pi)
## [1] 3
# Obcinanie części dziesiętnej liczby ujemnej
trunc(-exp(1)) # == trunc(-e)
## [1] -2


2 albo 3 cyfry znaczące.

signif(123456, 2) # ?signif
## [1] 120000
round(0.000123456, 3)    # 0
## [1] 0
signif(0.000123456, 3)   # 0.000123
## [1] 0.000123

signif(x, digits = 6)zaokrągla liczbę do podanej liczby cyfr znaczących, czyli cyfr liczonych od pierwszej niezerowej cyfry, niezależnie od położenia przecinka.




Notacja naukowa i liczba wyświetlanych cyfr


Przykład notacji wykładniczej

5834617 * 10^5
## [1] 583461700000

Przykład notacji naukowej

5.834617e+5
## [1] 583461.7

Ile cyfr R pokazuje? Domyślnie 7 cyfr znaczących (options(digits = 7)), dlatego wyszło co powyżej

To samo?

5.834617 * 10^5  == 5.834617e+5 
## [1] TRUE
5.834617e+5 == 583461.7
## [1] TRUE
exp(-67) # super  mała, w notacji naukowej
## [1] 7.984904e-30
exp(67) # super duża, w notacji naukowej
## [1] 1.252363e+29



Wyłączanie notacji naukowej (scientific notation)

Jeśli uruchomimy options(scipen = 50), to wówczas w konsoli pojawią nam się inne wyświetlenia

options(scipen = 50) # opcja systemowa scipen (scientific penalty)

exp(-67) # super  mała, pozbawiona notacji
## [1] 0.000000000000000000000000000007984904
exp(67) # super duża, analogicznie
## [1] 125236317084221369338226600842


Jeśli chcemy co do zasady w konsoli widzieć mniej znaków (i tym samym miejsc po przecinku), możemy osiągnąć to używając poniższej funkcji systemowe

pi # przykład obiektu, który istnieje bez naszej ingerencji, na podobnej zasadzie co np.`letters`
## [1] 3.141593
# albo nie istnieje ale stworzyliśmy
(e <- exp(1))
## [1] 2.718282
options(digits = 3) # zmieniamy opcje systemową

# wówczas
pi # znane nam "czyczternaście"
## [1] 3.14
# albo
e
## [1] 2.72

Przywracamy ustawienia domyślne – inaczej obowiązywałyby do końca dokumentu

options(scipen = 0, digits = 7)




Funkcje i ich argumenty


Ogólny schemat wywołania funkcji:

nazwa_funkcji(argument1 = wartość, argument2 = wartość).

Listę argumentów i ich wartości domyślnych podejrzymy przez ?nazwa_funkcji albo args(nazwa_funkcji).


PRZYPOMNIENIE

Pamiętamy czym jest logarytm?

Logarytm to odwrotność potęgowania

\[\log_a(x) = y \iff a^y = x\] Czyli: „do jakiej potęgi trzeba podnieść a, żeby otrzymać x?”

  • \(a > 0,\ a \neq 1\) — podstawa logarytmu

  • \(x > 0\) — liczba logarytmowana

  • \(y\) — wynik


W R do logarytmowania służy funkcja log()

Koniec Przypomnienia



Kolejnosć argumentów

Wracając do używania argumentów funkcji w R

# ?log()
log(100)
log(x = 100, base = 10)
log(100, base = 10)
log(base = 10, 100)
log(100, 10)
log(10, 100)

# ?round()
round(3.14159, 2)
round(digits = 2, x = 3.14159)

# ?seq
seq(1, 10, length.out = 4)  
seq(1, 10, len = 4)



log(100)                # domyślnie logarytm naturalny o podstawie e (exp(1))
## [1] 4.60517
log(x = 100, base = 10) # 2
## [1] 2
log(100, base = 10)     # 2
## [1] 2
log(base = 10, 100)     # 2: nazwane argumenty mogą być w dowolnej kolejności
## [1] 2
log(100, 10)            # 2: pozycyjnie
## [1] 2
log(10, 100)            # 0.5! pozycja ma znaczenie, gdy nie nazywamy
## [1] 0.5
####

round(3.14159, 2)
## [1] 3.14
round(digits = 2, x = 3.14159)
## [1] 3.14
####

seq(1, 10, length.out = 4)  
## [1]  1  4  7 10
seq(1, 10, len = 4)     # działa, bo R dopasowuje częściowo nazwy (length.out). Ciekawostka, nie nawyk!
## [1]  1  4  7 10



Funkcje można dowolnie w sobie zagnieżdżać, co już w sumie po poprzednich przykładach dało się intuicyjnie wyczuć.

przykład 1

exp(floor(42.345)) - exp(sum(20,20)+2)
## [1] 0

przykład 2

abs(-round(123 * exp(sqrt(log(3 ** 2 - 1 + 3 ** (2 - 1) / 3, base = 2))), -1)) - 730
## [1] 0


Długie zagnieżdżenia czyta się od środka. Rozpiszmy przykład 2 krok po kroku – tak samo szuka się błędów w zawiłym kodzie:

krok1 <- 3 ** 2 - 1 + 3 ** (2 - 1) / 3    # 9 - 1 + 1 = 9
krok2 <- log(krok1, base = 2)             # ok. 3.17
krok3 <- sqrt(krok2)                      # ok. 1.8
krok4 <- 123 * exp(krok3)                 # ok. 729.7
krok5 <- round(krok4, -1)                 # do dziesiątek; = 730
krok6 <- abs(-krok5)                      # wartość absolutna czyli 730
krok7 <- krok6 - 730                      # odejmowanie 730 - 730 = 0
print(krok7)
## [1] 0




= vs <-

if (exists("x")) rm(x)
mean(x = 1:10)
exists("x") # jaka jest wartość x ?
mean(x <- 1:10) 
exists("x") # jaka jest wartość x ?



if (exists("x")) rm(x) # dla wyczyszczenia środowiska, na wypadek, gdyby obiekt o nazwie x już istniał
mean(x = 1:10)
## [1] 5.5
exists("x")   # FALSE: x jest tylko nazwą argumentu funkcji do liczenia średniej
## [1] FALSE
mean(x <- 1:10)
## [1] 5.5
exists("x")   # TRUE: przy okazji powstał obiekt w środowisku
## [1] TRUE
# A jaka jest wartość x 

# [1]  1  2  3  4  5  6  7  8  9 10 

# czy 

# [1] 5.5

# ?

Stąd konwencja: <- służy do przypisania, = do argumentów funkcji




Operatory logiczne i porównania


PRZYPOMNIENIE i uzupełnienie poprzednich zajęć

Operator Znaczenie Przykład Wynik
==, != równe, różne 3 == 3 TRUE
<, >, <=, >= porównania 2 >= 3 FALSE
! negacja (NIE) !TRUE FALSE
& koniunkcja (I), element po elemencie c(TRUE, FALSE) & c(TRUE, TRUE) TRUE FALSE
| alternatywa (LUB), element po elemencie c(TRUE, FALSE) | c(FALSE, FALSE) TRUE FALSE
&&, || I / LUB dla pojedynczych wartości (np. w if) TRUE && FALSE FALSE
%in% czy element należy do zbioru 3 %in% 1:5 TRUE
any(), all() czy którykolwiek / czy wszystkie any(c(FALSE, TRUE)) TRUE
xor() alternatywa wykluczająca (ALBO) xor(TRUE, TRUE) FALSE


x y xor(x, y)
TRUE TRUE FALSE
FALSE FALSE FALSE
FALSE TRUE TRUE



Obejrzyjmy wektory składające się wyłącznie w elementów logicznych i zaaplikujmy do nich poznane operatory

x <- c(T, F, F)
y <- c(T, F, T)
x|y  # OR (lub)
## [1]  TRUE FALSE  TRUE
x||y  # OR (lub)
## Error in `x || y`:
## ! 'length = 3' in coercion to 'logical(1)'
!x # negacja
## [1] FALSE  TRUE  TRUE
x&y # AND (i)
## [1]  TRUE FALSE FALSE
x&&y # AND (i)
## Error in `x && y`:
## ! 'length = 3' in coercion to 'logical(1)'
TRUE && FALSE
## [1] FALSE
c(TRUE, FALSE) && TRUE   # błąd od wersji R 4.3
## Error in `c(TRUE, FALSE) && TRUE`:
## ! 'length = 2' in coercion to 'logical(1)'
T %in% x
## [1] TRUE
c("kot", "pies", "chomik") %in% c("pies", "kot")
## [1]  TRUE  TRUE FALSE
xor(x,y)
## [1] FALSE FALSE  TRUE
xor(c(TRUE, TRUE, FALSE), c(TRUE, FALSE, FALSE))
## [1] FALSE  TRUE FALSE
any(x) # Given a set of logical vectors, is at least one of the values true?
## [1] TRUE
any(c(3, 8, 12) > 10)
## [1] TRUE
all(x) # Given a set of logical vectors, are all of the values true?
## [1] FALSE
all(c(3, 8, 12) > 10)
## [1] FALSE





Zagadka: zadanie selekcyjne Peter’a Wasona (1966)


Na stole leżą cztery karty. Każda ma literę po jednej stronie i liczbę po drugiej. Widzimy: A, K, 4, 7. Reguła brzmi:

„Jeśli na karcie jest samogłoska, to na drugiej stronie jest liczba parzysta”.

Które karty trzeba odwrócić, żeby sprawdzić, czy reguła jest prawdziwa?

Wiki -> Test selekcji Wasona


















Poprawna odpowiedź

Trzeba odwrócić A i 7. Kartę A, bo jeśli z tyłu jest liczba nieparzysta, reguła jest złamana. Kartę 7, bo jeśli z tyłu jest samogłoska, reguła również jest złamana. Karta K jest nieistotna, bo reguła nic nie mówi o spółgłoskach. Karta 4 też nic nie rozstrzyga, bo reguła nie twierdzi, że liczba parzysta występuje tylko przy samogłoskach.


W kategoriach logiki chodzi o sprawdzenie poprzednika implikacji (p) i zaprzeczenia następnika (nie-q).

Jak odpowiadają ludzie

Poprawnie odpowiada zwykle tylko około 10% badanych. Najczęstszy błąd to wybór A i 4. Ludzie szukają przypadków, które potwierdzają regułę, zamiast takich, które mogłyby ją obalić.


Efekt treści

Najciekawsze jest to, że gdy to samo zadanie ma treść społeczną, wyniki dramatycznie się poprawiają. W wersji Leda Cosmides i J. Tooby reguła brzmi: „Jeśli ktoś pije piwo, musi mieć ukończone 18 lat”.

Karty to: „piwo”, „woda”, „25 lat” i „16 lat”. Większość ludzi od razu wskazuje „piwo” i „16 lat”, choć logicznie jest to identyczne zadanie.


Reguła to implikacja „p → q”. W R nie ma dla niej osobnego operatora, ale jest ona równoważna !p | q.

p <- c(TRUE, TRUE, FALSE, FALSE)   # samogłoska?
q <- c(TRUE, FALSE, TRUE, FALSE)   # liczba parzysta?
data.frame(p, q, implikacja = !p | q)   # implikacja jest fałszywa tylko dla p = TRUE i q = FALSE
##       p     q implikacja
## 1  TRUE  TRUE       TRUE
## 2  TRUE FALSE      FALSE
## 3 FALSE  TRUE       TRUE
## 4 FALSE FALSE       TRUE

Kartę trzeba odwrócić, jeśli KTÓRAŚ możliwa druga strona łamie regułę

mozliwe <- c(TRUE, FALSE)
any(!(!TRUE | mozliwe))      # A: samogłoska, parzystość nieznana   -> TRUE, odwracamy
## [1] TRUE
any(!(!FALSE | mozliwe))     # K: spółgłoska, parzystość nieznana   -> FALSE
## [1] FALSE
any(!(!mozliwe | TRUE))      # 4: parzysta, litera nieznana          -> FALSE
## [1] FALSE
any(!(!mozliwe | FALSE))     # 7: nieparzysta, litera nieznana       -> TRUE, odwracamy
## [1] TRUE





Podstawowe obiekty

Vectors


Wektor jest podstawową strukturą danych w języku R. Jest to uporządkowany zbiór elementów tego samego typu.

WAŻNE: W R nawet pojedyncza wartość jest wektorem o długości 1!

x <- 5 # To jest skalar, ale też wektor długości 1
x
## [1] 5
length(x)
## [1] 1


Tworzenie wektorów

Zdefiniujmy zmienne używając wszechobecnej w R funkcji c(). “c” prawdopodobnie od combine

# Wektor numeryczny
(oceny_stat_R <- c(4,5,2,2,3))
## [1] 4 5 2 2 3
# Wektor znakowy
(za_co <- c("klasowka", "aktywnosc", "sprawdzian", "sprawdzian", "projekt"))
## [1] "klasowka"   "aktywnosc"  "sprawdzian" "sprawdzian" "projekt"
# Wektor logiczny
(warunki <- c(TRUE, FALSE, TRUE, TRUE))
## [1]  TRUE FALSE  TRUE  TRUE


Nazywanie wektora

# nadanie wartościom wektora nazw
names(oceny_stat_R) <- za_co 
# Obejrzyjmy
print(oceny_stat_R)
##   klasowka  aktywnosc sprawdzian sprawdzian    projekt 
##          4          5          2          2          3

Swoją drogą, czy da się nakreślić jakiś “sznyt charakterologiczny” osoby z takimi ocenami?

# funkcja c() umożliwia nadanie nazw kolejnym elementom w konwencji nazwa = wartość
id <- c(pierwszy = 1, drugi = 2, trzeci = 3)


Strukturę obiektu można sprawdzić choćby funkcją str()

str(oceny_stat_R)
##  Named num [1:5] 4 5 2 2 3
##  - attr(*, "names")= chr [1:5] "klasowka" "aktywnosc" "sprawdzian" "sprawdzian" ...

Podstawowe operacje na wektorach

wektor_1 <- c(2,4,7,3)
wektor_2 <- c(3,8,9,2)

Dodawanie wektorów

print(suma_wektorow <- wektor_1 + wektor_2)
## [1]  5 12 16  5

Łączenie wektorów

(concat_wektorow <- c(wektor_1, wektor_2))
## [1] 2 4 7 3 3 8 9 2


To może coś wbudowaną funkcją by zrobić…

# gdybyśmy nie znali żadnej funkcji arytmetycznej w R musielibyśmy liczyć "na piechotę"
(4+5+2+2+3)/5
## [1] 3.2
# tak można
sum(suma_wektorow)
## [1] 38
length(oceny_stat_R)
## [1] 5
sum(concat_wektorow)
## [1] 38
mean(oceny_stat_R) # Średnia ocen z przedmiotu
## [1] 3.2
# albo...
sum(oceny_stat_R)/length(oceny_stat_R)
## [1] 3.2

Sekwencje

(id <- 1:3)
## [1] 1 2 3
(id <- c(1:3))
## [1] 1 2 3
10:1
##  [1] 10  9  8  7  6  5  4  3  2  1
-5:5
##  [1] -5 -4 -3 -2 -1  0  1  2  3  4  5
5:-5
##  [1]  5  4  3  2  1  0 -1 -2 -3 -4 -5

Zauważmy ostatnia wartość nie jest wartością wskazaną jako kończąca sekwencję, czemu?

5.1:8.2 # 
## [1] 5.1 6.1 7.1 8.1

seq(from=, to=, by=, length.out =...)

seq(1, 10, 1) # równoważnik 1:10
##  [1]  1  2  3  4  5  6  7  8  9 10
seq(1, 10, 2) # 9?
## [1] 1 3 5 7 9
(seq(1,10))
##  [1]  1  2  3  4  5  6  7  8  9 10
class(seq(1,10)) # <- wydaje się oczywiste
## [1] "integer"
(seq(1,10,1))
##  [1]  1  2  3  4  5  6  7  8  9 10
class(seq(1,10,1)) # <- wydaje się mnie oczywiste, dodaliśmy jedynie jawnie 3. argument w funkcji
## [1] "numeric"
(seq(1L,10L,1))
##  [1]  1  2  3  4  5  6  7  8  9 10
class(seq(1L,10L,1L)) # <- patent, żeby znów zachowywało się "intuicyjnie"
## [1] "integer"
# lub
(seq(1,10,1))
##  [1]  1  2  3  4  5  6  7  8  9 10
class(seq(1,10,1L))
## [1] "numeric"
(sekwencjaNumericow <- seq(from = 1,
                           to = 10, 
                           length.out = 20))
##  [1]  1.000000  1.473684  1.947368  2.421053  2.894737  3.368421  3.842105
##  [8]  4.315789  4.789474  5.263158  5.736842  6.210526  6.684211  7.157895
## [15]  7.631579  8.105263  8.578947  9.052632  9.526316 10.000000
(sekwencjaNumericow2 <- seq.int(from = 1,
                                to = 10,
                                length.out = 19)) # == seq(1, 10, by = 0.5), gdyż "przepis" na by to:
##  [1]  1.0  1.5  2.0  2.5  3.0  3.5  4.0  4.5  5.0  5.5  6.0  6.5  7.0  7.5  8.0
## [16]  8.5  9.0  9.5 10.0
# by = (to - from)/(length.out - 1)  -> ?seq

(sekwencjaNumericow3 <- seq(from = 0.5, # <- 
                            to = 10, 
                            length.out = 20))
##  [1]  0.5  1.0  1.5  2.0  2.5  3.0  3.5  4.0  4.5  5.0  5.5  6.0  6.5  7.0  7.5
## [16]  8.0  8.5  9.0  9.5 10.0

Powtarzanie wartości

rep(666, times = 10)
##  [1] 666 666 666 666 666 666 666 666 666 666
rep(1:5, times = 10)
##  [1] 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3
## [39] 4 5 1 2 3 4 5 1 2 3 4 5
rep(1:5, each = 10)
##  [1] 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4
## [39] 4 4 5 5 5 5 5 5 5 5 5 5
(szpital<-c(rep("lekarz",6),
            rep("administracja",7),
            rep("pielęgniarka",9))
  ) # rep() na wektorach
##  [1] "lekarz"        "lekarz"        "lekarz"        "lekarz"       
##  [5] "lekarz"        "lekarz"        "administracja" "administracja"
##  [9] "administracja" "administracja" "administracja" "administracja"
## [13] "administracja" "pielęgniarka"  "pielęgniarka"  "pielęgniarka" 
## [17] "pielęgniarka"  "pielęgniarka"  "pielęgniarka"  "pielęgniarka" 
## [21] "pielęgniarka"  "pielęgniarka"
trufolsy <- (rep(c(TRUE, FALSE), times = 5)) # rep() na trufolsach


Indeksowanie wektora

UWAGA: R indeksuje od 1, nie od 0!


# stwórzmy sobie znakowy wektor
owoce <- c("jabłko", "banan", "gruszka", "pomarańcza", "winogrono")


Pierwszy element

owoce[1]
## [1] "jabłko"


Trzeci element

owoce[3]
## [1] "gruszka"


Wiele elementów

owoce[c(1, 3, 5)]
## [1] "jabłko"    "gruszka"   "winogrono"


Zakres

owoce[2:4]
## [1] "banan"      "gruszka"    "pomarańcza"


Wszystko oprócz pierwszego

owoce[-1]
## [1] "banan"      "gruszka"    "pomarańcza" "winogrono"


Wszystko oprócz pierwszego i ostatniego

owoce[-c(1, 5)]
## [1] "banan"      "gruszka"    "pomarańcza"


# to samo co wyzej, ale bardziej uniwersalnie
owoce[-c(1, length(owoce))] # żeby nie robić "hard-coding'u"
## [1] "banan"      "gruszka"    "pomarańcza"


Indeksowanie logiczne

liczby <- c(5, 12, 8, 3, 15, 20, 7)


Które liczby są większe od 10?

liczby > 10
## [1] FALSE  TRUE FALSE FALSE  TRUE  TRUE FALSE


Wybierz liczby większe od 10

liczby[liczby > 10]
## [1] 12 15 20


Liczby parzyste

liczby[liczby %% 2 == 0]
## [1] 12  8 20


Liczby między 5 a 15

liczby[liczby >= 5 & liczby <= 15]
## [1]  5 12  8 15  7


Indeksowanie przez nazwy

Nadajemy nazwy elementom

wiek <- c(25, 30, 22, 28)
names(wiek) <- c("Anna", "Jan", "Kasia", "Piotr")
wiek
##  Anna   Jan Kasia Piotr 
##    25    30    22    28


Dostęp przez nazwę

wiek["Jan"]
## Jan 
##  30


Wiele nazw

wiek[c("Anna", "Kasia")]
##  Anna Kasia 
##    25    22


A co w sytuacji?

names(wiek) <- c("Anna", "Jan", "Kasia", "Anna") # 2x "Anna"
wiek["Anna"] # :(
## Anna 
##   25
wiek[c("Anna", "Anna")] # :(
## Anna Anna 
##   25   25
# wówczas:
wiek[names(wiek) == "Anna"]
## Anna Anna 
##   25   28


Recykling

WAŻNE Gdy wektory mają różne długości, R “recykluje” krótszy wektor

c(1, 2, 3, 4) + c(10, 20)  # c(10, 20) jest użyte dwa razy
## [1] 11 22 13 24

UWAGA Gdy długość nie jest wielokrotnością, dostaniemy ostrzeżenie

c(1, 2, 3) + c(10, 20)
## Warning in c(1, 2, 3) + c(10, 20): longer object length is not a multiple of
## shorter object length
## [1] 11 22 13


Zmiana elementów wektora

(wektor <- seq(10,50, by = 10))
## [1] 10 20 30 40 50

Zmiana pojedynczego elementu

wektor[3] <- 999
wektor
## [1]  10  20 999  40  50


Zmiana wielu elementów

wektor[c(1, 5)] <- c(111, 555)
wektor
## [1] 111  20 999  40 555


Dodawanie elementów na końcu

wektor <- c(wektor, 60, 70)
wektor
## [1] 111  20 999  40 555  60  70


Usuwanie elementów

wektor <- wektor[-c(2, 4)]
wektor
## [1] 111 999 555  60  70


Sortowanie i porządkowanie


Dwie przydatne funkcje: sort() & order().

Przydatne np. chcąc wyświetlić posortowaną zmienną (kolumnę) w tabeli lub wymusić inne niż domyślne rysowanie elementów na osiach wykresu.

(liczby <- c(23, 5, 17, 8, 42, 11))
## [1] 23  5 17  8 42 11


Sortowanie rosnąco

sort(liczby)
## [1]  5  8 11 17 23 42


Sortowanie malejąco

sort(liczby, decreasing = TRUE)
## [1] 42 23 17 11  8  5


Indeksy posortowanych elementów

order(liczby)
## [1] 2 4 6 3 1 5


Użycie order() do sortowania

liczby[order(liczby)]
## [1]  5  8 11 17 23 42


Czy operacje się zapisują samoistnie?

(a <- c(100, 10, 1000)) # Stwórzmy szybko przykładowy wektor trójelementowy

order(a)

sort(a)

a[order(a)] # można posortować wektor indeksując go funkcją order(), da taki sam rezultat jak sort()

a # w środku dotychczasowa kolejność

a <- a[order(a)] # chcąc mieć na stałe posortowany wektor musimy go sobie nadpisać

a


Można jeszcze skorzystać z funkcji rank(). w R służy do przypisywania rang wartościom w wektorze. Jest to przydatne, gdy chcemy uporządkować dane według ich wartości, ale zamiast sortować je bezpośrednio, przypisujesz im rangi.

(a <- c(100, 10, 1000))
## [1]  100   10 1000
rank(a)
## [1] 2 1 3

Nieco bardziej skomplikowana sytuacja.

(a <- c(100, 10, 1000, 10, NA))
## [1]  100   10 1000   10   NA
rank(a)
## [1] 3.0 1.5 4.0 1.5 5.0


📖 Samodzielna lektura, szczegóły argumentów funkcji rank() jak zwykle dostępne w dokumentacji funkcji ?rank()


Warto sięgnąć po większą liczbę argumentów tej funkcji. rank(x, na.last = TRUE, ties.method = c("average", "first", "last", "random", "max", "min")) gdzie:

  • x: Wektor numeryczny, złożony, znakowy lub logiczny, którego rangi chcesz obliczyć.

  • na.last: Określa, jak traktować wartości NA. Może być TRUE (domyślnie, NA na końcu), FALSE (NA na początku), NA (usuwa NA) lub “keep” (zachowuje NA z rangą NA).

  • ties.method: Określa sposób traktowania remisów (wartości równych). Opcje to:

    • "average": Przypisuje średnią rangę dla remisów (domyślnie).
    • "first": Przypisuje rangi według pierwszego wystąpienia.
    • "last": Przypisuje rangi według ostatniego wystąpienia.
    • "random": Przypisuje rangi losowo.
    • "max": Przypisuje maksymalną rangę dla remisów.
    • "min": Przypisuje minimalną rangę dla remisów.




Inne przydatne funkcje

(wektor <- c(3, 7, 2, 7, 9, 2, 5, 7))
## [1] 3 7 2 7 9 2 5 7


Unikalne wartości

unique(wektor)
## [1] 3 7 2 9 5


Ile razy występuje każda wartość?

table(wektor)
## wektor
## 2 3 5 7 9 
## 2 1 1 3 1


Czy element występuje w wektorze?

7 %in% wektor
## [1] TRUE
100 %in% wektor
## [1] FALSE


Pozycja elementu

which(wektor == 7)
## [1] 2 4 8


Liczba elementów spełniających warunek

sum(wektor > 5)
## [1] 4


Odwracanie kolejności

rev(wektor)
## [1] 7 5 2 9 7 2 7 3



Bywa, że chcemy szybko podejrzeć zawartość ramki danych lub wektora. Wiemy, że jest długi i print trwałby jakiś czas i zamazał de facto obraz w konsoli. Wówczas możemy posłużyć się funkcjami head(x,n) oraz tail(x,n). Pierwsza wyświetli tyle pierwszych elementów ile podamy w argumencie n, druga tyleż samo tyle że od końca.

# ?head
head(letters, 3)   # [+] pierwsze 3 elementy
## [1] "a" "b" "c"
tail(letters, 3)   # [+] ostatnie 3 elementy
## [1] "x" "y" "z"
head(letters, -23) # [+] ciekawostka: ujemne n = "wszystko oprócz ostatnich 23"
## [1] "a" "b" "c"




Przykłady zastosowań

Filtrowanie danych

Wyniki egzaminów studentów

wyniki <- c(45, 78, 92, 67, 83, 54, 88, 71, 96, 62)
imiona_studentow <- c("Anna", "Bartek", "Celina", "Darek", "Ewa", 
                      "Filip", "Gosia", "Heniek", "Iza", "Janek")


Kto zdał (>= 60 punktów)?

zdali <- wyniki >= 60
imiona_studentow[zdali]
## [1] "Bartek" "Celina" "Darek"  "Ewa"    "Gosia"  "Heniek" "Iza"    "Janek"


Ile wynosił najlepszy wynik?

max(wyniki)
## [1] 96


Kto ma najlepszy wynik?

imiona_studentow[which.max(wyniki)]
## [1] "Iza"


Modyfikacja wektorów

“Budowanie” wektora z innego wektora

id <- c(pierwszy = 1, drugi = 2, trzeci = 3)

# a budować wektor możemy z "części znalezionych na szrocie"
szesc = 6
id2 <- c(czwarty = id[2]*2, piąty = id[3]+2, szósty = szesc)

print(id2)
## czwarty.drugi  piąty.trzeci        szósty 
##             4             5             6




Normalizacja danych


PRZYPOMNIENIE

Normalizacja przeskalowuje wartości zmiennej do ustalonego zakresu, najczęściej \([0, 1]\). Najmniejsza wartość przyjmuje 0, a największa 1.

\[ x' = \frac{x - x_{\min}}{x_{\max} - x_{\min}} \]

Przeskalowanie do dowolnego zakresu \([a, b]\):

\[ x' = a + \frac{(x - x_{\min})(b - a)}{x_{\max} - x_{\min}} \]

Kiedy stosować: gdy potrzebujemy wartości w ograniczonym przedziale, np. w sieciach neuronowych, metodach opartych na odległościach (k-NN, k-średnich) lub przy porównywaniu zmiennych mierzonych w różnych jednostkach.

Uwaga: metoda jest wrażliwa na wartości odstające. Jedna skrajna obserwacja „ściska” pozostałe wartości w wąskim przedziale.

# Dane surowe
dane <- c(10, 15, 8, 20, 12)

# Normalizacja do zakresu [0, 1]
dane_norm <- (dane - min(dane)) / (max(dane) - min(dane))
dane_norm
## [1] 0.1666667 0.5833333 0.0000000 1.0000000 0.3333333
# Inaczej
as.vector(scale(x = dane,
                center = min(dane), 
                scale = max(dane) - min(dane)))
## [1] 0.1666667 0.5833333 0.0000000 1.0000000 0.3333333
# Jeszcze inaczej
scales::rescale(dane)
## [1] 0.1666667 0.5833333 0.0000000 1.0000000 0.3333333
# Takie same wyniki?
all.equal(dane_norm, scales::rescale(dane))
## [1] TRUE




Standaryzacja danych


PRZYPOMNIENIE

Standaryzacja przekształca zmienną tak, aby miała średnią równą 0 i odchylenie standardowe równe 1. Wynik mówi, o ile odchyleń standardowych dana obserwacja leży od średniej.

\[ z = \frac{x - \bar{x}}{s} \]

gdzie:

\[ \bar{x} = \frac{1}{n}\sum_{i=1}^{n} x_i, \qquad s = \sqrt{\frac{1}{n-1}\sum_{i=1}^{n}(x_i - \bar{x})^2} \]

Kiedy stosować: w PCA, regresji, analizie skupień, a także gdy chcemy porównać obserwacje z różnych rozkładów.

Uwaga: wartości nie są ograniczone do żadnego przedziału. Standaryzacja nie zmienia kształtu rozkładu, więc nie czyni danych normalnymi.

# Dane surowe
dane <- c(10, 15, 8, 20, 12)

# Standaryzacja (średnia = 0, sd = 1)
dane_std <- (dane - mean(dane)) / sd(dane)
dane_std
## [1] -0.6396021  0.4264014 -1.0660036  1.4924050 -0.2132007
round(mean(dane_std), 10)  # Sprawdzenie (w przybliżeniu 0)
## [1] 0
sd(dane_std)               # Sprawdzenie (równe 1)
## [1] 1
# To samo za pomocą wbudowanej funkcji
as.vector(scale(dane))
## [1] -0.6396021  0.4264014 -1.0660036  1.4924050 -0.2132007



sd() w R liczy odchylenie z próby, czyli z mianownikiem \(n-1\)

?sd() —> Details: Like var this uses denominator n−1

scale() zwraca macierz z atrybutami, stąd w przykładzie as.vector()



Cecha Normalizacja (min-max) Standaryzacja (z-score)
Wzór \(\frac{x - x_{\min}}{x_{\max} - x_{\min}}\) \(\frac{x - \bar{x}}{s}\)
Zakres wyników \([0, 1]\) nieograniczony
Średnia po przekształceniu zależy od danych 0
Odchylenie standardowe zależy od danych 1
Wrażliwość na outliery duża mniejsza


hist(dane)

hist(dane_norm)

hist(dane_std)




Losowość i symulacje


Funkcja set.seed() w R służy do ustawienia ziarna generatora liczb losowych, dzięki czemu wyniki losowań są powtarzalne.

Za każdym razem, gdy używamy ‘funkcji losowych’ (np. sample(), runif(), rnorm() itd.), R generuje liczby w sposób pseudo-losowy — tzn. opiera się na algorytmie, który startuje od pewnego ziarna (ang. seed). R korzysta z tzw. PRNG (Pseudo-Random Number Generator). Domyślnie jest to algorytm Mersenne Twister. To bardzo popularny i szybki algorytm generowania liczb pseudo-losowych (opracowany w 1997 roku przez Matsumoto i Nishimura).


Symulacja


Rzut kostką 1000 razy. Jaki to będzie rozkład? Jaką zmienną operujemy?

set.seed(123)  # Dla powtarzalności
rzuty <- sample(1:6, size = 1000, replace = TRUE)

# Rozkład wyników
table(rzuty)
## rzuty
##   1   2   3   4   5   6 
## 170 176 171 157 162 164
hist(rzuty)

plot(rzuty)

# Prawdopodobieństwo empiryczne każdej ścianki
table(rzuty) / length(rzuty)
## rzuty
##     1     2     3     4     5     6 
## 0.170 0.176 0.171 0.157 0.162 0.164


Rozkład jednostajny dyskretny (uniform discrete distribution)

  • skończona liczba możliwych wartości
  • każda wartość ma takie samo prawdopodobieństwo
  • zmienna jest dyskretna, bo przyjmuje konkretne, policzalne wartości (np. w konktrze do zmiennej ciągłej - dowolne wartości z pewnego przedziału)



Prawo wielkich liczb z cumsum() na rzutach kostką


Czy częstość szóstek naprawdę zbliża się do 1/6? Sprawdźmy, jak zmienia się wraz z kolejnymi rzutami.

Funkcja cumsum() liczy sumę narastającą, przykład:

1:10 # sekwencja
##  [1]  1  2  3  4  5  6  7  8  9 10
# ?cumsum()
cumsum(1:10)
##  [1]  1  3  6 10 15 21 28 36 45 55


I teraz mała wizualizacja ważnego prawa.

Rzucamy kostką.

# Rzucamy ponownie kostką:
set.seed(123)  # Dla powtarzalności
rzuty <- sample(1:6, # ?sample
                size = 1000, 
                replace = TRUE)

# Sprawdźmy   
(liczba_szostek <- sum(rzuty == 6))   # ile faktycznie wypadło
## [1] 164
(oczekiwana     <- length(rzuty) / 6) # ile "powinno" wypaść (np. 166.67 przy 1000 rzutach)
## [1] 166.6667
liczba_szostek - oczekiwana           # odchylenie w sztukach, szóstej jest mniej niż oczekiwano, o ile?
## [1] -2.666667
mean(rzuty == 6)                      # odsetek szóstek
## [1] 0.164
czestosc_szostek <- cumsum( # 2. cumsum(...) liczy sumę narastającą. R traktuje TRUE jako 1, a FALSE jako 0, więc wynik mówi, ile szóstek było do danego rzutu włącznie
  rzuty == 6 # 1. sprawdza każdy rzut, czy wypadła szóstka, i daje wektor logiczny: FALSE TRUE 
  ) / seq_along(rzuty) # 3. numery rzutów, dzielenie daje odsetek szóstek po każdym kolejnym rzucie
# Częstość szóstek w kilku "newralgicznych" momentach
czestosc_szostek[c(10, 50, 100, 500, 1000)]
## [1] 0.300 0.140 0.210 0.176 0.164


Jak szybko zbliżamy się do 1/6?

# 
plot(czestosc_szostek, 
     type = "l",
     xlab = "Liczba rzutów", 
     ylab = "Odsetek szóstek")
abline(h = 1/6, col = "red", lty = 2)

To prawo wielkich liczb: przy małej liczbie prób częstość skacze, przy dużej się stabilizuje. Złudzenie gracza (gambler’s fallacy) polega na oczekiwaniu, że to wyrównanie nastąpi „od razu” – np. że po serii orłów reszka jest „należna”.



Wektory „predefiniowane”

# przykłady
letters
##  [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s"
## [20] "t" "u" "v" "w" "x" "y" "z"
LETTERS
##  [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S"
## [20] "T" "U" "V" "W" "X" "Y" "Z"
month.abb
##  [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"
month.name
##  [1] "January"   "February"  "March"     "April"     "May"       "June"     
##  [7] "July"      "August"    "September" "October"   "November"  "December"
rev(LETTERS) # funkcja odwracająca kolejność wektora
##  [1] "Z" "Y" "X" "W" "V" "U" "T" "S" "R" "Q" "P" "O" "N" "M" "L" "K" "J" "I" "H"
## [20] "G" "F" "E" "D" "C" "B" "A"


Co można zrobić na takich wektorach? Na przykład zastosować funkcję length()

length(letters)
## [1] 26
length(c(month.abb, month.name))
## [1] 24


Konwersja typów przy łączeniu wektorów


Konwersja typów.

Przypomnijmy sobie typy danych 3 wektorów w środowisku

class(letters)
## [1] "character"
class(trufolsy)
## [1] "logical"
class(sekwencjaNumericow)
## [1] "numeric"
(wektor = c(letters, trufolsy)); class(wektor)
##  [1] "a"     "b"     "c"     "d"     "e"     "f"     "g"     "h"     "i"    
## [10] "j"     "k"     "l"     "m"     "n"     "o"     "p"     "q"     "r"    
## [19] "s"     "t"     "u"     "v"     "w"     "x"     "y"     "z"     "TRUE" 
## [28] "FALSE" "TRUE"  "FALSE" "TRUE"  "FALSE" "TRUE"  "FALSE" "TRUE"  "FALSE"
## [1] "character"
(wektor = c(letters, sekwencjaNumericow)); class(wektor) # co wynika ze wspomnianej wcześniej hierarchii typów
##  [1] "a"                "b"                "c"                "d"               
##  [5] "e"                "f"                "g"                "h"               
##  [9] "i"                "j"                "k"                "l"               
## [13] "m"                "n"                "o"                "p"               
## [17] "q"                "r"                "s"                "t"               
## [21] "u"                "v"                "w"                "x"               
## [25] "y"                "z"                "1"                "1.47368421052632"
## [29] "1.94736842105263" "2.42105263157895" "2.89473684210526" "3.36842105263158"
## [33] "3.84210526315789" "4.31578947368421" "4.78947368421053" "5.26315789473684"
## [37] "5.73684210526316" "6.21052631578947" "6.68421052631579" "7.15789473684211"
## [41] "7.63157894736842" "8.10526315789474" "8.57894736842105" "9.05263157894737"
## [45] "9.52631578947368" "10"
## [1] "character"
(wektor = c(trufolsy, sekwencjaNumericow)); class(wektor) # co wynika ze wspomnianej wcześniej hierarchii typów
##  [1]  1.000000  0.000000  1.000000  0.000000  1.000000  0.000000  1.000000
##  [8]  0.000000  1.000000  0.000000  1.000000  1.473684  1.947368  2.421053
## [15]  2.894737  3.368421  3.842105  4.315789  4.789474  5.263158  5.736842
## [22]  6.210526  6.684211  7.157895  7.631579  8.105263  8.578947  9.052632
## [29]  9.526316 10.000000
## [1] "numeric"


Transpozycja wektora

oceny_stat_R # obejrzyjmy
##   klasowka  aktywnosc sprawdzian sprawdzian    projekt 
##          4          5          2          2          3
class(oceny_stat_R)
## [1] "numeric"
is.vector(oceny_stat_R)
## [1] TRUE
t(oceny_stat_R) # transponujmy
##      klasowka aktywnosc sprawdzian sprawdzian projekt
## [1,]        4         5          2          2       3
class(t(oceny_stat_R)); is.vector(t(oceny_stat_R))
## [1] "matrix" "array"
## [1] FALSE
# macierz wierszowa (1×5)

t(t(oceny_stat_R)) # transponujmy "do kwadratu", odwrócimy proces? :)
##            [,1]
## klasowka      4
## aktywnosc     5
## sprawdzian    2
## sprawdzian    2
## projekt       3
class(t(t(oceny_stat_R))); is.vector(t(t(oceny_stat_R))) # macierz wierszowa (5X1)
## [1] "matrix" "array"
## [1] FALSE

================================

UTRWALENIE

  1. Utwórzmy nazwany wektor swoich czasów dojazdu na uczelnię (w minutach) z ostatnich 5 dni roboczych. Policz średnią, medianę i odchylenie standardowe. Który dzień był najgorszy?
  2. Z wektora c(5, 12, 8, 3, 15, 20, 7) wybierz elementy większe od jego średniej.
  3. Na podstawie wektora czestosc_szostek ustalmy, od którego rzutu odsetek szóstek już na stałe mieści się w przedziale 0.15–0.18. ★
# 1
dojazd <- c(pon = 42, wt = 38, sr = 55, czw = 40, pt = 61)
mean(dojazd); median(dojazd); sd(dojazd)
names(dojazd)[which.max(dojazd)]

# 2
v <- c(5, 12, 8, 3, 15, 20, 7)
v[v > mean(v)]

# 3
poza_przedzialem <- which(czestosc_szostek < 0.15 | czestosc_szostek > 0.18)
max(poza_przedzialem) + 1   # pierwszy rzut, po którym już nigdy nie wychodzimy poza przedział
plot(czestosc_szostek, type = "l")
abline(h = c(0.15, 0.18), col = "blue", lty = 3)
abline(v = max(poza_przedzialem) + 1, col = "darkgreen")



Do zapamiętania

  1. Wektor = podstawowa struktura danych w R
  2. Wszystkie elementy muszą być tego samego typu
  3. Indeksowanie od 1, nie od 0!
  4. Operacje są wektoryzowane (działają element po elemencie)
  5. Krótsze wektory są recyklingowane w operacjach
  6. NA wymaga specjalnej obsługi (na.rm = TRUE)
  7. Indeksowanie logiczne to potężne narzędzie





Matrices


Stwórzmy pierwszą macierz matrix()

# ?matrix
h <- help("matrix")
tools::Rd2txt(utils:::.getHelpFile(h),
              options = list(underline_titles = FALSE))
## Matrices
## 
## Description:
## 
##      'matrix' creates a matrix from the given set of values.
## 
##      'as.matrix' attempts to turn its argument into a matrix.
## 
##      'is.matrix' tests if its argument is a (strict) matrix.
## 
## Usage:
## 
##      matrix(data = NA, nrow = 1, ncol = 1, byrow = FALSE,
##             dimnames = NULL)
##      
##      as.matrix(x, ...)
##      ## S3 method for class 'data.frame'
##      as.matrix(x, rownames.force = NA, ...)
##      
##      is.matrix(x)
##      
## Arguments:
## 
##     data: an optional data vector (including a list or 'expression'
##           vector).  Non-atomic classed R objects are coerced by
##           'as.vector' and all attributes discarded.
## 
##     nrow: the desired number of rows.
## 
##     ncol: the desired number of columns.
## 
##    byrow: logical. If 'FALSE' (the default) the matrix is filled by
##           columns, otherwise the matrix is filled by rows.
## 
## dimnames: a 'dimnames' attribute for the matrix: 'NULL' or a 'list' of
##           length 2 giving the row and column names respectively.  An
##           empty list is treated as 'NULL', and a list of length one as
##           row names.  The list can be named, and the list names will be
##           used as names for the dimensions.
## 
##        x: an R object.
## 
##      ...: additional arguments to be passed to or from methods.
## 
## rownames.force: logical indicating if the resulting matrix should have
##           character (rather than 'NULL') 'rownames'.  The default,
##           'NA', uses 'NULL' rownames if the data frame has 'automatic'
##           row.names or for a zero-row data frame.
## 
## Details:
## 
##      If one of 'nrow' or 'ncol' is not given, an attempt is made to
##      infer it from the length of 'data' and the other parameter.  If
##      neither is given, a one-column matrix is returned.
## 
##      If there are too few elements in 'data' to fill the matrix, then
##      the elements in 'data' are recycled.  If 'data' has length zero,
##      'NA' of an appropriate type is used for atomic vectors ('0' for
##      raw vectors) and 'NULL' for lists.
## 
##      'is.matrix' returns 'TRUE' if 'x' is a vector and has a '"dim"'
##      attribute of length 2 and 'FALSE' otherwise.  Note that a
##      'data.frame' is *not* a matrix by this test.  The function is
##      generic: you can write methods to handle specific classes of
##      objects, see InternalMethods.
## 
##      'as.matrix' is a generic function.  The method for data frames
##      will return a character matrix if there is only atomic columns and
##      any non-(numeric/logical/complex) column, applying 'as.vector' to
##      factors and 'format' to other non-character columns.  Otherwise,
##      the usual coercion hierarchy (logical < integer < double <
##      complex) will be used, e.g., all-logical data frames will be
##      coerced to a logical matrix, mixed logical-integer will give a
##      integer matrix, etc.
## 
##      The default method for 'as.matrix' calls 'as.vector(x)', and hence
##      e.g. coerces factors to character vectors.
## 
##      When coercing a vector, it produces a one-column matrix, and
##      promotes the names (if any) of the vector to the rownames of the
##      matrix.
## 
##      'is.matrix' is a primitive function.
## 
##      The 'print' method for a matrix gives a rectangular layout with
##      dimnames or indices.  For a list matrix, the entries of length not
##      one are printed in the form 'integer,7' indicating the type and
##      length.
## 
## Note:
## 
##      If you just want to convert a vector to a matrix, something like
## 
##        dim(x) <- c(nx, ny)
##        dimnames(x) <- list(row_names, col_names)
##      
##      will avoid duplicating 'x' _and_ preserve 'class(x)' which may be
##      useful, e.g., for 'Date' objects.
## 
## References:
## 
##      Becker RA, Chambers JM, Wilks AR (1988). _The New S Language_.
##      Chapman and Hall/CRC, London. ISBN 053409192X.
## 
## See Also:
## 
##      'data.matrix', which attempts to convert to a numeric matrix.
## 
##      A matrix is the special case of a two-dimensional 'array'.
##      'inherits(m, "array")' is true for a 'matrix' 'm'.
## 
## Examples:
## 
##      is.matrix(as.matrix(1:10))
##      !is.matrix(warpbreaks)  # data.frame, NOT matrix!
##      warpbreaks[1:10,]
##      as.matrix(warpbreaks[1:10,])  # using as.matrix.data.frame(.) method
##      
##      ## Example of setting row and column names
##      mdat <- matrix(c(1,2,3, 11,12,13), nrow = 2, ncol = 3, byrow = TRUE,
##                     dimnames = list(c("row1", "row2"),
##                                     c("C.1", "C.2", "C.3")))
##      mdat
## 
#  3 wiersze zawierającą cyfry od 1 do 9
matrix(1:9, byrow = TRUE, nrow = 3)
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6
## [3,]    7    8    9


# oceny semestralne z trzech przedmiotów
stat_R <- c(2,3) # vector()
filozofia <- c(5,5)
matematyka <- c(4,4)

# połączone w wektor
oceny <- c(stat_R, filozofia, matematyka)

# ramka (matryca) z ocenami
oceny_matrix <- matrix(oceny, nrow = 3, byrow = T)
print(oceny_matrix)
##      [,1] [,2]
## [1,]    2    3
## [2,]    5    5
## [3,]    4    4


colnames() & rownames() -> nazywanie ramki

# szykowne nazwy wierszy i kolumn
nazwa_przedmiotu <- c("Statystyka w R", "Filozofia analityczna", "Matematyka Euklidesowa")
semestry <- c("Semestr 1", "Semestr 2")
# przypisanie ich do ramki
colnames(oceny_matrix) <- semestry
rownames(oceny_matrix) <- nazwa_przedmiotu
# Voici
print(oceny_matrix)
##                        Semestr 1 Semestr 2
## Statystyka w R                 2         3
## Filozofia analityczna          5         5
## Matematyka Euklidesowa         4         4


# metoda "od razu"
oceny_matrix <- matrix(oceny, 
                       nrow = 3, 
                       byrow = T, 
                       dimnames = list(nazwa_przedmiotu, semestry)) # jeszcze w sumie nie znamy typu lista
oceny_matrix
##                        Semestr 1 Semestr 2
## Statystyka w R                 2         3
## Filozofia analityczna          5         5
## Matematyka Euklidesowa         4         4
# średnia ocen na koniec roku?
rowMeans(oceny_matrix)
##         Statystyka w R  Filozofia analityczna Matematyka Euklidesowa 
##                    2.5                    5.0                    4.0


Atrybuty macierzy

# klasa utworzonego obiektu
class(oceny_matrix)
## [1] "matrix" "array"
# struktura
str(oceny_matrix)
##  num [1:3, 1:2] 2 5 4 3 5 4
##  - attr(*, "dimnames")=List of 2
##   ..$ : chr [1:3] "Statystyka w R" "Filozofia analityczna" "Matematyka Euklidesowa"
##   ..$ : chr [1:2] "Semestr 1" "Semestr 2"
# inne 
rownames(oceny_matrix)
## [1] "Statystyka w R"         "Filozofia analityczna"  "Matematyka Euklidesowa"
colnames(oceny_matrix)
## [1] "Semestr 1" "Semestr 2"
dimnames(oceny_matrix)
## [[1]]
## [1] "Statystyka w R"         "Filozofia analityczna"  "Matematyka Euklidesowa"
## 
## [[2]]
## [1] "Semestr 1" "Semestr 2"
nrow(oceny_matrix)
## [1] 3
ncol(oceny_matrix)
## [1] 2
dim(oceny_matrix)
## [1] 3 2


Rozszerzanie ramki

# zapomnieliśmy o kulturze ciała, skupiając się na kulturze umysłu
wych_fiz <- c(6,6)

# dodajmy zatem nowy przedmiot do naszej ramki
oceny_matrix <- rbind(oceny_matrix, wych_fiz)
oceny_matrix
##                        Semestr 1 Semestr 2
## Statystyka w R                 2         3
## Filozofia analityczna          5         5
## Matematyka Euklidesowa         4         4
## wych_fiz                       6         6
# poprawmy nazwę przedmiotu (jesteśmy wszakże estetami)
rownames(oceny_matrix)[4] <- c("Wychowanie fizyczne")

# obliczmy średnią ocen ponownie, przypiszmy do zmiennej
srednia_ocen <- rowMeans(oceny_matrix)
srednia_ocen
##         Statystyka w R  Filozofia analityczna Matematyka Euklidesowa 
##                    2.5                    5.0                    4.0 
##    Wychowanie fizyczne 
##                    6.0
# dodajmy podsumowującą kolumnę do ramki
oceny_matrix <- cbind(oceny_matrix, srednia_ocen)
oceny_matrix
##                        Semestr 1 Semestr 2 srednia_ocen
## Statystyka w R                 2         3          2.5
## Filozofia analityczna          5         5          5.0
## Matematyka Euklidesowa         4         4          4.0
## Wychowanie fizyczne            6         6          6.0
# a jak nam szło w poszczególnych semestrach?
colMeans(oceny_matrix)
##    Semestr 1    Semestr 2 srednia_ocen 
##        4.250        4.500        4.375


Wyciąganie elementów z matrycy

# Pani mówi, że 'włef' nie liczy się do średniej :(
dim(oceny_matrix)
## [1] 4 3
oceny_matrix2 <- oceny_matrix[1:3,] # wyrzucamy w-f
oceny_matrix2
##                        Semestr 1 Semestr 2 srednia_ocen
## Statystyka w R                 2         3          2.5
## Filozofia analityczna          5         5          5.0
## Matematyka Euklidesowa         4         4          4.0
srednia_semestr <- (colMeans(oceny_matrix2)) # obliczamy ponownie średnie semestralne

# i dodajemy do naszej ramki
oceny_matrix2 <- rbind(oceny_matrix2, srednia_semestr)
oceny_matrix2
##                        Semestr 1 Semestr 2 srednia_ocen
## Statystyka w R          2.000000         3     2.500000
## Filozofia analityczna   5.000000         5     5.000000
## Matematyka Euklidesowa  4.000000         4     4.000000
## srednia_semestr         3.666667         4     3.833333
# przypominam o tym, że jesteśmy estetami
colnames(oceny_matrix2)[3] <- "Średnia ocen z semestru"
oceny_matrix2
##                        Semestr 1 Semestr 2 Średnia ocen z semestru
## Statystyka w R          2.000000         3                2.500000
## Filozofia analityczna   5.000000         5                5.000000
## Matematyka Euklidesowa  4.000000         4                4.000000
## srednia_semestr         3.666667         4                3.833333


# Pan od w-f ogarnął się, że na studiach nie ma szóstek, to nie podstawówka
oceny_matrix[4,1:2] <- oceny_matrix[4,1:2] - 1
oceny_matrix
##                        Semestr 1 Semestr 2 srednia_ocen
## Statystyka w R                 2         3          2.5
## Filozofia analityczna          5         5          5.0
## Matematyka Euklidesowa         4         4          4.0
## Wychowanie fizyczne            5         5          6.0
# a Pan od R'a dostał poprawiony, tym razem zrobiony samodzielnie, projekt z R markdown'a
oceny_matrix[1,1] <- 3.5
oceny_matrix 
##                        Semestr 1 Semestr 2 srednia_ocen
## Statystyka w R               3.5         3          2.5
## Filozofia analityczna        5.0         5          5.0
## Matematyka Euklidesowa       4.0         4          4.0
## Wychowanie fizyczne          5.0         5          6.0
# tylko średnie trzeba by ponownie przeliczyć i podmienić
oceny_matrix[1,3] <- mean(c(oceny_matrix[1,1], oceny_matrix[1,2])) # funkcja mean()
oceny_matrix
##                        Semestr 1 Semestr 2 srednia_ocen
## Statystyka w R               3.5         3         3.25
## Filozofia analityczna        5.0         5         5.00
## Matematyka Euklidesowa       4.0         4         4.00
## Wychowanie fizyczne          5.0         5         6.00


Skoro już jesteśmy tacy schludni i perfekcyjni, to stwierdzamy, że drażni nas, że R zrobił nam z dwóch kolumn liczby wysokiej precyzji (typu double, numeric, what ever language do you use) i duża liczba znaków się nam wyświetla

# przypomnijmy sobie kształt ramki
oceny_matrix2
##                        Semestr 1 Semestr 2 Średnia ocen z semestru
## Statystyka w R          2.000000         3                2.500000
## Filozofia analityczna   5.000000         5                5.000000
## Matematyka Euklidesowa  4.000000         4                4.000000
## srednia_semestr         3.666667         4                3.833333
# poznajmy round()
oceny_matrix2[,1] <- round(oceny_matrix2[,1], 2)
oceny_matrix2[,3] <- round(oceny_matrix2[,3], 2) # [poprawka: było round(oceny_matrix2[,1], 2)]

oceny_matrix2 # much better
##                        Semestr 1 Semestr 2 Średnia ocen z semestru
## Statystyka w R              2.00         3                    2.50
## Filozofia analityczna       5.00         5                    5.00
## Matematyka Euklidesowa      4.00         4                    4.00
## srednia_semestr             3.67         4                    3.83


I taka jeszcze własność macierzy

oceny_matrix # przypomnijmy sobie kształt naszej ramki
##                        Semestr 1 Semestr 2 srednia_ocen
## Statystyka w R               3.5         3         3.25
## Filozofia analityczna        5.0         5         5.00
## Matematyka Euklidesowa       4.0         4         4.00
## Wychowanie fizyczne          5.0         5         6.00
# koleżanka nie lubi kolegi, nie odpowiedział na jej afekt, a że jest kocórką IT, zhakowała USOS i obniżyła mu oceny ze wszystkich przedmiotów
oceny_matrix - 1
##                        Semestr 1 Semestr 2 srednia_ocen
## Statystyka w R               2.5         2         2.25
## Filozofia analityczna        4.0         4         4.00
## Matematyka Euklidesowa       3.0         3         3.00
## Wychowanie fizyczne          4.0         4         5.00
# szczęśliwie nie nadpisała wyników ;)
oceny_matrix[oceny_matrix == 4]
## [1] 4 4 4
which(oceny_matrix == 4, arr.ind = TRUE)
##                        row col
## Matematyka Euklidesowa   3   1
## Matematyka Euklidesowa   3   2
## Matematyka Euklidesowa   3   3


Drobny wtręt. Badając klasę (class()) macierzy otrzymaliśmy informację, że nasz obiekt to zarówno matrix, jak i array.

Krótka różnica między dwoma obiektami na przykładzie poniżej:

# Macierz 2x3
(macierz_przyklad <- matrix(data = 1:6, 
                            nrow = 2, 
                            ncol = 3)
 )
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    2    4    6
# Tablica - # Array z 3 wymiarami (wiersze × kolumny × "warstwy")
(tablica_przyklad <- array(1:24, dim = c(3, 4, 2)))
## , , 1
## 
##      [,1] [,2] [,3] [,4]
## [1,]    1    4    7   10
## [2,]    2    5    8   11
## [3,]    3    6    9   12
## 
## , , 2
## 
##      [,1] [,2] [,3] [,4]
## [1,]   13   16   19   22
## [2,]   14   17   20   23
## [3,]   15   18   21   24
# To tworzy strukturę:
# - 3 wiersze
# - 4 kolumny  
# - 2 "warstwy" (można myśleć o tym jak o dwóch matrycach 3×4)

Główną różnicą między tymi dwoma obiektami jest to, że macierz (matrix) jest szczególnym, bo dwuwymiarowym typem tablicy (array).

Tablica jest co do zasady wielowymiarową strukturą danych, która - ważne zastrzeżenie - przechowuje elementy tego samego typu (w przeciwieństwie do listy, która co prawda jest jednowymiarowa, ale może przechowywać elementy różnych typów, a zatem również listy, co de facto czynią ją w jakimś sensie wielowymiarową… bełkot, szczegóły później).

📖 Poniższe przykłady tablic trójwymiarowych – do samodzielnej lektury.

“Dane o sprzedaży”

# Wyobraźmy sobie dane o sprzedaży:
# - 3 produkty (wiersze)
# - 4 kwartały (kolumny)
# - 2 lata (trzeci wymiar)

sprzedaz <- array(
  data = c(100, 150, 200, 120, 180, 220, 110, 160, 210, 130, 170, 230,
           105, 155, 205, 125, 185, 225, 115, 165, 215, 135, 175, 235),
  dim = c(3, 4, 2),
  dimnames = list(
    Produkt = c("A", "B", "C"),
    Kwartal = c("Q1", "Q2", "Q3", "Q4"),
    Rok = c("2023", "2024")
  )
)

print(sprzedaz)
## , , Rok = 2023
## 
##        Kwartal
## Produkt  Q1  Q2  Q3  Q4
##       A 100 120 110 130
##       B 150 180 160 170
##       C 200 220 210 230
## 
## , , Rok = 2024
## 
##        Kwartal
## Produkt  Q1  Q2  Q3  Q4
##       A 105 125 115 135
##       B 155 185 165 175
##       C 205 225 215 235
# Dostęp do danych z 2024 roku
sprzedaz[, , "2024"]
##        Kwartal
## Produkt  Q1  Q2  Q3  Q4
##       A 105 125 115 135
##       B 155 185 165 175
##       C 205 225 215 235
# Sprzedaż produktu B w Q2 w 2023
sprzedaz["B", "Q2", "2023"]
## [1] 180
# Wszystkie kwartały produktu A w 2024
sprzedaz["A", , "2024"]
##  Q1  Q2  Q3  Q4 
## 105 125 115 135


“Dane klimatyczne”

# Temperatura w 12 miastach, przez 365 dni, przez 5 lat
temperatura <- array(
  data = rnorm(12 * 365 * 5, mean = 15, sd = 8), # ?rnorm
  dim = c(12, 365, 5),
  dimnames = list(
    Miasto = paste("Miasto", 1:12), # paste()
    Dzien = paste("Dzien", 1:365),
    Rok = 2020:2024
  )
)

# Średnia temperatura w Mieście 1 w 2023 roku
mean(temperatura["Miasto 1", , "2023"])
## [1] 14.71933
# Średnia temperatura w 100. dniu we wszystkich miastach i latach
mean(temperatura[, "Dzien 100", ])
## [1] 13.29183
# Temperatura w pierwszych 7 dniach 2024 we wszystkich miastach
temperatura[, 1:7, "2024"]
##            Dzien
## Miasto        Dzien 1   Dzien 2   Dzien 3   Dzien 4   Dzien 5    Dzien 6
##   Miasto 1   7.477119  3.713587 29.225071 18.058943  6.203891 20.6495119
##   Miasto 2  30.695097 19.435071 19.370700 11.037804 10.086581 16.0537982
##   Miasto 3  22.202342  6.356843 14.189831 18.532499  2.014956  4.0519486
##   Miasto 4   6.160996 26.635796 10.939643 11.178563 22.917492 25.4904154
##   Miasto 5  11.621271 29.959395  7.268408 22.996425 10.880762 24.2685480
##   Miasto 6  22.335050 14.009295 22.959724 15.492688  4.215175 12.7449977
##   Miasto 7  19.537517 23.920289 15.160943  4.977832  5.282400 11.7093016
##   Miasto 8   1.916444 25.572578  7.943781 29.849256 18.175985  8.9591161
##   Miasto 9  10.159352 23.848407 17.606966  1.925264  9.418478 24.5315721
##   Miasto 10 20.811871 18.800776  7.600795 10.947246 12.540863 -0.3562451
##   Miasto 11 11.418740 25.395391  5.554431  9.370389 22.475856  6.0891554
##   Miasto 12 29.717829 13.747817 16.787108 25.590283  3.909891 20.1860768
##            Dzien
## Miasto        Dzien 7
##   Miasto 1  33.017186
##   Miasto 2  11.050696
##   Miasto 3  24.807897
##   Miasto 4  14.071722
##   Miasto 5   9.294620
##   Miasto 6  14.403130
##   Miasto 7   9.612360
##   Miasto 8  17.964550
##   Miasto 9  14.360051
##   Miasto 10  9.595822
##   Miasto 11  5.575756
##   Miasto 12 27.045535





Factors


Factor to tzw. zmienna kategorialna - nominalna bądź porządkowa.

Dane kategorialne (jakościowe) to takie, które przyjmują wartości z ograniczonego zestawu kategorii.

Przykłady:

  • Płeć: “kobieta”, “mężczyzna”

  • Grupa krwi: “A”, “B”, “AB”, “0”

  • Poziom wykształcenia: “podstawowe”, “średnie”, “wyższe”

  • Ocena: “niedostateczny”, “dostateczny”, “dobry”, “bardzo dobry”, “celujący”



* Nominalna

# Zdefiniujmy 'zwykły' wektor zawierający płcie 
# (załóżmy, omijając zgrabnie współczesne dywagacje, że mamy dwie płcie)
(sex_vector <- c("Mężczyzna", "Kobieta", "Kobieta", "Mężczyzna", "Mężczyzna"))
## [1] "Mężczyzna" "Kobieta"   "Kobieta"   "Mężczyzna" "Mężczyzna"
# skonwertujmy typ zmiennej na factor, przypiszmy do nowej zmiennej i zobaczmy rezultat
(factor_sex_vector <- as.factor(sex_vector))
## [1] Mężczyzna Kobieta   Kobieta   Mężczyzna Mężczyzna
## Levels: Kobieta Mężczyzna


* Porządkowa

temperatura_vector <- c("Wysoka", "Niska", "Wysoka","Niska", "Średnia")
factor_temperatura_vector <- factor(temperatura_vector, 
                                    order = TRUE, # dookreślamy zarówno czy jest to zmienna porządkowa
                                    levels = c("Niska", "Średnia", "Wysoka")) #  oraz jak ten porządek rzeczy się układa

factor_temperatura_vector
## [1] Wysoka  Niska   Wysoka  Niska   Średnia
## Levels: Niska < Średnia < Wysoka
# Możemy sprawdzić liczbę poziomów w następujący sposób
nlevels(factor_temperatura_vector)
## [1] 3


Poziomy zmiennej

# tworzymy wektor 
vector_plci <- c("M", "F", "F", "M", "M")
# 'faktoryzujemy' go
factor_vector_plci <- as.factor(vector_plci)
# w kolejnym kroku nazywamy poziomy
levels(factor_vector_plci) <- c("Kobieta", "Mężczyzna") # nieco podobnie jak nazywaliśmy elementy wektora funkcją names()
# i otrzymujemy
factor_vector_plci
## [1] Mężczyzna Kobieta   Kobieta   Mężczyzna Mężczyzna
## Levels: Kobieta Mężczyzna
# zobaczmy różnice w podsumowaniu pozornie identycznych obiektów
summary(vector_plci)
##    Length  N.unique   N.blank Min.nchar Max.nchar 
##         5         2         0         1         1
summary(factor_vector_plci)
##   Kobieta Mężczyzna 
##         2         3


Własność kategorialnej zmiennej porządkowej

gosciu <- factor_vector_plci[1]
gosciuwa <- factor_vector_plci[2]
# czy facet jest "silniejszy"/większy od kobiety? Sprawdźmy operatorem >
gosciu > gosciuwa # [+] NA + ostrzeżenie: factor nominalny nie ma porządku – i bardzo dobrze :)
## [1] NA
# ale co z temperaturą? Czy średnia jest niższa niż wysoka?
srednia <- factor_temperatura_vector[5] # [poprawka: było temperatura_vector[5], czyli zwykły tekst]
wysoka <- factor_temperatura_vector[1]  # [poprawka: było temperatura_vector[1]]
srednia < wysoka # zaiste :P
## [1] TRUE


Tabele częstości - zliczanie wystąpień

# factor
(kolory <- factor(c("czerwony", "niebieski","czerwony", "czerwony", "zielony", 
                   "niebieski", "czerwony", "zielony","czerwony", "niebieski")))
##  [1] czerwony  niebieski czerwony  czerwony  zielony   niebieski czerwony 
##  [8] zielony   czerwony  niebieski
## Levels: czerwony niebieski zielony
# wygląda jak tabela częstości
summary(kolory) 
##  czerwony niebieski   zielony 
##         5         3         2
# Tabela częstości - "jawnie"
table(kolory)
## kolory
##  czerwony niebieski   zielony 
##         5         3         2
# Proporcje
prop.table(table(kolory)) # ?prop.table
## kolory
##  czerwony niebieski   zielony 
##       0.5       0.3       0.2
# Procenty
prop.table(table(kolory)) * 100
## kolory
##  czerwony niebieski   zielony 
##        50        30        20


# Dane
plec <- factor(c("K", "M", "K", "M", "K", "M", "K", "M"))
kierunek <- factor(c("Info", "Info", "Mat", "Mat", "Info", "Fiz", "Mat", "Fiz"))

# Tabela krzyżowa
table(plec, kierunek)
##     kierunek
## plec Fiz Info Mat
##    K   0    2   2
##    M   2    1   1
# Z proporcjami
prop.table(table(plec, kierunek))
##     kierunek
## plec   Fiz  Info   Mat
##    K 0.000 0.250 0.250
##    M 0.250 0.125 0.125
# Proporcje w wierszach
prop.table(table(plec, kierunek), margin = 1)
##     kierunek
## plec  Fiz Info  Mat
##    K 0.00 0.50 0.50
##    M 0.50 0.25 0.25
# Proporcje w kolumnach
prop.table(table(plec, kierunek), margin = 2)
##     kierunek
## plec       Fiz      Info       Mat
##    K 0.0000000 0.6666667 0.6666667
##    M 1.0000000 0.3333333 0.3333333


Modyfikowanie faktorów

# Tworzenie faktora
oceny <- factor(c("dobry", "dostateczny", "bardzo dobry", "dobry"))

# Dodanie nowego elementu - trzeba najpierw dodać poziom!
oceny[5] <- "celujący"  # Ostrzeżenie! Powstaje NA

# Prawidłowy sposób:
levels(oceny) <- c(levels(oceny), "celujący")
oceny[5] <- "celujący"
print(oceny)
## [1] dobry        dostateczny  bardzo dobry dobry        celujący    
## Levels: bardzo dobry dobry dostateczny celujący
# Usunięcie nieużywanych poziomów
oceny <- factor(c("dobry", "dostateczny", "bardzo dobry", "dobry"))
oceny <- oceny[oceny != "bardzo dobry"]  # Usunięcie elementu
print(levels(oceny))  # "bardzo dobry" nadal jest poziomem!
## [1] "bardzo dobry" "dobry"        "dostateczny"
oceny <- droplevels(oceny)  # Usunięcie nieużywanych poziomów
print(levels(oceny))
## [1] "dobry"       "dostateczny"


Dodawanie nowych wartości

kolor <- factor(c("czerwony", "niebieski"))
kolor[3] <- "zielony"  # Ostrzeżenie! Powstaje NA
kolor
## [1] czerwony  niebieski <NA>     
## Levels: czerwony niebieski
# Najpierw dodaj poziom:
levels(kolor) <- c(levels(kolor), "zielony")
kolor[3] <- "zielony"  # Teraz działa


Konwersja typów

# Factor na character
(oceny <- factor(c("dobry", "dostateczny", "bardzo dobry"))); class(oceny)
## [1] dobry        dostateczny  bardzo dobry
## Levels: bardzo dobry dobry dostateczny
## [1] "factor"
oceny_text <- as.character(oceny)
print(oceny_text)
## [1] "dobry"        "dostateczny"  "bardzo dobry"
class(oceny_text)
## [1] "character"
# Factor na numeric - UWAGA!
liczby_f <- factor(c("10", "20", "5", "15"))
as.numeric(liczby_f)  # BŁĄD! Daje 1, 3, 2, 4 (indeksy poziomów!)
## [1] 1 3 4 2
# Prawidłowa konwersja:
as.numeric(as.character(liczby_f))  # Daje 10, 20, 5, 15
## [1] 10 20  5 15
# Character na factor
tekst <- c("jabłko", "gruszka", "jabłko", "śliwka")
owoce <- as.factor(tekst)
print(owoce)
## [1] jabłko  gruszka jabłko  śliwka 
## Levels: gruszka jabłko śliwka


Zmiana kolejności poziomów

# Oryginalny factor
oceny <- factor(c("dobry", "dostateczny", "bardzo dobry", "celujący"))
print(levels(oceny))  # Alfabetycznie
## [1] "bardzo dobry" "celujący"     "dobry"        "dostateczny"
# Zmiana kolejności poziomów
oceny <- factor(oceny, 
                levels = c("niedostateczny", 
                           "dostateczny",
                           "dopuszczający",
                          "dobry", 
                          "bardzo dobry", 
                          "celujący"))
print(levels(oceny))
## [1] "niedostateczny" "dostateczny"    "dopuszczający"  "dobry"         
## [5] "bardzo dobry"   "celujący"
# Odwrócenie kolejności
oceny_odwrocone <- factor(oceny, levels = rev(levels(oceny)))
print(levels(oceny_odwrocone))
## [1] "celujący"       "bardzo dobry"   "dobry"          "dopuszczający" 
## [5] "dostateczny"    "niedostateczny"


# R domyślnie sortuje poziomy alfabetycznie
miesiace <- factor(c("Styczeń", "Luty", "Marzec"))
print(levels(miesiace))  # Alfabetycznie!
## [1] "Luty"    "Marzec"  "Styczeń"
# Określ właściwą kolejność:
miesiace <- factor(miesiace, 
                   levels = c("Styczeń", "Luty", "Marzec", "Kwiecień", 
                             "Maj", "Czerwiec", "Lipiec", "Sierpień",
                             "Wrzesień", "Październik", "Listopad", "Grudzień"))


plec <- as.factor(c("M", "K", "M", "K", "K"))
levels(plec) <- c("Kobieta","Mężczyzna") # skąd on to wie?
summary(plec)
##   Kobieta Mężczyzna 
##         3         2
levels(plec) <- c("Mężczyzna", "Kobieta") # skąd on to wie lub nie? 
summary(plec)
## Mężczyzna   Kobieta 
##         3         2
# Najlepszy sposób - od razu określ właściwą kolejność
plec <- factor(c("M", "K", "M", "K", "K"),
               levels = c("K", "M"),
               labels = c("Kobieta", "Mężczyzna"))
summary(plec)
##   Kobieta Mężczyzna 
##         3         2



Jaka jest logika przypisania?


x <- factor(c("Ź", "A", "C"))
levels(x) <- c("pierwszy", "drugi", "trzeci")
print(x)







# Odpowiedź:
# [1] trzeci  pierwszy drugi
# Levels: pierwszy drugi trzeci

# Dlaczego?
# Alfabetycznie: "A" < "C" < "Ź" (polskie znaki na końcu!)
# Więc: A=1, C=2, Ź=3
# Po zmianie: A="pierwszy", C="drugi", Ź="trzeci"






Factor w praktyce: skala Likerta

W kwestionariuszach psychologicznych odpowiedzi często mają postać skali Likerta.

To modelowy przykład zmiennej porządkowej.

poziomy <- c("zdecydowanie nie", 
             "raczej nie", 
             "trudno powiedzieć",
             "raczej tak", 
             "zdecydowanie tak")

odp <- c("raczej tak", 
         "zdecydowanie tak", 
         "trudno powiedzieć",
         "raczej tak",
         "zdecydowanie nie", 
         "raczej tak", 
         "zdecydowanie tak")

likert <- factor(odp, levels = poziomy, ordered = TRUE)
likert
## [1] raczej tak        zdecydowanie tak  trudno powiedzieć raczej tak       
## [5] zdecydowanie nie  raczej tak        zdecydowanie tak 
## 5 Levels: zdecydowanie nie < raczej nie < trudno powiedzieć < ... < zdecydowanie tak
table(likert)                 # pokazuje też poziom, którego nikt nie wybrał (0)!
## likert
##  zdecydowanie nie        raczej nie trudno powiedzieć        raczej tak 
##                 1                 0                 1                 3 
##  zdecydowanie tak 
##                 2
table(odp)                    # zwykły tekst: brak pustej kategorii, kolejność alfabetyczna
## odp
##        raczej tak trudno powiedzieć  zdecydowanie nie  zdecydowanie tak 
##                 3                 1                 1                 2
likert >= "raczej tak"        # porównania mają sens, bo kolejność jest zadana
## [1]  TRUE  TRUE FALSE  TRUE FALSE  TRUE  TRUE
mean(likert >= "raczej tak")  # odsetek osób zgadzających się
## [1] 0.7142857
as.integer(likert)            # tu zamiana na liczby JEST sensowna: kodowanie 1–5
## [1] 4 5 3 4 1 4 5
median(as.integer(likert))
## [1] 4



Pytanie do dyskusji

Czy wolno liczyć średnią z odpowiedzi na skali Likerta?

Czy „odległość” między raczej nie a trudno powiedzieć jest taka sama jak między raczej tak a zdecydowanie tak?





Data frames


Data frame w R to dwuwymiarowa struktura danych, która jest jedną z najczęściej używanych do przechowywania i manipulacji danymi. Można ją porównać do tabeli w bazie danych lub arkusza kalkulacyjnego, gdzie dane są zorganizowane w wiersze i kolumny.

Kluczowe cechy:

  • Każda kolumna może mieć inny typ danych (numeric, character, logical, factor)

  • Wszystkie kolumny muszą mieć taką samą długość

  • Wiersze i kolumny mogą mieć nazwy



Okazuje się, że mamy pod ręką kilka zbiorów danych by default. Ramką, którą zna każdy R’owiec jest m.in. zestaw mtcars Obejrzyjmy dokumentację, żeby sprawdzić co zawiera: https://www.rdocumentation.org/packages/datasets/versions/3.6.2/topics/mtcars a następnie obejrzyjmy ją w konsoli

mtcars
##                      mpg cyl  disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4           21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag       21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710          22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
## Hornet 4 Drive      21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
## Hornet Sportabout   18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
## Valiant             18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1
## Duster 360          14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4
## Merc 240D           24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
## Merc 230            22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2
## Merc 280            19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4
## Merc 280C           17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4
## Merc 450SE          16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3
## Merc 450SL          17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3
## Merc 450SLC         15.2   8 275.8 180 3.07 3.780 18.00  0  0    3    3
## Cadillac Fleetwood  10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4
## Lincoln Continental 10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4
## Chrysler Imperial   14.7   8 440.0 230 3.23 5.345 17.42  0  0    3    4
## Fiat 128            32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
## Honda Civic         30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
## Toyota Corolla      33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
## Toyota Corona       21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1
## Dodge Challenger    15.5   8 318.0 150 2.76 3.520 16.87  0  0    3    2
## AMC Javelin         15.2   8 304.0 150 3.15 3.435 17.30  0  0    3    2
## Camaro Z28          13.3   8 350.0 245 3.73 3.840 15.41  0  0    3    4
## Pontiac Firebird    19.2   8 400.0 175 3.08 3.845 17.05  0  0    3    2
## Fiat X1-9           27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
## Porsche 914-2       26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
## Lotus Europa        30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2
## Ford Pantera L      15.8   8 351.0 264 4.22 3.170 14.50  0  1    5    4
## Ferrari Dino        19.7   6 145.0 175 3.62 2.770 15.50  0  1    5    6
## Maserati Bora       15.0   8 301.0 335 3.54 3.570 14.60  0  1    5    8
## Volvo 142E          21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2


Zabieg ten bywa niekiedy głupotą przy większych zbiorach. Posprawdzajmy ten zbiór “rezolutniej”, odpowiednimi funkcjami.

str(mtcars) # czy to w ogóle jest data.frame?
## 'data.frame':    32 obs. of  11 variables:
##  $ mpg : num  21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
##  $ cyl : num  6 6 4 6 8 6 8 4 4 6 ...
##  $ disp: num  160 160 108 258 360 ...
##  $ hp  : num  110 110 93 110 175 105 245 62 95 123 ...
##  $ drat: num  3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
##  $ wt  : num  2.62 2.88 2.32 3.21 3.44 ...
##  $ qsec: num  16.5 17 18.6 19.4 17 ...
##  $ vs  : num  0 0 1 1 0 1 0 1 1 1 ...
##  $ am  : num  1 1 1 0 0 0 0 0 0 0 ...
##  $ gear: num  4 4 4 3 3 3 3 4 4 4 ...
##  $ carb: num  4 4 1 1 2 1 4 2 2 4 ...
# można sprawdzić jeszcze inaczej
is.data.frame(mtcars)
## [1] TRUE
head(mtcars) # może pierwsze kilka wierszy, żeby nie zaśmiecać konsoli
##                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
## Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
## Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
tail(mtcars, 2)  # ostatnie 2 wiersze
##                mpg cyl disp  hp drat   wt qsec vs am gear carb
## Maserati Bora 15.0   8  301 335 3.54 3.57 14.6  0  1    5    8
## Volvo 142E    21.4   4  121 109 4.11 2.78 18.6  1  1    4    2


Wymiary data.frame

nrow(mtcars)  # liczba wierszy
## [1] 32
ncol(mtcars)  # liczba kolumn
## [1] 11
dim(mtcars)   # wymiary: [wiersze, kolumny]
## [1] 32 11


Nazwy kolumn i wierszy

# Nazwy kolumn
colnames(mtcars)
##  [1] "mpg"  "cyl"  "disp" "hp"   "drat" "wt"   "qsec" "vs"   "am"   "gear"
## [11] "carb"
names(mtcars)  # to samo co colnames()
##  [1] "mpg"  "cyl"  "disp" "hp"   "drat" "wt"   "qsec" "vs"   "am"   "gear"
## [11] "carb"
# Nazwy wierszy
rownames(mtcars)
##  [1] "Mazda RX4"           "Mazda RX4 Wag"       "Datsun 710"         
##  [4] "Hornet 4 Drive"      "Hornet Sportabout"   "Valiant"            
##  [7] "Duster 360"          "Merc 240D"           "Merc 230"           
## [10] "Merc 280"            "Merc 280C"           "Merc 450SE"         
## [13] "Merc 450SL"          "Merc 450SLC"         "Cadillac Fleetwood" 
## [16] "Lincoln Continental" "Chrysler Imperial"   "Fiat 128"           
## [19] "Honda Civic"         "Toyota Corolla"      "Toyota Corona"      
## [22] "Dodge Challenger"    "AMC Javelin"         "Camaro Z28"         
## [25] "Pontiac Firebird"    "Fiat X1-9"           "Porsche 914-2"      
## [28] "Lotus Europa"        "Ford Pantera L"      "Ferrari Dino"       
## [31] "Maserati Bora"       "Volvo 142E"

Zmiana nazw kolumn bądź wierszy

polskie_nazwy_mtcars <- c(
  "MilePerGalon",    # mpg - Zużycie paliwa (mil na galon)
  "Cylindry",        # cyl - Liczba cylindrów
  "Pojemnosc",       # disp - Pojemność skokowa silnika
  "KonieMechaniczne",# hp - Moc (koni mechanicznych)
  "PrzelozenieOsi",  # drat - Przełożenie osi
  "Waga",            # wt - Masa własna (w tysiącach funtów)
  "Czas1/4mili",     # qsec - Czas przejazdu 1/4 mili
  "SilnikVS",        # vs - Typ silnika: (0 = w kształcie V, 1 = prosty)
  "SkrzyniaBiegow",  # am - Typ skrzyni biegów: (0 = automatyczna, 1 = manualna)
  "Biegi",           # gear - Liczba biegów (przełożeń do przodu)
  "Gazniki"          # carb - Liczba gaźników
)
colnames(mtcars) <- polskie_nazwy_mtcars

rownames(mtcars)[18] <- "Taki Maluch"

rm(mtcars) # [+] usuwamy NASZĄ zmodyfikowaną kopię; oryginał z pakietu datasets wciąż jest dostępny (sprawdź names(mtcars))


Stwórzmy własny data.frame from scratch

# Tworzenie prostego data.frame
df <- data.frame(
  imie = c("Anna", "Jan", "Maria", "Piotr"),
  wiek = c(23, 25, 22, 24),
  ocena = c(4.5, 3.5, 5.0, 4.0)
)

print(df)
##    imie wiek ocena
## 1  Anna   23   4.5
## 2   Jan   25   3.5
## 3 Maria   22   5.0
## 4 Piotr   24   4.0


Albo z istniejących wektorów.

# pojedyncze wektory, które staną się zmiennymi
name <- c("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune")

type <- c("Terrestrial planet", "Terrestrial planet", "Terrestrial planet", "Terrestrial planet", "Gas giant", "Gas giant", "Gas giant", "Gas giant")

diameter <- c(0.382, 0.949, 1, 0.532, 11.209, 9.449, 4.007, 3.883)

rotation <- c(58.64, -243.02, 1, 1.03, 0.41, 0.43, -0.72, 0.67)

rings <- c(FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE)

# sklejenie wektorów w data.frame
planets_df <- data.frame(name, 
                         type, 
                         diameter,
                         rotation, 
                         rings)

rm(name, type, diameter, rotation, rings) # mogę usunę zbędne już wektory ze środowiska
str(planets_df)
## 'data.frame':    8 obs. of  5 variables:
##  $ name    : chr  "Mercury" "Venus" "Earth" "Mars" ...
##  $ type    : chr  "Terrestrial planet" "Terrestrial planet" "Terrestrial planet" "Terrestrial planet" ...
##  $ diameter: num  0.382 0.949 1 0.532 11.209 ...
##  $ rotation: num  58.64 -243.02 1 1.03 0.41 ...
##  $ rings   : logi  FALSE FALSE FALSE FALSE TRUE TRUE ...
planets_df
##      name               type diameter rotation rings
## 1 Mercury Terrestrial planet    0.382    58.64 FALSE
## 2   Venus Terrestrial planet    0.949  -243.02 FALSE
## 3   Earth Terrestrial planet    1.000     1.00 FALSE
## 4    Mars Terrestrial planet    0.532     1.03 FALSE
## 5 Jupiter          Gas giant   11.209     0.41  TRUE
## 6  Saturn          Gas giant    9.449     0.43  TRUE
## 7  Uranus          Gas giant    4.007    -0.72  TRUE
## 8 Neptune          Gas giant    3.883     0.67  TRUE


Wybierzmy z ramki jakieś interesujące nas informacje

# znajdźmy średnicę Ziemi
planets_df[3,3] # trzeci wiersz, trzecia kolumna
## [1] 1
# czy na Marsie da się mieszkać? Nie wiem, ale przyjrzyjmy się wszystkim informacjom na jego temat, którymi dysponujemy
planets_df[4,] # czwarty wiersz
##   name               type diameter rotation rings
## 4 Mars Terrestrial planet    0.532     1.03 FALSE
# Średnice pierwszych trzech planet
planets_df[1:3, "diameter"] 
## [1] 0.382 0.949 1.000
# Średnice pierwszych trzech planet - alternatywnie
planets_df[1:3,3] # Zapytają Państwo, która metoda jest lepsza? Odp.: to zależy...
## [1] 0.382 0.949 1.000
# Cała kolumna
planets_df[, 2]   # druga kolumna
## [1] "Terrestrial planet" "Terrestrial planet" "Terrestrial planet"
## [4] "Terrestrial planet" "Gas giant"          "Gas giant"         
## [7] "Gas giant"          "Gas giant"
# Wiele wierszy lub kolumn
planets_df[1:3, ]      # wiersze 1-3
##      name               type diameter rotation rings
## 1 Mercury Terrestrial planet    0.382    58.64 FALSE
## 2   Venus Terrestrial planet    0.949  -243.02 FALSE
## 3   Earth Terrestrial planet    1.000     1.00 FALSE
planets_df[, c(1:3)]  # kolumny od 1 do 3
##      name               type diameter
## 1 Mercury Terrestrial planet    0.382
## 2   Venus Terrestrial planet    0.949
## 3   Earth Terrestrial planet    1.000
## 4    Mars Terrestrial planet    0.532
## 5 Jupiter          Gas giant   11.209
## 6  Saturn          Gas giant    9.449
## 7  Uranus          Gas giant    4.007
## 8 Neptune          Gas giant    3.883
planets_df[, c(1, 3)]  # kolumny 1 i 3
##      name diameter
## 1 Mercury    0.382
## 2   Venus    0.949
## 3   Earth    1.000
## 4    Mars    0.532
## 5 Jupiter   11.209
## 6  Saturn    9.449
## 7  Uranus    4.007
## 8 Neptune    3.883
planets_df[c(1, 3), c(1, 2)]  # wybrane wiersze i kolumny
##      name               type
## 1 Mercury Terrestrial planet
## 3   Earth Terrestrial planet




Ściągawka: jak dostać się do fragmentów ramki danych?

Zapis Co zwraca Przykład
df[i, j] pojedynczy element lub fragment planets_df[3, 3]
df[i, ] wiersz(e) jako data.frame planets_df[4, ]
df[, j] kolumnę jako wektor planets_df[, 2]
df[, j, drop = FALSE] kolumnę jako data.frame planets_df[, 2, drop = FALSE]
df$nazwa kolumnę jako wektor planets_df$name
df[["nazwa"]] kolumnę jako wektor planets_df[["name"]]
df["nazwa"] kolumnę jako data.frame planets_df["name"]
df[warunek, ] wiersze spełniające warunek planets_df[planets_df$rings, ]





Wyrzuciliśmy wcześniej z workspace’u wszystkie wektory tworzące zbiór planet. Teraz jednak potrzebujemy pod ręką niektórych. Zreplikujmy je używając naszego data.frame’u

name_vector <- planets_df$name
rings_vector <- planets_df$rings


Wybieranie wierszy z uwagi na warunek

# Pani od wuefu powiedziała, że nie da się biegać na gazowych planetach, zatem na których planetach można?
planets_df[planets_df$type == "Terrestrial planet",]
##      name               type diameter rotation rings
## 1 Mercury Terrestrial planet    0.382    58.64 FALSE
## 2   Venus Terrestrial planet    0.949  -243.02 FALSE
## 3   Earth Terrestrial planet    1.000     1.00 FALSE
## 4    Mars Terrestrial planet    0.532     1.03 FALSE
# Oraz, że lepiej biegać na tych bez ringu ("Bo jeszcze na łeb spadnie" - cytuję)
planets_df[rings_vector == F,]
##      name               type diameter rotation rings
## 1 Mercury Terrestrial planet    0.382    58.64 FALSE
## 2   Venus Terrestrial planet    0.949  -243.02 FALSE
## 3   Earth Terrestrial planet    1.000     1.00 FALSE
## 4    Mars Terrestrial planet    0.532     1.03 FALSE
# czyli na obu nie bardzo?
planets_df[planets_df$rings != T & planets_df$type != "Gas giant",] # omówmy
##      name               type diameter rotation rings
## 1 Mercury Terrestrial planet    0.382    58.64 FALSE
## 2   Venus Terrestrial planet    0.949  -243.02 FALSE
## 3   Earth Terrestrial planet    1.000     1.00 FALSE
## 4    Mars Terrestrial planet    0.532     1.03 FALSE
# które planety mają średnicę mniejszą niż ziemska?
subset(planets_df, subset = diameter <1)
##      name               type diameter rotation rings
## 1 Mercury Terrestrial planet    0.382    58.64 FALSE
## 2   Venus Terrestrial planet    0.949  -243.02 FALSE
## 4    Mars Terrestrial planet    0.532     1.03 FALSE
# nasza ramka jest ułożona w porządku występowania względem Słońca, ustawmy ją w kolejności leksykalnej
pozycja <-  order(planets_df$name)
planets_df[pozycja,]
##      name               type diameter rotation rings
## 3   Earth Terrestrial planet    1.000     1.00 FALSE
## 5 Jupiter          Gas giant   11.209     0.41  TRUE
## 4    Mars Terrestrial planet    0.532     1.03 FALSE
## 1 Mercury Terrestrial planet    0.382    58.64 FALSE
## 8 Neptune          Gas giant    3.883     0.67  TRUE
## 6  Saturn          Gas giant    9.449     0.43  TRUE
## 7  Uranus          Gas giant    4.007    -0.72  TRUE
## 2   Venus Terrestrial planet    0.949  -243.02 FALSE
# albo względem wielkości. Która zmienna odpowiada za "gabaryty" planety?
pozycja <-  order(planets_df$diameter)
planets_df[pozycja,]
##      name               type diameter rotation rings
## 1 Mercury Terrestrial planet    0.382    58.64 FALSE
## 4    Mars Terrestrial planet    0.532     1.03 FALSE
## 2   Venus Terrestrial planet    0.949  -243.02 FALSE
## 3   Earth Terrestrial planet    1.000     1.00 FALSE
## 8 Neptune          Gas giant    3.883     0.67  TRUE
## 7  Uranus          Gas giant    4.007    -0.72  TRUE
## 6  Saturn          Gas giant    9.449     0.43  TRUE
## 5 Jupiter          Gas giant   11.209     0.41  TRUE


Dodanie nowej kolumny

planets_df$DaSieMieszkac <- c(F,F,T,T,F,F,F,F)

Dodanie nowego wiersza

nowa_planeta <- data.frame(
  name = "Maluch's Best Planet",
  type = "Terrestrial planet",
  diameter = 1.000,
  rotation = 1.00,
  rings = T,
  DaSieMieszkac = T
)

planets_df <- rbind(planets_df, nowa_planeta)


Modyfikacja wartości

planets_df[9, "rings"] <- FALSE # [poprawka: było planets_df[-9, "rings"] <- FALSE]


Usunięcie kolumny lub wiersza

planets_df$DaSieMieszkac <- NULL

planets_df <- planets_df[-9, ]


Przydatne

# Unikalne wartości
unique(planets_df$type)
## [1] "Terrestrial planet" "Gas giant"
# Liczba unikalnych wartości
length(unique(planets_df$rings))
## [1] 2


Podsumowania i statystyki

# Podsumowanie statystyczne
summary(mtcars)
##       mpg             cyl             disp             hp       
##  Min.   :10.40   Min.   :4.000   Min.   : 71.1   Min.   : 52.0  
##  1st Qu.:15.43   1st Qu.:4.000   1st Qu.:120.8   1st Qu.: 96.5  
##  Median :19.20   Median :6.000   Median :196.3   Median :123.0  
##  Mean   :20.09   Mean   :6.188   Mean   :230.7   Mean   :146.7  
##  3rd Qu.:22.80   3rd Qu.:8.000   3rd Qu.:326.0   3rd Qu.:180.0  
##  Max.   :33.90   Max.   :8.000   Max.   :472.0   Max.   :335.0  
##       drat             wt             qsec             vs        
##  Min.   :2.760   Min.   :1.513   Min.   :14.50   Min.   :0.0000  
##  1st Qu.:3.080   1st Qu.:2.581   1st Qu.:16.89   1st Qu.:0.0000  
##  Median :3.695   Median :3.325   Median :17.71   Median :0.0000  
##  Mean   :3.597   Mean   :3.217   Mean   :17.85   Mean   :0.4375  
##  3rd Qu.:3.920   3rd Qu.:3.610   3rd Qu.:18.90   3rd Qu.:1.0000  
##  Max.   :4.930   Max.   :5.424   Max.   :22.90   Max.   :1.0000  
##        am              gear            carb      
##  Min.   :0.0000   Min.   :3.000   Min.   :1.000  
##  1st Qu.:0.0000   1st Qu.:3.000   1st Qu.:2.000  
##  Median :0.0000   Median :4.000   Median :2.000  
##  Mean   :0.4062   Mean   :3.688   Mean   :2.812  
##  3rd Qu.:1.0000   3rd Qu.:4.000   3rd Qu.:4.000  
##  Max.   :1.0000   Max.   :5.000   Max.   :8.000
# Statystyki dla konkretnych kolumn
mean(mtcars$cyl)     # średnia liczba cylindrów
## [1] 6.1875
median(mtcars$hp)  # mediana liczby koni mechanicznych
## [1] 123
sd(mtcars$wt)       # odchylenie standardowe masy własnej
## [1] 0.9784574
min(mtcars$gear)     # minimum liczby biegów
## [1] 3
max(mtcars$carb)      # maksimum liczny gaźników
## [1] 8
# Tabele częstości
table(mtcars$cyl)
## 
##  4  6  8 
## 11  7 14




Lists

Lista to najbardziej elastyczna struktura danych w R. W przeciwieństwie do wektorów, które mogą zawierać tylko elementy tego samego typu, lista może przechowywać:

  • różne typy danych (liczby, tekst, wartości logiczne)

  • obiekty o różnych długościach

  • inne struktury danych (wektory, macierze, ramki danych)

  • nawet inne listy!


Listy są szczególnie przydatne, gdy chcemy zgrupować różnorodne informacje w jeden obiekt.


Tworzenie list

Podstawowa składnia. Do tworzenia list używamy funkcji list():

# Prosta lista z różnymi typami danych
moja_lista <- list(
  imie = "Anna",
  wiek = 25,
  student = TRUE,
  oceny = c(4.5, 5.0, 4.0, 5.0)
)

moja_lista
## $imie
## [1] "Anna"
## 
## $wiek
## [1] 25
## 
## $student
## [1] TRUE
## 
## $oceny
## [1] 4.5 5.0 4.0 5.0


Lista bez nazw

Elementy listy nie muszą mieć nazw:

lista_bez_nazw <- list(
  "Warszawa",
  500000,
  c("Polska", "Europa")
)

lista_bez_nazw
## [[1]]
## [1] "Warszawa"
## 
## [[2]]
## [1] 5e+05
## 
## [[3]]
## [1] "Polska" "Europa"


Zagnieżdżone listy

Lista może zawierać inne listy:

osoba <- list(
  dane_osobowe = list(
    imie = "Jan",
    nazwisko = "Kowalski",
    wiek = 30
  ),
  adres = list(
    miasto = "Kraków",
    kod_pocztowy = "30-001"
  )
)

osoba
## $dane_osobowe
## $dane_osobowe$imie
## [1] "Jan"
## 
## $dane_osobowe$nazwisko
## [1] "Kowalski"
## 
## $dane_osobowe$wiek
## [1] 30
## 
## 
## $adres
## $adres$miasto
## [1] "Kraków"
## 
## $adres$kod_pocztowy
## [1] "30-001"


Dostęp do elementów listy

Istnieją trzy główne sposoby odwoływania się do elementów listy:

  1. Operator $ (dla nazwanych elementów)
# Najczęściej używany sposób
moja_lista$imie
## [1] "Anna"
moja_lista$oceny
## [1] 4.5 5.0 4.0 5.0
  1. Operator [[]] (zwraca element)
# Przez nazwę
moja_lista[["wiek"]]
## [1] 25
# Przez indeks
moja_lista[[1]]
## [1] "Anna"
  1. Operator [] (zwraca pod-listę)
# Zwraca listę z jednym elementem
moja_lista[1]
## $imie
## [1] "Anna"
# Różnica między [] a [[]]
class(moja_lista[1])    # lista
## [1] "list"
class(moja_lista[[1]])  # character
## [1] "character"



Analogię spopularyzował Hadley Wickham, „Advanced R”)


Pomocna analogia: lista to pociąg, a jej elementy to wagony. lista[1] odczepia pierwszy wagon – dostajemy krótszy pociąg (wciąż listę). lista[[1]] zagląda do środka wagonu i wyjmuje ładunek.


Zapis Co zwraca Klasa wyniku dla moja_lista
lista[1] podlistę (wagon z ładunkiem) list
lista[[1]] sam element (ładunek) character
lista$imie sam element, po nazwie character
lista[c(1, 2)] podlistę z kilkoma elementami list



Dostęp do zagnieżdżonych elementów

# Sposób 1: łańcuchowy operator $
osoba$dane_osobowe$imie
## [1] "Jan"
# Sposób 2: wielokrotne [[]]
osoba[["adres"]][["miasto"]]
## [1] "Kraków"


Modyfikacja list

  1. Zmiana wartości
moja_lista$wiek <- 26
moja_lista$wiek
## [1] 26
  1. Dodawanie nowych elementów
moja_lista$kierunek <- "Statystyka"
moja_lista$rok_studiow <- 3

# Sprawdzamy zaktualizowaną listę
names(moja_lista)
## [1] "imie"        "wiek"        "student"     "oceny"       "kierunek"   
## [6] "rok_studiow"
  1. Usuwanie elementów
moja_lista$rok_studiow <- NULL
names(moja_lista)
## [1] "imie"     "wiek"     "student"  "oceny"    "kierunek"


Podstawowe informacje

# Nazwy elementów
names(moja_lista)
## [1] "imie"     "wiek"     "student"  "oceny"    "kierunek"
# Długość listy (liczba elementów)
length(moja_lista)
## [1] 5
# Struktura listy
str(moja_lista)
## List of 5
##  $ imie    : chr "Anna"
##  $ wiek    : num 26
##  $ student : logi TRUE
##  $ oceny   : num [1:4] 4.5 5 4 5
##  $ kierunek: chr "Statystyka"


📖 Przekształcanie list i przechowywanie wyników analiz – do samodzielnej lektury. Warto tylko zapamiętać, że wiele funkcji statystycznych, np. lm(), zwraca właśnie listę.


Przekształcanie list

  1. Lista liczb na wektor
liczby_lista <- list(a = 1, b = 2, c = 3)
liczby_lista
## $a
## [1] 1
## 
## $b
## [1] 2
## 
## $c
## [1] 3
wektor <- unlist(liczby_lista)
wektor
## a b c 
## 1 2 3
  1. Lista na ramkę danych (gdy elementy mają tę samą długość)
dane_lista <- list(
  imie = c("Anna", "Jan", "Ewa"),
  wiek = c(25, 30, 28),
  student = c(TRUE, FALSE, TRUE)
)
dane_lista
## $imie
## [1] "Anna" "Jan"  "Ewa" 
## 
## $wiek
## [1] 25 30 28
## 
## $student
## [1]  TRUE FALSE  TRUE
df <- as.data.frame(dane_lista)
df
##   imie wiek student
## 1 Anna   25    TRUE
## 2  Jan   30   FALSE
## 3  Ewa   28    TRUE


Przechowywanie wyników analizy

# Symulacja danych
set.seed(123)
dane <- rnorm(100, mean = 50, sd = 10)

# Zapisanie różnych statystyk w jednej liście
analiza <- list(
  dane_surowe = dane,
  statystyki = list(
    srednia = mean(dane),
    mediana = median(dane),
    odch_std = sd(dane)
  ),
  histogram = hist(dane, plot = FALSE)
)

# Łatwy dostęp do wyników
analiza$statystyki$srednia
## [1] 50.90406


Przechowywanie modeli statystycznych

# Tworzenie danych
x <- 1:10
y <- 2 * x + rnorm(10)

# Model liniowy zwraca listę!
model <- lm(y ~ x)

# Możemy łatwo wyciągać informacje
class(model)
## [1] "lm"
names(model)
##  [1] "coefficients"  "residuals"     "effects"       "rank"         
##  [5] "fitted.values" "assign"        "qr"            "df.residual"  
##  [9] "xlevels"       "call"          "terms"         "model"
model$coefficients
## (Intercept)           x 
##  -0.4856184   2.0163219



Podsumowanie

Listy w R to potężne narzędzie, które pozwala:

  • Grupować różnorodne dane w jedną strukturę
  • Przechowywać złożone wyniki analiz
  • Organizować dane hierarchicznie
  • Pracować z wynikami funkcji statystycznych

Kluczowe operacje:

  • Tworzenie: list(nazwa1 = wartość1, nazwa2 = wartość2)
  • Dostęp: lista$nazwa, lista[[indeks]], lista[indeks]
  • Modyfikacja: lista$nowy_element <- wartość
  • Informacje: names(), length(), str(), etc.





Statystyka opisowa i symulacje


Generowanie danych losowych


Funkcja runif(n) generuje n losowych liczb z rozkładu jednostajnego (uniform distribution).

W rozkładzie jednostajnym każda wartość w określonym przedziale ma jednakowe prawdopodobieństwo wystąpienia. Używana do generowania pseudolosowych liczb w określonym przedziale, co jest przydatne w symulacjach, testach statystycznych i innych analizach wymagających losowych danych.

Składnia: runif(n, min = 0, max = 1)

Gdzie:

  • n: Liczba losowych wartości do wygenerowania.

  • min: Minimalna wartość przedziału (domyślnie 0).

  • max: Maksymalna wartość przedziału (domyślnie 1).

# Generowanie 1000 losowych liczb z przedziału 0 do 100
# ?runif
runif_result <- runif(10^6, min = 0, max = 100)
# rezultat
hist(runif_result)



Funkcja rnorm(n) generuje n losowych liczb z rozkładu normalnego (Gaussian distribution). Rozkład normalny jest symetryczny i ma kształt dzwonu (kapelusza, słonia w wężu), z wartościami skupionymi wokół średniej. Używana do generowania pseudolosowych liczb, które mają rozkład normalny, co jest przydatne w analizach statystycznych, modelowaniu i symulacjach, gdzie dane często przyjmują rozkład normalny.

Składnia: rnorm(n, mean = 0, sd = 1)

Gdzie:

  • n: Liczba losowych wartości do wygenerowania.

  • mean: Średnia rozkładu (domyślnie 0).

  • sd: Odchylenie standardowe rozkładu (domyślnie 1).

# Generowanie 1000 losowych liczb z rozkładu normalnego o średniej 10 i odchyleniu standardowym 2
# ?rnorm
rnorm_result <- rnorm(10^6, mean = 10, sd = 2)
# rezultat
hist(rnorm_result)



Miary statystyki opisowej

Funkcja Co liczy Odporna na wartości odstające?
mean() średnia arytmetyczna nie
median() mediana (wartość środkowa) tak
min(), max(), range() wartości skrajne nie
quantile() kwantyle (np. kwartyle, percentyle) środkowe – tak
IQR() rozstęp międzykwartylowy tak
sd(), var() odchylenie standardowe, wariancja nie
summary() kilka miar naraz –

Większość z nich ma argument na.rm = TRUE, który pomija braki danych.



Do szybkiego stworzenia prostych statystyk opisowych możemy posłużyć się następującymi funkcjami:


  • max() maksimum
max(rnorm_result) # nasz pseudolosowy wektor z poprzedniego przykładu
## [1] 19.70153
max(runif_result) # nasz pseudolosowy wektor z poprzedniego przykładu
## [1] 99.99999


  • min() minimum
min(rnorm_result)
## [1] 0.4016187
print(min(runif_result), digits = 22)
## [1] 5.133915692567825317383e-05


  • sum() suma
sum(rnorm_result)
## [1] 9996060
sum(runif_result)
## [1] 49952880


  • mean() średnia
mean(rnorm_result)
## [1] 9.99606
mean(runif_result)
## [1] 49.95288


  • median() mediana
median(rnorm_result)
## [1] 9.998284
median(runif_result)
## [1] 49.91531


  • range() liczy zakres wartości vide minimum i maksimum.
range(rnorm_result)
## [1]  0.4016187 19.7015340
range(runif_result)
## [1] 5.133916e-05 9.999999e+01


  • quantile() służy do obliczania kwantyli dla danego wektora danych.

Kwantyle to wartości, które dzielą uporządkowany zbiór danych na równe części. Używane do opisu rozkładu danych, zrozumienia struktury danych, identyfikacji wartości odstających i porównywania różnych zbiorów danych.

Argumenty:

  • x: Wektor danych, dla którego chcemy obliczyć kwantyle.

  • probs: Wektor wartości prawdopodobieństwa, które określają, jakie kwantyle mają być obliczone (domyślnie kwartyle: 0, 0.25, 0.5, 0.75, 1).

  • na.rm: Jeśli ustawione na TRUE, pomija wartości NA w obliczeniach (domyślnie FALSE).

# domyślnie liczy kwartyle, gdy wywołujemy funkcję bez dodatkowych argumentów
quantile(rnorm_result) 
##         0%        25%        50%        75%       100% 
##  0.4016187  8.6478257  9.9982844 11.3431772 19.7015340
quantile(runif_result)
##           0%          25%          50%          75%         100% 
## 5.133916e-05 2.491638e+01 4.991531e+01 7.494999e+01 9.999999e+01
# można również percentyle
quantile(rnorm_result, probs = seq(0, 1, 0.1), na.rm = TRUE) 
##         0%        10%        20%        30%        40%        50%        60% 
##  0.4016187  7.4359834  8.3132521  8.9478016  9.4903093  9.9982844 10.5039282 
##        70%        80%        90%       100% 
## 11.0446890 11.6782194 12.5546469 19.7015340
quantile(runif_result, probs = seq(0, 1, 0.1), na.rm = FALSE)
##           0%          10%          20%          30%          40%          50% 
## 5.133916e-05 9.981419e+00 1.989810e+01 2.992358e+01 3.991508e+01 4.991531e+01 
##          60%          70%          80%          90%         100% 
## 5.994133e+01 6.996262e+01 7.999512e+01 8.999585e+01 9.999999e+01


  • sd() w R służy do obliczania odchylenia standardowego wektora danych.

Odchylenie standardowe mierzy, jak bardzo wartości w zbiorze danych różnią się od średniej, ale w tych samych jednostkach co oryginalne dane.

(x <- c(1, 2, 3, 4, 5)) # wektor
## [1] 1 2 3 4 5
sd(x)
## [1] 1.581139
sd(rnorm_result)
## [1] 1.997519
sd(runif_result)
## [1] 28.8812


  • var() wariancja

Wariancja mierzy średnią kwadratową odległość wartości w zbiorze danych od ich średniej. Jest to miara rozproszenia danych.

(x <- c(1, 2, 3, 4, 5)) # wektor
## [1] 1 2 3 4 5
var(x)
## [1] 2.5
sd(x)^2
## [1] 2.5
var(rnorm_result)
## [1] 3.99008
var(runif_result)
## [1] 834.1237


  • cumsum() służy do obliczania skumulowanej sumy elementów wektora. Oznacza to, że każdy element wynikowego wektora jest sumą wszystkich poprzednich elementów oryginalnego wektora do tego punktu włącznie.
(x <- c(1, 2, 3, 4, 5)) # wektor
## [1] 1 2 3 4 5
cumsum(x) # skumulowana suma wektora
## [1]  1  3  6 10 15


  • cumprod() służy do obliczania skumulowanego iloczynu elementów wektora. Oznacza to, że każdy element wynikowego wektora jest iloczynem wszystkich poprzednich elementów oryginalnego wektora do tego punktu włącznie.
x <- c(2, 3, 4, 5) # wektor
cumprod(x) # skumulowany iloczyn wektora
## [1]   2   6  24 120


  • cummax()
(x <- c(1, 2, 3, 4, 5)) # wektor
## [1] 1 2 3 4 5
cummax(x)
## [1] 1 2 3 4 5


  • cummin()
(x <- c(1, 2, 3, 4, 5)) # wektor
## [1] 1 2 3 4 5
cummin(x)
## [1] 1 1 1 1 1


Zobaczmy obie funkcje na danych, które nie są uporządkowane: najlepszy (najkrótszy) czas reakcji „do tej pory” w kolejnych próbach treningowych.

proby <- c(520, 480, 495, 430, 460, 410, 445, 405)   # czasy reakcji w ms
cummin(proby)                          # rekord życiowy po każdej próbie
## [1] 520 480 480 430 430 410 410 405
which(diff(cummin(proby)) < 0) + 1     # w których próbach padł nowy rekord?
## [1] 2 4 6 8
cummax(proby)                          # najgorszy czas "do tej pory"
## [1] 520 520 520 520 520 520 520 520




  • cor() korelacja

Funkcja cor() oblicza współczynnik korelacji między dwoma wektorami danych. Korelacja mierzy siłę i kierunek związku między dwiema zmiennymi.


Składnia:

cor(x, y = NULL, use = "everything", method = c("pearson", "kendall", "spearman"))

Gdzie:

  • x: Wektor numeryczny lub macierz.

  • y: Opcjonalnie, drugi wektor numeryczny (jeśli nie podano, używa x).

  • use: Metoda obsługi wartości NA.

  • method: Metoda korelacji (“pearson” domyślnie, “kendall”, “spearman”).

(x <- c(1, 2, 3, 4, 5)); (y <- 3*x)
## [1] 1 2 3 4 5
## [1]  3  6  9 12 15
cor(x, y)
## [1] 1
# idealnie!
cor(rnorm_result, runif_result)
## [1] -0.001903933
# jakiś komentarz co do wyniku?




Ile korelacji znajdziemy w czystym szumie?

set.seed(7)
szum <- matrix(rnorm(30 * 20), ncol = 20)   # 30 "osób", 20 "zmiennych" czystego szumu
(kor <- cor(szum))                          # macierz korelacji 20 x 20
##               [,1]         [,2]          [,3]         [,4]        [,5]
##  [1,]  1.000000000 -0.260481138 -0.1080071615 -0.006557698 -0.10355131
##  [2,] -0.260481138  1.000000000  0.0638825984  0.431181149  0.28926018
##  [3,] -0.108007161  0.063882598  1.0000000000 -0.077940107 -0.06806709
##  [4,] -0.006557698  0.431181149 -0.0779401072  1.000000000  0.05285692
##  [5,] -0.103551313  0.289260180 -0.0680670905  0.052856925  1.00000000
##  [6,]  0.020287787 -0.204094050 -0.3431884119 -0.105025258  0.10091227
##  [7,]  0.142805108 -0.057000277  0.1195553144  0.007184798 -0.01835438
##  [8,]  0.098436456 -0.220210462 -0.2894911762 -0.416884503 -0.14746116
##  [9,] -0.278301611  0.161294562  0.1818099131  0.012718782  0.23281337
## [10,]  0.009873806  0.072067012  0.5240728362  0.027333197  0.01689046
## [11,]  0.488169575  0.204913108 -0.1945506824  0.118772483  0.05492099
## [12,] -0.260193883  0.035944049  0.2932410222 -0.084025929 -0.32443882
## [13,]  0.186538552  0.011364226 -0.0439376557  0.341386418 -0.03953204
## [14,]  0.089677141  0.134942987 -0.5112365519  0.098792378  0.46588885
## [15,] -0.101539837  0.227555443  0.0009629415 -0.008048254  0.29228220
## [16,] -0.013843415 -0.020620085 -0.1072119200 -0.105749847  0.22364861
## [17,] -0.067871422  0.277345131 -0.0062925920  0.148046060  0.36597941
## [18,] -0.256506735  0.104382276  0.1538254301 -0.141704486 -0.10260952
## [19,]  0.004802103  0.126144519  0.1116610911  0.278656847  0.16978901
## [20,] -0.003699918  0.004075751 -0.1092850765  0.060630504 -0.14001185
##               [,6]         [,7]        [,8]         [,9]        [,10]
##  [1,]  0.020287787  0.142805108  0.09843646 -0.278301611  0.009873806
##  [2,] -0.204094050 -0.057000277 -0.22021046  0.161294562  0.072067012
##  [3,] -0.343188412  0.119555314 -0.28949118  0.181809913  0.524072836
##  [4,] -0.105025258  0.007184798 -0.41688450  0.012718782  0.027333197
##  [5,]  0.100912273 -0.018354377 -0.14746116  0.232813370  0.016890463
##  [6,]  1.000000000 -0.354441415 -0.10496375 -0.009921481 -0.088601797
##  [7,] -0.354441415  1.000000000 -0.01607924  0.031329654 -0.091981612
##  [8,] -0.104963754 -0.016079236  1.00000000  0.135901595 -0.278434952
##  [9,] -0.009921481  0.031329654  0.13590160  1.000000000 -0.162246077
## [10,] -0.088601797 -0.091981612 -0.27843495 -0.162246077  1.000000000
## [11,]  0.045725475  0.126140442  0.16656159 -0.083738366 -0.066654045
## [12,] -0.328930145  0.257856333 -0.13820619  0.150254630  0.128521201
## [13,]  0.295088654 -0.131602950 -0.11755768 -0.114872009  0.157272305
## [14,]  0.201520844 -0.162246545  0.08307674  0.202135493 -0.081892032
## [15,]  0.180129346 -0.224380233  0.06894077  0.147072327  0.128300648
## [16,]  0.528599055 -0.026561097 -0.14015556 -0.001864470 -0.319081519
## [17,] -0.049225541  0.195328943 -0.19346302 -0.103173890  0.320145783
## [18,]  0.015443663  0.279486584 -0.21951535 -0.077725739  0.044311420
## [19,] -0.015884107  0.083009184 -0.24665413  0.187106926 -0.121793519
## [20,]  0.282971054 -0.081789213 -0.17493554  0.027619681  0.045548253
##             [,11]        [,12]        [,13]       [,14]         [,15]
##  [1,]  0.48816957 -0.260193883  0.186538552  0.08967714 -0.1015398366
##  [2,]  0.20491311  0.035944049  0.011364226  0.13494299  0.2275554432
##  [3,] -0.19455068  0.293241022 -0.043937656 -0.51123655  0.0009629415
##  [4,]  0.11877248 -0.084025929  0.341386418  0.09879238 -0.0080482542
##  [5,]  0.05492099 -0.324438818 -0.039532037  0.46588885  0.2922821984
##  [6,]  0.04572547 -0.328930145  0.295088654  0.20152084  0.1801293457
##  [7,]  0.12614044  0.257856333 -0.131602950 -0.16224654 -0.2243802329
##  [8,]  0.16656159 -0.138206190 -0.117557685  0.08307674  0.0689407682
##  [9,] -0.08373837  0.150254630 -0.114872009  0.20213549  0.1470723274
## [10,] -0.06665404  0.128521201  0.157272305 -0.08189203  0.1283006483
## [11,]  1.00000000 -0.100678671  0.117865529  0.15396684  0.0860324871
## [12,] -0.10067867  1.000000000  0.004680883 -0.30195812 -0.1739015383
## [13,]  0.11786553  0.004680883  1.000000000  0.06624502  0.2877882187
## [14,]  0.15396684 -0.301958120  0.066245019  1.00000000  0.0641114892
## [15,]  0.08603249 -0.173901538  0.287788219  0.06411149  1.0000000000
## [16,] -0.19906332 -0.379928384 -0.086726804  0.08508370  0.1956154078
## [17,]  0.08242674 -0.323441114 -0.049588963  0.29652870  0.2956183817
## [18,]  0.02772117  0.063967740 -0.117170376 -0.26119126 -0.0256562728
## [19,]  0.04973950 -0.078001615  0.133111868  0.04732604  0.2523204698
## [20,]  0.14257428  0.063143467  0.089116551 -0.26118159 -0.0093352177
##             [,16]        [,17]       [,18]        [,19]        [,20]
##  [1,] -0.01384342 -0.067871422 -0.25650673  0.004802103 -0.003699918
##  [2,] -0.02062008  0.277345131  0.10438228  0.126144519  0.004075751
##  [3,] -0.10721192 -0.006292592  0.15382543  0.111661091 -0.109285077
##  [4,] -0.10574985  0.148046060 -0.14170449  0.278656847  0.060630504
##  [5,]  0.22364861  0.365979412 -0.10260952  0.169789007 -0.140011853
##  [6,]  0.52859905 -0.049225541  0.01544366 -0.015884107  0.282971054
##  [7,] -0.02656110  0.195328943  0.27948658  0.083009184 -0.081789213
##  [8,] -0.14015556 -0.193463020 -0.21951535 -0.246654128 -0.174935539
##  [9,] -0.00186447 -0.103173890 -0.07772574  0.187106926  0.027619681
## [10,] -0.31908152  0.320145783  0.04431142 -0.121793519  0.045548253
## [11,] -0.19906332  0.082426737  0.02772117  0.049739503  0.142574279
## [12,] -0.37992838 -0.323441114  0.06396774 -0.078001615  0.063143467
## [13,] -0.08672680 -0.049588963 -0.11717038  0.133111868  0.089116551
## [14,]  0.08508370  0.296528695 -0.26119126  0.047326036 -0.261181587
## [15,]  0.19561541  0.295618382 -0.02565627  0.252320470 -0.009335218
## [16,]  1.00000000  0.100767497  0.01373553  0.126416694 -0.197290469
## [17,]  0.10076750  1.000000000  0.36931421  0.162260155 -0.024033161
## [18,]  0.01373553  0.369314211  1.00000000  0.218975775  0.208214618
## [19,]  0.12641669  0.162260155  0.21897577  1.000000000  0.044153708
## [20,] -0.19729047 -0.024033161  0.20821462  0.044153708  1.000000000
diag(kor) <- NA                             # korelacja zmiennej z samą sobą nas nie interesuje
max(abs(kor), na.rm = TRUE)                 # najsilniejsza korelacja w szumie
## [1] 0.5285991
which(abs(kor) == max(abs(kor), na.rm = TRUE), arr.ind = TRUE) # która para zmiennych dała tę najsilniejszą korelację
##      row col
## [1,]  16   6
## [2,]   6  16
sum(abs(kor) > 0.3, na.rm = TRUE) / 2       # ile par ma |r| > 0.3? (każda para liczona raz)
## [1] 19


Przełomowe odkrycie: zmienna 6 koreluje ze zmienną 16 (r ≈ 0,53)!


Przy 20 zmiennych mamy 190 par (20 · 19 / 2 = 190 par), więc „coś” wyjdzie zawsze.

Z zastrzeżeniem !

Działamy tu na przykładzie kontrolowanego jednak szumu generowanego pseudolosowo. W życiu może być inaczej.

W uproszczeniu !

Próg istotności statystycznej (p < 0,05) dla n = 30 to mniej więcej |r| > 0,36. Na 190 par spodziewamy się więc około 9–10 korelacji „istotnych statystycznie”, które w całości są fałszywymi alarmami. Tak właśnie działa poziom istotności 5%: 5% ze 190 to 9,5.

Jeśli w badaniu mierzymy wiele zmiennych i korelujemy „wszystko ze wszystkim”, to znalezienie kilku istotnych korelacji o niczym jeszcze nie świadczy. Szczególnie niebezpieczne jest wybranie najsilniejszej z nich i opisanie jej, jakby była hipotezą od początku. To jedna z form p-hackingu.

Pokusa zabłyśnięcia oraz konieczność zrewanżowania się za przyznany grand może prowadzić niektórych w tę stronę.



wyniki <- replicate(1000, {
  k <- cor(matrix(rnorm(30 * 20), ncol = 20))
  sum(abs(k[upper.tri(k)]) > 0.3) 
})


mean(wyniki) # średnio ok. 20 par z |r| > 0,3, powtarzajmy wielokrotnie bez set.seed()
## [1] 20.372



To można policzyć też tak.

Przy braku prawdziwej zależności statystyka

\[ t = \frac{r\sqrt{n-2}}{\sqrt{1-r^2}} \]

ma rozkład \(t\)-Studenta z \(n-2\) stopniami swobody.

Dla \(r = 0{,}3\) i \(n = 30\):

\[ t = \frac{0{,}3 \cdot \sqrt{28}}{\sqrt{1-0{,}09}} = \frac{0{,}3 \cdot 5{,}292}{0{,}954} \approx 1{,}66 \]

Prawdopodobieństwo, że \(|t|\) przekroczy \(1{,}66\) przy 28 stopniach swobody, wynosi około \(0{,}107\).

Oznacza to, że każda pojedyncza para czystego szumu ma mniej więcej 11% szans na \(|r| > 0{,}3\). Przy 190 parach daje to średnio

\[ 190 \cdot 0{,}107 \approx 20 \text{ par.} \]

Można posprawdzać jeszcze w R

n <- 30; r <- 0.3
t <- r * sqrt(n - 2) / sqrt(1 - r^2)
p <- 2 * pt(-t, df = n - 2)   # ok. 0,107

# pt(-1.664, df = 28)   # ok. 0,0536 -> Funkcja pt(x, df) w R zwraca pole pod krzywą rozkładu t na lewo od x. Dla x = −1,66
# Czyli w około 5,4% przypadków t wychodzi mniejsze niż −1,66, co odpowiada korelacji r < −0,3.
# Prawy ogon. Rozkład t jest symetryczny, więc na prawo od +1,66 leży tyle samo, około 0,0536. To przypadki z r > 0,3.
# 0,0536 + 0,0536 ≈ 0,107

p * 190                       # ok. 20




Kombinatoryka


📖 Fragment do samodzielnej lektury – na zajęciach tylko go sygnalizujemy.



Funkcja choose(n,x) w R służy do obliczania współczynnika dwumianowego, czyli liczby sposobów, w jakie można wybrać (k) elementów z (n) elementów bez uwzględniania kolejności. Symbol Newtona, czytany „n po k” (liczba kombinacji bez powtórzeń)

# Liczba sposobów na wybranie 2 elementów z 5
choose(5, 2)
## [1] 10
# chcąc sprawdzić jakie to będą pary, można posłużyć się takim kodem
# Zbiór elementów
elements <- c(1, 2, 3, 4, 5) # pięć elementów

# Generowanie wszystkich kombinacji 2-elementowych
combinations <- combn(elements, 2)
print(combinations)
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
## [1,]    1    1    1    1    2    2    2    3    3     4
## [2,]    2    3    4    5    3    4    5    4    5     5



Symbol Newtona wzór:


\[ \binom{n}{k} = \frac{n!}{k!\,(n-k)!}, \qquad 0 \le k \le n \]

gdzie \(n! = 1 \cdot 2 \cdot \ldots \cdot n\), a z definicji \(0! = 1\).


Przydatne własności:

\[ \binom{n}{0} = \binom{n}{n} = 1, \qquad \binom{n}{k} = \binom{n}{n-k} \]



Przykład (Lotto). Liczba sposobów wybrania 6 liczb spośród 49:

\[ \binom{49}{6} = \frac{49!}{6!\,43!} = 13\,983\,816 \]

więc prawdopodobieństwo trafienia „szóstki” jednym zakładem wynosi

\[ P = \frac{1}{\binom{49}{6}} \approx 7{,}15 \cdot 10^{-8}. \]

Szybciutko w R

choose(49, 6)
## [1] 13983816
1 / choose(49, 6)
## [1] 7.151124e-08



Funkcja factorial() w R służy do obliczania silni liczby. Silnia liczby (n) (oznaczana jako (n! )) to iloczyn wszystkich liczb całkowitych od 1 do (n).


\[ n! = 1 \cdot 2 \cdot 3 \cdot \ldots \cdot n = \prod_{i=1}^{n} i, \qquad 0! = 1 \]

Silnię można też zdefiniować rekurencyjnie:

\[ n! = n \cdot (n-1)!, \qquad 0! = 1 \]


Interpretacja kombinatoryczna: \(n!\) to liczba permutacji, czyli sposobów ustawienia \(n\) różnych elementów w kolejności. Na przykład 3 książki można ustawić na półce na \(3! = 6\) sposobów.

Silnia pojawia się we wzorze na symbol Newtona.



Szybciutko w R

# Obliczanie silni liczby 5
factorial(5)
## [1] 120
# tak nie, wykrzyknik służył do negacji
5!

Nie znając funkcji choose(), a znając factorial() i wzory, można np. samemu policzyć…

# choose(n = 5, k = 2)

factorial(5) / (factorial(2) * factorial(5-2))
## [1] 10




ZADANIE

Mądrość tłumu

W 1907 roku Francis Galton opisał konkurs na jarmarku, w którym kilkaset osób zgadywało wagę wołu. Mediana odpowiedzi okazała się zaskakująco bliska prawdziwej wadze. Sprawdźmy to na sobie!

Pytanie do sali: ile metrów wysokości ma Pałac Kultury i Nauki w Warszawie (z iglicą)? Każdy podaje swoją odpowiedź, a my wpisujemy je na bieżąco do wektora.

zgadywania <- c(..., ..., ...)






zgadywania <- c(150, 300, 230, 180, 400, 250, 210, 120, 280, 260, 190, 500)  # "jakieś"  odpowiedzi z sali

################

prawda <- 237

mean(zgadywania); median(zgadywania)
## [1] 255.8333
## [1] 240
abs(mean(zgadywania) - prawda)     # błąd "tłumu" (średnia)
## [1] 18.83333
abs(median(zgadywania) - prawda)   # błąd "tłumu" (mediana)
## [1] 3
# Ilu z nas indywidualnie trafiło lepiej niż mediana grupy?
sum(abs(zgadywania - prawda) < abs(median(zgadywania) - prawda))
## [1] 0

Efekt działa, gdy błędy poszczególnych osób są niezależne i znoszą się nawzajem. Pytanie kontrolne: co by się stało, gdybym przed zbieraniem odpowiedzi powiedział głośno „pewnie ze 400 metrów”? (efekt zakotwiczenia)


PYTANIE

Usuń z wektora zgadywania największą wartość i porównaj średnią i medianę przed i po. Która miara zmieniła się bardziej?

bez_max <- zgadywania[-which.max(zgadywania)]
mean(zgadywania) - mean(bez_max)
median(zgadywania) - median(bez_max)




Praca z tekstem



Funkcje paste() i cat() w R służą do łączenia (konkatenacji) ciągów znaków, ale mają różne zastosowania i zachowania.


Kluczowe różnice:

  • paste() zwraca wartość, cat() tylko wyświetla wynik
  • paste() domyślnie dodaje spację, cat() nie dodaje separatora, chyba że jest to określone


Poniżej kilka przykładów użycia.

tekst <- paste("Ta książka Vonnegut'a to bodaj \"Kocia kołyska\"!"); tekst
## [1] "Ta książka Vonnegut'a to bodaj \"Kocia kołyska\"!"
cat(tekst) # nie ma znaków specjalnych, wyłączających inne znaki specjalne
## Ta książka Vonnegut'a to bodaj "Kocia kołyska"!
tekst2 <- paste('Ta książka Vonnegut\'a to bodaj \"Kocia kołyska\"!'); tekst2 # to samo co powyżej, ale zamiana cudzysłowu na apostrof 
## [1] "Ta książka Vonnegut'a to bodaj \"Kocia kołyska\"!"
cat(tekst2)
## Ta książka Vonnegut'a to bodaj "Kocia kołyska"!
tekst==tekst2
## [1] TRUE
# można ręcznie łamać tekst (\n)
tekst3 <- paste("Ta książka Vonnegut'a to bodaj \n\"Kocia kołyska\"\n!"); tekst3
## [1] "Ta książka Vonnegut'a to bodaj \n\"Kocia kołyska\"\n!"
cat(tekst3) # cat() znów printuje ładny output 
## Ta książka Vonnegut'a to bodaj 
## "Kocia kołyska"
## !
# a można o tym zapomnieć i po prostu klikać Enter pisząc kod
tekst4 <- paste("Ta książka Vonnegut'a to bodaj 
\"Kocia kołyska\"
!"); tekst4
## [1] "Ta książka Vonnegut'a to bodaj \n\"Kocia kołyska\"\n!"
cat(tekst4)
## Ta książka Vonnegut'a to bodaj 
## "Kocia kołyska"
## !
tekst3 == tekst4
## [1] TRUE
# po prostu konkatenacja wytworzonych przed momentem wektorów
paste(tekst, tekst2, tekst3, tekst4)
## [1] "Ta książka Vonnegut'a to bodaj \"Kocia kołyska\"! Ta książka Vonnegut'a to bodaj \"Kocia kołyska\"! Ta książka Vonnegut'a to bodaj \n\"Kocia kołyska\"\n! Ta książka Vonnegut'a to bodaj \n\"Kocia kołyska\"\n!"
cat(tekst, tekst2, tekst3, tekst4)
## Ta książka Vonnegut'a to bodaj "Kocia kołyska"! Ta książka Vonnegut'a to bodaj "Kocia kołyska"! Ta książka Vonnegut'a to bodaj 
## "Kocia kołyska"
## ! Ta książka Vonnegut'a to bodaj 
## "Kocia kołyska"
## !

Sugerowałbym, z uwagi na bardziej eleganckie wyświetlenie napisów, używać cat() np. do wyświetlania komunikatów użytkownikowi.

Dodatkowo, istnieje funkcja paste0(), bliźniacza do paste() z nieco innymi domyślnymi argumentami. Potrafiliby Państwo powiedzieć czym się różnią? Można sprawdzić empirycznie, można użyć dokumentacji obu funkcji. Nie wpisujcie Państwo proszę frazy w Google czy prompt jakiegoś modelu językowego, zróbcie to oldschool’owo.





Szyfr Cezara

Ponoć Juliusz Cezar szyfrował listy, przesuwając każdą literę o 3 pozycje w alfabecie.

Zróbmy to w R, korzystając z wbudowanego wektora LETTERS, indeksowania i reszty z dzielenia %%.

# Zdekoduj wiadomość od prowadzącego
paste(LETTERS[c(9, 18, 15, 14, 13, 1, 14)], collapse = "")
## [1] "IRONMAN"
imie <- "ania"
litery <- strsplit(imie, "")[[1]]  # strsplit() zwraca listę, stąd [[1]]
pozycje <- match(litery, LETTERS)  # na której pozycji alfabetu jest każda litera?
pozycje
## [1] NA NA NA NA
zaszyfrowane <- paste(LETTERS[(pozycje + 3 - 1) %% 26 + 1], collapse = "")
zaszyfrowane
## [1] "NANANANA"
# A co z polskimi znakami?
match(strsplit("łucja", "")[[1]], LETTERS)   # NA – "ł" nie ma w alfabecie łacińskim!
## [1] NA NA NA NA NA



Odszyfrujmy słowo "vwdwbvwbnd", przesuwając litery o 3 pozycje wstecz.

poz <- match(strsplit("vwdwbvwbnd", "")[[1]], LETTERS)
paste(LETTERS[(poz - 3 - 1) %% 26 + 1], collapse = "")


Własna funkcja.

szyfruj <- function(tekst, k) {
  poz <- match(strsplit(tekst, "")[[1]], LETTERS)
  paste(LETTERS[(poz + k - 1) %% 26 + 1], collapse = "")
}
szyfruj("ania", 3)    # "dqld"
## [1] "NANANANA"
szyfruj("dqld", -3)   # "ania"
## [1] "NANANANA"
szyfruj("vwdwbvwbnd", -3)   # 
## [1] "NANANANANANANANANANA"




Środowisko pracy i pakiety

Funkcja ls() pokazuje nam wszystkie obiekty w środowisku pracy (workspace)

ls()
##   [1] "a"                         "analiza"                  
##   [3] "bez_max"                   "combinations"             
##   [5] "concat_wektorow"           "czestosc_szostek"         
##   [7] "d"                         "dane"                     
##   [9] "dane_lista"                "dane_norm"                
##  [11] "dane_std"                  "df"                       
##  [13] "dojazd"                    "e"                        
##  [15] "elements"                  "factor_sex_vector"        
##  [17] "factor_temperatura_vector" "factor_vector_plci"       
##  [19] "filozofia"                 "gosciu"                   
##  [21] "gosciuwa"                  "h"                        
##  [23] "id"                        "id2"                      
##  [25] "imie"                      "imiona_studentow"         
##  [27] "kierunek"                  "kolor"                    
##  [29] "kolory"                    "kor"                      
##  [31] "krok1"                     "krok2"                    
##  [33] "krok3"                     "krok4"                    
##  [35] "krok5"                     "krok6"                    
##  [37] "krok7"                     "liczba_szostek"           
##  [39] "liczby"                    "liczby_f"                 
##  [41] "liczby_lista"              "likert"                   
##  [43] "lista_bez_nazw"            "litery"                   
##  [45] "macierz_przyklad"          "matematyka"               
##  [47] "miesiace"                  "model"                    
##  [49] "moja_dokladna_liczba"      "moja_liczba"              
##  [51] "moja_lista"                "moja_wart_logiczna"       
##  [53] "moja_wart_logiczna2"       "moje_slowo"               
##  [55] "mozliwe"                   "n"                        
##  [57] "name_vector"               "nazwa_przedmiotu"         
##  [59] "nowa_planeta"              "oceny"                    
##  [61] "oceny_matrix"              "oceny_matrix2"            
##  [63] "oceny_odwrocone"           "oceny_stat_R"             
##  [65] "oceny_text"                "oczekiwana"               
##  [67] "odp"                       "osoba"                    
##  [69] "owoce"                     "p"                        
##  [71] "planets_df"                "plec"                     
##  [73] "polskie_nazwy_mtcars"      "poprawne"                 
##  [75] "poz"                       "poza_przedzialem"         
##  [77] "poziomy"                   "pozycja"                  
##  [79] "pozycje"                   "prawda"                   
##  [81] "proby"                     "q"                        
##  [83] "r"                         "rings_vector"             
##  [85] "rnorm_result"              "runif_result"             
##  [87] "rzuty"                     "sekwencjaNumericow"       
##  [89] "sekwencjaNumericow2"       "sekwencjaNumericow3"      
##  [91] "semestry"                  "sex_vector"               
##  [93] "sprzedaz"                  "srednia"                  
##  [95] "srednia_ocen"              "srednia_semestr"          
##  [97] "stat_R"                    "suma_wektorow"            
##  [99] "szesc"                     "szpital"                  
## [101] "szum"                      "szyfruj"                  
## [103] "t"                         "tablica_przyklad"         
## [105] "tekst"                     "tekst2"                   
## [107] "tekst3"                    "tekst4"                   
## [109] "temperatura"               "temperatura_vector"       
## [111] "trufolsy"                  "v"                        
## [113] "vector_plci"               "warunki"                  
## [115] "wektor"                    "wektor_1"                 
## [117] "wektor_2"                  "wektor_z_brakami"         
## [119] "wiek"                      "wych_fiz"                 
## [121] "wyniki"                    "wysoka"                   
## [123] "x"                         "y"                        
## [125] "za_co"                     "zaszyfrowane"             
## [127] "zdali"                     "zgadywania"

Funkcja rm() usuwa poszczególne obiekty ze środowiska

rm(oceny_matrix)

Kombinacja powyższych funkcji czyści cały workspace

rm(list=ls()) 



Uwaga o rm(list = ls())

Uwaga: rm(list = ls()) usuwa tylko obiekty. Nie odłącza wczytanych pakietów i nie przywraca zmienionych ustawień (options()). Jeśli chcemy naprawdę zacząć od zera, restartujemy sesję R: Session → Restart R lub Ctrl + Shift + F10 (na Macu: Cmd + Shift + F10).


Można to również “wyklikać” w RStudio.
Można to również “wyklikać” w RStudio.

Gdyby nam zależało na pozostawieniu pojedynczych obiektów?

rm(list=setdiff(ls(), "oceny_stat_R"))



📖 Fragment do samodzielnej lektury – na zajęciach tylko go sygnalizujemy.

Funkcja gc() w R jest używana do zarządzania pamięcią poprzez przeprowadzanie tzw. garbage collection (zbieranie śmieci). gc() oczyszcza pamięć, usuwając nieużywane obiekty, co może zwolnić zasoby systemowe. Po wykonaniu, funkcja zwraca raport o aktualnym zużyciu pamięci, co może być przydatne do monitorowania i optymalizacji kodu. Rekomenduje się używać jej po usunięciu dużych obiektów ze środowiska (czasem już ich nie widać, a zalegają nam gdzieś w RAMie).

gc()
##           used (Mb) gc trigger (Mb) max used (Mb)
## Ncells  849528 45.4    1362484 72.8  1362484 72.8
## Vcells 1615758 12.4    8388608 64.0  8339338 63.7




library() vs require() oraz pakiet::funkcja()


Pakiety: library() czy require()?

Obie funkcje wczytują zainstalowany pakiet, ale różnie reagują na jego brak.

library(pakiet) require(pakiet)
Gdy pakietu nie ma błąd – skrypt się zatrzymuje ostrzeżenie – skrypt idzie dalej
Co zwraca (niewidocznie) listę wczytanych pakietów TRUE lub FALSE
Kiedy używać na początku skryptu (domyślnie) wewnątrz warunków, gdy brak pakietu da się obsłużyć
library(pakietKtoregoNieMa)              # błąd
## Error in `library()`:
## ! there is no package called 'pakietKtoregoNieMa'
wynik <- suppressWarnings(require(pakietKtoregoNieMa))
wynik                                     # FALSE
## [1] FALSE
# Notacja pakiet::funkcja() – użycie funkcji bez wczytywania całego pakietu
stats::sd(c(1, 2, 3, 4))
## [1] 1.290994
base::mean(c(1, 2, 3, 4))
## [1] 2.5

Notacja pakiet::funkcja() przydaje się też wtedy, gdy dwa pakiety mają funkcję o tej samej nazwie – od razu widać, której używamy.




Podsumowanie

Struktura Wymiary Typy elementów Tworzenie Dostęp
wektor 1 jeden typ c(), :, seq(), rep() x[i]
macierz 2 jeden typ matrix() m[i, j]
tablica dowolnie wiele jeden typ array() a[i, j, k]
factor 1 kategorie (poziomy) factor() f[i], levels(f)
ramka danych 2 każda kolumna może mieć inny typ data.frame() df[i, j], df$kolumna
lista 1 (ale zagnieżdżalna) dowolne obiekty list() l[[i]], l$nazwa






Źródła i inspiracje



techniczne

pomarancz

szary

zielony

czerwony

niebieski