Przymus

Na koniec krótko wspomnimy, czym jest przymus w R, ponieważ jest to temat zamieszania dla nowicjuszy. Kiedy wywołujesz funkcję z argumentem innego typu niż oczekiwano, R spróbuje wymusić wartości, aby funkcja działała, a to może wprowadzić błędy, jeśli nie zostanie poprawnie obsłużona. R będzie postępować zgodnie z mechanizmem podobnym do tego, który był używany podczas tworzenia wektorów. Języki z silnymi typami (takie jak Java) będą zgłaszać wyjątki, gdy obiekt przekazany do funkcji jest niewłaściwego typu i będą próbować nie konwertować obiektu na typ zgodny. Jednak, jak wspomnieliśmy wcześniej, R został zaprojektowany do działania po wyjęciu z pudełka w wielu nieprzewidzianych kontekstach, więc wprowadzono przymus. W poniższym przykładzie pokazujemy, że jeśli wywołasz naszą funkcję distance() i przekażemy wektory logiczne zamiast liczbowych, R przekształci wektory logiczne w wektory numeryczne, używając TRUE jako 1 i FALSE jako 0 i kontynuuj obliczenia. Aby uniknąć tego problemu we własnych programach, należy jawnie wymuszać typy danych za pomocą funkcji as.*(), o których wspominaliśmy wcześniej:

x <- c(1,2,3)

y <- c(TRUE, FALSE, TRUE)

distance(x,y)

#> [1] 8

Operatory to funkcje

Teraz, gdy już wiesz, jak działają funkcje ,powinieneś wiedzieć, że nie wszystkie wywołania funkcji wyglądają jak te, które pokazaliśmy do tej pory, gdzie używasz nazwy funkcji, po której następuje nawias zawierający argumenty funkcji. Właściwie wszystkie instrukcje w R, w tym ustawianie zmiennych i operacje arytmetyczne, są funkcjami w tle, nawet jeśli najczęściej nazywamy je inną składnią. Pamiętaj, że wspomnieliśmy, że do obiektów R można się odwoływać za pomocą prawie każdego łańcucha, ale powinieneś tego unikać. Tutaj pokazujemy, jak używanie tajemniczych nazw może być przydatne w pewnych kontekstach. Poniższy przykład pokazuje, jak operatory przypisania, wyboru i dodawania są zwykle używane ze składnią cukru (termin używany do opisania składni, która istnieje w celu ułatwienia użycia), ale w tle używają funkcji o nazwie [ <- , [, i +, odpowiednio. Funkcja [<- () otrzymuje trzy argumenty: wektor, który chcemy zmodyfikować, pozycję jaką chcemy zmodyfikować w wektorze i wartość, jaką chcemy, aby miał on w tej pozycji. Funkcja [()otrzymuje dwa argumenty, wektor, z którego chcemy pobrać wartość oraz pozycję wartości, którą chcemy pobrać. Ostatnia funkcja +() otrzymuje dwie wartości jakie chcemy dodać. Poniższy przykład pokazuje lukier składni, po którym następuje wywołanie funkcji w tle, które wykonuje dla nas R:

x <- c(1,2,3,4,5)

x

#> [1] 1 2 3 4 5

x[1] <- 10

x

#> [1] 10 2 3 4 5

[<- (x,1,20)

#> [1] 20 2 3 4 5

x

#> [1] 10 2 3 4 5

x[1]

#> [1] 10

‘[‘(x,1)

#> [1] 10

x[1]+x[2]

#> [1] 12

‘+’ (c[1],x[2])

#> [1] 12

‘+’ (‘[‘(x,1),’[‘(x,1))

#> [1] 20

W praktyce prawdopodobnie nigdy nie napisałbyś tych instrukcji jako jawnych wywołań funkcji. Cukier składniowy jest znacznie bardziej intuicyjny i łatwiejszy do odczytania. Jednak aby skorzystać z niektórych zaawansowanych technik przedstawionych w tej książce, warto wiedzieć, że każda operacja w języku R jest funkcją.

Funkcje jako argumenty

Czasami, gdy chcesz uogólnić funkcje, możesz chcieć podłączyć określoną funkcjonalność do funkcji. Możesz to zrobić na różne sposoby. Na przykład, możesz użyć warunków warunkowych, co zobaczymy w następnej sekcji tego rozdziału, aby zapewnić im różne funkcje w zależności od kontekstu. Jednak w miarę możliwości należy unikać warunkowych, ponieważ mogą one niepotrzebnie komplikować nasz kod. Lepszym rozwiązaniem byłoby przekazanie funkcji jako parametru, który będzie wywoływany w razie potrzeby, a jeśli chcemy zmienić sposób zachowania funkcji, możemy zmienić funkcję, przez którą przechodzimy dla określonego zadania. Może się to wydawać skomplikowane, ale w rzeczywistości jest to bardzo proste. Zacznijmy od utworzenia funkcji l1_norm, która oblicza odległość między dwoma wektorami, ale używa sumy bezwzględnych różnic między odpowiadającymi im współrzędnymi zamiast sumy kwadratów różnic, tak jak robi to nasza funkcja l2_norm. Zauważ, że używamy tego samego podpisu dla naszych dwóch funkcji, co oznacza, że ​​obie otrzymują te same wymagane i opcjonalne argumenty, którymi w tym przypadku są x i y. Jest to ważne, ponieważ jeśli chcemy zmienić zachowanie poprzez przełączanie funkcji, musimy upewnić się, że są one w stanie pracować z tymi samymi danymi wejściowymi, w przeciwnym razie możemy uzyskać nieoczekiwane wyniki a nawet błędy:

l1_norm <- function(x,y = 0) sum (abs(x-y)

l1_norm(a)

#> [1] 6

l1_norm(a,b)

#> [1] 9

Teraz, gdy nasze l2_norm() i l1_norm() są zbudowane tak, że można je przełączać aby zapewnić inne zachowanie, stworzymy trzecią funkcję distance(), która przyjmie dwa wektory jako argumenty, ale otrzyma również argument norm, który będzie zawierał funkcję, której chcemy użyć do obliczyć odległość. Zauważ, że określamy, że chcemy domyślnie używać l2_norm() w przypadku, gdy nie ma jawnego wyboru podczas wywoływania funkcji, i aby to zrobić, po prostu określamy symbol, który zawiera obiekt funkcji, bez nawiasów . Na koniec zwróć uwagę, że jeśli chcemy uniknąć wysyłania wektora 􀁚, ale chcemy określić, jaka norma ma być użyta, to musimy przekazać ją jako nazwany argument, w przeciwnym razie R zinterpretowałby drugi argument jako wektor 􀁚, a nie funkcja norm:

distance <- function(x,y = 0, norm = l2_norm) norm(x,y)

distance(a)

#> [1] 14

distance(a,b)

#> [1] 27

distance(a,b,l2_norm)

#> [1] 27

distance(a,b,l1_norm)

#> [1] 9

distance(a,norm = l1_norm)

#> [1] 6

Opcjonalne argumenty

Podczas tworzenia funkcji możesz określić domyślną wartość argumentu, a jeśli to zrobisz, argument ten jest uważany za opcjonalny. Jeśli nie określisz wartości domyślnej dla argumentu i nie określisz wartości podczas wywoływania funkcji, otrzymasz błąd, jeśli funkcja spróbuje użyć argumentu. W poniższym przykładzie pokazujemy, że jeśli pojedynczy wektor numeryczny zostanie przekazany do naszej funkcji l2_norm() w obecnym kształcie, spowoduje to błąd, ale jeśli przedefiniujemy go tak, aby drugi wektor był opcjonalny, to po prostu zwrócimy normę pierwszego wektora, a nie odległość między dwoma różnymi wektorami Aby to osiągnąć, zapewnimy zerowy wektor o długości jeden, ale ponieważ R powtarza elementy wektorowe aż wszystkie wektory zaangażowane w operację będą miały tę samą długość , jak widzieliśmy wcześniej w tym rozdzial, automatycznie rozszerzy nasz zerowektor do odpowiedniego wymiaru:

l2_norm(a)

#> Błąd w l2_norm (a): brak argumentu “y”, brak wartości domyślnej

l2_norm <-  function(x,y,= 0) sum ((x-y)^2)

l2_norm(a)

#> [1] 14

l2_norm(a,b)

#> [1] 27

Jak widać, teraz nasza funkcja może opcjonalnie otrzymać wektor 􀁚, ale bez niego będzie działać zgodnie z oczekiwaniami. Zwróć również uwagę, że wprowadziliśmy kilka komentarzy do naszego kodu. Cokolwiek pojawi się po symbolu 􀀅 w wierszu, R zignoruje, co pozwala nam wyjaśnić nasz kod tam, gdzie jest to konieczne. Wolę unikać komentarzy, ponieważ uważam, że kod powinien być wyrazisty i przekazywać swój zamiar bez potrzeby komentowania, ale są one przydatne od czasu do czasu.

Dziel i zwyciężaj funkcjami

Funkcje są podstawowym budulcem języka R. Aby opanować wiele z bardziej zaawansowanych technik opisanych w tej książce, potrzebujesz solidnych podstaw ich działania. Skorzystaliśmy już z kilku funkcji powyżej, ponieważ bez nich nie można zrobić nic ciekawego w R. Są po prostu tym, co pamiętasz z zajęć z matematyki, sposobem przekształcania danych wejściowych w wyniki. W szczególności w języku R funkcja jest obiektem, który przyjmuje inne obiekty jako dane wejściowe, zwane argumentami, i zwraca obiekt wyjściowy. Większość funkcji ma następującą postać f(argumet_1,argument_2, …) Gdzie f to nazwa funkcji, a argumet_1,argument_2 i tak dalej to argumenty funkcji. Zanim przejdziemy dalej, powinniśmy pokrótce wspomnieć o roli nawiasów klamrowych ({}) w R. Często są one używane do grupowania zestawu operacji w treści funkcji, ale mogą być również używane w innych później, Dodawanie interaktywności z pulpitami nawigacyjnymi). Nawiasy klamrowe służą do oceny serii wyrażeń, które są oddzielone znakami nowej linii lub średnikami i zwracają jako wynik tylko ostatnie wyrażenie. Na przykład poniższy wiersz wyświetla tylko operację x + y na ekranie, ukrywając wynik operacji x * y, która zostałaby wydrukowana, gdybyśmy wpisywali wyrażenia krok po kroku. W tym sensie nawiasy klamrowe służą do hermetyzacji zestawu zachowań i zapewniają wynik tylko z ostatniego wyrażenia:

{ x <- 1; y <- 2; x * y ; x = y }

#> [1] 3

Możemy stworzyć własną funkcję używając konstruktora function90 i przypisać ją do symbolu. Konstruktor fuctio() przyjmuje dowolną liczbę nazwanych argumentów, których można użyć w treści funkcji. Nienazwane argumenty można również przekazywać przy użyciu notacji argumentów „…”, ale jest to zaawansowana technika, której nie będziemy omawiać . Zachęcamy do przeczytania dokumentacji funkcji, aby dowiedzieć się więcej o nich. Podczas wywoływania funkcji argumenty mogą być przekazywane według pozycji lub nazwy. Porządek pozycyjny musi odpowiadać kolejności podanej w sygnaturze funkcji (czyli specyfikacji function() z odpowiednimi argumentami), ale używając nazwanych argumentów, możemy je przesłać w dowolnej kolejności . Jak pokazuje poniższy przykład. W poniższym przykładzie tworzymy funkcję, która oblicza odległość euklidesową między dwoma wektorami numerycznymi i pokazujemy, jak można zmienić kolejność argumentów, jeśli użyjemy nazwanych argumentów. Aby zrealizować ten efekt, używamy funkcji print(), aby upewnić się, że w konsoli możemy zobaczyć, co R odbiera jako wektory x i y. Podczas tworzenia własnych programów używanie funkcji w podobny sposób jest bardzo przydatne, aby zrozumieć, co się dzieje. Zamiast używać nazwy funkcji takiej jak euclidian_distance, użyjemy 12_norm, ponieważ jest to uogólniona nazwa takiej operacji podczas pracy ze spacjami dowolne wymiary liczbowe oraz ponieważ ułatwi to zrozumienie kolejnego przykładu. Zwróć uwagę, że chociaż poza wywołaniem funkcji nasze wektory nazywane są a i b, ponieważ są one przekazywane do argumentów x i y, to są to nazwy, których musimy używać w naszej funkcji. Początkującym łatwo jest pomylić te obiekty jako takie same, gdybyśmy użyli nazw x i y w obu miejscach:

12_norm <- function(x,y) {

print(„x”)

print(x)

print(„y”)

print(y)

element_to_element_difference <- x – y

result ,- sum(element_to_element_difference^2)

return(result)

}

a <- c(1,2,3)

b <- c(4,5,6)

12_norm(a,b)

#> [1] “x”

#> [1] 1 2 3

#> [1] “y”

#> [1] 4 5 6

#> [1] 27

12_norm(b,a)

#> [1] “x”

#> [1] 4 5 6

#> [1] “y”

#> [1] 1 2 3

#> [1] 27

12_norm(x =a , y = b)

#> [1] “x”

#> [1] 1 2 3

#> [1] “y”

#> [1] 4 5 6

#> [1] 27

12_notm(y = b, x = a)

#> [1] “x”

#> [1] 1 2 3

#> [1] “y”

#> [1] 4 5 6

#> [1] 27

Funkcje mogą używać funkcji return() do określenia wartości zwracanej przez funkcję. Jednak R po prostu zwróci ostatnie oszacowane wyrażenie jako wynik funkcji, więc możesz zobaczyć kod, który nie używa jawnie funkcji return(). Nasza poprzednia implementacja funkcji 12_norm() wydaje się być nieco zagracona. Jeśli funkcja ma jedno wyrażenie, możemy uniknąć używania nawiasów klamrowych, co możemy osiągnąć, usuwając wywołania funkcji print() i unikając tworzenia obiektów pośrednich, a ponieważ wiemy, że działa dobrze, możemy zrób to bez wahania. Ponadto unikamy jawnego wywoływania funkcji return(), aby jeszcze bardziej uprościć nasz kod. Jeśli to zrobimy, nasza funkcja wygląda znacznie bliżej swojej matematycznej definicji i jest łatwiejsza do zrozumienia, prawda? 12_norm <- function(x,y)  sum ((x-y)^2)

Ponadto, jeśli nie zauważyłeś, ponieważ używamy operacji wektoryzowanych, możemy wysłać wektory o różnych długościach (wymiarach), pod warunkiem, że oba wektory mają tę samą długość, a funkcja będzie działać tak, jak się tego spodziewamy, bez względu na wymiarowość przestrzeni, z którą pracujemy. Jak wspomniałem wcześniej, wektoryzacja może być dość potężna. W poniższym przykładzie pokazujemy takie zachowanie z wektorami o wymiarze 1 (matematycznie nazywanymi skalarami), a także wektorami o wymiarze 5 utworzonymi za pomocą składni skrótu „:”:

12_norm(1,2)

#> [1] 1

12_norm(1:5, 6:10)

#> [1] 125

Zanim przejdziemy dalej, chcę tylko wspomnieć, że należy zawsze starać się przestrzegać zasady pojedynczej odpowiedzialności, która mówi, że każdy przedmiot (w tym przypadku funkcjonuje) powinien skupiać się na robieniu jednej rzeczy i robić to bardzo dobrze. Ilekroć opisujesz funkcję utworzyłeś jako robiąc „coś” i „coś innego”, prawdopodobnie robisz to źle, ponieważ „i” powinny informować Cię, że funkcja wykonuje więcej niż jedną rzecz, i powinieneś podzielić ją na dwie lub więcej funkcji, które ewentualnie zadzwonić do siebie.

Ramki danych

Teraz przejdźmy do ramek danych, które bardzo przypominają arkusze kalkulacyjne lub tabele bazy danych. W kontekście naukowym eksperymenty składają się z pojedynczych obserwacji (wierszy), z których każda obejmuje kilka różnych zmiennych (kolumn). Często zmienne te zawierają różne typy danych, których nie można przechowywać w macierzach, ponieważ muszą zawierać jeden typ danych. Ramka danych jest naturalnym sposobem reprezentowania takich heterogenicznych danych tabelarycznych. Każdy element w kolumnie musi być tego samego typu, ale różne elementy w wierszu mogą być różnych typów, dlatego mówimy, że ramka danych jest heterogeniczną strukturą danych. Z technicznego punktu widzenia ramka danych to lista, której elementami są wektory o równej długości i dlatego pozwala na niejednorodność. Ramki danych są zwykle tworzone przez wczytywanie danych za pomocą read.table(), read.csv() lub innych podobnych funkcji ładowania danych. Jednak można je również utworzyć jawnie za pomocą funkcji data.frame() lub wymusić na podstawie innych typów obiektów, takich jak listy. Aby utworzyć ramkę danych za pomocą funkcji data.frame(), zwróć uwagę, że wysyłamy wektor (który, jak wiemy, musi zawierać elementy jednego typu) do każdej z nazw kolumn, które chcemy nasze ramki danych, którymi w tym przypadku są A, B i C. Ramka danych, którą utworzymy poniżej, ma cztery wiersze (obserwacje) i trzy zmienne, odpowiednio z typami liczbowymi, znakowymi i logicznymi. Na koniec wyodrębnij podzbiory danych za pomocą technik macierzowych, które widzieliśmy wcześniej, ale możesz również odwoływać się do kolumn za pomocą operatora $, a następnie wyodrębnić z nich elementy:

x <- data.frame(

A = c(1,2,3,4),

B= c(„D”, „E”, „F”, „G”),

C = c(TRUE ,FALSE, NA, FALSE)

)

x[1, ]

#> A B C

#> 1 1 D TRUE

x[, 1]

#> [1] 1 2 3 4

x[1:2, 1:2]

#> A B

#> 1 1 D

#> 2 2 E.

x$B

#> [1] D E F G

#> Levels: D E F G

x$B[2]

#> [1] E.

#> Levels: D E F G

W zależności od sposobu organizacji danych, ramka danych może mieć szeroki lub wąski format. Wreszcie, jeśli chcesz zachować tylko obserwacje, dla których masz pełne obserwacje, czyli tylko wiersze, które nie zawierają żadnych wartości NA dla żadnej ze zmiennych, wtedy powinieneś użyć complete.cases() funkcja, która zwraca logiczny wektor o długości równej liczbie wierszy i która zawiera TRUE wartość dla tych wierszy, które nie mają żadnych NA wartości i FALSE dla tych, które mają co najmniej jedną taką wartość. Zauważ, że kiedy stworzyliśmy ramkę danych x, kolumna C zawiera NA w swojej trzeciej wartości. Jeśli użyjemy funkcjicomplete.cases() na x, otrzymamy wartość FALSE dla tego wiersza i wartość TRUE dla wszystkich pozostałych. Możemy następnie użyć tego wektora logicznego do podzbioru ramki danych, tak jak to zrobiliśmy wcześniej z macierzami. Może to być bardzo przydatne podczas analizowania danych, które mogą nie być czyste i dla których chcesz zachować tylko te obserwacje, dla których masz pełne informacje:

x

#> A B C

#> 1 1 D TRUE

#> 2 2 E FAŁSZ

#> 3 3 F NA

#> 4 4 G FAŁSZ

cmplete.cases(x)

#> [1] TRUE TRUE FALSE TRUE

x[complete.cases(x), ]

#> A B C

#> 1 1 D TRUE

#> 2 2 E FALSE

#> 4 4 G FALSE

Listy

Lista to uporządkowany zbiór obiektów, takich jak wektory, ale listy mogą w rzeczywistości łączyć obiekty różnych typów. Elementy listy mogą zawierać obiekty dowolnego typu, które istnieją w języku R, w tym ramki danych i funkcje (wyjaśnione w poniższych sekcjach). Listy odgrywają kluczową rolę w R ze względu na ich elastyczność i są podstawą ramek danych, programowania obiektowego i innych konstrukcji. Nauka prawidłowego korzystania z nich jest podstawową umiejętnością programistów R i tutaj ledwo dotkniemy powierzchni, ale zdecydowanie powinieneś zbadać je dalej. Dla osób zaznajomionych z Pythonem listy R są podobne do słowników Pythona. Listy można jawnie tworzyć za pomocą funkcji list(), która pobiera dowolną liczbę argumentów i możemy odwoływać się do każdego z tych elementów za pomocą obu pozycji, a w przypadku są określane, także przez nazwy. Jeśli chcesz odwoływać się do elementów listy według nazw, możesz użyć notacji $. Poniższy przykład pokazuje, jak elastyczne mogą być listy. Pokazuje, że lista zawierająca liczby, znaki, logiki, macierze, a nawet inne listy (są to listy zagnieżdżone), i jak widać, możemy wyodrębnić każdy z tych elementów, aby działał niezależnie od nich. Po raz pierwszy pokazujemy wyrażenie wieloliniowe. Jak widać, możesz to zrobić, aby zachować czytelność i uniknąć bardzo długich wierszy w kodzie. Układanie kodu w ten sposób jest uważane za dobrą praktykę. Jeśli wpisujesz to bezpośrednio w konsoli, w każdym nowym wierszu pojawią się symbole plus (+), o ile masz niedokończone wyrażenie, aby Cię poprowadzić.

x <- list(

A = 1,

 B=”A’,

C = TRUE,

D = matrix(c (1,2,3,4), row = 2),

E = list(F=2, G= „B”, H = FALSE)

)

x

#> $ A

#> [1] 1

#>

#> $ B

#> [1] „A”

#>

#> $ C

#> [1] TRUE

#>

#> $ D

#> [, 1] [, 2]

#> [1,] 1 3

#> [2,] 2 4

#>

#> $ E

#> $ E $ F

#> [1] 2

#>

#> $ E $ G

#> [1] „B”

#>

#> $ E $ H

#> [1] FAŁSZ

x[1]

#> $ A

#> [1] 1

x$A

#> [1] 1

x[2]

#> $ B

#> [1] „A”

x$B

#> [1] „A”

Podczas pracy z listami możemy użyć funkcji lapply(), aby zastosować funkcję do każdego elementu na liście. W tym przypadku chcemy poznać klasę i typ każdego z elementów z właśnie utworzonej listy:

lapply(x,class)

#> $ A

#> [1] “numeryczne”

#>

#> $ B

#> [1] “znak”

#>

#> $ C

#> [1] “logiczne”

#>

#> $ D

#> [1] “matryca”

#>

#> $ E

#> [1] “lista”

lapply(x,typeof)

#> $ A

#> [1] “podwójne”

#>

#> $ B

#> [1] “znak”

#>

#> $ C

#> [1] “logiczne”

#>

#> $ D

#> [1] “podwójne”

#>

#> $ E

#> [1] “lista”

Macierze

Macierze są powszechnie używane w matematyce i statystyce, a znaczna część mocy R pochodzi z różnych operacji, które można na nich wykonać. W R macierz jest wektorem z dwoma dodatkowymi atrybutami, liczbą wierszy i liczbą kolumn. A ponieważ macierze są wektorami, są ograniczone do jednego typu danych. Możesz użyć funkcji matrix() do tworzenia macierzy. Możesz przekazać mu wektor wartości, a także liczbę wierszy i kolumn, które powinna mieć macierz. Jeśli określisz wektor wartości i jeden z wymiarów, drugi zostanie obliczony automatycznie  i może być najniższą liczbą, która ma sens dla przekazanego wektora. Możesz jednak określić oba z nich jednocześnie, jeśli wolisz, co może spowodować różne zachowanie w zależności od przekazanego wektora, jak widać w następnym przykładzie. Domyślnie macierze konstruowane są według kolumn, co oznacza, że ​​wpisy można przemyśleć zaczynające się w lewym górnym rogu i spływające po kolumnach. Jeśli jednak wolisz tworzyć ją wierszami, możesz wysłać parametr byRow = TRUE. Zwróć również uwagę, że możesz utworzyć pustą lub niezainicjowaną macierz, określając liczbę wierszy i kolumn bez przekazywania żadnych rzeczywistych danych do jego konstrukcji, a jeśli w ogóle nic nie określisz, zostanie zwrócona niezainicjowana macierz 1 na 1. Na koniec zwróć uwagę, że ten sam mechanizm powtarzania elementów, który widzieliśmy dla wektorów, jest stosowany podczas tworzenia macierzy, więc zachowaj ostrożność podczas tworzenia ich w ten sposób:

matrix()

#> [, 1]

#> [1,] NA

matrix(nrow = 2 col = 3)

#> [, 1] [, 2] [, 3]

#> [1,] NA NA NA

#> [2,] NA NA NA

matrix(c(1,2,3), nrow =2)

#> Ostrzeżenie w macierzy (c (1, 2, 3), nrow = 2):

długość danych [3] nie jest podrzędną

#> wielokrotność lub wielokrotność liczby wierszy [2]

#> [, 1] [, 2]

#> [1,] 1 3

#> [2,] 2 1

matrix(c(1,2,3), nrow =2 ncol = 3)

#> [, 1] [, 2] [, 3]

#> [1,] 1 3 2

#> [2,] 2 1 3

matrix(c(1,2,3,4,5,6), nrow =2, byRow = TRUE)

#> [, 1] [, 2] [, 3]

#> [1,] 1 2 3

#> [2,] 4 5 6

Podzbiory macierzy można określić na różne sposoby. Używając notacji macierzowej, możesz określić wybór wierszy i kolumn za pomocą tych samych mechanizmów, które pokazaliśmy wcześniej dla wektorów, za pomocą których możesz używać wektorów z indeksami lub wektorów z logikami, a na wypadek, gdybyś zdecydowałj się na użycie wektorów z logikami, wektor użyty do podzbioru musi mieć taką samą długość jak wymiar macierzy, dla której go używasz. Ponieważ w tym przypadku mamy do czynienia z dwoma wymiarami, musimy oddzielić zaznaczenie wierszy i kolumn za pomocą przecinka (,) między nimi (zaznaczenie wiersza jest pierwsze), a R zwróci ich przecięcie. Na przykład x[1,2] mówi R, aby pobierał element z pierwszego wiersza i drugiej kolumny, x[1:2,1] mówi R, aby pobierał elementy od pierwszego do drugiego z trzeciego rzędu, czyli odpowiednik użycia x[c(1,2),3]. Możesz także użyć wektorów logicznych do zaznaczenia. Na przykład, x[c(TRUE, FALSE), c(TRUE,FALSE,TRUE)] mówi R, aby pobierał pierwszy wiersz z unikaniem drugi i z tego wiersza, aby uzyskać pierwszą i trzecią kolumnę. Równoważny wybór to x[1,c(1,3)]. Zwróć uwagę, że jeśli chcesz określić pojedynczy wiersz lub kolumnę, możesz użyć samej liczby całkowitej, ale jeśli chcesz określić dwa lub więcej, musisz użyć notacji wektorowej. Wreszcie, jeśli pominiesz jedną ze specyfikacji wymiarów, R zinterpretuje jako otrzymywanie wszystkich możliwości dla tego wymiaru:

x <- matrix(c(1,2,3,4,5,6), nrow =2, ncol=3, byRow = TRUE)

x[1,2]

#> [1] 2

x[1:2,1]

#> [1] 2 5

x[c(1,2),3]

#> [1] 3 6

x[c(TRUE, FALSE), c(TRUE,FALSE,TRUE)]

#> [1] 1 3

x[1,c(1,3)]

#> [1] 1 3

x[ ,1]

#> [1] 1 4

x[1, ]

#> [1] 1 2 3

Jak wspomniano wcześniej, macierze to podstawowe narzędzia matematyczne, a R daje dużą elastyczność podczas pracy z nimi. Najbardziej powszechną operacją na macierzach jest transpozycja, która jest wykonywana przy użyciu funkcji, oraz mnożenie macierzy przez wektor, mnożenie macierzy przez macierz i mnożenie macierzy przez macierz, które są wykonywane za pomocą operatora 􀀇􀀌􀀇, którego używaliśmy wcześniej do obliczenia iloczyn skalarny dwóch wektorów. Zwróć uwagę, że obowiązują te same ograniczenia wymiarowości, co w przypadku notacji matematycznej, co oznacza, że ​​jeśli spróbujesz wykonać jedną z tych operacji, a wymiary nie mają matematycznego sensu, R zwróci błąd, jak widać w ostatniej części przykład:

A <- matrix(c(1,2,3,4,5,6), nrow =2, ncol=3, byRow = TRUE)

x <- c(7,8)

y <- c(9, 10 ,11)

A

#> [, 1] [, 2] [, 3]

#> [1,] 1 2 3

#> [2,] 4 5 6

x

#> [1] 7 8

y

#> [1] 9 10 11

t(A)

#> [, 1] [, 2]

#> [1,] 1 4

#> [2,] 2 5

#> [3,] 3 6

t(x)

#> [, 1] [, 2]

#> [1,] 7 8

t(y)

#> [, 1] [, 2] [, 3]

#> [1,] 9 10 11

x % * % A

#> [, 1] [, 2] [, 3]

#> [1,] 39 54 69

A % * % t(x)

#> Błąd w A% *% t (x): niezgodne argumenty

A % * % y

#> [, 1]

#> [1,] 62

#> [2,] 152

t(y) % * % A

#> Błąd w t (y)% *% A: niezgodne argumenty

A % * % t(A)

#> [, 1] [, 2]

#> [1,] 14 32

#> [2,] 32 77

t(A) %*% A

#> [, 1] [, 2] [, 3]

#> [1,] 17 22 27

#> [2,] 22 29 36

#> [3,] 27 36 45

A %*% x

#> Błąd w A% *% x: niezgodne argumenty

Współczynniki

Podczas analizy danych dość często napotykamy wartości kategorialne. R zapewnia dobry sposób reprezentowania wartości jakościowych za pomocą współczynników, które są tworzone za pomocą funkcji factor() i są wektorami całkowitymi z powiązanymi etykietami dla każdej liczby całkowitej. Różne wartości, które może przyjąć współczynnik, nazywane są poziomami. Funkcja levels() pokazuje wszystkie poziomy z czynnika, a parametr poziomów funkcji factor()może być użyty do jawnego zdefiniowania ich kolejności, która jest alfabetyczna, jeśli nie jest jawnie zdefiniowane. Zauważ, że zdefiniowanie wyraźnej kolejności może być ważne w modelowaniu liniowym, ponieważ pierwszy poziom jest używany jako poziom odniesienia dla funkcji takich jak lm ()(modele liniowe.

Ponadto wyświetlanie współczynnika pokazuje nieco inne informacje niż drukowanie wektora znakowego. W szczególności zwróć uwagę, że cudzysłowy nie są pokazane, a poziomy są następnie wyraźnie drukowane w kolejności:

x <- c(„Blue”, „Red”, „Black”, „Blue”)

y <- factor(c(„Blue”, „Red”, „Black”, „Blue”)

z <- factor(c(„Blue”, „Red”, „Black”, „Blue”), levels=c(„Red”, „Black”, „Blue”))

x

#> [1] ” Niebieski ” ” Czerwony ” ” Czarny ” ” Niebieski “

y

#> [1] Niebieski Czerwony Czarny Niebieski

#> Levels : Black Blue Red

z

#> [1] Blue  Red Black Blue

#> Levels : Red Black Blue

levels(y)

#> [1] “Black” “Blue” “Red”

levels(z)

#> [1] “Red” “Black” “Blue”

Czasami trudno jest pracować z czynnikami, ponieważ ich typy są różnie interpretowane w zależności od tego, jaka funkcja jest na nich używana. Pamiętasz funkcje class() i typeof() których używaliśmy wcześniej? Stosowane w odniesieniu do współczynników mogą powodować nieoczekiwane rezultaty. Jak widać poniżej, funkcja class() zidentyfikuje x i y jako odpowiednio znak i współczynnik. Jednak funkcja typeof() poinformuje nas, że są to odpowiednio znaki i liczby całkowite. Mylące, prawda? Dzieje się tak, ponieważ tak jak wspomniano,  czynniki są przechowywane wewnętrznie jako liczby całkowite i używają mechanizmu podobnego do tablic przeglądowych, aby pobrać rzeczywisty ciąg związany z każdym z nich. Z technicznego punktu widzenia, sposób, w jaki czynniki przechowują ciągi znaków powiązane z ich wartościami całkowitymi, odbywa się za pomocą atrybutów

class(x)

#> [1] “character”

class(y)

#> [1] “factor”

typeof(x)

#> [1] “character”

typeof(y)

#> [1] “integer”

Chociaż czynniki wyglądają i często zachowują się jak wektory znakowe, jak wspomnieliśmy, są w rzeczywistości wektorami całkowitymi, dlatego należy uważać, traktując je jak ciągi. Niektóre metody łańcuchowe, takie jak gsub() i grepl(), zmuszają czynniki do znaków, podczas gdy inne, takie jak nchar(), będą zgłaszać błąd, a jeszcze inne, takie jak c(), użyją bazowych wartości całkowitych. Z tego powodu zwykle najlepiej jest jawnie przekonwertować czynniki na potrzebny typ danych:

gsub(„Black”, White”, x)

#> [1] “Blue” “Red” “White” “Blue”

gsub(„Black”, White”, y)

#> [1] “Blue” “Red” “White” “Blue”

nchar(x)

#> [1] 4 3 5 4

nchar(y)

#> Błąd w nchar (y): ‘nchar ()’ wymaga wektora znakowego

c(x)

#> [1] “Blue” “Red” “Black” “Blue”

c(y)

#> [1] 2 3 1 2

Jeśli nie zauważyłeś, nchar() zastosował się do każdego z elementów współczynnika x. Ciągi „Blue”, „Red” i „Black” mają odpowiednio 4, 3 i 5 znaków. To kolejny przykład operacji wektoryzowanych, o których wspominaliśmy wcześniej w sekcji wektorów.

Wektory

Podstawowym typem danych w R jest wektor, który jest uporządkowanym zbiorem wartości. Pierwszą rzeczą, którą powinieneś wiedzieć, jest to, że w przeciwieństwie do innych języków, pojedyncze wartości liczb, ciągów i logiki są specjalnymi przypadkami wektorów (wektorów o długości jeden), co oznacza, że ​​nie ma pojęcia skalarów w R. -wymiarowa struktura danych i wszystkie jej elementy są tego samego typu danych. Najprostszym sposobem utworzenia wektora jest użycie funkcji c(), która oznacza kombinację i przekształca wszystkie argumenty w jeden typ. Przymus przejdzie od prostszych do bardziej złożonych typów. To znaczy, jeśli utworzymy wektor zawierający logiki, liczby, oraz znaki, jak pokazuje poniższy przykład, nasz wynikowy wektor będzie zawierał tylko znaki, które są bardziej złożone z trzech typów. Jeśli utworzymy wektor zawierający wartości logiczne i numeryczne, nasz wynikowy wektor będzie numeryczny, ponownie, ponieważ jest to typ bardziej złożony. Wektory można nazwać lub nie. Dostęp do nienazwanych elementów wektorów można uzyskać tylko poprzez odniesienia pozycyjne, natomiast do nazwanych wektorów można uzyskać dostęp za pośrednictwem odniesień pozycyjnych, jak również odniesień do nazw. W poniższym przykładzie wektor y jest wektorem nazwanym, w którym każdy element jest nazwany literą od A do I. Oznacza to, że w przypadku x mamy dostęp do elementów tylko za pomocą ich pozycji (pierwsza pozycja jest traktowana jako 1 zamiast 0 używanego w innych językach), ale w przypadku y możemy również użyć przypisanych nam nazw. Zauważ również, że specjalne wartości, o których wspomnieliśmy wcześniej, czyli NA, NULL, NaN i Inf, zostaną przekształcone w znaki, jeśli jest to typ bardziej złożony, z wyjątkiem NA, który pozostaje taki sam. W przypadku, gdy wymuszenie dotyczy liczb, wszystkie pozostają takie same, ponieważ są prawidłowymi wartościami liczbowymi. Wreszcie, jeśli chcemy poznać długość wektora, po prostu wywołaj na nim funkcję length :

x <- c(TRUE, FALSE, -1,0,1,”A”, „B”, NA, NULL, NaN, Inf

x

#> [1] “TRUE” “FALSE” “-1” “0” “1” “A” “B” NA

#> [9] “NaN” “Inf”

x[1]

#> [1] „TRUE”

x[5]

#> [1] “1”

Y <- c(A=TRUE,  B= FALSE, C= -1, D= 0, E = 1, F = NA, G = NULL, H =NaN, I = Inf)

y

#> A B C D E F H I.

#> 1 0-1 0 1 ND NaN Inf

y[1]

#> A

#> 1

y[„A”]

#> A

#> 1

y[5]

#> E.

#> 1

y[„E”]

#> E.

#> 1

legth(x)

#> [1] 10

legth(y)

#> [1] 8

Ponadto możemy wybierać zbiory lub zakresy elementów za pomocą wektorów z numerami indeksu dla wartości, które chcemy pobrać. Na przykład użycie selektora c (1, 2) zwróci pierwsze dwa elementy wektora, podczas gdy użycie c (1, 3, 5) zwróci pierwszy, trzeci i piąty element. Funkcja: (tak, jest to funkcja, mimo że zwykle nie używamy składni podobnej do funkcji, którą widzieliśmy do tej pory w innych przykładach, aby ją wywołać), jest często używana jako skrót do tworzenia selektorów zakresu. Na przykład składnia 1: 5 oznacza, że ​​chcemy mieć wektor z elementami od 1 do 5, co byłoby równoważne jawnemu użyciu c (1, 2, 3, 4, 5). Ponadto, jeśli wyślemy wektor logiki, który musi mieć taką samą długość jak wektor, z którego chcemy pobrać wartości, każda z wartości logicznych zostanie powiązana z odpowiadającą pozycją w wektorze, z którego chcemy pobrać, i jeśli odpowiadająca mu logika to TRUE, wartość zostanie pobrana, ale jeśli jest FALSE, nie będzie. Poniższy przykład przedstawia wszystkie te metody wyboru:

x[c(1,2,3,4,5)]

#> [1] “TRUE” “FALSE” “-1” “0” “1”

X[1:5]

#> [1] “TRUE” “FALSE” “-1” “0” “1”

x[c(1,3,50]

#> [1] “TRUE” “-1” “1”

X[c(TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE, TRUE)]

#> [1] “TRUE” “-1” “1” “B” “NaN” NA

Następnie porozmawiamy o operacjach między wektorami. W przypadku wektorów numerycznych możemy zastosować operacje element do elementu, używając po prostu operatorów, tak jak zwykle. W tym przypadku R dopasuje elementy dwóch wektorów parami i zwróci wektor. Poniższy przykład pokazuje, jak dwa wektory są dodawane, odejmowane, mnożone i dzielone w sposób od elementu do elementu. Ponadto, ponieważ pracujemy z wektorami o tej samej długości, możemy chcieć uzyskać ich iloczyn skalarny, co możemy zrobić za pomocą operatora %*% który wykonuje mnożenie macierzowe, w tym przypadku wektor do wektora:

x <- c (1,2,3,4)

y <- c (5,6,7,8)

x + y

#> [1] 6 8 10 12

x-y

#> [1] -4 -4 -4 -4

x*y

#> [1] 5 12 21 32

x/y

#> [1] 0,2000 0,3333 0,4286 0,5000

x %*% y

#> [, 1]

#> [1,] 70

Jeśli chcesz połączyć wiele wektorów w jeden, możesz po prostu użyć na nich c() rekurencyjnie, a to automatycznie je spłaszczy. Powiedzmy, że chcemy połączyć x i y w z tak, aby elementy y pojawiały się jako pierwsze. Ponadto załóżmy, że po tym jak chcemy je posortować, więc stosujemy funkcję sort() na z:

z <- c(y,x)

z

#> [1] 5 6 7 8 1 2 3 4

sort(z)

#> [1] 1 2 3 4 5 6 7 8

Częstym źródłem nieporozumień jest sposób, w jaki R radzi sobie z wektorami o różnych długościach. Jeśli zastosujemy operację element-element, tak jak te, które omówiliśmy wcześniej, ale używając wektorów o różnych długościach, możemy spodziewać się, że R zgłosi błąd, tak jak ma to miejsce w innych językach. Jednak tak nie jest. Zamiast tego powtarza elementy wektorowe w kolejności, dopóki wszystkie nie będą miały tej samej długości. Poniższy przykład przedstawia trzy wektory, każdy o innej długości, oraz wynik ich dodania. Sposób, w jaki domyślnie skonfigurowano R, faktycznie otrzymasz komunikat ostrzegawczy, aby poinformować Cię, że wektory, na których operowałeś, nie miały tej samej długości, ale ponieważ R można skonfigurować tak, aby uniknąć wyświetlania ostrzeżeń, nie powinieneś na nich polegać:

c(1,2) + c(3,4,5) + c(6,7,8,9)

#> Ostrzeżenie w c (1, 2) + c (3, 4, 5):

dłuższa długość obiektu nie jest wielokrotnością

#> krótsza długość obiektu

#> Ostrzeżenie w c (1, 2) + c (3, 4, 5) + c (6, 7, 8, 9):

dłuższa długość obiektu to

#> nie jest wielokrotnością krótszej długości obiektu

#> [1] 10 13 14 13

Pierwszą rzeczą, która może przyjść na myśl, jest to, że pierwszy wektor jest rozszerzany do c(1,2,1,2), drugi wektor jest rozszerzany do c(3,4,5,3) i trzecia jest zachowana bez zmian, ponieważ jest największa. Jeśli dodamy do siebie te wektory, otrzymamy wynik c(10,13,14,14). Jednak, jak widać na przykładzie, wynik w rzeczywistości to c(10,13,14,13). Więc czego nam brakuje? Źródłem nieporozumień jest to, że R robi to krok po kroku, co oznacza, że ​​najpierw wykona dodawanie c(1,2) + c(3,4,5), które po rozwinięciu jest c(1,2,1) + c(3,4,5) i daje w wyniku c(4,6,6), a następnie biorąc pod uwagę ten wynik, następny krok, który wykonuje R. to c(4,6,6) + c(6,7,8,9), które po rozwinięciu to c(4,6,6,4) + c(6,7,8,9) i stąd pochodzi wynik, jaki otrzymujemy. Na początku może to być mylące, ale pamiętaj tylko, aby wyobrazić sobie operacje krok po kroku. Na koniec krótko wspomnimy o bardzo potężnej funkcji języka R, znanej jako wektoryzacja. Wektoryzacja oznacza, że ​​wykonujesz operację na wektorze od razu, zamiast niezależnie robić to na każdym z jego elementów. Jest to funkcja, którą powinieneś dość dobrze poznać. Programowanie bez tego jest uważane za zły kod R i to nie tylko ze względów składniowych, ale także dlatego, że kod wektorowy wykorzystuje wiele wewnętrznych optymalizacji w R, co skutkuje znacznie szybszym kodem. Mimo że kod zwektoryzowany na początku może wydawać się przerażający lub magiczny, w rzeczywistości R sprawia, że ​​w niektórych przypadkach jest dość prosty do wdrożenia. Na przykład, możemy podnieść do kwadratu każdy z elementów w wektorze x, używając symbolu x, tak jakby to była pojedyncza liczba. R jest wystarczająco inteligentny, aby zrozumieć, że chcemy zastosować operację do każdego elementu wektora. Wiele funkcji w R można zastosować za pomocą tej techniki:

x^2

#> [1] 1 4 9 16

Zobaczymy więcej przykładów, które naprawdę pokazują, jak wektoryzacja może świecić w następnej sekcji o funkcjach, gdzie zobaczymy, jak stosować operacje wektoryzowane, nawet jeśli operacje zależą od innych parametrów.