Ta strona wykorzystuje ciasteczka ("cookies") w celu zapewnienia maksymalnej wygody w korzystaniu z naszego serwisu. Czy wyrażasz na to zgodę?

Czytaj więcej
< All Topics
Print

R, programowanie

Miłosława Sokół
R dla Biologów, programowanie

Zasady cytowania podręcznika: Miłosława Sokół (2023) “R dla Biologów, programowanie”, Wydział Biologii Uniwersytetu warszawskiego, online: https://skrypty.biol.uw.edu.pl/r-programowanie

 

Wstęp

W tej części podręcznika pokazano podstawowe procedury programistyczne służące do przetwarzania symboli: instrukcję warunkową, instrukcja wyboru i pętle. Wymyślono je bardzo dawno, kilkadziesiąt lat wcześniej niż skonstruowano pierwsze komputery. Opisał je bowiem już w połowie XIX wieku Charles Babbage w dokumentacji swojej maszyny analitycznej (automatu, który przetwarzał symbole za pomocą wymienialnych programów zapisanych na metalowych płytkach), a jego popularyzatorka, Augusta Ada King (Hrabina Lovelas), wymyśliła i pokazała konstrukcję wielu programów wykorzystujących te możliwości istniejącej jeszcze wtedy tylko na papierze maszyny.

Pętle i instrukcja warunkowa kryją się za wieloma funkcjami zaimplementowanymi do R i wszystkimi uogólnieniami działań i funkcji na liczbach, które w R działają na ciągach. Powoduje to, że wiele operacji, które w innych językach modelowania wymagają zastosowanie pętli, udaje się tu zapisać bez pisania specjalnych formuł. Nie mniej, nie dla wszystkich operacji znajdzie się odpowiednią funkcję w podstawowych bibliotekach R. Często łatwiej i szybciej jest napisać własną funkcję z użyciem pętli i instrukcji warunkowych, niż szukać pakietu, który umożliwi nam rozwiązanie problemu za pomocą napisanych przez innych funkcji.

W tej części, rozdziały zawierające opisy podstawowych instrukcji i plansze w nich, podobnie jak w części pierwszej, służą przede wszystkim do oglądania. Można je skopiować do skryptu w R, zmodyfikować i uruchomić przez skopiowanie ich do konsoli. W ten sposób biolodzy bardzo często rozwiązują własne problemy. Nikt bowiem od nich nie wymaga samodzielnego programowania, ale zrozumienie kopiowanego skryptu i możliwość jego prawidłowej modyfikacji, wymaga znajomości podstawowych procedur.

Skrypty

Programy pisze się z reguły w skryptach – plikach tekstowych, które można zapamiętać w katalogu roboczym pod nazwą kojarzącą się z treścią programu. Programy zapisane w skryptach kopiuje się do konsoli (metodą zaznacz, Ctrl+C, Ctrl+V), sprawdza się, które linijki generują błędy, poprawia i zapamiętuje poprawnie wykonujące się programy pod odpowiednimi nazwami. Takie gotowe skrypty można w kolejnej sesji otworzyć i ponownie użyć. Otwartych skryptów można mieć jednocześnie kilka, co w niektórych sytuacjach jest bardzo korzystne.

Skrypty w R (bez nakładki RStudio)

Po włączeniu “czystego” R zazwyczaj pojawia się tylko okienko konsoli. Jednak tworząc kilkulinijkowe programy, w których ewentualne błędy pokażą się dopiero po wykonaniu wszystkich linijek, lepiej jest zapisywać je w skryptach. Okienko skryptów otwiera się klikając na menu Plik  i wybierając Nowy Skrypt.

W skryptach mysz działa tak, jak w systemie operacyjnym. Edytor umożliwia kopiowanie lub przenoszenie w inne miejsce większych fragmentów, automatyczne znajdywanie różnych fraz. Wszystko po to, by łatwiej było pisać i poprawiać programy. Gotowy program można zaznaczyć, skopiować, uruchomić w konsoli. Konsola całą zawartość skryptu podzieli na linijki kodu i każdą po kolei wykona lub opisze rodzaj błędu, który uniemożliwi jej wykonanie. Można je poprawiać i kopiować na nowo, do skutku.

Skrypty zapamiętuje się w plikach z odpowiednią do zawartości nazwą (nawet wtedy gdy zawarty w nich program ma błędy). Dostają one rozszerzenie .R. Są to pliki tekstowe, które można odczytać i modyfikować w notatniku. Zazwyczaj gromadzi się je w katalogu roboczym związanym z jakimś tematem badawczym. Otwiera się je w R przez wybór w menu “Otwórz skrypt”. Gdy program nie uruchamia się tak, jak trzeba i potrzebna jest konsultacja ze znawcą R, można je przesyłać pocztą mailową lub zamieścić w którymś z forów internetowych dotyczących języka R opisując swój problem. Zazwyczaj zawsze znajdzie się ktoś, kto poda rozwiązanie tego problemu.

W tej części podręcznika pojawiają się plansze z programami przeznaczonymi do kopiowania właśnie do skryptu. Wyglądać to będzie następująco:

agg=aggregate(weight~Time+Diet, ChickWeight, function(x) c(mean(x), sd(x)))
sred=agg[,3][,1]
odch=agg[,3][,2]
sred=round(sred,1)
odch=round(odch,1)
wyn=paste(sred,intToUtf8(177),odch)
mac=matrix(wyn,12,4,dimnames=list(dzien=c(0:10*2,21), dieta=1:4))
print(mac,quote=FALSE)

 

Jest to program do wyświetlenia tabeli ze średnimi i odchyleniami standardowymi obrazującymi wzrost ciężaru kurczaków karmionych czterema rodzajami diety. Korzysta z zaimplementowanej do R bazy danych ChickWeight.

Skrypty z poprawnie napisanym programem (nie generującycm komunikatów o błędzie i liczacym to co trzeba) mozna zapamietać. Nalezy aktywowac okienko skryptu i wykonac Zapisz jako: w meny Plik. Plik tekstowy z rozszerzeniem .R zostanie zapamietany w katalogu roboczym (patrz R, podstawowe operacje).

Nawiasem mówiąc skróty myślowe postaci: “napisałem nowy skrypt” (zamiast “napisałem nowy program w skrypcie”), “przesyłam ci skrypt dotyczący komórek śledziony” (zamiast “przesyłam ci skrypt z programem dotyczącym komórek śledziony”) spowodował, że słowo skrypt zaczęto utożsamiać z programem i stało się to dość powszechne.

Skrypty w RStudio

Opisana w na początku pierwszej części podręcznika nakładka na R zwana RStudio zazwyczaj otwiera kilka okien jednocześnie, w tym okienko skryptu i konsoli.

Po pierwszym uruchomieniu Rstudio można uzyskać komunikat, że RStudio nie znalazł R i trzeba mu podać ścieżkę dostępu do R. W systemie Windows 10 standardowo R instaluje się w folderze Dokumenty i wtedy ścieżka dostępu ma postać: “…/Dokumenty/R/R-3.3.3./bin/x64” lub “…/Dokumenty/R/R-3.3.3./bin/i386”. Przy kolejnych uruchomieniach nie trzeba już tego wykonywać. W ten sposób zamiast uruchamiać R możemy uruchamiać RStudio i robić to samo.

Po uruchomieniu RStudio pokazują się 4 standardowe okna – skrypt, pod spodem – konsola, z boku wykaz obiektów, z dołu okno które można przełączać na “Files” pokazującym pliki w katalogu roboczym, “Plots” pokazującym wykonane wykresy, Packages – pokazującym załadowane pakiety, Help z dostępem do całej oficjalnej dokumentacji R i Viewer z innymi wynikami graficznymi uzyskiwanymi podczas pracy z RStudio. Przykładowe okna RStudio wyglądają następująco:

Skrypt pokazuje się zazwyczaj powyżej konsoli. Zapisuje się w program (często kopiuje z naszego podręcznika) i podmienia nazwy zmiennych na własne. Następnie trzeba wszystko zaznaczyć i kliknąć na ->Run. Program zostanie przeniesiony do konsoli i wykonany. Można poprawić ewentualne błędy i ponownie przenieść do konsoli. Potem trzeba sprawdzić, czy wyniki wyglądają tak, jak tego oczekiwaliśmy. Można bowiem coś zaprogramować poprawnie pod względem informatycznym, ale błędnie liczącym lub przetwarzającym dane. Dopiero po wyeliminowaniu wszelkich błędów skrypt zapamiętujemy, co robi się podobnie jak w czystym R.

Programowanie warunków

Instrukcja warunkowa

Przy programowaniu wielu zagadnień potrzebna jest selekcja danych względem najróżniejszych warunków. Służą nam do tego wyrażenia warunkowe często spotykane jako elementy składowe wielu funkcji, choć nierzadko wykorzystywane także jako pojedyncze polecenia. Mają one następującą składnię.

if (warunek) instrukcja

lub

if (warunek) {instrukcja.1
instrukcja.2

instrukcja.n}

lub

if (warunek) instrukcja.1 else instrukcja.2

lub

if (warunek) {instrukcja.1.1
instrukcja.1.2

instrukcja.1.n} else {
instrukcja.2.1
instrukcja.2.2

instrukcja.2.m}

Warunek jest wyrażeniem, które przyjmuje wartość TRUE lub FALSE. Instrukcje po nim wykonywane są tylko wtedy, gdy warunek ma wartość TRUE. Gdy dodamy do tej instrukcji else możemy umieścić instrukcje, które maja być wykonane gdy warunek ma wartość FALSE

W powyższych schematach zapisano instrukcje w sposób, który ułatwia dostrzeżenie szczegółów składni pogrubiając słowa kluczowe if i else oraz nawiasy okrągłe i klamrowe. Pogrubienia kodu nie są w programowaniu konieczne. Gdy wykonuje się warunek zapisany w kilku linijkach konieczne jest zapisanie słowa else w tej samej linijce w której zamyka się instrukcje wykonywane gdy warunek ma wartość TRUE. Przerzucenie go do nowej linijki (co robią osoby programujące w C++) powoduje, ze instrukcje po nim nie są wcale wykonywane.

Zapisanie instrukcji warunkowej w konsoli wygląda następująco:

> x = 1
> y = 2
>
> if (x < y) cat("OK\n")
OK
> if (x > y) cat("OK\n")
> 

 

Wersja instrukcji warunkowej bez else powoduje, że gdy warunek ma wartość FALSE instrukcja nie jest wykonywana i możemy nie zobaczyć żadnej reakcji programu.

> x = 1
> y = 2
>
> if (x < y) cat("OK\n") else cat("NOT OK\n") 
OK
> x = 3
> y = 2
> if (x < y) cat("OK\n") else cat("NOT OK\n")
NOT OK

Przykładem zastosowania instrukcji warunkowej jest sprawdzenie czy trzy przykładowe liczby mogą być długościami boków jakiegoś trójkąta. Warunkiem koniecznym takiego zdarzenia jest: suma każdej pary liczb jest większa od trzeciej liczby. To pozwala na wykonanie następującego programu:

> a = 123
> b = 186
> c = 138
> if ((a+b>c) & (a+c>b) & (b+c>a)) {
+ cat("Istnieje trójkąt o bokach",a,", ",b," i ",c,"\n")} else {
+ cat("Nie istnieje trójkąt o bokach",a,", ",b," i ",c,"\n")}
Istnieje trójkąt o bokach 123 , 186 i 138 

Poniżej pokazano program , który można skopiować do skryptu, a który wylicza się pierwiastki rzeczywiste równania kwadratowego ax2+bx+c=0.

a = 1
b = -1
c = -2
delta=b^2-4*a*c
if (delta < 0) cat("brak pierwiastków rzeczywistych\n") else {
x1=(-b-sqrt(delta))/(2*a)
x2=(-b+sqrt(delta))/(2*a)
cat("Są pierwiastki rzeczywiste:  x1 = ",x1,"  x2 = ",x2, "\n")}

Program ten skopiowany do konsoli daje następujący obraz:

> a = 1
> b = -1
> c = -2
> delta=b^2-4*a*c
> if (delta < 0) cat("brak pierwiastków rzeczywistych\n") else {
+ x1=(-b-sqrt(delta))/(2*a)
+ x2=(-b+sqrt(delta))/(2*a)}
> cat("Są pierwiastki rzeczywiste:  x1 = ",x1,"  x2 = 2",x2, "\n")
Są pierwiastki rzeczywiste:  x1 = -1   x2 = 2
 

 

Funkcja ifelse()

W instrukcji warunkowej warunek nie może być wektorem logicznym (typu (TRUE, TRUE, FALSE,…)). Gdy zatem sprawdzamy warunek dla kolejnych wyrazów wektora i w zależności, czy przyjmuje on wartość TRUE lub FALSE, przekształcamy je inną funkcją, to najlepiej jest zastosować funkcję ifelse(). Jest to funkcja której argumentami są: więc ciąg wartości logicznych, funkcje dla TRUE i dla FALSE rozdzielone przecinkami.

> x=3:-3
> x
[1]  3  2  1  0 -1 -2 -3
> x>0
[1] TRUE TRUE TRUE FALSE FALSE FALSE FALSE
> ifelse(x>0,log(x),exp(-x))
[1] 1.0986123 0.6931472 0.0000000 1.0000000 2.7182818 7.3890561 20.0855369
Warning message:
In log(x) : NaNs produced

Inny przykład: jak zrobić by takie funkcje jak pierwiastek, czy logarytm dawały wartość pomijalną NA zamiast nieokreślonej NaN dla liczb nie należących do ich dziedziny?

> x=3:-3
> x
[1]  3  2  1  0 -1 -2 -3
> sqrt(x)
[1] 1.732051 1.414214 1.000000 0.000000   NaN  NaN  NaN
Warning message:
In sqrt(x) : NaNs produced
> ifelse(x>=0,sqrt(x),NA)
[1] 1.732051 1.414214 1.000000 0.000000   NA   NA   NA
Warning message:
In sqrt(x) : NaNs produced
> sqrt(ifelse(x>=0,x,NA))
[1] 1.732051 1.414214 1.000000 0.000000   NA   NA   NA

Gdy wektor warunków jest jednoelementowy, funkcja ifelse() może zastępować instrukcję warunkową, aczkolwiek z pewnymi problemami. Przykładowo program wyliczający pierwiastki równania kwadratowego postaci:

> a = 1
> b = -1
> c = -2
> delta=b^2-4*a*c
> ifelse (delta < 0, cat("brak pierwiastków rzeczywistych\n"),
+ c((-b-sqrt(delta))/(2*a),(-b+sqrt(delta))/(2*a)))
[1] -1

wyświetla tylko pierwszy z podanych pierwiastków. Aczkolwiek zapis:

> a = 1
> b = -1
> c = -2
> delta=b^2-4*a*c
> ifelse (delta < 0, cat("brak pierwiastków rzeczywistych\n"),
+ x<-c((-b-sqrt(delta))/(2*a),(-b+sqrt(delta))/(2*a)))
[1] -1 
> x
[1] -1  2

powoduje, że ostatecznie x jest wektorem z parą pierwiastków. Uwaga: w funkcji ifelse() nie działa przypisanie za pomocą znaku “=”. Gdy tak zmienimy parametry a, b, c tak, że delta wyjdzie ujemna, uzyskamy komunikat o błędzie. Aby uniknąć takich komunikatów związanych z tym, że funkcja ifelse() nie “lubi” funkcji cat(), lepiej jest zastosować instrukcję warunkową.

 

Funkcja wyboru switch()

Instrukcjami wykonywalnymi pod określonym warunkiem w instrukcji warunkowej mogą być inne instrukcje warunkowe. Oznacza to, że instrukcja if (…) … else … może być wykorzystywana do wykonywania różnych instrukcji zależnych od kilku wartości jakiejś zmiennej. Nie wygląda to jednak przejrzyście.

> a = 1
> if (a == 1) a=4 else {
+ if (a == 2) a=2 else {
+ if (a == 3) a=5 else {
+ if (a == 4) a=3 else a=1}}}
> a
[1] 4

Ta pojedyncza, choć złożona, instrukcja nie może być zastąpiona przez zestaw pięciu instrukcji if (…) …, gdyż wcześniej uzyskane przez a wartości będą rozważane w niżej występujących instrukcjach i ostatecznie wynik będzie błędny.

> a = 1
> if (a == 1) a=4
> if (a == 2) a=2
> if (a == 3) a=5
> if (a == 4) a=3
> if (a > 4) a=1
> a
[1] 3

Rolę wielokrotnego wyboru warunkowego różnych instrukcji pełnią w językach programowania specjalnie skonstruowane instrukcje wyboru warunkowego. zastępują one wielokrotne złożenie funkcji if (…) … else …. W R rolę tę spełnia funkcja switch(), której pierwszym argumentem jest tzw. klucz przyjmujący różne uporządkowane wartości, a pozostałe argumenty rozdzielone przecinkami oznaczają wartości lub instrukcje wykonywane dla kolejnych wartości klucza.

Jedna z działających poprawnie składni z użyciem instrukcji warunkowej i funkcji switch() wygląda następująco:

> a = 1
> if (a==1 | a==2 | a==3 | a==4) switch(a, 4, 2, 5, 3)->a else a=1
> a
[1] 4

Bardziej popularny sposób polega na ujęciu wartości klucza (które może on posiadać na początku) w znaki cudzysłowu i przyporządkowaniu im odpowiednich wartości. Brak wartości w cudzysłowie wskazuje na wykonanie instrukcji alternatywnej. Wygląda to następująco:

> a = 1
> a=switch(as.character(a), "1"=4, "2"=2, "3"=5, "4"=3, 1)
> a
[1] 4

Ta składnia jest najczęściej stosowana. Wymaga jednak zmiany typu klucza na typ tekstowy.

W opracowaniu danych pomocna jest funkcja cut(), która zamienia liczby na przedziały, w których te liczby się znajdują. Została już ona opisana w rozdziale o czynnikach. Umożliwia ona napisanie funkcji, która przyjmuje różne wartości dla liczb należących do kilku różnych przedziałów. Przykładowo, chcemy wyliczyć x2+1 gdy x jest ujemne x+1 gdy x należy do przedziału [0,3] oraz cos(2πx)+3 gdy x jest większe od 3, to możemy zrobić to następująco:

> x = 7.43
> cut(x,c(-Inf,0,3,Inf))
[1] (3, Inf]
Levels: (-Inf,0] (0,3] (3, Inf]
> switch(cut(x,c(-Inf,0,3,Inf)),x^2+1,x+1,cos(2*pi*x)+3)
[1] 2.095173
Warning message:
In switch(cut(x, c(-Inf, 0, 3, Inf)), x^2 + 1, x + 1, cos(2 * pi * :
EXPR is a "factor", treated as integer.
Consider using 'switch(as.character( * ), ...)' instead.
> switch(cut(x,c(-Inf,0,3,Inf)),
+ "(-Inf,0]"=x^2+1,
+ "(0,3]"=x+1,
+ "(3, Inf]"=cos(2*pi*x)+3)
[1] 2.095173
Warning message:
In switch(cut(x, c(-Inf, 0, 3, Inf)), x^2 + 1, x + 1, cos(2 * pi * :
EXPR is a "factor", treated as integer.
Consider using 'switch(as.character( * ), ...)' instead.
> switch(as.character(cut(x,c(-Inf,0,3,Inf))),
+ "(-Inf,0]"=x^2+1,
+ "(0,3]"=x+1,
+ "(3, Inf]"=cos(2*pi*x)+3)
[1] 2.095173

W dwóch pierwszych składniach poza poprawnie wyliczonym wynikiem pojawia się jeszcze komunikat ostrzegawczy. Zastosowanie funkcji as.character() spowodowało wreszcie brak komunikatu o błędzie. W dwóch ostatnich instrukcjach ważne było, by przedział (3,Inf) zapisać nie jako “(3,Inf)” ale “(3, Inf)”, czyli dokładnie tak jak wyglądały wartości czynnika utworzonego przez funkcje cut(). W przeciwnym wypadku funkcja switch() nie wygeneruje żadnego wyniku.

Pętla iteracyjna for

Składnia pętli for (…) {…}

W R bardzo dużo można zrobić wpisując pojedyncze polecenia, ale nie jest to zbyt wydajny sposób pracy. Pewne operacje na danych wykonuje się wielokrotnie podczas jednej analizy. Żeby nie wpisywać określonego polecenia dziesiątki czy setki razy używa się pętli. Najpopularniejsza z nich to pętla for (…) {…}. Może mieć ona następującą konstrukcję:

for ( iterator in wektor ) instrukcja    

albo

for ( iterator in wektor ) { instrukcja }

albo

for ( iterator in wektor ) { instrukcja 1
instrukcja 2

instrukcja n}

Stałe elementy tej instrukcji to słowa kluczowe for i in, nawiasy okrągłe oraz nawiasy klamrowe dla wielolinijkowych instrukcji. Za iterator można wstawić dowolny literał, podobnie wektor może (ale nie musi) być zdefiniowany wcześniej i można wstawić tu jego nazwę. Instrukcje mogą mieć dowolny charakter (przypisania, funkcje, instrukcje warunkowe, pętle, w tym także pętle “for”). Mogą, ale nie muszą wykorzystywać chwilową wartość iteratora. Jedyne ograniczenie, to brak możliwości zmiany wartości iteratora wewnątrz nawiasów klamrowych przez jakieś przypisanie, np iterator=2 lub iterator=f(iterator).

Iteratorem jest jakiś literał, który przybiera kolejne wartości wektora. Instrukcje wykonywane są dla kolejnych wyrazów wektora wyszczególnionego w nawiasie, a więc powtarzają się tyle razy, ile jest wyrazów w tym ciągu. Instrukcje te mogą, ale nie muszą one wykorzystywać wartości wyrazów wektora, czyli chwilowej wartości iteratora.

W informatyce przyjęto rozpoczynanie nauki programowania od wyświetlania na ekranie napisu “Hello word!”. W R skromnym odpowiednikiem tej czynności jest zastosowanie funkcji cat():

> cat("Hello World!")
Hello World!> cat(Hello world!\n")
Hello World!

Dopisywanie “\n” na koniec tekstu powoduje przejście kursora do nowej linijki. Żeby wydrukować ten napis dziesięciokrotnie można oczywiście tyleż razy przywołać to polecenie w konsoli R, ale będzie to żmudne zadanie. Lepszym rozwiązaniem jest użycie pętli for:

> for (i in 1:10) cat("Hello World!\n")
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
> 

Iterator w instrukcji for nie zostaje umieszczony w katalogu obiektów R. Ma on tylko wartości wewnątrz pętli. Można ten fakt wykorzystać w następujący sposób:

> for (i in 1:10) cat("To jest napis nr", i, "\n") 
To jest napis nr 1
To jest napis nr 2
To jest napis nr 3
To jest napis nr 4
To jest napis nr 5
To jest napis nr 6
To jest napis nr 7
To jest napis nr 8
To jest napis nr 9
To jest napis nr 10
> 

 

Funkcje zastępujące w R pętlę iteracyjną

Wiele instrukcji, dla których w językach programowania C++, Pascal, Java itp., należałoby tworzyć pętle, w R wykonywanych jest za pomocą jednej funkcji. Skraca to znacznie kod źródłowy i powoduje, że jest on bardziej czytelny. Pętle są jednak w R możliwe. Przykładowo, wyliczanie średniej wartości ciągu liczb można wykonać za pomocą funkcji mean() lub za pomocą pętli “for”.

> ciag=c(3, 4, 5, 2, 4, 3, 4, 2, 1, 6)
> srednia=0
> for (i in 1:length(ciag)) srednia=srednia+ciag[i]
> srednia=srednia/length(ciag)
> srednia
[1] 3.4
> mean(ciag)
[1] 3.4

Bardziej złożone funkcje umożliwiają uniknięcie podwójnej pętli, którą należałoby użyć np. w C++ do wyliczania sum wyrazów w wierszach jakiejś macierzy liczbowej. Taką podwójną pętlę zastępuje funkcja apply(macierz,1,sum), gdzie za słowo macierz podaje się nazwę analizowanej macierzy, liczba 1 na drugiej pozycji oznacza, że chodzi o zastosowanie funkcji sum dla wierszy. W R wyliczenie sum z wierszy macierzy można zrealizować na trzy sposoby:

> mat=matrix(1:12,4,3)
> mat
    [,1] [,2] [,3]
[1,]    1    5    9
[2,]    2    6   10
[3,]    3    7   11
[4,]    4    8   12
> sumy=rep(0,nrow(mat))
> for (i in 1:nrow(mat)) for (j in 1:ncol(mat)) sumy[i]=sumy[i]+mat[i,j]
> sumy
[1] 15 18 21 24
> sumy=NULL
> for (i in 1:nrow(mat)) sumy=c(sumy,sum(mat[i,]))
> sumy
[1] 15 18 21 24
> sumy=apply(mat,1,sum)
> sumy
[1] 15 18 21 24

Przy wyliczaniu sum jakiś wymiarów w macierzy wielowymiarowej zastąpienie np. potrójnej pętli za pomocą specjalnej funkcji nie jest już możliwe. Czasami także łatwiej jest “ogarnąć” problem obliczeniowy używając pętli niż posługiwać się gotowymi w R funkcjami, których ograniczeń nie jesteśmy pewni. Przykładem może być problem z wyliczenie wszystkich osób, które przeżyły lub nie, katastrofy Titanica z podziałem na status (klasa podróży lub załoga), ale nie zależnie od wieku i płci uczestników podróży. Podstawowe dane znajdują się w czterowymiarowej tablicy Tytanic zaimplementowanej do R. Obliczenia można zrealizować w następujący sposób:

> Titanic
, , Age = Child, Survived = No
#
Sex
Class  Male Female
1st     0      0
2nd     0      0
3rd    35     17
Crew    0      0
#
, , Age = Adult, Survived = No
#
Sex
Class  Male Female
1st   118      4
2nd   154     13
3rd   387     89
Crew  670      3
#
, , Age = Child, Survived = Yes
#
Sex
Class  Male Female   1st     5      1
2nd    11     13
3rd    13     14
Crew    0      0
#
, , Age = Adult, Survived = Yes
#
Sex
Class  Male Female
1st    57    140
2nd    14     80
3rd    75     76
Crew  192     20
#
> #Pierwszy wymiar to status, drugi – Płeć, trzeci – wiek, czwarty – Przeżycie
> sumy=outer(rep(0,length(Titanic[,1,1,1])),rep(0,length(Titanic[1,1,1,])))
> dimnames(sumy)[[1]]=dimnames(Titanic)[[1]] > dimnames(sumy)[[2]]=dimnames(Titanic)[[4]] > for (i in 1:length(Titanic[,1,1,1])) {
+ for (j in 1:length(Titanic[1,1,1,])) sumy[i,j]=sum(Titanic[i,,,j])}
> sumy
      No Yes
1st  122 203
2nd  167 118
3rd  528 178
Crew 673 212

 

Przykłady stosowania pętli for (…) {…}

Przykładem konieczności stosowania pętli, której nie można już zastąpić jedną funkcją istniejącą w podstawowych bibliotekach R, jest wyliczanie wartości ciągów rekurencyjnych. Poniższy przykład pokazuje sposób wykreślania przebiegu ciągu xn, gdzie xn+1=xn+xn*(−0.01*xn+2.6) oraz x0=10. Jest to przykład modelu dynamiki liczebności populacji – tzw. modelu logistycznego w czasie dyskretnym.

Program ten zapisany w skrypcie wygląda następująco:

a=-0.01
b=2.6
x=rep(10,100)
for (i in 2:100) x[i]=x[i-1]+x[i-1]*(a*x[i-1]+b)
x

Po przeniesieniu do konsoli uzyskamy następujące wyniki:

> a=-0.01
> b=2.6
> x=rep(10,100)
> for (i in 2:100) x[i]=x[i-1]+x[i-1]*(a*x[i-1]+b)
> x
[1] 10.0000 35.0000 113.7500 280.1094 223.7811 304.8321 168.1694 322.6004
[9] 120.6513 288.7774 205.6748 317.4080 135.1903 303.9209 170.4360 323.0853
[17] 119.2660 287.1138 209.2664 315.4348 140.5742 308.4561 158.9904 319.5860
[25] 129.1576 298.1505 184.4046 323.8060 117.1984 284.5595 214.6730 311.9778
[33] 149.8186 314.8908 142.0446 309.5939 156.0542 318.2660 132.8252 301.7454
[41] 175.7807 323.8220 117.1524 284.5018 214.7938 311.8939 150.0399 315.0239
[49] 141.6854 309.3199 156.7636 318.6007 131.8985 300.8625 177.9226 323.9568
[57] 116.7643 284.0124 215.8142 311.1735 151.9353 316.1237 138.7034 306.9459
[65] 162.8474 321.0579 125.0267 293.7794 194.5425 321.8852 122.6860 291.1510
[73] 200.4544 319.8162 128.5144 297.4924 185.9555 323.6453 117.6602 285.1375
[81] 213.4611 312.8036 147.6321 313.5232 145.7156 312.2458 149.1105 314.4584
[89] 143.2094 310.4645 153.7901 317.1304 135.9525 304.5982 168.7530 322.7350
[97] 120.2671 288.3198 206.6683 316.8880

Inny przykład to wypisanie początkowych wyrazów ciągu Fibonacciego. Jest to ciąg xn taki, że x0=1, x1=1, …, xn=xn-2+xn-1. Gdy chcemy wypisać wszystkie jego wyrazy do n=100, to najlepiej jest zrobić następujący program:

x=c(1,1)
for (i in 2:100) x=c(x,x[i-1]+x[i])
x

Po przeniesieniu do konsoli uzyskujemy ciąg liczb zapisanych w formacie naukowym. Możemy je wyświetlić dokładnie używając funkcji format() z opcją scientific=FALSE.

> x=c(1,1)
> for (i in 2:100) x=c(x,x[i-1]+x[i])
> x
 [1] 1.000000e+00 1.000000e+00 2.000000e+00 3.000000e+00 5.000000e+00 8.000000e+00
[7] 1.300000e+01 2.100000e+01 3.400000e+01 5.500000e+01 8.900000e+01 1.440000e+02
[13] 2.330000e+02 3.770000e+02 6.100000e+02 9.870000e+02 1.597000e+03 2.584000e+03
[19] 4.181000e+03 6.765000e+03 1.094600e+04 1.771100e+04 2.865700e+04 4.636800e+04
[25] 7.502500e+04 1.213930e+05 1.964180e+05 3.178110e+05 5.142290e+05 8.320400e+05
[31] 1.346269e+06 2.178309e+06 3.524578e+06 5.702887e+06 9.227465e+06 1.493035e+07
[37] 2.415782e+07 3.908817e+07 6.324599e+07 1.023342e+08 1.655801e+08 2.679143e+08
[43] 4.334944e+08 7.014087e+08 1.134903e+09 1.836312e+09 2.971215e+09 4.807527e+09
[49] 7.778742e+09 1.258627e+10 2.036501e+10 3.295128e+10 5.331629e+10 8.626757e+10
[55] 1.395839e+11 2.258514e+11 3.654353e+11 5.912867e+11 9.567220e+11 1.548009e+12
[61] 2.504731e+12 4.052740e+12 6.557470e+12 1.061021e+13 1.716768e+13 2.777789e+13
[67] 4.494557e+13 7.272346e+13 1.176690e+14 1.903925e+14 3.080615e+14 4.984540e+14
[73] 8.065155e+14 1.304970e+15 2.111485e+15 3.416455e+15 5.527940e+15 8.944394e+15
[79] 1.447233e+16 2.341673e+16 3.788906e+16 6.130579e+16 9.919485e+16 1.605006e+17
[85] 2.596955e+17 4.201961e+17 6.798916e+17 1.100088e+18 1.779979e+18 2.880067e+18
[91] 4.660047e+18 7.540114e+18 1.220016e+19 1.974027e+19 3.194043e+19 5.168071e+19
[97] 8.362114e+19 1.353019e+20 2.189230e+20 3.542248e+20 5.731478e+20
> format(s, scientific=FALSE)
  [1] "                    1" "                    1" "                    2"
[4] "                    3" "                    5" "                    8"
[7] "                   13" "                   21" "                   34"
[10] "                   55" "                   89" "                  144"
[13] "                  233" "                  377" "                  610"
[16] "                  987" "                 1597" "                 2584"
[19] "                 4181" "                 6765" "                10946"
[22] "                17711" "                28657" "                46368"
[25] "                75025" "               121393" "               196418"
[28] "               317811" "               514229" "               832040"
[31] "              1346269" "              2178309" "              3524578"
[34] "              5702887" "              9227465" "             14930352"
[37] "             24157817" "             39088169" "             63245986"
[40] "            102334155" "            165580141" "            267914296"
[43] "            433494437" "            701408733" "           1134903170"
[46] "           1836311903" "           2971215073" "           4807526976"
[49] "           7778742049" "          12586269025" "          20365011074"
[52] "          32951280099" "          53316291173" "          86267571272"
[55] "         139583862445" "         225851433717" "         365435296162"
[58] "         591286729879" "         956722026041" "        1548008755920"
[61] "        2504730781961" "        4052739537881" "        6557470319842"
[64] "       10610209857723" "       17167680177565" "       27777890035288"
[67] "       44945570212853" "       72723460248141" "      117669030460994"
[70] "      190392490709135" "      308061521170129" "      498454011879264"
[73] "      806515533049393" "     1304969544928657" "     2111485077978050"
[76] "     3416454622906707" "     5527939700884757" "     8944394323791464"
[79] "    14472334024676220" "    23416728348467684" "    37889062373143904"
[82] "    61305790721611584" "    99194853094755488" "   160500643816367072"
[85] "   259695496911122560" "   420196140727489664" "   679891637638612224"
[88] "  1100087778366101888" "  1779979416004713984" "  2880067194370816000"
[91] "  4660046610375530496" "  7540113804746346496" " 12200160415121876992"
[94] " 19740274219868225536" " 31940434634990100480" " 51680708854858326016"
[97] " 83621143489848426496" "135301852344706760704" "218922995834555203584"
[100] "354224848179261997066" "573147844013817200640"

Ostatni przykład pokazuje sposób szyfrowania tekstu kodem Cezara. Wymaga on klucza, który można zmieniać co jakiś czas za obopólną wiedzą. Osoby korespondujące ze sobą umawiają się, że będą używać w korespondencji małych i dużych liter alfabetu, polskich liter, cyfr arabskich, pauzy, kropki, przecinka i znaku zapytania. Decydują się na porządek alfabetyczny stosowanych znaków tak, by przypisywać im liczby stanowiące pozycję w tym ciągu. Klucz jest ustalonym teksem stosującym te znaki.

Podczas szyfrowania poszczególne znaki tekstu i klucza zostają zamienione na liczby – numer miejsca w stosowanym “alfabecie”. Ciąg liczbowy związany z kluczem zostaje wielokrotnie powtórzony tak, aby miał długość tekstu. Poszczególne liczby z tekstu i klucza są dodawane do siebie. Gdy uzyskana liczba przekracza liczbę stosowanych znaków, wielkość ta zostaje zmniejszona o liczbę stosowanych znaków. Następnie uzyskane liczby są znowu zamienione na tekst. Ten tekst przesyła się odbiorcy. Bez znajomości klucza trudno go rozszyfrować.

Programy szyfrujący i deszyfrujący napisane w skrypcie R wyglądają następująco:

##Szyfrowanie tekstu kodem Cezara.
#
##Ustalony zestaw stosowanych znaków:
alfabet=c(letters,LETTERS,as.character(c(0,1,2,3,4,5,6,7,8,9)),
c("ą","Ą","ć","Ć","ę","Ę","ł","Ł","ń","Ń","ó","Ó","ś","Ś","ż","Ż","ź","Ź"," ",".",",","?"))
alfabet=sort(alfabet)
#
##Ustalony klucz i jego szyfr
klucz="informatyka"
szyfrkl=NULL
for (i in 1:nchar(klucz)) szyfrkl=c(szyfrkl,which(alfabet==substr(klucz,i,i)))
#
##Przesyłany tekst i jego szyfrowanie
tekst="Król Karol kupił królowej Karolinie korale koloru koralowego."
szyfr=NULL
for (i in 1:nchar(tekst)) szyfr=c(szyfr,which(alfabet==substr(tekst,i,i)))
szyfr=szyfr+szyfrkl
szyfr=ifelse(szyfr>length(alfabet),szyfr-length(alfabet),szyfr)
szyfrtekst=paste(alfabet[szyfr],collapse="")
szyfrtekst #Tekst wysyłany odbiorcy
#
##deszyfrowanie tekstu "szyfrtekst"
szyfr=NULL
for (i in 1:nchar(szyfrtekst)) szyfr=c(szyfr,which(alfabet==substr(szyfrtekst,i,i)))
deszyfr=szyfr-szyfrkl
deszyfr=ifelse(deszyfr<0,deszyfr+length(alfabet),deszyfr)
cat(alfabet[deszyfr],"\n",sep="")

 

Program ten można zmodyfikować zmieniając zestaw stosowanych znaków, sposób ich uporządkowania oraz oczywiście klucz.

Pętle warunkowe while i repeat

Pętla while(…){…}

Składnia pętli while(…){…} wygląda następująco:

while (warunek) {  instrukcja zmieniająca warunek
instrukcja 1
instrukcja 2

instrukcja n}

Działa ona podobnie działa podobnie, jak pętla for(…){…}, ale jest bardziej uniwersalna. Oznacza to, że w każdym programie pętlę for(…){…} można zastąpić pętlą while(…){…}, ale są sytuacje, gdy pętli warunkowych nie można zastąpić iteracją. Dzieje się tak wtedy, gdy liczby “obrotów” pętli nie można z góry przewidzieć.

Przykładem zastąpienia pętli for(…){…} pętlą while(…){…} jest program pokazany w poprzednim rozdziale powodujący 10-krotne wyświetlenie się napisu “Hallo world!”.

> i=1
> while (i <= 10) { cat("Hello World!\n")
+ i=i+1 }
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!

Brak iteracji spowodował, że najpierw utworzono obiekt i potem nadano mu wartość początkową. Potem pojawił się warunek i <= 10, a wśród instrukcji było przypisanie i=i+1. Te elementy były konieczne do tego, by pętla zakończyła się. Stosowanie pętli iteracyjnej jest krótsze i bezpieczniejsze. Gdyby przez pomyłkę napisano warunek i <= -10 albo i = i-1, uruchomiony program trwałby i trwał. Zablokowałby wszystkie działania w R. Wtedy należałoby kliknąć w górnym menu na “Różne” a następnie “Przerwij bieżące obliczenia” albo nacisnąć klawisz Esc.

Gdy nie potrafimy przewidzieć krotności powtórzeń wykonania napisu musimy zastosować pętlę while(…){…}. Przykładowo funkcja samlple(5,1) oznacza wylosowanie jednej liczby ze zbioru {1,2,3,4,5}. W R losowanie liczby z ciągów 1:n uzyskuje się za pomocą funkcji sample(n,1). Jeżeli z pewnych powodów wymagane jest wykonywanie napisu, dopóki nie zostanie wylosowana liczba 5, to należy napisać:

> i=1
> while (i != 5) { cat("Hello World!\n")
+ i=sample(5,1) }
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!

Przykładem programu, w którym pętla while(…){…}, nie może zostać zastąpiona pętlą for(…){…} jest program wyliczający największy wspólny mianownik dwóch liczb całkowitych metodą Euklidesa. Algorytm ten polega na dzieleniu liczby większej przez mniejszą, a następnie zastępowaniu liczby większej mniejszą, a mniejszej resztą z dzielenia. Proces ten jest powtarzany aż do uzyskania reszty zerowej. Program wygląda bardzo prosto:

a = 12528
b = 144288
c = 1
while (c != 0) {
c = b %% a
b = a
a = c}
cat("Największy wspólny podzielnik = ",b,"\n")

Po skopiowaniu go do konsoli uzyskamy:

> a = 12528
> b = 144288
> c = 1
> while (c != 0) {
+ c = b %% a
+ b = a
+ a = c}
> cat("Największy wspólny podzielnik = ",b,"\n")
Największy wspólny podzielnik = 432

 

Pętla repeat{…}

Składnia pętli repeat wygląda następująco:

repeat {  instrukcja 1
instrukcja 2

instrukcja n}

Pętla repeat{…} pozornie nie jest pętlą warunkową (w jej definicji nie podano warunku). Ale wśród instrukcji, które występują w nawiasach klamrowych musi zaistnieć instrukcja warunkowa postaci:

if (warunek) break

gdyż jej brak uniemożliwi wyjście z pętli i uzyskanie jakichkolwiek wyników. Ponadto instrukcje w nawiasach klamrowych powinny zmieniać warunek, tak by mógł on być prawdziwy. Nieumiejętne używanie pętli repeat{…} jest dobrym sposobem na zablokowanie sobie komputera. Każe ona bowiem powtarzać instrukcje zawarte wewnątrz niej bez końca, o ile nie zostanie w niej umieszczona instrukcja warunkowa z poleceniem break. Uruchomienie instrukcji:

> repeat cat("Hello World!\n")

wymagać będzie porzerwania wykonywania programu, bo R nie przestanie wyliczać ile razy ma powtórzyć wykonanie napisu. Należy kliknąć na “Różne” (Misc) i kliknąć na “przerwij bieżące obliczenia” (“stop current computation”) lub nacisnąć klawisz Esc. Prawidłowo zastosowana pętla repeat wygląda następująco:

> repeat { cat("Hello World!\n")
+ i=sample(5,1)
+ if (i==5) break }
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!

Pętla repeat{…} może całkowicie zastąpić pętlę while(…){…} i vice versa. Przy takim zastępowaniu trzeba zmienić warunek na przeciwny, gdyż pętla while(…){…} przestaje działać gdy warunek w nawiasach okrągłych ma wartość FALSE a pętla repeat{…} przestaje działać gdy warunek w instrukcji warunkowej ma wartość TRUE. W zależności od problemu jedna z tych opcji daje bardziej przejrzysty kod.

Generalnie pętli repeat{…} wygodniej jest używać, gdy pewien zestaw instrukcji musi zostać co najmniej raz wykonany. Wtedy instrukcję warunkową z poleceniem break umieszcza się na końcu. Przykładem może być losowanie współrzędnych punktu należącego do koła jednostkowego. Funkcja, która losuje liczby z przedziału [-1,1], nosi w R nazwę runif() z opcjami min= i max=.

repeat{ x=runif(1,min=-1,max=1)
y=runif(1,min=-1,max=1)
if (x^2+y^2<1) break}
cat(x," ",y,"\n")

Po przeniesieniu do konsoli wygląda to następująco:

> repeat{ x=runif(1,min=-1,max=1)
+ y=runif(1,min=-1, max=1)
+ if (x^2+y^2<1) break}
> cat(x," ",y,"\n")
0.8355987 -0.208568

Liczba pierwsza to liczba naturalna (całkowita dodatnia), która ma tylko dwa (różne) dzielniki dające resztę 0. Jest to 1 i ona sama. Najmniejsza liczba pierwszą jest 2, choć wielu matematyków włącza to także 1. Zadaniem jest utworzenie ciągu liczb pierwszych od 2 do z góry zadanej liczby N.

N=1000
x=2:N
y=NULL
repeat{ a=x[1]
y=c(y,a)
x=x[x %% a != 0]
if (length(x)==0) break}
y

Po przeniesieniu do konsoli uzyskamy następujący obraz:

> N=1000
> x=2:N
> y=NULL
> repeat{ a=x[1]
+   y=c(y,a)
+   x=x[x %% a != 0]
+   if (length(x)==0) break}
> y
  [1]   2   3   5   7  11  13  17  19  23  29  31  37  41  43  47  53  59  61  67  71  73
[22]  79  83  89  97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181
[43] 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307
[64] 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433
[85] 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541 547 557 563 569 571
[106] 577 587 593 599 601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 701
[127] 709 719 727 733 739 743 751 757 761 769 773 787 797 809 811 821 823 827 829 839 853
[148] 857 859 863 877 881 883 887 907 911 919 929 937 941 947 953 967 971 977 983 991 997

 

Własne funkcje

Składnia

Najważniejsze funkcje zaimplementowane do podstawowych baz R opisano w części pierwszej. Składały się one z literału, nawiasów okrągłych, w które wpisywano wartości określonych obiektów. Zostały one napisane przez inne osoby i wierzymy, że wyniki przez nie pokazywane są prawdziwe. Pomimo, że w R jest znacznie więcej funkcji niż w innych językach programowania, nie znajduje się tu wszystko, co liczą biolodzy. Czasem warto napisać sobie własną funkcję i stosować ją do analizy różnych danych. Ponadto niektóre zaimplementowane do R funkcje mają wiele różnych opcji, wiele standardowych ustawień typowych dla innych dziedzin niż biologia (o czym nie informuje żaden komentarz, a standardowy help R niczego wyraźnie nie wyjaśnia tylko odsyła do trudno dostępnych podręczników). Używając ich czasem można wprowadzić się w błąd jakimś wynikiem. Pisząc własne funkcje lub modyfikując napisany otwartym tekstem kod programu, doskonale wiemy co liczymy.

Własne funkcje pisze się i zapamiętuje jako obiekty R. Potem używa się ich tak, jak wszystkich innych funkcji, które są w podstawowych bibliotekach R. Podczas pisania własnej funkcji używa się następującej składni:

 

nazwa.funkcji = function(arg1, arg2, …) {instrukcja 1
instrukcja 2

instrukcja n}

gdzie “arg1”, “arg2”, itd. są nazwami kolejnych argumentów nowej funkcji. Obowiązkowymi elementami składni są: nazwa funkcji (która jest jakimś literałem i obowiązują dla niego takie same zasady, jak dla wszystkich literałów używanych do nazywania obiektów), znak przypisania (= lub <-), słowo kluczowe function i nawiasy okrągłe. W nawiasach klamrowych podaje się instrukcje, które mają być wykonane przez funkcję. Podobnie jak w przypadku pętli nawiasy klamrowe można pominąć, jeżeli instrukcja mieści się w jednej linii. Istotą działania funkcji jest to, że wykonywane instrukcje korzystają z konkretnych wartości argumentów podanych przy wywołaniu funkcji. Innymi słowy funkcję zapamiętujemy pod wymyśloną nazwą, a następnie uruchamiamy ją po podaniu wartości poszczególnych argumentów.

> powtarzanie = function(napis, n) {for (i in 1:n) cat(napis) }
> powtarzanie("Hello world!\n", 5)
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!

Liczba argumentów może być dowolna (łącznie z ich brakiem). Podobnie nie ma ograniczeń na długość i stopień skomplikowania instrukcji wpisywanych między nawiasami klamrowymi.

> #Najprostsza funkcja
> nic.nie.rób = function(){}
> nic.nie.rób
function(){}
> nic.nie.rób()
NULL
> nic.nie.rób(1)
Błąd w poleceniu 'nic.nie.rób(1)':nieużywany argument (1)

Wywołanie funkcji polega na napisaniu jej nazwy a po niej musza wystąpić nawiasy okrągłe. Lepiej unikać spacji między nazwą funkcji, a nawiasem otwierającym. Liczba argumentów i ich typy muszą być takie, jakie podano w definicji funkcji.

> #funkcja wstawiajaca znak +/- między dwie liczby
> plus.minus = function(x,y) cat(x,intToUtf8(177),y,"\n")
> plus.minus
function(x,y) cat(x,intToUtf8(177),y,"\n")
> plus.minus(7.3,3.7)
7.3 ± 3.7
>

Funkcja złożona z jednej instrukcji nie wymaga nawiasów klamrowych.

Funkcja jako obiekt w R

Nazwa funkcji po jej prawidłowym zdefiniowaniu staje się obiektem w R. Gdy zamykając program R zdecydujemy się na zapisanie wszystkich obiektów, nazwa ta znajdzie się w liście obiektów i będzie nadal wykonywała zdefiniowane w niej polecenia.

> przes7 = function(x) x-7
> odl.eukl = function(x,y) sqrt(x^2+y^2)
> ls()
[1] "odl.eukl" "przes7" 

Po napisaniu nazwy własnej funkcji bez nawiasów klamrowych pojawia się jej składnia.

> odl.eukl = function(x,y) sqrt(x^2+y^2)
> odl.eukl 
function(x,y) sqrt(x^2+y^2)

Wyświetlanie się składni funkcji dotyczy także funkcji zaimplementowanych do R. Wiele jednak funkcji zostało skompilowanych (co oznacza, że nie są interpretowane przez system) uzyskamy tylko informacje, że to funkcja, jakie ma opcje i komunikat: .Primitive(” “). Czasem jednak pokażą się zmiany istniejącej pod dana nazwą wcześniejszej funkcji, które rozszerzają jej zakres stosowania.

> log
function (x, base = exp(1)) .Primitive("log")
> substr
function (text, first, last = 1000000L)
{
if (!is.character(text))
text <- as.character(text)
n <- max(lt <- length(text), length(first), length(last))
if (lt && lt < n)
text <- rep_len(text, length.out = n)
.Internal(substr(text, as.integer(first), as.integer(last)))
}
<bytecode: 0x0000000015a36d40>
<environment: namespace:base>

Pokazane zmiany w funkcji substr() wyciągającej podtekst z tekstu stanowiącego pierwszy argument funkcji pokazują, że za pierwszy argument można wpisać liczbę i będzie ona traktowana jako tekst. Inne zmiany dotyczą przypadku, gdy za first i last (pozycje pierwszego i ostatniego znaku podtekstu) zostaną wpisane wektory, nie pojedyncze liczby. Wtedy zostanie użytych tylko tyle wyrazów wektorów z ilu elementów składa się tekst (gdy jest to wektor tekstowy).

Nazwy funkcji nie są specjalnie chronione. Dotyczy to zarówno funkcji zdefiniowanych w standardowych bibliotekach jak i przez użytkownika. Gdy podczas pracy z R nazwie istniejącego już obiektu lub funkcji przypiszemy inną wartość – od tego momentu ta inna wartość będzie obowiązywać.

> cos(0.75*pi)
[1] -0.7071068
> cos = function(x) x/pi
> cos(0.75*pi)
[1] 0.75

Po nadpisaniu sobie różnych standardowych funkcji innymi własnymi funkcjami najlepiej jest przed zapamiętaniem sesji wyświetlić sobie wszystkie obiekty i usunąć te nazwy, które nie są potrzebne.

Funkcje wielolinijkowe

Gdy wykonanie funkcji wymaga kilku działań, jej zapis wykonuje się w kilku linijkach. Wtedy po pierwsze trzeba zastosować nawiasy klamrowe, po drugie lepiej jest napisać funkcje w skrypcie. Po przeniesieniu do konsoli można sprawdzić gdzie są błędy i usunąć je w skrypcie. W końcu funkcje zadziałają i obiekt z nazwą funkcji pojawi się wśród obiektów R. Funkcje zapisane w skrypcie można zapamiętać jako pliki tekstowe, co umożliwi nam korzystanie z własnych funkcji po usunięciu ich nazwy z wykazu obiektów, na nowych wersjach R, a także na innych komputerach, gdy te pliki tekstowe zapiszemy sobie na jakiś nośnikach elektronicznych.

piramida = function(z1,z2,z3,z4){
y1=log(z1^2+z2^2)
y2=log(z3^2+z4^2)
exp(sqrt(y1+y2))}

Zapis tego typu jest bardziej przejrzysty niż również dopuszczalny:
piramida = function(z1,z2,z3,z4){y1=log(z1^2+z2^2); y2=log(z3^2+z4^2); exp(sqrt(y1+y2))} albo
piramida = function(z1,z2,z3,z4) exp(sqrt(log(z1^2+z2^2)+log(z3^2+z4^2)))
Zwłaszcza w ostatniej instrukcji łatwo pomylić się w rozmieszczeniu nawiasów.

Zapis kilku instrukcji przypisania w obrębie definiowanej funkcji ma też inną zaletę. Zdefiniowane w jej obrębie obiekty pomocnicze nie zostają zapamiętane, nie zajmują miejsca w liście obiektów nawet po wykonaniu funkcji. Jest tam tylko jej nazwa.

Po przeniesieniu do konsoli tekstu funkcji pozbawionej błędów formalnych pozornie nic sie nie dzieje. Aby sprawdzić, czy funkcja działa, trzeba wyliczyć jej wartość na kilku przykładowych argumentów.

> piramida = function(z1,z2,z3,z4){
+ y1=log(z1^2+z2^2)
+ y2=log(z3^2+z4^2)
+ exp(sqrt(y1+y2))}
> piramida(1, 1, 2, 2)
[1] 5.286251

 

Funkcje z opcjami

Każdy argument funkcji wieloargumentowej może być traktowany jako jej opcje. Z reguły opcjom nadaje się wartości domyślne, co pozwala na ich brak przy wywołaniu funkcji.

> trzeciapot = function(x,przes=0) (x+przes)^3
> trzeciapot(2)
[1] 8
> trzeciapot(2,1)
[1] 27

Wartość domyślną można nadać wszystkim argumentom i tylko od interpretacji funkcji zależy, które nazwiemy argumentami, które parametrami.

> lin = function(x=1, plus=0, razy=1) razy*(x+plus)
> lin()
[1] 1
> lin(3)
[1] 3
> lin(3,1)
[1] 4
> lin(3,2)
[1] 8

Przy zamianie wartości domyślnych parametrów innymi wielkościami obowiązuje kolejność taka, w jakim opcje zostały wymienione w nawiasach okrągłych. Aby to zmienić można posłużyć się nazwą opcji.

> lin = function(a=1, b=1) if (a==1) paste("x + ",b,sep="") else paste(a,"x + ",b,sep="")
> lin(3,5)
[1] "3x + 5"
> lin(b=3,5)
[1] "5x + 3"

Wykorzystanie danej opcji powoduje jej eliminację z kolejki do przypisania wartości.

Funkcje działające na całym wektorze

Większość standardowych funkcji działających w R działa na całych wektorach. Funkcje jednoargumentowe przekształcają wektor argumentów na wektor wyników zastosowania funkcji na poszczególnych wyrazach pierwszego wektora. Własne funkcje przeważnie piszemy tak, by działały dobrze na jednej wartości liczbowej lub tekstowej. Jeżeli taka funkcje zastosujemy na wektorze – może okazać się, że podziała ona zgodnie z ogólnymi zasadami R o ile wszystkie zapisane operacje przekształcają wektor na wektor. Jeżeli choć jedna operacja w obszarze funkcji nie jest wykonywana na wektorach – działanie całej funkcji ograniczy się do pierwszego wyrazu wektora. Przykładem operacji nie wykonywanej na wektorach jest instrukcja warunkowa.

Rozważmy działanie funkcji przyporządkowującej exp(x) dla liczb ujemnych i log(x) dla liczb dodatnich.

> papa = function(x) if (x<=0) exp(x) else log(x)
> papa(-3:3)
[1] [1] 0.04978707 0.13533528 0.36787944 1.00000000 2.71828183 7.38905610 20.08553692
Komunikat ostrzegawczy:
W poleceniu 'if (x <= 0) exp(x) else log(x)':
warunek posiada długość > 1 i tylko pierwszy element będzie użyty
> papa = function(x) ifelse(x<=0,exp(x),log(x))
> papa(-3:3)
[1] 0.04978707 0.13533528 0.36787944 1.00000000 0.00000000 0.69314718 1.09861229
Komunikat ostrzegawczy:
W poleceniu 'log(x)': wyprodukowano wartości NaN
> papa = function(x) for (i in 1:length(x)) if (x[i]<=0) exp(x[i]) else log(x[i])
> papa(-3:3)
> papa = function(x) {wyn=NULL
for (i in 1:length(x)) {if (x[i]<=0) wyn=c(wyn,exp(x[i])) else wyn=c(wyn,log(x[i]))}
print(wyn)}
[1] 0.04978707 0.13533528 0.36787944 1.00000000 0.00000000 0.69314718 1.09861229

Funkcja ifelse() jest przystosowana do działań warunkowych na wektorze, ale daje komunikaty ostrzegawcze, choć z warunku wynika, że nie logarytmuje liczb ujemnych. W pętli for(…){…} nie wyświetlają się sukcesywnie uzyskiwane wyniki. Dopiero funkcja z programem tworzącym nowy wektor wyników i wyświetlaniem go tak, jak wyświetla się wektory, dała poprawne wyniki bez komunikatów ostrzegawczych. W taki mniej więcej sposób zamienia się własne funkcje nie działające na wektorach tak, aby na nich działały zgodnie ze schematem przyjętym w R.

Funkcje dwuargumentowe także mają swój schemat w R, gdy działają na wektorach. Drugi wektor jest równany do pierwszego przez dopisywane swoich wyrazów do końca, a funkcja jest wykonywana na odpowiadających sobie parach liczb z pierwszego i odpowiednio wydłużonego drugiego wektora. Aby własne funkcje dwuargumentowe działały w taki sam sposób, należy w ich kodzie źrółowym zastosować program z pętlą while(…), pętlą for(…){…} i funkcją print().

> lin = function(a=1, b=1) if (a==1) paste("x + ",b,sep="") else paste(a,"x + ",b,sep="")
> lin(1:3,3:1)
[1] "x + 3" "x + 2" "x + 1"
Komunikat ostrzegawczy:
W poleceniu 'if (a == 1) paste("x + ", b, sep = "") else paste(a, "x + ", ':
warunek posiada długość > 1 i tylko pierwszy element będzie użyty
> funlin = function(a,b) {while (length(b)<length(a)) b=c(b,b)
for (i in 1:length(a)) {
if (a[i]==1) c=paste("x + ",b[i],sep="") else c=paste(a[i],"x + ",b[i],sep="")
print(c) }}
> funlin(1:3,3:1)
[1] "x + 3"
[1] "2x + 2"
[1] "3x + 1"

 

Koniec częsci II

 

 

Tags:
Spis treści