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

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"

 

Spis treści