1 Instrukcje warunkowe
if
…else
…
W języku R, podobnie jak w większości języków programowania, mamy możliwość korzystania z instrukcji warunkowych. Umożliwia ona warunkowe wykonanie kodu w zależności od prawdziwości pewnego warunku logicznego. Jej składnia jest następująca
lub
lub gdy potrzebujemy więcej warunków
if(warunek logiczny 1){
blok instrukcji 1
} else if(warunek logiczny 2){
blok instrukcji 2
} else if(warunek logiczny 3){
blok instrukcji 3
} else{
blok instrukcji 4
}
Na przykład
a <- 200
b <- 33
if (b > a) {
print("b jest większe od a")
} else if (a == b) {
print("a i b są równe")
} else {
print("a jest większe od b")
}
## [1] "a jest większe od b"
1.1 Zadania - instrukcje warunkowe
1.1.1 Zadanie
Zadanie z numerem PESEL.
Numer PESEL to jedenastocyfrowy symbol numeryczny, który pozwala na łatwą identyfikację osoby, która go posiada. Numer PESEL zawiera datę urodzenia, numer porządkowy, oznaczenie płci oraz liczbę kontrolną. (więcej szczegóły znajdziesz na stronie: PESEL_gov)
W zmiennej pesel
zainicjuj wektor z jedenastu cyfr,
które będą oznaczać numer PESEL.
Dla zadanego peselu napisz kod sprawdzający, czy jest on poprawny (odpowiednia długość i sprawdzenie liczby kontrolnej). Następnie, jeśli jest poprawny wypisz:
datę urodzenia,
płeć i wiek właściciela (bazując na dacie urodzenia),
jeżeli natomiast PESEL nie jest poprawny wypisz komunikat, że podany numer PESEL jest błędny.
Przykładowo:
- pesel = 02070803628, powinniśmy otrzymać:
[1] “PESEL jest poprawny!”
[1] “Numer PESEL należy do kobiety”
[1] “Data urodzenia: 0 8 - 0 7 - 19 0 2”
[1] “Wiek: 122”
- pesel = 98083454361, powinniśmy otrzymać:
[1] “Numer PESEL jest błędny”
Wywołaj swój kod dla podanych numerów PESEL:
c(8,4,0,7,2,1,3,8,2,6,9)
,c(0,1,2,6,3,0,3,7,5,3,8)
,c(8,1,0,2,0,5,2,7,3,5,8)
.
Wskazówka: Do wypisania tych informacji najwygodniej jest
użyć funkcji paste()
służącej do łączenia tekstu ze
zmiennymi, jak w przykładzie:
## [1] "Liczba 0.868564323987812 jest liczbą losową"
Wskazówka: operator %%
dzielenia modularnego
można wykorzystać do sprawdzenia parzystości liczby:
## [1] FALSE
## [1] TRUE
2 Pętle
Uwaga: W R należy unikać pętli, ponieważ są one ogólnie nieefektywne,
ze względu na długie czasy obliczeń. Wynika to z faktu, że operacje
wektorowe są zoptymalizowane na poziomie wewnętrznym i mogą korzystać z
niskopoziomowych bibliotek matematycznych napisanych w językach takich
jak C, co pozwala na bardziej efektywne zarządzanie pamięcią i
procesorem. W poniższym kodzie porównamy wydajności operacji
wykonywanych za pomocą pętli oraz operacji wektorowych w języku R,
wykorzystując bibliotekę microbenchmark
do pomiaru czasu
wykonywania oraz ggplot2
do wizualizacji wyników.
library(microbenchmark)
library(ggplot2)
#instrukcje testowe
x=rnorm(1000) # losujemy 1000 liczb ze standardowego rozkładu normalnego
y1=numeric(1000) #alokacja pamięci na 1000 liczb
y2=numeric(1000) #j.w.
#test właściwy
pomiary1=microbenchmark( #funkcja microbenchmark 100 razy powtarza każdą testowaną instrukcję
petla = {for(i in 1:1000) y1[i]=sin(x[i])}, #obliczenie wartości f. sinus 1000x za pomocą pętli
wektorowe = {y2=sin(x)} #obliczenie f. sinus na wektorze o długości 1000
)
autoplot(pomiary1) #narysowanie wykresu
W pętli sinus jest obliczany dla każdej wartości wektora
x
i jest zapisywany w wektorze y1
. Natomiast,
w kolejnej linii, sinus jest obliczany dla całego wektora x
naraz. W większości przypadków jest możliwa wektoryzacja obliczeń, aby
uniknąć pętli. W przypadku bardziej skomplikowanych sytuacji można użyć
też funkcji: outer
, apply
,
lapply
, tapply
i mapply
, aby
wykonywać pętle bardziej efektywnie.
2.1 Pętla
for
Ogólny schemat pętli for to:
Prosty przykład pętli for
:
x=NULL #inicjalizacja
for(i in 1:10) #pętla będzie wykonywana dla i od 1 do 10
{
x[i]=i #
} #ciąg instrukcji kończymy nawiasem klamrowym
x #wypisanie wektora x
## [1] 1 2 3 4 5 6 7 8 9 10
Po wykonaniu pętli w oknie Enviroment
pojawia się
zmienna o nazwie i
o wartości 10L
- litera
L
oznacza zmienną typu interger
- całkowitą
(32-bit).
Inny przykład wykorzystania pętli for
s=0
x=rnorm(10000) #generuje próbę 10.000 elementową ze standardowego rozkładu normalnego
for(i in 1:10000) #wykonanie pętli dla każdego elementu x od 1 do 10.000
{
if(x[i]<10)
{
s=s+x[i]
} else
{
s=s+2*x[i]
}
}
s
## [1] -123.5473
Powyższą pętlę można zastąpić wydajniejszym poleceniem:
## [1] 394.0486
Inne przykłady
wekt=c(1,5,8,3,-2)
for(i in wekt) #pętla będzie wykonywana dla i z zadanego wektora `wekt`
#- nie musi być on uporządkowany
{
print(i^2)
} #ciąg instrukcji kończymy nawiasem klamrowym
## [1] 1
## [1] 25
## [1] 64
## [1] 9
## [1] 4
2.2 Pętla
while
Pętla while
stanowi trochę bardziej elastyczne
narzędzie, gdy liczba powtórzeń pętli nie jest ustalona. Ma następujący
szablon:
Przykład:
x=NULL #inicjalizacja
i=0 #inicjalizacja
while(i<10) # warunek stopu: blok instrukcji wykonywany jest tak długo, dopóki i <10
{
i=i+1 #bez tej instrukcji pętla while byłaby nieskończona
x[i]=i #instrukcja wewnątrz pętli
} #koniec pętli while
x #wypisanie wyniku
## [1] 1 2 3 4 5 6 7 8 9 10
2.3 Pętla
repeat
W przypadku pętli while
(patrz powyższy przykład)
konieczne jest zainicjalizowanie zmiennej i przed rozpoczęciem pętli.
Można tego uniknąć używając pętli repeat
, która ma
następujący składnię
Zauważmy, że powyższa pętla będzie działać tak długo aż…. powiemy
stop wykorzystując break
Przykład:
x=NULL
i=1
repeat{
x[i]=i
if (i>=10){
break #wyjście z pętli repeat gdy `i` osiągnie lub przekroczy wartość 10
} #koniec if
i=i+1 #właściwa instrukcja
} #koniec pętli repeat
x
## [1] 1 2 3 4 5 6 7 8 9 10
2.4 Zadania - pętle
2.4.1 Zadanie
a. Napisz kod, który obliczającą \(n!\) (n silnia). Należy pamiętać o odpowiednich ograniczenia (klasyczną silnię liczymy dla liczb naturalnych łącznie z zerem). Może być to wersja rekurencyjną lub iteracyjna
b. Oblicz wartość \(n!\) dla wektora
\([1,\ldots, 10]\) korzystając z
wbudowanej funkcji factorial
oraz sapply
.
Wynikiem powinien być wektor silni kolejnych liczb. Alternatywnie możesz
użyć funkcji lapply
, której wynikiem wynikiem jest lista,
więc aby dostać wektor można np. użyć unlist
(sprawdź, jak
działa w pomocy) lub inna wersja apply
.
c. Napisz kod obliczającą wartość symbolu Newtona \({n \choose k}=\frac{n!}{k!(n-k)!}\)
wykorzystującą funkcję factorial
.
2.4.2 Zadanie
Wyjaśnić powód różnego działania dwóch podobnych pętli:
## [1] -3
## [1] -2
## [1] -1
## [1] 0
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
oraz
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
2.4.3 Zadanie
Wygeneruj próbę 50-cio elementową \(x=(x_{1},…,x_{50})\) z rozkładu jednostajnego na przedziale \([0,1]\) używając funkcji
runif(50)
Utwórz wektor \(y=(y_1,\dots, y_{50})\), taki że \(y_i=0,\) gdy \(x_i<0.5\) wygenerowane w podpunkcie a. oraz \(y_i=1\) w przeciwnym przypadku za pomocą instrukcji
if
oraz, kolejno, pętlifor
,while
irepeat
.
3 Funkcje
Funkcje są to fragmenty kodu programu wywoływane w różnych miejscach z “niewielką” różnicą w argumentach. Pozwalają na uniknięcie duplikowania kodu, rozbicie kodu na mniejsze, łatwiejsze do zrozumienia części, uproszczenie programu i zwiększenie jego czytelności.
Schemat funkcji:
nazwa_funckji <- function(argumenty_po_przecinku){
ciało funckji
return(wartość_zwracana_przez_funkcję)
}
Najprostszy przykład funkcji (bez nazwy, nie jest ona wymagana):
## function(a,b){
## a+b^2
## }
to funkcja, która do pierwszego argumentu funkcji dodaje kwadrat
drugiego. W nawiasach po słowie kluczowym function
umieszcza się argumenty funkcji, po czym w nawiasach {…}
znajduje się tak zwane ciało funkcji czyli zbiór instrukcji do
wykonania przez funkcję.
W R funkcja jest zwykłym obiektem, takim jak liczba czy napis. Nawet
gdy nie ma nazwy można ją wywołać za pomocą operatora ()
jak niżej:
## [1] 5
gdzie wywołujemy funkcję z argumentami a=1
oraz
b=2
.
3.1 Nazywanie funkcji
Zacznie prościej używa się funkcji, gdy ma ona nazwę. Aby nazwać funkcję przypisujemy ją do dowodnego wyrazu. Na przykład:
3.2 Argumenty i wartości
Funkcję mogą przekazywać (zwracać) wartości. Wtedy za wynik funkcji
przyjmowana jest wartość wyznaczona w ostatniej linii ciała funkcji.
Jeżeli potrzebne jest zwrócenie innych wartości, można skorzystać z
polecenia return()
z argumentami będącymi wartościami do
zwrócenia.
Na przykład:
Funkcja nie musi mieć argumentów, na przykład funkcja
## [1] "2024-10-14 18:01:44 CEST"
zwraca aktualny czas.
Funkcje mogą mieć dowolną liczbę argumentów, każdy argument musi mieć unikalną nazwę. Do każdego argumentu możemy wskazać wartość domyślną, która będzie stosowana, jeśli dany argument nie zostanie użyty przy wywołaniu funkcji.
Przy wywołaniu funkcji można przez nazwę wskazać, który argument jest określany. Jeżeli wywołujemy funkcję z wartościami, ale bez podania nazw argumentów, to wartości będą przypisane do kolejnych argumentów, jak w poniższych przykładach:
moja_funkcja2 <- function(a=10,b=5){ # definiujemy funkcje z domyślnymi wartościami argumentów
c=a+b^2
return(c^2)
}
moja_funkcja2(3,2) # wywołanie funkcji moja_funkcja2, gdzie a=3 oraz b=2
## [1] 49
## [1] 784
## [1] 3481
## [1] 1225
3.3 Zmienne lokalne i globalne
Argumenty przekazywane są do funkcji poprzez wartości, wewnątrz funkcji zmienne są lokalne.
rm(n) #usuwa "n", o ile gdzieś się pojawiła taka zmienna
# a gdy n nie jest zadeklarowane to pojawia się warning:
# Warning message:
#In rm(n) : object 'n' not found
y=5 #przypisanie wartości zmiennej "y"
power=function(x,n){ # nowa funkcja
y=x^n
return(y)
}
power(3,5) #wywołanie funkcji
## [1] 243
## [1] 5
W czasie wykonywania funkcji przekazywana jest kopia argumentów, a funkcja nie zmienia wartości zmiennych dostępnych w środowisku pracy. Obiekty ze środowiska pracy mogą zostać wykorzystane przez funkcję:
rm(n) #usuwa "n", o ile gdzieś się pojawiła taka zmienna
power2=function(x){# n nie jest już argumentem
y=x^n
return(y)
}
n=4 # tworzy globalnego "n"
power2(3) # wywołanie ma teraz sens, choć jakiś fragment kodu może tego "n" podmienić
## [1] 81
Powyższy sposób (korzystanie ze zmiennych globalnych) jest częstym źródłem błędów (nie tylko w R), bezpieczniej jest zadeklarować wszystkie zmienne wykorzystywane przez funkcję jako dane wejściowe.
3.4 Zwracanie więcej niż jednej wartości
Jeżeli zachodzi potrzeba, by funkcja zwracała więcej niż jedna wartość można posłużyć się listą lub wektorem (jeśli zwracane elementy są tego samego typu).
Na przykład:
moja_funkcja3 <- function(a=10,b=5){ # definiujmy funkcje z domyślnymi wartościami argumentów
c=a+b^2
return(c(c^2,c))
}
moja_funkcja3()
## [1] 1225 35
Poniższa funkcja jako argumenty przyjmuje długość n
próby prostej z rozkładu normalnego o średniej sr
i
odchyleniu standardowym odch
. Funkcja generuje wspomnianą
próbę, oblicza średnią i odchylenie “empiryczne” wygenerowanej próbki, a
następnie zwraca listę - trójkę: wygenerowaną próbę, jej średnią i
odchylenie standardowe w postaci listy:
losuj=function(n=50,sr=0,odch=1)
{x=rnorm(n,mean=sr, sd=odch)
sr_emp=mean(x)
odch_emp=sd(x)
return(list(x,sr_emp, odch_emp))
#list(x,sr_emp, odch_emp) też powinno działać
}
Zadanie: sprawdzić działanie powyższej funkcji.
3.5 Funkcja jako wynik
Wynikiem działania funkcji może być inna funkcja:
## [1] 25
## [1] 25
## [1] 1331
## [1] 1.414214
3.6 Funkcja jako argument
Funkcja może być też argumentem innej funkcji. Poniższa funkcja
generuje próbę losową z rozkładu rozklad.
Domyślnie
przypisany jest rozkład normalny. Trzy kropki …
w
argumencie funkcji służy do pisania “wszystkich pozostałych argumentów”
i wykorzystywany jest do przekazywania przez funkcję swoich argumentów
do innej funkcji w swoim ciele.
losuj10 = function(rozklad=rnorm, ...)
{
rozklad(10,...)
}
losuj10()#standardowy rozkład normalny, domyślny dla funkcji
## [1] 1.08339000 -1.55275732 -0.61582402 0.89511856 -1.09530857 -0.06194751
## [7] 0.98463001 0.81915844 -0.42377786 -0.31378233
losuj10(runif, -5,5)#... służą do zadania przedziału na którym określony jest rozkład jednostajny (standardowo jest to przedział [0,1])
## [1] -0.3488881 2.0144489 -3.5552565 3.8457630 -4.6239221 -4.4487770
## [7] 1.8737070 -3.8423000 -3.9716759 -4.8357000
Inny przykład funkcji i wywołania
wykres=function(funkcja=sin,a=0,b=10)
{
x=seq(a,b,length.out=1001)#sprawdzić, za co odpowiada argument length.out
y=funkcja(x)
plot(x,y)
}
wykres() #wywołanie z domyślnymi argumentami
3.7 Leniwe argumenty, wartościowanie leniwe w R
Wartościowanie leniwe (ang. lazy evaluation, ewaluacja leniwa) – strategia wyznaczania wartości argumentów funkcji tylko wtedy, kiedy są potrzebne (na żądanie).
Zaletami tego podejścia są możliwość obliczenia wartości funkcji nawet wtedy, gdy nie jest możliwe wyznaczenie wartości któregoś z jej argumentów, o ile tylko nie jest on używany, wzrost wydajności dzięki uniknięciu wykonywania niepotrzebnych obliczeń oraz możliwość tworzenia nieskończonych struktur danych. Wadą wartościowania leniwego jest to, że mogą nie wystąpić (być może oczekiwane) skutki uboczne procesu wyznaczania wartości argumentów.
Przeciwieństwem wartościowania leniwego jest wartościowanie zachłanne, stosowane w większości popularnych języków programowania.
(przekopiowane z Wikipedii)
Przykład:
leniwiec=function(x=y)
{
y=2
cat(x) #polecenie służące wypisaniu "x" w konsoli
}
y=1
leniwiec(5) #działa jak można by się spodziewać
## 5
## 2
Jaka jest różnica?
W przypadku pierwszego wywołania leniwiec(5)
funkcja ma
wypisać w konsoli x
. I teraz dopiero funkcja zastanawia
się, co to jest ten “x”? Domyślnie to byłby jakiś “y”, ale wywołanie
funkcji mówi, że “x” ma być równe 5! Zatem funkcja wypisze liczbę
5.
W drugim przypadku funkcja leniwiec()
ma to samo zadanie
(wypisanie “x”a), ale nie ma zadanego argumentu w wywołaniu, ale ma
argument domyślny. Mam wypisać “x”. Ale co to jest ten “x”?.
Domyślnie “x=y”. Ale co to jest ten “y”? Są dwa “igreki”. Jeden
globalny, ten y=1, i ten lokalny y=2. Funkcja leniwa wypisze “x=y” dla
tego lokalnego y=2. Funkcja nie “zastanawia się”, tzn. nie
przypisuje wartości do zmiennych w momencie wywołania funkcji, a
dopiero, gdy w ciele funkcji wartość jest jej potrzebna.
3.8 Zadania - funkcje
3.8.1 Zadanie
Napisz funkcję funkcja_kwadratow()
, która jako argumenty
przyjmuje zmienne a
, b
, c
, które
będą odpowiadać współczynnikami równania kwadratowego. Następnie zwrócić
w postaci listy, której nazwy elementów to “miejsce_zerowe” i
“ekstremum” odpowiednio miejsca zerowe funkcji oraz ekstremum funkcji.
Przyjmij jakieś wartości domyśle na argumentów.
3.8.2 Zadanie
Napisz funkcję BMI()
, która będzie pobierać wagę i
wzrost jako argumenty. Funkcja powinna obliczyć Indeks Masy Ciała (BMI),
a następnie sklasyfikować wynik od wygłodzenia do otyłości (szczegóły
znajdziesz na stronie: BMI
wikipedia).
Jako wynik funkcja powinna zwracać zestaw 4 elementów: masę, wzrost, obliczony BMI, klasyfikację.
Przetestuj swoją funkcję dla trzech różnych zestawów danych.
3.8.3 Zadanie
Rok przestępny: jeżeli rok A nie jest podzielny przez 4, to
A nie jest rokiem przestępnym. Jeżeli A jest podzielne
przez 4, to A jest rokiem przestępnym, chyba że A jest
podzielne przez 100, ale nie przez 400. Przykłady:
- rok 1901 nie jest rokiem przestępnym, bo nie jest podzielny przez
4
- rok 2004 jest rokiem przestępnym, ponieważ jest podzielny przez 4, a
nie przez 100
- rok 2100 nie jest rokiem przestępnym, ponieważ dzieli się przez 4,
przez 100, ale nie przez 400
- rok 2000 jest rokiem przestępnym, bo jest podzielny przez 4, przez 100
i przez 400.
Napisz funkcję
przestepny=funkcja(A)
, przytrzymując w argumencie rok A, która określa, czy rok A jest rokiem przestępnym, czy nie. Możesz użyć np. poleceniaA%%4
służącego do obliczania reszty z dzielenia liczbyA
przez4
.Przetestować program na powyższych przykładach.
Uwaga: program powinien wypisać odpowiedź pełnym zdaniem, np. “Rok A
jest/nie jest rokiem przestępnym”. Można wykorzystać polecenie
paste
.
4 Wybrane funkcje matematyczne*
Przedstawimy klika funkcji matematycznych, które można znaleźć w programie R.
4.1 Operacje na zbiorach
union(x,y) |
Suma wektorów x i y |
intersect(x,y) |
Część wspólna wektorów x i y |
seldiff(x,y) |
Różnica wektorów x minus y |
setequal(x,y) |
Wartość logiczna TRUE lub FALSE w
zależności czy zawartości wektorów są sobie równe |
is.element(el,set) |
Wartość logiczna TRUE lub FALSE w
zależności, czy element el należy do wektora
set |
4.2 Wielomiany
Funkcje do tworzenia i operowania na wielomianach są zebrane w
bibliotece polynom
. Zatem, za pierwszym razem
prawdopodobnie będzie wymagana instalacja biblioteki za pomocą komendy
install.packages("polynom")
. Za kolejnymi razem wystarczy
załadować bibliotekę używając komendy library(polynom)
.
Zdefiniujmy dwa wielomiany za pomocą funkcji
polynomial(coef=c(0,1))
, gdzie w argumencie
coef
można podawać współczynniki wielomiany w porządku
rosnącym.
## 2 + x^2
Na wielomianach możemy wykonywać między innymi poniższe operacje:
p1+p2 |
Dodawanie dwóch wielomianów |
integral(p1, c(0,1)) |
Całkowanie wielomianów na przedziale (0,1) |
deriv(p1) |
Różniczkowanie wielomianu |
solve(p1) |
Wyznaczanie miejsc zerowych wielomianu |
4.3 Ekstrema funkcji
Do numerycznego wyznaczenia ekstremów funkcji można użyć funkcji
optimize()
, która wyznacza ekstrema lub funkcji
uniroot()
, która wyznacza miejsca zerowe.
Warto podkreślić ze funkcja uniroot()
zadziała tylko gdy
pierwiastki funkcji są przeciwnych znaków, co jest dość dużym
ograniczeniem. Sposób zastosowanie tych funkcji przedstawimy na
poniższym przykładzie. Aby dowiedzieć się więcej o argumentach jakie
funkcje optimize()
i uniroot()
mogą przyjmować
zachęcamy do sprawdzenia w helpie.
Przykład:
## $minimum
## [1] 1.99994
##
## $objective
## [1] 23.00066
## $maximum
## [1] -1.99994
##
## $objective
## [1] 82.99885
## $root
## [1] 4.807418
##
## $f.root
## [1] -4.69523e-06
##
## $iter
## [1] 8
##
## $init.it
## [1] NA
##
## $estim.prec
## [1] 6.103516e-05
4.4 Rachunek różniczkowo-całkowy
Do symbolicznego różniczkowania można użyć funkcji D()
lub deriv()
, które obliczają to samo, natomiast różnią się
sposobem przekazywania argumentów. Spójrzmy na poniższe przykłady:
## 3 * (5 * x^4) - (x - 6)^((-1/2) - 1) * (-1/2)
## expression({
## .expr3 <- x - 6
## .expr5 <- -1/2
## .value <- 3 * x^5 - .expr3^.expr5
## .grad <- array(0, c(length(.value), 1L), list(NULL, c("x")))
## .grad[, "x"] <- 3 * (5 * x^4) - .expr3^(.expr5 - 1) * .expr5
## attr(.value, "gradient") <- .grad
## .value
## })
Do całkowania numerycznego używamy funkcji
integrate()
## 50.21832 with absolute error < 2.4e-08