x = 10 # liczba
imie = "Tomek" # tekstEksploracja danych geoprzestrzennych
Wprowadzenie do R
R to interpretowany język programowania wysokiego poziomu wykorzystywany przede wszystkim do przetwarzania, analizy i wizualizacji danych. Jest powszechnie używany zarówno w środowisku akademickim, jak i w przemyśle, szczególnie w takich dziedzinach jak statystyka, uczenie maszynowe, systemy informacji geograficznej czy bioinformatyka.
Cechy języka R:
- darmowy i otwarty,
- język interpretowany (kod wykonywany jest linia po linii, bez potrzeby kompilacji),
- język wysokiego poziomu (pominięcie aspektów związanych z zarządzaniem pamięcią),
- dynamiczne typowanie (zmienne nie wymagają jawnych deklaracji przed użyciem),
- wsparcie dwóch paradygmatów programowania, tj. obiektowego i funkcjonalnego,
- duży ekosystem (biblioteki, wsparcie, tutoriale),
- indeksowanie od 1,
- zwektoryzowane operacje (brak konieczności pisania pętli).
Zmienne
Do przypisania wartości zmiennej można wykorzystać dwa operatory: <- oraz =. Są między nimi pewne różnice, jednakże dla spójności z innymi językami programowania można korzystać z operatora = w następujący sposób:
Znak # służy do komentowania kodu. Wartościowe i zwięzłe komentarze sprawiają, że kod staje się zrozumiały.
Utworzone zmienne można wyświetlić wywołując bezpośrednio stworzony obiekt lub używając funkcji print().
x[1] 10
print(imie)[1] "Tomek"
Typy danych
Typ danych definiuje rodzaj wartości, jaką zmienna może przechowywać, oraz operacje, jakie można na niej wykonywać. Możemy wyróżnić następujące podstawowe typy danych:
- numeryczny numeric:
- liczby całkowite integer (np. -10, 0, 10),
- liczby zmiennoprzecinkowe double (np. 0.01, 21.37),
- tekstowy character (np. “Ala ma kota.”, ‘rower’),
- logiczny logical (TRUE, FALSE).
Do sprawdzenia typu danych służy funkcja typeof().
typeof(10.1)[1] "double"
typeof(10)[1] "double"
typeof("Tomek")[1] "character"
typeof(TRUE)[1] "logical"
Wartości logiczne TRUE i FALSE odpowiadają kolejno wartościom binarnym 1 i 0, w związku z czym można wykorzystać je w obliczeniach numerycznych (najczęściej do sumowania wystąpień wartości TRUE).
Uwaga! Domyślnie wszystkie liczby traktowane są jako zmiennoprzecinkowe, tak jak w powyższym przykładzie 10 posiada typ double, mimo iż jest całkowita. Aby traktować ją jako całkowitą należy do liczby dodać literę L.
typeof(10L)[1] "integer"
Struktury danych
Struktura danych to format organizowania, przetwarzania i przechowywania danych. Jest to sposób na uporządkowanie danych w pamięci komputera lub na dysku, tak aby można było z nich efektywnie korzystać.
Różne rodzaje struktur danych sprawdzają się w różnych zadaniach. Wybór właściwej dla danej sytuacji jest kluczową częścią programowania i tworzenia efektywnych algorytmów.
Wektor
Wektory (vector) stanowią najprostszą strukturę danych. Są to jednowymiarowe tablice, które mogą przechowywać zbiór elementów tego samego typu danych (np. tylko liczby, tylko tekst lub tylko wartości logiczne). Wektory są podstawą bardziej złożonych struktur danych, takich jak macierze czy ramki danych.
Do tworzenia wektorów wykorzystuje się funkcje c() (combine) łączącą elementy.
# stworzenie wektora liczbowego
vec = c(1, 2, 3, 4, 5)
vec[1] 1 2 3 4 5
Wektor liczb całkowitych można stworzyć jako ciąg liczbowy (sekwencję) o interwale 1 (ciąg rosnący) lub -1 (ciąg malejący) używając dwukropka (:), gdzie określony jest początek i koniec zakresu (start:koniec).
vec = 1:5 # sekwencja
vec[1] 1 2 3 4 5
# stworzenie wektora tekstowego
vec = c("a", "b", "c", "d", "e")
vec[1] "a" "b" "c" "d" "e"
Aby odwołać się do poszczególnych elementów wektora używa się pojedynczych nawiasów kwadratowych ([]) oraz indeksów.
# selekcja poprzez indeks
vec[3][1] "c"
vec[1:3][1] "a" "b" "c"
vec[-5][1] "a" "b" "c" "d"
Czynnik
Czynnik (factor) używany jest do reprezentowania zmiennych kategorycznych podczas pracy z danymi jakościowymi, takimi jak etykiety czy klasy, ponieważ przechowują unikalne wartości kategorii. Mogą być również uporządkowane (stopniowane) według określonej kolejności (np. niski < średni < wysoki).
Czynniki są przechowywane jako liczby całkowite, tj. każdej kategorii przypisywany jest unikalny kod liczby całkowitej, co efektywnie ogranicza ilość zajmowanej pamięci. Dodatkowo, przechowywane są etykiety (opisy) powiązane z tymi liczbami całkowitymi.
Do tworzenia czynników służy funkcja factor().
fct = factor(c("Drzewo", "Skała", "Budynek", "Drzewo", "Drzewo"))
length(fct) # liczba wszystkich elementów w wektorze[1] 5
nlevels(fct) # liczba kategorii[1] 3
levels(fct) # wyświetl kategorie[1] "Budynek" "Drzewo" "Skała"
Macierz
Macierze (matrix) to dwuwymiarowe tablice składające się z wierszy oraz kolumn, w których wszystkie elementy są tego samego typu.
Do tworzenia macierzy służy funkcja matrix().
mat = matrix(1:9, ncol = 3)
mat [,1] [,2] [,3]
[1,] 1 4 7
[2,] 2 5 8
[3,] 3 6 9
Podobnie jak w przypadku wektorów, selekcja danych odbywa się poprzez nawiasy kwadratowe i wskazanie dwóch indeksów w tej kolejności: [wiersze, kolumny].
# selekcja pierwszego wiersza
mat[1, ][1] 1 4 7
# selekcja pierwszej kolumny
mat[, 1][1] 1 2 3
Lista
Lista jest uniwersalną strukturą danych, która może przechowywać dowolne zbiory obiektów o różnych typach i długościach. Listy mogą zawierać inne listy (a nawet inne struktury), tworząc zagnieżdżone hierarchie o dowolnej głębokości.
Do tworzenia list służy funkcja list().
lst = list(imie = "Ania", wiek = 25, oceny = c(4, 4, 5), czy_student = TRUE)
lst$imie
[1] "Ania"
$wiek
[1] 25
$oceny
[1] 4 4 5
$czy_student
[1] TRUE
Selekcja w przypadku list jest bardziej zaawansowana niż w przypadku wektorów, ponieważ można dokonać jej na trzy różne sposoby:
- znak dolara
$jeśli lista posiada nazwy, - podwójne nawiasy kwadratowe
[[]](zwraca tylko zawartość listy), - pojedynczy nawias kwadratowy
[](zwraca listę i jej zawartość).
lst$imie[1] "Ania"
lst["oceny"] # zwrócona jest lista$oceny
[1] 4 4 5
lst[["oceny"]] # zwrócony jest wektor[1] 4 4 5
Jeśli mamy do czynienia ze strukturą zagnieżdżoną, to do selekcji używamy kolejne nawiasy kwadratowe lub symbol dolara, które odnoszą się do kolejnych poziomów w hierarchii.
lst = list(
osoba1 = list(imie = "Ania", wiek = 25),
osoba2 = list(imie = "Andrzej", wiek = 30)
)
lst$osoba1$wiek # wiek pierwszej osoby[1] 25
Ramka danych
Ramka danych (data frame) jest fundamentalną i najczęściej używaną strukturą danych do przechowywania i przetwarzania danych tabelarycznych (tj. zorganizowane w wierszach i kolumnach, jak w arkuszu kalkulacyjnym czy bazie danych). Znajomość ramek danych jest absolutnie niezbędna do przeprowadzania wszelkiego rodzaju analiz danych!
Ramka danych ma dwuwymiarowy, prostokątny format, składający się z wierszy i kolumn. Każdy wiersz reprezentuje pojedynczą obserwację (rekord). Każda kolumna reprezentuje zmienną (atrybut), które mogą być różnego typu. Co więcej, kolumny muszą posiadać nazwy oraz dokładnie tę samą długość. W sytuacji, gdy kolumny nie mają tej samej długości, używa się brakujących wartości NA (Not Available).
Ramkę danych można stworzyć używając funkcję data.frame() i definiując wektory jako kolumny.
df = data.frame(
imie = c("Ania", "Andrzej"),
wiek = c(25, 30),
miasto = c("Warszawa", "Kraków")
)
df imie wiek miasto
1 Ania 25 Warszawa
2 Andrzej 30 Kraków
Selekcja danych wygląda identycznie jak w przypadku list (ponieważ formalnie ramka danych jest listą).
df$imie[1] "Ania" "Andrzej"
df$imie[1][1] "Ania"
df[1, 1][1] "Ania"
df[df$wiek == 25, "imie"][1] "Ania"
Operatory
Arytmetyczne
- Dodawanie
+, - Odejmowanie
-, - Mnożenie
*, - Dzielenie
/, - Modulo
%%(reszta z dzielenia), - Potęgowanie
^.
a = 10
b = 3a + b[1] 13
a - b[1] 7
a * b[1] 30
a / b[1] 3.333333
a %% b[1] 1
a ^ b[1] 1000
Porównawcze
- Operator równości
==, - Operator negacji równości
!=, - Operatory nierówności
>,>=,<,<=.
a = 10
b = 3a == b[1] FALSE
a != b[1] TRUE
a < b[1] FALSE
a > b[1] TRUE
Operatory porównawcze są bardzo przydatne do indeksowania elementów.
vec = c(1, 2, 3, 4, 5)
vec[1] 1 2 3 4 5
# zwraca wartość logiczną dla każdego elementu
vec >= 3[1] FALSE FALSE TRUE TRUE TRUE
# zwraca tylko te elementy, które spełniają warunek
vec[vec >= 3][1] 3 4 5
Logiczne
- Koniunkcja
&(TRUE, jeśli obie wartości są prawdziwe), - Alternatywa
|(TRUE, jeśli przynajmniej jedna wartość jest prawdziwa), - Negacja
!(odwraca wartość logiczną).
a = 10
a > 0 & a < 100[1] TRUE
TRUE & FALSE[1] FALSE
TRUE | FALSE[1] TRUE
Operatory logiczne mogą być pojedyncze (&, |) lub podwójne (&&, ||). Pojedyncze działają element po elemencie wektorów, podczas gdy podwójne działają dla wektorów składających się wyłącznie z jednego elementu (tzw. skalarów). Dodatkowo, jeśli pierwszy warunek jest wystarczający do określenia wyniku, to nie jest wykonywana ewaluacja kolejnych warunków.
# zwraca dwie wartości logiczne
c(TRUE, FALSE) & c(TRUE, TRUE)[1] TRUE FALSE
Operatory pojedyncze stosuje się najczęściej do przetwarzania danych (np. filtrowania ramek danych), natomiast operatory podwójne wykorzystuje się w przypadku instrukcji warunkowych, gdzie wymagana jest jednoznaczna wartość logiczna.
Wbudowane funkcje
R posiada szereg wbudowanych funkcji, które są bardzo przydatne do analizy danych i znacząco ułatwiają pracę:
sample()– losowanie elementów z wektora (ze zwracaniem lub bez).seq()– generowanie sekwencji liczb z określonym interwałem.rnorm()– generowanie liczb losowych z rozkładu normalnego.runif()– generowanie liczb losowych z rozkładu jednostajnego.rep()– powtarzanie elementów wektora lub całego wektora.rev()– odwrócenie kolejności elementów w wektorze.sort()– sortowanie elementów w kolejności rosnącej lub malejącej.round()– zaokrąglenie wartości liczbowych.
# wylosuj 5 liczb z zakresu od 1 do 10
sample(1:10, size = 5)[1] 3 2 9 1 4
# stwórz sekwencję liczb od 1 do 10
seq(1, 10, by = 1) [1] 1 2 3 4 5 6 7 8 9 10
# wylosuj 5 liczb (rozkład normalny)
rnorm(5)[1] -1.55668271 0.05747562 0.36100498 0.20525265 1.85881946
# wylosuj 5 liczb (rozkład jednostajny)
runif(5)[1] 0.97682360 0.09953012 0.90656300 0.50895791 0.87066627
# powtórz cały wektor 3 razy
rep(c(1, 2), times = 3)[1] 1 2 1 2 1 2
# powtórz każdy element 3 razy
rep(c(1, 2), each = 3)[1] 1 1 1 2 2 2
# odwróć kolejność
rev(c("a", "b", "c"))[1] "c" "b" "a"
# sortuj elementy wektora
sort(c(4, 1, 3, 2), decreasing = FALSE)[1] 1 2 3 4
# zaokrąglij liczbę do części setnej
round(1.1833, digits = 2)[1] 1.18
Eksploracyjna analiza danych
Eksploracyjna analiza danych (exploratory data analysis) to proces analizowania i opisywania zbioru danych w celu zrozumienia jego struktury. Obejmuje ona stosowanie różnych metod statystycznych i graficznych w celu opisania rozkładu zmiennych, identyfikacji relacji między zmiennymi i wykrywania wszelkich nietypowych przypadków (np. nieprawidłowe wartości, duplikaty, brakujące wartości czy wartości odstające).
Eksploracja danych jest niezbędnym pierwszym krokiem w każdym projekcie dotyczącym analizy danych, ponieważ zapobiega potencjalnym błędom. Pamiętaj o zasadzie: “garbage in, garbage out”!
Przeznaczeniem eksploracji jest znalezienie odpowiedzi na następujące pytania:
- Jakiego typu jest zmienna?
- Jak wygląda rozkład zmiennej?
- Czy występują jakieś anomalie (np. brakujące/odstające wartości, błędy)?
- Czy występuje jakaś zależność (korelacja) pomiędzy zmiennymi?
Opisowa
str()– wyświetla strukturę obiektu, obejmującą nazwy kolumn, typy danych i przykładowe wartości.summary()– zwraca podstawowe statystyki opisowe.head()– domyślnie wyświetla pierwsze 6 wierszy zbioru danych.tail()– domyślnie wyświetla ostatnie 6 wierszy zestawu danych.unique()– zwraca unikalne wartości.length()– zwraca liczbę elementów obiektu.dim()– zwraca liczbę wierszy i kolumn macierzy lub ramki danych.ncol()– zwraca liczbę kolumn w macierzy lub ramce danych.nrow()– zwraca liczbę wierszy w macierzy lub ramce danych.rownames()– zwraca lub modyfikuje nazwy wierszy macierzy lub ramki danych.colnames()– zwraca lub modyfikuje nazwy kolumn macierzy lub ramki danych.class()– zwraca klasę obiektu (np. ramka danych).
mtcars to wbudowany zestaw danych w R, który zawiera informacje o 32 różnych modelach samochodów z magazynu Motor Trend US z 1974 r. Zawiera 11 zmiennych (kolumn) związanych z osiągami i konstrukcją tych samochodów. Sprawdź dokumentację tego zbioru używając ?mtcars.
# wczytanie danych do sesji
mtcars = mtcars
# struktura danych
str(mtcars)'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 ...
Sprawdź samodzielnie pozostałe funkcje do eksploracji i odpowiedz na wcześniej postawione pytania.
Uwaga! Obliczanie statystyk opisowych (takich jak średnia, mediana czy odchylenie standardowe) dla zmiennej jakościowej (np. rodzaj skrzyni biegów, klasy pokrycia terenu) nie ma sensu, ponieważ są to właściwie tylko etykiety bez znaczenia numerycznego. W przypadku zmiennych jakościowych (kategorycznych) można wyznaczyć modę (dominantę) czy częstość występowania poszczególnych kategorii. Aby środowisko R poprawnie traktowało te zmienne jako kategorie (grupy), należy dokonać konwersji z typu numerycznego na czynnikowy, używając funkcji as.factor().
Wizualna
Wykres słupkowy
Wykres słupkowy służy do wizualnego przedstawiania danych kategorycznych za pomocą prostokątnych słupków. Wysokość lub długość słupków jest proporcjonalna do wartości, które reprezentują.
# zlicz wystąpienia dla każdego cylindra (grupy)
cyl_counts = table(mtcars$cyl)
cyl_counts
4 6 8
11 7 14
barplot(cyl_counts, xlab = "Liczba cylindrów", ylab = "Liczba samochodów")Wykres pudełkowy
Wykres pudełkowy (nazywany również wykresem ramka-wąsy) to sposób przedstawienia rozkładu danych w oparciu o pięć następujących statystyk:
- Wartość minimalna,
- Pierwszy kwartyl (Q1, 25. percentyl),
- Mediana (Q2, 50. percentyl),
- Trzeci kwartyl (Q3, 75. percentyl),
- Wartość maksymalna.
“Pudełko” pokazuje zakres międzykwartylowy (\(IQR = Q3 - Q1\)), który zawiera środkowe 50% danych, natomiast “wąsy” rozciągają się od “pudełka” do najbardziej ekstremalnych punktów w zbiorze danych, które nie są klasyfikowane jako wartości odstające. Istnieje kilka sposobów definiowania długości “wąsów”, ale najbardziej standardową metodą jest ich wyznaczenie z równań: \(Q3 + 1.5 \times IQR\) dla górnej części oraz \(Q1 - 1.5 \times IQR\) dla dolnej części. Jeśli jakieś punkty wykraczają poza ten zakres zdefiniowany przez “wąsy”, to wtedy mamy do czynienia z wartościami odstającymi (outliers).
Wykresy pudełkowe wykorzystuje się przede wszystkim do porównaniu rozkładu danych w wielu grupach, oceny symetrii i rozrzutu wartości oraz wykrywania wartości odstających.
Do stworzenia prostego wykresu pudełkowego służy funkcja boxplot().
boxplot(mtcars$mpg, ylab = "Mile na galon (MPG)")Używając odpowiednich parametrów możemy poprawić wygląd ryciny oraz uwzględnić zmienność w poszczególnych grupach, tj. zależność od liczby cylindrów (formuła mpg ~ cyl, czytaj mpg zależy od cyl).
boxplot(mpg ~ cyl, data = mtcars,
main = "Mile na galon (MPG) w zależności od liczby cylindrów",
xlab = "Liczba cylindrów",
ylab = "Mile na galon (MPG)",
col = "lightblue",
border = "navy")Histogram
Histogram, podobnie jak wykres pudełkowy, służy również do przedstawienia rozkładu danych liczbowych, z tą różnicą iż w postaci słupków. Histogram grupuje dane w przedziały (interwały) i pokazuje częstość wystąpień obserwacji mieszczących się w każdym z przedziałów. Kształt histogramu dostarcza istotnych informacji o rozkładzie danych (np. czy jest skośny czy symetryczny).
Podczas tworzenia histogramu należy wybrać odpowiednią liczbę przedziałów, ponieważ zbyt mała liczba może spowodować nadmierne uproszczenie danych (generalizację), natomiast zbyt duża liczba może spowodować zaszumienie i utrudnić interpretację.
hist(mtcars$hp, xlab = "Konie mechaniczne [KM]", ylab = "Częstość", main = NULL)Wykres gęstości
Wykres gęstości to kolejna metoda wizualizacji rozkładu zmiennej ciągłej. Jest to wygładzona wersja histogramu pokazująca ciągłą krzywą zamiast dyskretnych przedziałów jak w histogramie. Oś X przedstawia wartości zmiennej wejściowej, natomiast oś Y prezentuje szacowaną gęstość prawdopodobieństwa, co pomaga zidentyfikować najczęstsze wartości w danych.
d = density(mtcars$hp)
plot(d, main = NA, xlab = "Konie mechaniczne [KM]", ylab = "Gęstość")Wykres rozrzutu
Wykres rozrzutu przedstawia związek między dwiema zmiennymi ciągłymi. Każdy punkt na wykresie przedstawia pojedynczą obserwację, przy czym oś X reprezentuje zmienną niezależną (wyjaśniającą), a oś Y zmienną zależną (modelowaną). Podstawowym zastosowaniem jest wizualne zbadanie potencjalnego związku między dwiema zmiennymi, tj. czy istnieje między nimi jakakolwiek korelacja (liniowa bądź nieliniowa). Jeśli tak, czy jest silna?
W tym miejscu należy przypomnieć, że korelacja nie oznacza związku przyczynowego!
plot(mtcars$wt, mtcars$mpg,
main = "Spalanie paliwa w zależności od wagi samochodu",
xlab = "Waga [1000 funtów]",
ylab = "Mile na galon (MPG)",
pch = 19, # kształt punktu
col = "darkred")Do wykresu możemy także dodać legendę używając funkcji legend().
plot(mtcars$wt, mtcars$mpg,
main = "Spalanie paliwa w zależności od wagi i liczby cylindrów samochodu",
xlab = "Waga [1000 funtów]",
ylab = "Mile na galon (MPG)",
pch = 19,
col = mtcars$cyl)
legend("topright",
legend = sort(unique(mtcars$cyl)),
col = sort(unique(mtcars$cyl)),
pch = 19,
title = "Cylindry")Zadania
- Oblicz powierzchnię koła o promieniu 10 cm ze wzoru \(\pi r^2\). Jako liczby \(\pi\) użyj stałej
pi. - Definiując dwie zmienne
imieorazwiek, wykorzystaj funkcjępaste0()do stworzenia tekstu o następującej treści:Cześć! Moje imię to <imie>. Mam <wiek> lat. - Używając funkcji
seq(), wygeneruj wektor (sekwencje) liczb parzystych od 2 do 20 i następnie oblicz sumę, średnią oraz odchylenie standardowe jego elementów używając odpowiednich funkcji. - Dokonaj eksploracji opisowej dostępnego zbioru danych
iris. W szczególności sprawdź strukturę i typ danych, liczbę kolumn i wierszy, unikalne gatunki oraz statystyki opisowe. - Stwórz wykres rozrzutu wykorzystując dane ze zbioru
irisoraz zmienneSepal.LengthiSepal.Width. Jako kolor uwzględnij gatunek irysów (zmiennaSpecies) oraz dodaj legendę do wykresu. - W zbiorze
irisznajdź ten gatunek irysów, którego wartośćSepal.Lengthjest większa niż 7 cm. Jaki to gatunek?