Wstęp do programowania

Typy i struktury danych

Author

Krzysztof Dyba

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:

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:

x = 10           # liczba
imie = "Tomek"   # tekst

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 oaz 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 = 3
a + 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 = 3
a == 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.

Zadania

  1. Oblicz powierzchnię koła o promieniu 10 cm ze wzoru \(\pi r^2\). Jako liczby \(\pi\) użyj stałej pi.

  2. Stwórz wektor liczb od 1 do 6. Jeśli liczba jest parzysta, to zamień jej wartość na 0 używając operacji modulo.

  3. Obiekt letters zawiera litery alfabetu. Wybierz z niego 5 pierwszych liter.

  4. Stwórz ramkę danych składającą się z następujących kolumn:

    • ID: 1, 2, 3, 4, 5
    • miasto: Warszawa, Poznań, Kraków, Toruń, Wrocław
    • temperatura: 22, 18, 17, 22, 18

    Następnie wyświetl tylko te nazwy miast, w których odnotowano temperaturę powyżej 20 stopni Celsjusza.