Rachunek Prawdopodobieństwa i Statystyka lab 3. Kaja Gutowska (Kaja.Gutowska@cs.put.poznan.pl) 1. Funkcje: - Funkcje nie powinny korzystać ze zmiennych globalnych. - Funkcje powinny być możliwie krótkie. - Funkcje w R są traktowane jak zwykłe obiekty. - Nazwa funkcji nie jest związana z jej definicją, a tylko z nazwą zmiennej, w której funkcja jest zapamiętana. - Funkcje mogą zwracać wartości. Wynikiem funkcji jest wartość wyznaczona w ostatniej linii ciała funkcji lub można wykorzystać funkcję return(). - Do funkcji można przekazać argumenty, powinny być rozdzielone przecinkami. - R pozwala na podanie niepełnej, ale jednoznacznej nazwy argumentu. Oznacza to, że w przypadku długiej nazwy argumentów, można podać fragment nazwy, który jednoznacznie identyfikuje argument. Poczytaj o funkcji match.arg(). - Przykładowo mamy funkcję przyjmującą 1 argument: - Składnia: > funkcja <- function(liczba = 5) cat("wynik działania: ", liczba*2) > funkcja(li = 15) #nie trzeba podawać pełnej nazwy argumentu, zamiast "liczba" podano "li". function(argumenty){ instrukcje # jeśli w ciele funkcji mamy tylko jedną instrukcję to można pominąć klamry # argumenty to lista par składających się z nazwy argumentu i jego domyślnej wartości (co jest opcjonalne). Funkcja jest obiektem, przypisanie funkcji o nazwie function() do zmiennej o nazwie funkcja1 funkcja1 <- function(){ cat("dzisiejsza data") cat(format(sys.time(), "%A %B %C")) funkcja1() Przypisując do zmiennej funkcję, została utworzona zmienna typu funkcyjnego. Chcąc edytować ciało zmiennej należy wykorzystać funkcję edit() lub fix(): > edit(funkcja1)
2. Argumenty domyślne: - Definiując funkcję można określić domyślne wartości argumentów. W przypadku wywołania funkcji bez podania argumentów, wykorzystana zostanie wartość domyślna. - Funkcję można wywołać z dowolną kolejnością argumentów. Jeśli rezygnujemy z kolejności argumentów, należy pamiętać o podaniu nazwy, tak żeby było wiadomo, który argument jest wprowadzany. - W przypadku, gdy zdefiniowane są argumenty z wartościami domyślnymi to możemy je pominąć w wywoływaniu funkcji. - W funkcji można wywołać argumenty nadmiarowe, używając wielokropka " ". Argumenty nadmiarowe nie będą wykorzystane w ciele funkcji, ale mogą być przekazane dalej, w wywołaniach innych funkcji. - Można globalnie określić domyślne wartości dla pewnych argumentów, używając funkcji options(). - Pakiet Defaults pozwala na określenie domyślnej wartości dla argumentów wskazanej funkcji. #tworzymy wektor liczb > liczby <- c(5,4,3,7,10,2) #tworzymy funkcję, która wyświetli nam pewien zakres liczb z wektora #zdefiniowano argumenty domyślne > wyswietl <- function(wektor, do = 4, od = 1){ wektor[od:do] #wywołano funkcję z jednym argumentem, z wektorem liczb, bez określania zakresu > wyswietl(liczby) #wywołano funkcję z dwoma argumentami (wektor i do) > wyswietl(liczby, 5) #wywołano funkcję z dwoma argumentami (wektor i od) > wyswietl(liczby, od = 5) #wywołano funkcję ze zmienioną kolejnością argumentów > wyswietl(do=5, wektor = liczby) #wywołano funkcję z pominięciem argumentu do, ma zdefiniowaną wartość domyślną > wyswietl(liczby,, 2)
Przykład 2: Funkcja z nadmiarową ilością argumentów: > liczby <- c(5,4,3,7,10,2) > rysuj <- function(wektor, ile=3,...){ plot(wektor[1:ile],...) > rysuj(liczby, ile=5, lwd=3, type="l", col="red") 3 Funkcje anonimowe: - Funkcja, której argumentem jest inna funkcja, sposoby tworzenia: - Zdefiniowanie funkcji i przypisanie jej do zmiennej, a następnie przekazanie jej jako argument (w tym przypadku nie trzeba podawać nazwy zmiennej, bo nazwa nie ma żadnego znaczenia). - Można jako argument podać bezpośrednio ciało funkcji. Tym sposobem utworzono funkcję anonimową, czyli funkcję, która nie jest przypisana do żadnej zmiennej. Funkcja anonimowa nie ma nazwy. Przykład 1 - argumentem funkcji jest inna funkcja: > naszafunkcja <- function(x){ x^2 > sapply(c(2,4,6), naszafunkcja) Przykład 2 - użycie funkcji anonimowej: > sapply(c(2,4,6), function(x) x^2) 4. Przeciążanie funkcji: - Definiowanie funkcji polimorficznych czyli mechanizm przeciążania funkcji. - Przeciążanie funkcji oznacza, że funkcja dla określonych argumentów zachowuje się w określony sposób. - Nie każdą funkcję można przeciążyć. - Często przeciążanymi funkcjami są: plot(), anova(), print() itd. - Można sprawdzić czy zadeklarowane są jakieś przeciążone wersje funkcji lub czy istnieją przeciążone funkcje dla jakiej klasy - wykorzystując funkcję methods(). - Mechanizm przeciążania funkcji pozwala tworzyć obiekty określonej klasy i przeciążać dla nich podstawowe funkcje.
Przykład 1 - przeciążanie istniejącej funkcji plot(), która w ogólności służy do rysowania, aby w zależności od klasy pierwszego argumentu zachowywała się w określony sposób. Chcemy, aby dla argumentów o typie logicznym zamiast rysować wypisała wartość argumentu. - Jak to zrobić? Należy zdefiniować nową funkcję i przypisać ją do zmiennej o nazwie plot.typ (typ to nazwa klasy, dla której ta funkcja jest dedykowana np. logic). - Informacja dodatkowa: Wywołanie funkcji bez suffiksu.typ, ale z argumentem klasy typ spowoduje wywołanie funkcji z suffiksem.typ. Co oznacza to, że jeśli funkcja plot() jest polimorficzna, a obiekt wynik jest klasy summary to otrzymamy ten sam efekt pisząc plot(wynik) i plot.summary(wynik). - Deklarujemy funkcję o nazwie plot.logical, ale wywołujemy ją po prostu przez nazwę plot z argumentem logicznym: plot.logical<-function(obj){ cat(ifelse(obj,"true","false")) plot(1:10 < 5) Ważna informacja! Nie każda funkcja może być przeciążona. Jeśli chcemy, żeby R wiedział, że jakaś funkcja może być przeciążona, należy ją oznaczyć używając funkcji UseMethod(). Przykład 2: Niech funkcja rozmiar() wyznacza i przekazuje liczbę elementów w danym obiekcie, bez względu czy jest to wektor, macierz czy ramka danych. - 1 krok - definiujemy funkcję o nazwie rozmiar, która może być przeciążana: rozmiar <- function(x) UseMethod("rozmiar") - 2 krok - określamy domyślne zachowanie funkcji: rozmiar.default <- function(x) length(x) - 3 krok - określamy zachowanie dla konkretnych klas argumentów: rozmiar.character <- function(x) length(x) rozmiar.matrix <- function(x) dim(x)[1] * dim(x)[2] rozmiar.array <- function(x) prod(dim(x)) - 4 krok - wywołanie z różnymi argumentami: rozmiar(1:10) # argumentem jest wektor rozmiar(matrix(0,10,10)) # argumentem jest macierz
5. Funkcje a zasięg: Operatory przypisania wartości do zmiennej: ->,<-,=. Wyróżnić można jeszcze inne operatory przypisania: ->> i <<-. Różnica jest taka, że pierwsze trzy operatory (->, <-, =) przypisują wartość zmiennej o lokalnym zasięgu. Zatem użycie któregoś z wymienionych operatorów funkcji, zmienia wartość lokalnie w funkcji. W przypadku, operatorów (->>, <<-) jest to przypisanie wartości zmiennej o globalnym zasięgu, co oznacza, że zmiany są widoczne poza funkcją. > wartosc1 <- 1 > wartosc2 <- 1 > funkcja <- function(){ wartosc1 <-2 wartosc2 <<-2 cat(paste("wartość 1: ", wartosc1, "wartość 2: ", wartosc2, "\n")) #stan zmiennych globalnych: > cat(paste("wartość 1: ", wartosc1, "wartość 2: ", wartosc2, "\n")) #wywołanie funkcji, stan zmiennych lokalnych: > funkcja() #stan zmiennych globalnych() > cat(paste("wartość 1: ", wartosc1, "wartość 2: ", wartosc2, "\n")) 6. Funkcje a operatory: R umożliwia własne definiowanie operatorów, przez otoczenie znakiem % dowolnego ciągu znaków. Technicznie, operatory są zwykłymi funkcjami z różnicą w sposobie wywołania. > "%u%" <- function(a,b) a*b + a+b > 2 %u% 3 7. Leniwa i Gorliwa ewaluacja: - Leniwa ewaluacja: Dotyczy wszystkich funkcji w R. Wartości argumentów zdefiniowane w miejscu wywołania funkcji nie są wyznaczane dopóki ich wartość nie jest potrzebna. - Gorliwa ewaluacja: Argumenty funkcji są wyznaczane przed jej uruchomieniem (do funkcji trafiają wyniki ewaluacji argumentów).
Przykład leniwej ewaluacji: > f <- function(a, b = c^2) { c <- 1:a print(b) # "b" ma przypisaną wartość domyślną, którą jest wyrażenie z^2, # "c" jest określone wewnątrz funkcji, # w przestrzeni w której jest wywoływana funkcja, zmienna "c" może nie istnieć. Gdyby wartościowanie było gorliwe, to R zwróciłby błąd związany z brakiem wartości zmiennej "c". > f(6) # podano tylko pierwszy argument. Dlaczego nie został zgłoszony błąd? Wartość "b" jest wyznaczona na końcu ciała funkcji, w tym momencie jest znana wartość zmiennej "c". > f(6, 1:5) # gdy podamy oba argumenty funkcja działa poprawnie, zgodnie z oczekiwaniami. > f(6, c^2) # wystąpi błąd (object not found), zmienna "c" w wywołaniu znajduje się w innej przestrzeni nazw niż zmienna "c" wewnątrz funkcji. 8. Zarządzanie obiektami w przestrzeni nazw: - Przestrzeń nazw jest to zbiór symboli, do których przypisywane są wartości. - Przestrzeń nazw ma strukturę drzewa, na samej górze znajduje się przestrzeń nazw pusta - R_EmptyEnv, która jak nazwa wskazuje jest pusta i nie zawiera żadnego symbolu. - Wszystkie przestrzenie nazw (poza R_EmptyEnv), mają przestrzeń nadrzędną do której można się odwołać poprzez funkcję: parent.env() - Można wyróżnić główną przestrzeń nazw czyli.globalenv (tutaj znajdują się nazwy zmiennych określone jako globalne). - W przypadku tworzenia funkcji, tworzy się nowa przestrzeń nazw. Zmienne zdefiniowane wewnątrz funkcji nie są widziane poza funkcją. O takich zmiennych mówi się, że znajdują się w innej przestrzeni nazw. - Wewnątrz funkcji mamy możliwość odwołać się z zmiennych znajdujących się w tej funkcji, lub do zmiennych z nadrzędnych przestrzeni nazw. - R umożliwia zarządzanie przestrzeniami nazw, jedną z takich funkcji jest ls().
Przydatne funkcje: - Funkcja ls() i objects() wyświetla nazwy obiektów w aktualnej przestrzeni nazw. - Funkcja rm() usuwa z pamięci operacyjnej niepotrzebny obiekt. Podobne działanie jest związane z przypisaniem do niepotrzebnej zmiennej wartości NULL. Różnica jest taka, że rm() usuwa obiekt o podanej nazwie i zwalnia pamięć zajmowaną przez zmienną. Natomiast przypisanie NULL, zwalania pamięć, ale nazwa zmiennej zostaje w przestrzeni. - Funkcja save.image() zapisuje do pliku zmienne z aktualnej przestrzeni nazw. - Funkcja sessioninfo() pokazuje informacje o stanie środowiska R. Zadania: Zadania pod koniec zajęć prześlij na maila (Kaja.Gutowska@cs.put.poznan.pl) w postaci skryptu.r. Proszę nadać tytuł maila zgodnie z opisem: RPiS_Imię_Nazwisko_lab3 1. Napisz funkcję, która przyjmuje dwa argumenty: pierwszym jest wektor, a drugim dowolna liczba b. Program zwraca tylko te elementy wektora, które dzielą się bez reszty przez b. 2. Napisz funkcję, która wyznacza pierwiastki równania kwadratowego. Funkcja ta przyjmuje trzy argumenty: a, b i c 3. Napisz funkcję, która jako argument przyjmuje wektor 5 liczb i sortuje te liczby wykorzystując funkcję sort(). Następnie w funkcji wyświetl pierwsze 3 liczby (czyli 3 najmniejsze liczby z wektora 5-elementowego). Nie wykorzystuj funkcji return(). 4. Napisz funkcję, która jako argumenty przyjmuje wektor liczb. Funkcja ma zwrócić w wyniku 3 elementy najmniejsze i 3 elementy największe. Funkcja powinna obsłużyć przypadek, w którym podany wektor zawiera mniej niż 3 elementy. W takim przypadku funkcja powinna wyświetlić komunikat, że wektor jest za krótki. 5. Napisz przeciążoną funkcję, która jako pierwszy argument przyjmuje wektor lub macierz, a za drugi argument przyjmuje liczbę x. Niech funkcja dla wektora zwróci wektor pomnożony o x, a dla macierzy niech zwróci macierz pomnożoną przez x.