print("Hello world")
Dzięki temu samouczkowi nauczysz się programować w języku o wdzięcznej nazwie R. R jest szczególnie lubiany przez statystyków, ponieważ obfituje w funkcje umożliwiające wyliczanie w prosty sposób rozmaitych statystyk, testów statystycznych etc., oraz naukowców - z powodu mnogości dodatkowych pakietów umożliwiających np. analizę widm ze spektroskopii masowej czy eksperymentów mierzących ekspresję genów. A przy tym nadaje się do wygodnych obliczeń wszelakiej maści.
Autorką tego samouczka jest Ania Macioszek. Jeżeli cokolwiek w nim będzie niejasne / błędne / niegramatycznie napisane, lub po prostu będziesz miał(a) jakieś pytania czy sugestie pisz śmiało na r at davinci.mimuw.edu.pl.
A tymczasem nie ma co czekać, zaczynamy pRogRamować!
Zanim postawimy nasze pierwsze kroki w R-ze, trzeba będzie zainstalować coś, co będzie rozumiało język R i umiało wykonywać napisane w R-ze programy. Instalacja powinna przebiec prosto i bezboleśnie. Wejdź na stronę
https://cran.r-project.org/mirrors.html
i wybierz serwer znajdujący się najbliżej Ciebie. Jeśli jesteś w Polsce, najlepiej skorzystać z serwera wrocławskiego:
Jeżeli pracujesz na windowsie, powinieneś znaleźć tam plik .exe, który wystarczy uruchomić i postępować zgodnie z kolejnymi poleceniami. Jeśli pracujesz na linuxie, instalacja może różnić się nieznacznie zależnie od dystrybucji i od tego jakim menadżerem pakietów dysponujesz; tu na przykład instrukcja dla Ubuntu:
https://cran.r-project.org/bin/linux/ubuntu/README
Jeżeli podejrzewasz, że nie jest ona właściwa dla Twojej dystrybucji, to polecam poszukanie instrukcji np. właśnie na stronie
lub forach / listach dyskusyjnych, a w razie dalszych kłopotów, skontaktuj się ze mną: r at davinci.mimuw.edu.pl. Jest też dostępna wersja na Maca, z tego co wiem również prosta w instalacji.
W czasie pracy z tym tutorialem na pewno nie raz zdarzy się, że będziesz miał jakieś pytanie, na które odpowiedzi nie znalazłeś/aś w przerobionej do tej pory części tutoriala; w takiej sytuacji, polecam kilka miejsc, w które warto po ową odpowiedź się udać:
1. autor tutoriala
2. R-owe FAQ:
http://cran.r-project.org/faqs.html
W tym np. rozdział o instalacji:
https://cran.r-project.org/doc/FAQ/R-FAQ.html#How-can-R-be-installed_003f
3. Różne podręczniki:
http://cran.r-project.org/manuals.html
4. Bardzo lubiany wśród programistów serwis do zadawania pytań, czy też raczej przeważnie - do sprawdzania, czy ktoś już przed nami nie zadał pytania, które nas trapi:
http://stackoverflow.com
5. Bardzo przystępna książka Przemysława Biecka "Przewodnik po pakiecie R" (po polsku!), której całkiem pokaźna i jak na początek całkowicie wystarczająca próbka jest do pobrania ze strony autora:
http://www.biecek.pl/R/
6. I rozmaite tutoriale inne od tego, chociaż ciężko o dobry polskojęzyczny
(tu przykładowy: http://www.r-tutorial.x25.pl/)
Korzystanie z R-a może odrobinę się różnić, zależnie od systemu operacyjnego. Jeśli pracujesz na Linuxie, po zainstalowaniu wystarczy wpisać w terminalu R i nacisnąć enter; jeśli na Windowsie, w katalogu, w którym zainstalował się R (pewnie C:/Program Files/; warto zwrócić na to uwagę podczas instalowania) powinny w katalogu bin znajdować się dwa pliki .exe: R.exe i RGui.exe; z punktu widzenia tego tutoriala jest zupełnie bez różnicy, z którego z tych plików będziesz korzystał(a); na początek polecam raczej RGui. Jeśli udało Ci się uruchomić R / R.exe / RGui.exe i zobaczyłeś/aś (w swoim linuxowym terminalu lub w nowootwartym okienku) kilka linijek informacji o R-ze (jak wersja, informacja o darmowości R-a, kilka podpowiedzi jak uzyskać pomoc) oraz na koniec pustą linijkę zaczynającą się od '>', to wszystko w porządku i jesteś gotowa/gotów do kolejnego rozdziału :) R jest już gotów do wykonania Twoich poleceń, które wpiszesz po znaku '>'.
...zanim jednak przejdziemy do kolejnego rozdziału, zwrócę uwagę na jeszcze jeden program, który warto zainstalować. Jest to RStudio, bardzo wygodny program umożliwiający korzystanie z R-a i pisanie R-owych skryptów. RStudio działa na Linuxie, Macach i Windowsach, jest darmowy, a ściągnąć go można stąd:
Po zainstalowaniu i uruchomieniu zobaczysz okno podzielone na 4 części - nas chwilowo najbardziej będzie interesować lewa dolna, gdzie R wypisał jakieś podstawowe informacje o sobie i znak '>', po którym spodziewa się dostać od nas jakąś komendę. W tym tutorialu nie będę specjalnie obszernie omawiać funkcjonalności RStudio, ale mimo wszystko polecam rozważenie zainstalowania.
Zanim wyruszymy w podRóż po zakRętach R-a, warto zrobić małą Rozgrzewkę. Jeśli przebrnęłaś/ąłeś szczęśliwie przez poprzedni rozdział, masz już zainstalowanego R-a na komputerze, zdecydowałeś/aś się już na (nie)używanie RStudio / RGui i jesteś gotów do pracy. Zatem - do dzieła :)
Uruchom R-a - RGui, RStudio czy po prostu wpisz R w terminalu. Powinien pojawić się zestaw podstawowych informacji o R-ze, a poniżej znak '>', oznaczający, że R czeka cierpliwie, aż wpiszemy mu po owym znaku jakieś polecenie. Nie dajmy mu długo czekać - ustawmy kursor po owym znaku (o ile nie był już tam ustawiony) i wzorem wielu pokoleń informatyków zaczynających przygodę z programowaniem przywitajmy się ze światem, wpisując polecenie:
print("Hello world")
a następnie naciśnijmy enter. R posłusznie zrobił, o co go poprosiliśmy: przeczytał najpierw słowo "print", znalazł wśród swoich funkcji, że ma taką funkcję, i że jej działanie polega na tym, że ma wydrukować na ekran to, co będzie napisane w nawiasach. O ile nie popełniliśmy literówki, to właśnie zaszło:
> print("Hello world") [1] "Hello world"
...tylko z jakąś dodatkową jedynką na początku. Wyjaśnienie jedynki jest proste: w skrócie, R wszystko traktuje jako wektory: jeśli jest to jeden napis albo jedna liczba, dla R-a jest to wektor jednoelementowy. Przy wyświetlaniu wektorów R numeruje kolejne elementy (nie wszystkie, tylko te, od których zaczyna się linia); tak więc owa jedynka w kwadratowych nawiasach informuje nas, że napis "Hello world" jest pierwszym (i tutaj jedynym) elementem wektora.
Jeśli chcemy jeszcze raz wywołać komendę, którą wywołaliśmy przed chwilą, albo chociaż komendę podobną (np. tym razem print("Hello world!"), albo chcemy poprawić literówkę) wystarczy nacisnąć strzałkę do góry - R wpisze od razu ostatnio używane polecenie, które możemy wykonać (naciskając enter) lub poprawić. Naciskając strzałkę do góry więcej razy możemy wrócić również do starszych poleceń, aż do pierwszego polecenia po uruchomieniu programu.
Jeżeli wpisując polecenie zorientowaliśmy się, że nie to chcieliśmy napisać, możemy nacisnąć ctrl+c - R po prostu przeskoczy nam do następnej linijki, zapominając, że coś zaczęliśmy mu wyżej pisać. Jest to również przydatne, gdy napiszemy jakieś polecenie, naciśniemy enter (a więc R zacznie je już wykonywać), po czym odmyślimy się - uznamy na przykład, że jednak to polecenie jest błędne, albo że za długo trwają obliczenia - możemy przerwać R-owi pracę właśnie naciskając ctr+c. Jak na razie jednak większość poleceń, które będziemy wpisywać będzie na tyle prosta, i R będzie sobie z nimi radził na tyle szybko, że potrzeba by niezłego refleksu żeby zdążyć mu przerwać.
Czasem zdarza się, że chcemy w naszych poleceniach napisać jakiś komentarz dla siebie, albo dla dowolnej osoby, która będzie później je czytać. Możemy to zrobić dzięki znakowi "#": R wykonuje całe polecenie od znaku ">" aż do końca linii, chyba, że napotka po drodze #: od # wszystko ignoruje. Za pomocą takich właśnie komentarzy będę czasem opisywać co się dzieje w przykładowych poleceniach, które będę tu pokazywać, np. w ten sposób:
W R-ze jest wbudowana całkiem obszerna pomoc, z której możemy skorzystać, wpisując help() i naciskając enter (R informuje zresztą o tym zaraz po uruchomieniu). Jeśli korzystasz z RStudio, help uruchomił Ci się w okienku w prawym dolnym rogu; jeśli nie, uruchomił się tam, gdzie wpisywaliśmy przed chwilą komendy. Żeby z niego wyjść, naciśnij q. Help() sam w sobie jest mało helpful, bardziej przydatne są helpy dotyczące konkretnych funkcji:
Można też wpisać krócej:
Ilekroć będziesz chciał użyć jakiejś funkcji, ale zapomnisz, jak się to dokładnie robiło, polecam skorzystanie właśnie w ten sposób z helpa.
R nie zawsze zaczyna linię od znaku ">"; jeśli zaczniemy pisać jakieś polecenie, i R dobrze wie, że nie jest ono jeszcze skończone (bo np. nie zamknęliśmy jakiegoś nawiasu, albo cudzysłowu) a my mimo to naciśniemy enter, R kolejną linię zacznie od plusa, na znak, że czeka na coś więcej:
> print("elo" + ) [1] "elo"
I to chyba tyle, jeśli chodzi o RRozgrzewkę, pora przejść do rzeczy!
...a co najmniej do następnego rozdziału.
W poprzednim rozdziale wydawaliśmy R-owi polecenia bezpośrednio. Dopóki nie wymagamy od R-a niczego bardziej wyrafinowanego niż przywitanie się ze światem jest to całkiem w porządku; właściwie przez cały ten tutorial nie będziemy wymagać wielu wyrafinowanych rzeczy i wszystkie przykłady poleceń będą zademonstrowane tak, jak byśmy wydawali je R-owi bezpośrednio. Można sobie jednak wyobrazić, że napiszemy kiedyś coś dłuższego i bardziej wartościowego; coś, do czego może będziemy chcieli wrócić, uruchomić jeszcze wielokrotnie, poczytać wieczorem do poduszki, wysłać znajomym etc. Albo po prostu coś na tyle skomplikowanego, że w pewnym momencie zauważymy, że chyba gdzieś wcześniej popełniliśmy błąd; można oczywiście przeglądać komendy wstecz, ale jeśli błąd popełniony 50 poleceń wstecz rzutował na wszystkie dalsze polecenia, poprawienie go będzie oznaczało nie tylko uruchomienie jeszcze raz owej poprawionej już linijki, ale i 49 następujących po niej. Żmudne. Warto wiedzieć więc, jak napisać program w R-ze i jak go potem uruchomić.
Nasz pierwszy program będzie - nie zgadniecie - drukował "Hello world". Napisanie go jest proste - najpierw uruchamiamy dowolny edytor tekstowy. Jak na początek może to być zwykły notatnik, ale nie polecam pisania programów w notatniku - lepsze są programy specjalnie pisane z myślą o programowaniu; te bardziej wyrafinowane będą umiały nam podpowiadać funkcje, które prawdopodobnie chcemy użyć, pilnować zamykania nawiasów czy wpisywania poprawnych poleceń, te mniej wyrafinowane - chociaż ładnie pokolorują nam składnię, zaznaczając innym kolorem napisy, innym polecenia, innym komentarze etc., pozwalając na pierwszy rzut oka wygodniej zinterpretować, co napisaliśmy. Jak widać, w przykładach w tym tutorialu składnia jest kolorowana (Ponadto może zauważyłeś, że na niektóre słowa-klucze, np. nazwę funkcji print można kliknąć i przejść na stronę z dokładnym opisem danej funkcji czy obiektu). Przykładowe edytory, które bym poleciła, to gedit i geany. Warto też rozważyć zainstalowanie RStudio - jeśli już to zrobiłeś/aś, być może zauważyłeś/aś już że w prawym górnym oknie jest miejsce do wpisywania skryptów, które możemy następnie zapisać sobie na dysku i/lub bezpośrednio uruchomić w R-ze. Jeśli zdecydowałeś się już na jakiś edytor tekstu, otwórz go i napisz w nim znane już Ci polecenie "print("Hello world")". (Bez cudzysłowów dookoła polecenia, naturalnie.) Zapisz program, na przykład pod nazwą moj_pierwszy_skrypt.R; programy pisane w R-ze powinny mieć rozszerzenie .R. Następnie uruchom R-a i wpisz:
> source("sciezka_do_twojego_skryptu/moj_pierwszy_skrypt.R")
wpisując ścieżkę do Twojego skryptu zamiast frazy sciezka_do_twojego_skryptu. Może to być ścieżka bezwzględna, np. "C:/Dokumenty/skrypty/moj_pierwszy_skrypt.R", albo względna, tj. względem katalogu, w którym uruchomiłeś/aś R-a; jeśli nie jesteś pewien/pewna, co to za katalog, wpisz polecenie getwd(), czyli "get working directory", które wypisze Ci, w jakim konkretnie katalogu pracuje R. Na przykład mi napisał:
> getwd() [1] "/home/maciosz/vinci/r-tutorial"
Ponieważ mój skrypt drukujący "Hello world" zapisałam dokładnie w tym katalogu, nie muszę podawać całej ścieżki, wystarczy, że podam nazwę skryptu:
> source("moj_pierwszy_skrypt.R") [1] "Hello world"
Udało się! Mój pierwszy skrypt zadziałał i przywitał się ze światem.
Jeżeli zdecydowałeś się na używanie RStudio, swój skrypt piszesz w prawym górnym rogu, zapisujesz go poleceniem "save", a uruchamiasz jednym z poleceń z prawej strony paska: możesz np. zaznaczyć tylko te linijki skryptu, które Cię interesują, i kliknąć "Run": będzie to jednoznaczne z wpisywaniem linijka po linijce tego, co zaznaczyłeś (jest to wygodne kiedy chcesz przetestować tylko fragment skryptu); albo możesz uruchomić cały skrypt naraz za pomocą polecenia source.
Ważna uwaga. Jeżeli bezpośrednio w interpreterze R-a wpiszemy
> "Hello world"
R wydrukuje nam poniżej ten napis:
> "Hello world" [1] "Hello world"
To dlatego, że interpreter wykonuje polecenia, które mu wpisaliśmy, i jeśli dostanie jakiś wynik tych poleceń to go od razu nam drukuje. Polecenie "Hello world" właściwie trudno nazwać poleceniem, ale w praktyce jest nim: jest to banalne polecenie, które daje interpreterowi napis, i właściwie nie każe nic z nim zrobić. Wynikiem tego polecenia jest oczywiście tenże napis, dlatego interpreter drukuje poniżej "Hello world". Ale! Jeśli taką linijkę napiszemy w skrypcie, nic się nie stanie. (Polecam wypróbowanie.) To dlatego, że kiedy już uruchamiamy skrypt, R robi tylko i wyłącznie te rzeczy, o których zrobienie wprost go poprosimy. Kazaliśmy mu wydrukować? Nie, no to nie wydrukuje. Bezpośrednio w interpreterze nie będzie różnicy, czy zastosujemy funkcję print, czy nie:
> "Hello world" [1] "Hello world" > print("Hello world") [1] "Hello world"
I to tyle jeśli chodzi o pisanie skryptów i uruchamianie ich. Jeżeli będziesz rozwiązywał co bardziej skomplikowane zadania z naszego serwisu, prawdopodobnie napisanie rozwiązania w skrypcie będzie praktyczniejsze niż wpisywanie wszystkiego bezpośrednio do R-a.
Jak wspomniałam we wstępie, R służy wielu użytkownikom do rozmaitych obliczeń. Zacznijmy więc przygodę z R-em od zobaczenia, jak można policzyć różne proste rzeczy w R-ze.
Uruchom R-a (RGui, RStudio lub w terminalu) i wpisz jakieś proste działanie, np.:
> 40+2
Kiedy naciśniesz enter, R sprawdzi co napisałaś/eś, zrobi to, co od niego chcesz - w tym wypadku wykona dodawanie - i napisze, ile mu wyszło. O ile nie żyjesz w alternatywnej rzeczywistości, w której matematyka rządzi się innymi prawami niż u mnie, w linijce poniżej powinno się pokazać
[1] 42
Jeśli martwi Cię owo [1], przypominam, że w pewnym sensie R wszystko traktuje jako wektory - dla nas 42 to po prostu liczba, dla R-a wektor z jednym elementem 42. R po prostu informuje nas ową jedynką, że to pierwszy element wektora.
Zawsze kiedy poznaje się nowy język programowania dobrym pomysłem jest przetestować jego cierpliwość i spróbować go "zepsuć". Daje nam to po pierwsze dobre pojęcie o tym, co jest dla programu zrozumiałe a czego już nie zdzierża, a po drugie oswaja z komunikatami błędu - prędzej czy później na pewno pomylimy się pisząc jakąś komendę i R poinformuje nas grzecznie acz stanowczo, że nie umie zrobić tego, czego od niego chcemy; dobrze jest wtedy nie wpadać w panikę i spokojnie przeczytać komunikat, który nam wyświetli. Sprawdźmy na przykład, czy R dalej będzie wiedział o co nam chodzi, kiedy powciskamy dodatkowe spacje w nasze odawanie:
> 40 + 2 [1] 42 > 40 + 2 [1] 42 > 4 0 +2 Error: unexpected numeric constant in "4 0"
Ups. Wszystko szło dobrze, póki spacje wciskaliśmy dookoła liczb i operatora "+", ale nie spodobało się R-owi, kiedy rozdzieliliśmy spacją cyfry 4 i 0. Jeśli podobnie jak ja masz angielskojęzyczną wersję R-a komunikat brzmi tak jak powyżej, jeśli polskojęzyczną: BŁĄD: nieoczekiwano: stała liczbowa in "4 0" (albo coś w tym rodzaju, być może w nowszej wersji tłumaczenie jest zgrabniejsze). Wyjaśnienie błędu jest proste: R czyta sobie liczbę 4 i spodziewa się, że coś z nią chciemy zrobić, na przykład że dalej będzie znak +, -, * czy /, wtem! jakaś nowa liczba 0 (czy też "stała liczbowa", jak to ujął R), z którą nie wiadomo co trzeba zrobić; o co chodzi w takiej linijce? R nie rozumie, nie próbuje nawet czytać dalej, bo i po co, i od razu pisze błąd. Czyli spacje mogą oddzielać poszczególne składniki polecenia, ale jeśli coś jest samodzielnym składnikiem, to spacji do niego wciskać nie można, bo zmienia to jego znaczenie (możesz spróbować np. powciskać spacje do polecenia "print" i zobaczyć, cz R zrozumie o co chodzi).
Oczywiście dodawanie to nie wszystko co można robić:
> 21*2 [1] 42 > 50.75 - 2.34 - 6.41 [1] 42 > 84 / 2 [1] 42 > 35 * 1.2 [1] 42
...i oczywiście 42 to nie jedyny wynik, który można uzyskać.
Możemy jeszcze potęgować (operator ^ lub **), pierwiastkować (funkcja sqrt()), wyliczać resztę z dzielenia, czyli tzw. dzielenie modulo (%%):
> 2^10 [1] 1024 > 10 ** 2 [1] 100 > sqrt(100) [1] 10 > 1000 ** 100 [1] 1e+300 > 5 %% 2 [1] 1 > 5 %% 3 [1] 2 > 5 %% 5 [1] 0
Zauważmy, jak R radzi sobie z dużymi liczbami: zamiast piasć pierdyliard zer, używa notacji wykładniczej, pisząc 1e+300, co oznacza: 1 * 10 ^ 300. Podobnie będzie robił z małymi liczbami:
> 4.2 / 1000000000 [1] 4.2e-09
Zamiast napisać 0.0000000042, pisze 4.2 * 10^-9, co wygodniej się czyta. Zwróć uwagę, że liczby z przecinkiem nie są tak naprawdę liczbami z przecinkiem, tylko z kropką.
Można liczyć nawet sinusy czy cosinusy:
Zaraz, coś bez sensu, przecież sinus z pi to zero, o co ci chodzi R-ze? Mamy tu do czynienia ze swego rodzaju błędami obliczeniowymi - przy obliczeniach na bardzo małych liczbach może się zdarzyć, że program pomyli nam się o jedną biliardową w te czy we wte. Zauważmy, że to co dostaliśmy, jest tak naprawdę bardzo bliskie zeru: przecież to 1.2 * 10^-16, to prawie zero.
W R-ze obowiązuje kolejność działań jak wszędzie, którą jak wszędzie można zmienić dodając nawiasy (ale tylko okrągłe).
> 1 + 2 * 3 [1] 7 > (1 + 2) * 3 [1] 9 > 3 ** 5 %% 2 [1] 1 > (3 ** 5) %% 2 [1] 1 > 3 ** (5 %% 2) [1] 3 > 5 %% 4 ** 2 [1] 5 > (5 %% 4) ** 2 [1] 1 > 4 * ( ( 12 + 423 / 54 ) - 0.54) [1] 77.17333
Możemy też zrobić najnudniejsze działanie ever, czyli żadne działanie:
> 42 [1] 42
R czyta liczbę 42, robi z nią wszystko, co od niego chcemy, czyli nic, i pisze co mu wyszło. Przypomnienie z poprzedniego rozdziału: interpreter R-a wypisuje nam od razu wynik polecenia, które mu daliśmy; gdybyśmy umieścili w skrypcie linijkę "2 + 2" albo "42" R wykonałby wprawdzie działanie, ale nic by nie wydrukował, bo nikt go nie prosi. W skrypcie należy zapisać "print(2+2)".
Powiedzmy, że chcemy zrobić długą serię obliczeń na kilku szczególnie upierdliwych liczbach, których nie chce nam się za każdym razem wpisywać, bo łatwo o literówkę. Na przykład, zrobiliśmy zakupy i chcemy podliczyć różne statystyki - ile wydaliśmy na owoce, jaki to był procent całej kwoty, ile powinien każdy zapłacić, jeśli zakupy robiliśmy w 10 osób etc. Za każdym razem wpisywać ceny to bez sensu, w końcu się pomylimy. Ale nic prostszego! Nazwiemy sobie te liczby nazwami, które łatwo zapamiętać, i kiedy tylko będziemy chcieli ich użyć, po prostu wpiszemy nazwę, zamiast całej liczby. Na przykład:
> cena_ogorkow = 1.23 > waga_ogorkow = 0.64 > cena_bananow = 5.23 > waga_bananow = 1.02 > cena_czekolady = 4.57 > liczba_czekolad = 1 > cena_ogorkow * waga_ogorkow [1] 0.7872
Informujemy R-a, że pod nazwą cena_ogorkow będzie się kryła liczba 1.23. Od teraz za każdym razem, kiedy napiszemy cena_ogorkow R będzie wiedział, że chodzi nam o liczbę 1.23. Mówimy, że cena_ogorkow to zmienna. Nic nie stoi na przeszkodzie, żeby na zmienne zapisywać wyniki obliczeń:
> koszt_owocow = cena_bananow * waga_bananow
Jeśli ciekawi nas, co kryje się pod jakąś zmienną, wpisujemy po prostu jej nazwę:
> koszt_owocow [1] 5.3346
Działa to tak samo, jak wpisanie np. liczby 42, jak było pokazane wyżej.
Mniej więcej tutaj zawołałaś/eś może: "hola hola, wpisywanie cena_ogorkow jest jeszcze nudniejsze i bardziej literówkogenne niż wpisywanie 1.23! Rozumiem, że może to mieć sens dla dłuższych liczb, ale może proste warto wpisywać ot tak? Albo chociaż nazwać moją zmienną x zamiast cena_ogorkow?" No więc - nie. Dlaczego zmienne warto nazywać długimi nazwami opiszę dokładniej w rozdziale zmienne. Tymczasem powiem tylko, że R wie, że pisanie długich nazw może być upierdliwe, i może Ci podpowiedzieć, jaką nazwę zmiennej chcesz wpisać: wystarczy, że wpiszesz początek, np. "liczb" i naciśniesz tabulator a R sam uzupełni nazwę. Jeśli jest kilka zmiennych zaczynających się tak samo (np. kiedy wpiszesz "cena"), R po pierwszym wciśnięciu podpowie tylko tyle, ile jest pewien ("cena_"), a przy drugim wyświetli Ci podpowiedzi zmiennych, które zaczynają się od "cena_" - potem wystarczy, że napiszesz "o", naciśniesz tab i R sam uzupełni do "cena_ogorkow". Oprocz nazw zmiennych R podpowiada również nazwy funkcji i w ogóle wszystkiego, co możesz wpisać a co zaczyna się daną frazą. Jeśli chcesz sobie przypomnieć nazwy wszystkich zmiennych, które już stworzyłaś/eś, wpisz "ls()" - R wydrukuje wszystkie nazwy:
> ls() [1] "cena_bananow" "cena_czekolady" "cena_ogorkow" "koszt_calkowity" [5] "koszt_owocow" "liczba_czekolad" "waga_bananow" "waga_ogorkow"
Przy okazji, zauważmy, pojawiło się [5] - bo koszt_owocow to piąty element wektora nazw.
Możemy jeszcze trochę zaszaleć:
> cena_ogorkow * waga_ogorkow + cena_bananow * waga_bananow + cena_czekolady * liczba_czekolad [1] 10.6918 > koszt_calkowity = cena_ogorkow * waga_ogorkow + cena_bananow * waga_bananow + cena_czekolady * liczba_czekolad > koszt_calkowity [1] 10.6918 > koszt_calkowity / 10 [1] 1.06918 > koszt_owocow / koszt_calkowity [1] 0.4989431
I znowu zachęcam żeby potestować jakie nazwy zmiennych są akceptowalne i spróbować zdenerwować R-a:
> nazwaZmiennej = 10 > a_moze_z_liczbami123 = 666 > a.teraz.z.kropkami = 1234 > sprobojmy+z+plusami = 100 Error in sprobojmy + z + plusami = 100 : object 'sprobojmy' not found > zWykrzyknikiem! = 23 Error: unexpected '!' in "zWykrzyknikiem!" > ze spacjami = 0 Error: unexpected symbol in "ze spacjami" > 22 = 11 Error in 22 = 11 : invalid (do_set) left-hand side to assignment
R zrozumiał trzy pierwsze polecenia, dalej już zupełnie mu namieszaliśmy: najpierw myślał, że chcemy dodać obiekt (zmienną) sprobojmy do z, ale nie ma takiego obiektu, stąd error. Podobnie z wykrzyknikiem - tak jak +, ! ma znaczenie dla R-a, o którym zaraz. Ze spacjami nie zrozumiał, myślał, że piszemy o jakiejś zmiennej "ze", chcemy ją pewnie utworzyć czy coś, a dalej zamiast coś robić piszemy kolejną nazwę której nie zna. No i wreszcie - liczby jako nazwa zmiennej? To już zupełnie bez sensu.
Co jeszcze można by robić z liczbami? Na przykład porównywać je:
> 42 > 0 [1] TRUE > 28 <= 13 [1] FALSE > cena_ogorkow > cena_bananow [1] FALSE > waga_ogorkow == waga_bananow [1] FALSE
R tym razem jako wynik podaje nie liczbę, ale TRUE lub FALSE. Zauważ, że kiedy pytamy, czy dwie wartości są równe, piszemy dwa znaki równości - jeden był wykorzystywany przy zapisywaniu na zmienną jakiejś wartości.
Na zakończenie obiecane "zaraz", kiedy wyjaśnię, jakie znaczenie dla R-a ma wykrzyknik. Otóż "!" to zaprzeczenie. Kiedy jakieś wyrażenie jako wynik nie daje liczby, ale TRUE / FALSE (wartość logiczną) możemy zaprzeczyć całe to wyrażenie i sprawić, że będzie odwrotne. "!" jest też wykorzystywany w operatorze "!=", czyli "różny od".
> ! 2 < 0 [1] TRUE > 4 != 4.0 [1] FALSE > 2 + 7 != 3 ** 2 [1] FALSE
Nie ma czegoś takiego jak "!<" czy "!>=", bo nie mają większego sensu, zamiast "!<" można napisać ">=" etc.
I to tyle jeśli chodzi o korzystanie z R-a jako kalkulatora :) Dysponując tą wiedzą na pewno poradzisz już sobie z zadaniem Wyrażenia arytmetyczne. A za chwilę zanurzymy się jeszcze głębiej w odmęty R-a, poznając bodaj najczęściej używane struktury danych, jakimi są wektory i macierze.
Jako zapowiedziałam w poprzednim rozdziale, zanurzamy się teraz głębiej w odmęty R-a, poznając bodaj najczęściej używane struktury danych, jakimi są wektory i macierze.
Wektor to zbiór wartości, ułożonych w jakimś porządku. Tworzy się go tak:
moj_pierwszy_wektor = c(1, 2, 3, 4)
c to skrót od concatenate, czyli połącz - c wziął wszystkie liczby, które podaliśmy w nawiasie, i połączył je w jeden wektor. Zobaczmy, co można robić z wektorami, a co R-owi już się nie spodoba:
> moj_pierwszy_wektor = c(1,2,3,4) > moj_pierwszy_wektor [1] 1 2 3 4 > moj_pierwszy_wektor + 1 [1] 2 3 4 5 > moj_pierwszy_wektor * 3 [1] 3 6 9 12 > moj_pierwszy_wektor ** 2 [1] 1 4 9 16 > moj_pierwszy_wektor > 2 [1] FALSE FALSE TRUE TRUE > moj_pierwszy_wektor == 2 [1] FALSE TRUE FALSE FALSE > moj_pierwszy_wektor + moj_pierwszy_wektor [1] 2 4 6 8 > moj_drugi_wektor = c(10, 11, 12) > moj_pierwszy_wektor + moj_drugi_wektor [1] 11 13 15 14 Warning message: In moj_pierwszy_wektor + moj_drugi_wektor : longer object length methods/html/is.html">is not a multiple of shorter object length > inny_wektor = c(100, 200) > moj_pierwszy_wektor + inny_wektor [1] 101 202 103 204 > c(moj_pierwszy_wektor, moj_drugi_wektor) [1] 1 2 3 4 10 11 12
Prześledźmy co się działo w tym kawałku kodu. Widać, że różne operacje, które działają na dwóch liczbach - jak dodawanie, mnożenie, potęgowanie, porównywanie etc. - zadziałają też na parze wektor i liczba - po prostu takie działanie jako wynik daje wejściowy wektor, na którego każdym elemencie wykonano żądaną operację, np. do każdego dodano 1, albo sprawdzono, czy jest większy niż 2, jak w przykładzie. Oczywiście pozostawia to samą zmienną moj_pierwszy_wektor nienaruszoną. Jeżeli po obu stronach działania "+" stały wektory, R dodał odpowiadające sobie elementy: pierwszy do pierwszego, drugi do drugiego etc. Kiedy spróbowaliśmy z dwoma wektorami, ale o różnej długości, R zwrócił nam uwagę, że długość dłuższego obiektu nie jest wielokrotnością długości krótkiego. Cośtam zrobił, konkretnie spętlił za krótki wektor i do ostatniego elementu moj_pierwszy_wektor dodał pierwszy element z moj_drugi_wektor, ale wydrukował ostrzeżenie. To sugeruje, że wektory nie muszą być tej samej długości, i rzeczywiście, z wektorem dwuelementowym zadziałało już bez ostrzeżeń - R dodał pierwszy element do pierwszego, drugi do drugiego, trzeciego już w drugim wektorze nie było, więc wziął znowu pierwszy etc. No i na koniec pokazaliśmy, że funkcja c() może łączyć dwa wektory w jeden.
Co jeszcze ciekawego można robić z wektorami? Można na przykład dostać się do pojedynczego elementu, albo do jakiegoś fragmentu. Posłużą nam do tego nawiasy kwadratowe:
> wektor = c(11,12,13,14,15,16,17,18,19,20) > wektor[3] # bierzemy trzeci element wektora [1] 13 > wektor[4:7] # elementy od 4 do 7 [1] 14 15 16 17 > wektor[14] # ciekawe co sie stanie jak nie ma takiego elementu? [1] NA > # NA znaczy not available, czyli ze nie ma tego czegos, o co poprosilismy R-a > wektor[-4] # caly wektor oprocz elementu 4 [1] 11 12 13 15 16 17 18 19 20 > wektor[5:2] # elementy od 5 do 2, czyli w odwrotnej kolejnosci [1] 15 14 13 12 > wektor[ c(3,5,7) ] # chcemy kilka konkretnych elementow, podajemy ich indeksy za pomoca wektora [1] 13 15 17
Uwaga. 4:7 to tak naprawdę też wektor, tak jak w ostatnim poleceniu. Polecam wpisać i zobaczyć, co wyjdzie.
Kolejną rzeczą godną poznania są macierze. Macierz to tak jakby tabela wartości. Można ją stworzyć na przykład tak:
> matrix(1, 3, 4) [,1] [,2] [,3] [,4] [1,] 1 1 1 1 [2,] 1 1 1 1 [3,] 1 1 1 1
Funkcja matrix tworzy macierz; w tym wypadku, kazaliśmy jej stworzyć macierz z samych jedynek (to pierwsza liczba w nawiasie, czyli pierwszy argument funkcji), z trzema wierszami (drugi argument) i czterema kolumnami (trzeci argument). Trochę nudna taka macierz, więc stwórzmy jakąś z różnymi wartościami:
Tym razem jako pierwszy argument podaliśmy wektor wartości, potem poinformowaliśmy R-a, że macierz ma mieć dwa wiersze i cztery kolumny i R porozkładał wartości z wektora po macierzy o takich wymiarach (zauważ, że najpierw rozkłada w pierwszej kolumnie, potem w drugiej etc.; idzie kolumnami, nie wierszami). W zasadzie nie musimy mu mówić, ile ma być kolumn jak już podaliśmy liczbę wierszy, bo może to sobie sam wyliczyć. Gorzej, jeśli się pomylimy i podamy taką liczbę wierszy, która nie jest dzielnikiem długości wektora i takiej macierzy nie będzie się dało zrobić.
> matrix( c(1,2,3,4,5,6,7,8), 2) # to bedzie ok [,1] [,2] [,3] [,4] [1,] 1 3 5 7 [2,] 2 4 6 8 > matrix( c(1,2,3,4,5,6,7,8), 3) # to juz troche nie [,1] [,2] [,3] [1,] 1 4 7 [2,] 2 5 8 [3,] 3 6 1 Warning message: In matrix(c(1, 2, 3, 4, 5, 6, 7, 8), 3) : data length [8] methods/html/is.html">is not a sub-multiple or multiple of the number of rows [3]
R ostrzegł nas, że coś jest nie tak, ale macierz stworzył, na tyle, na ile mu się udało. Po prostu zapętlił sobie wektor, który podaliśmy - tak, jak to robił w przypadku dodawania wektorów o różnej długości. Właściwie pierwszy przykład, kiedy zamiast wektora podaliśmy mu jedną liczbę, jest skrajnym przypadkiem takiego zachowania: R zapętlił sobie pojedynczą wartość 12 razy.
Osobiście często zapominam, czy najpierw podaje się liczbę kolumn, czy wierszy; na szczęście nie trzeba tego pamiętać, wystarczy wprost powiedzieć R-owi, czy chodzi nam o liczbę kolumn (ncol, jak number of columns), czy o liczbę wierszy (nrow, jak number of rows):
Przy wpisywaniu argumentów funkcji również działa podpowiadanie za pomocą tabulatora - jeśli nie pamiętasz, czy miało być "ncol" czy "ncolumns" czy co tam jeszcze zaczynasz pisać ncol, naciskasz tabulator (prawdopodobnie dwukrotnie) i patrzysz, jakie w funkcji, którą właśnie chcesz wywołać są dostępne argumenty. Możesz też wpisać "?matrix" i poczytać helpa funkcji matrix, gdzie wypisane i opisane będą wszystkie argumenty. Przypominam, że z helpa wychodzimy naciskając q (chyba, że pracujemy w RStudio, wtedy nie musimy wychodzić).
Odwoływanie się do elementów macierzy, podobnie jak przy wektorze, odbywa się za pomocą nawiasów kwadratowych; najpierw podaje się numer wiersza, potem numer kolumny. R sygnalizuje to podczas drukowania macierzy: zauważ, że numery wierszy są napisane z lewej stony przecinka, a kolumn z prawej.
> macierz = matrix( c(1,2,3,4,5,6,7,8,9), 3) > macierz [,1] [,2] [,3] [1,] 1 4 7 [2,] 2 5 8 [3,] 3 6 9 > macierz[1,2] [1] 4 > macierz[3] [1] 3 > macierz[2,] [1] 2 5 8 > macierz[,3] [1] 7 8 9 > macierz[1:2,3] [1] 7 8 > macierz[1:2,2:3] [,1] [,2] [1,] 4 7 [2,] 5 8 > macierz[,c(1,3)] [,1] [,2] [1,] 1 7 [2,] 2 8 [3,] 3 9
Kilka rzeczy godnych zwrócenia uwagi: po pierwsze, można odwołać się do elementu macierzy zupełnie jak do wektora (polecenie macierz[3]). Po drugie, jeśli nic nie wpiszemy po prawej stronie przecinka, czyli tam, gdzie powinien być numer kolumny, R zrozumie, że chodzi nam o wszystkie kolumny (a więc polecenie macierz[2,] wydrukowało elementy z drugiego wiersza, z wszystkich kolumn). Analogicznie jeśli nie wpiszemy nic przed przecinkiem (macierz[,3] wydrukowało całą trzecią kolumnę).
Zobaczmy, jakie operacje możemy wykonać na macierzach:
> pierwsza_macierz = matrix( c(10,11,12,13), 2) > druga_macierz = matrix( c(100,101,102,103), 2) > pierwsza_macierz + 3 [,1] [,2] [1,] 13 15 [2,] 14 16 > pierwsza_macierz + druga_macierz [,1] [,2] [1,] 110 114 [2,] 112 116 > wektor = c(1000, 2000) > pierwsza_macierz + wektor [,1] [,2] [1,] 1010 1012 [2,] 2011 2013 > pierwsza_macierz * wektor [,1] [,2] [1,] 10000 12000 [2,] 22000 26000 > wektor + pierwsza_macierz [,1] [,2] [1,] 1010 1012 [2,] 2011 2013 > c(pierwsza_macierz, druga_macierz) [1] 10 11 12 13 100 101 102 103
Hmm... To ostatnie zadziałało chyba nie tak, jak byśmy mieli nadzieję. Kiedy chcemy połączyć dwie macierze, to raczej nie w wektor. Oczywiście czasem może się to przydać, ale dobrze też umieć połączyć dwie macierze w jedną:
Elementami zarówno wektora, jak i macierzy nie muszą być liczby, ale muszą to być elementy tego samego typu, inaczej R wszystkie uzna za napisy.
> c("a","b","c") # wektor napisow, spoko [1] "a" "b" "c" > c(TRUE, FALSE, FALSE, TRUE) # wektor wartosci logicznych, moze byc [1] TRUE FALSE FALSE TRUE > c(1, TRUE, "spinacz biurowy") # to troche nie wyjdzie [1] "1" "TRUE" "spinacz biurowy" > wektor_z_roznymi_typami = c(1, TRUE, "spinacz biurowy") > wektor_z_roznymi_typami[1] # sprawdzmy czy pierwszy element to liczba 1 czy napis "1" [1] "1" > wektor_z_roznymi_typami[1] + 1 # pewnie nie wyjdzie... Error in wektor_z_roznymi_typami[1] + 1 : non-numeric argument to binary operator
I jeszcze na deser dwie pożyteczne funkcje, tworzące pewne charakterystyczne wektory:
> seq(10) # wektor od 1 do 10 (seq jak sequence) [1] 1 2 3 4 5 6 7 8 9 10 > seq(3,10) # jak wyzej, ale od 3 [1] 3 4 5 6 7 8 9 10 > seq(3,10,2) # jak wyzej, ale co dwa [1] 3 5 7 9 > rep(3, 5) # powtorz 5 razy liczbe 3 (rep jak replicate) [1] 3 3 3 3 3 > rep( c(1,2), 3) # powtorz 3 razy wektor (1,2) [1] 1 2 1 2 1 2
Miałaś/eś już okazję trochę posługiwać się zmiennymi, przyszła dobra pora, żeby uporządkować, co już o nich wiemy.
Po pierwsze, co może być w nazwie zmiennej?
W nazwie mogą być litery, cyfry oraz znaki '_' i '.'. Teoretycznie mogą być polskie znaki, ale niekoniecznie w każdej wersji R-a, więc lepiej na wszelki wypadek nie używać, żeby się nie przyzwyczajać bez potrzeby. Mimo, że nazwa może zawierać cyfry i znaki musi się chociaż zacząć literą. Należy też pamiętać, że R rozróżnia duże i małe litery, tj. moja_zmienna to inna nazwa niż Moja_zmienna.
Po drugie, jak warto nazywać zmienne?
Wielu początkujących programistów uważa, że nazwy warto skracać, bo po co takie długie. (Wielu zaawansowanych niestety też.) Nazywają zmienne a, b, c, albo l_zad, sr_oc zamiast liczba_zadan czy srednia_ocen. Nie wiem, czy czas, jaki zaoszczędzili na pisaniu tych nazw jest rzeczywiście wart robienia tego, mam nadzieję, żę chociaż robią z nim coś wartościowego. Zmienne warto nazywać tak, żeby dokładnie wiedzieć, co się pod nimi kryje, i to nie tylko w tej chwili, ale też kiedy za miesiąc zajrzy się do swojego skryptu czy w historię komend. Kiedy wszystkie zmienne są ponazywane po ludzku program można normalnie przeczytać i zrozumieć, o co w nim chodzi; linijka "wydatki = srednia_pensja * liczba_pracownikow" jest zupełnie jasna, linijka "c = a * b" zupełnie nie. Oczywiście obie robią to samo (o ile na odpowiednich zmiennych są te same wartości), ale kiedy po pięciu minutach obliczeń wrócisz parę linijek do góry i będziesz próbował zrozumieć czemu jakiś wynik wychodzi Ci dziwny, ciężko będzie Ci zrozumieć co właściwie siedzi pod zmienną c i skąd się tam wzięło. Dużo łatwiej jest prześledzić własny tok rozumowania z jasno nazwanymi zmiennymi. Jest pewna bardzo cenna zasada: program powinno pisać się w pierwszej kolejności po to, żeby mogło się go wygodnie przeczytać, dopiero w drugiej kolejności żeby działał.
Po trzecie, jak się tworzy zmienną?
To już pojawiało się w poprzednich rozdziałach, ale przypomnę: piszemy nazwę zmiennej, pojedynczy znak równości, a potem wartość, którą chcemy przypisać na zmienną. Wartość nie musi być podana wprost, może to być jakieś skomplikowane wyrażenie, które R dopiero musi obliczyć. W tym miejscu wypada wspomnieć również o drugim sposobie zapisywania wartości na zmienną: za pomocą operatora "<-", oczywiście bez spacji pomiędzy < a -:
> a = 10 > a [1] 10 > a <- 4 * 2 > a [1] 8 > a < - "to chyba nie wyjdzie" Error in -"to chyba nie wyjdzie" : invalid argument to unary operator
Te dwa sposoby przypisania różnią się nieznacznie, ale nie jest to specjalnie istotne. Osobiście używam "=", głównie przez przyzwyczajenie z innych języków programowania. Operator "<-" ma tę ciekawą własność, że można go też zapisać w drugą stronę:
> 3 -> a > a [1] 3
I jeszcze jedna pożyteczna rzecz przy tworzeniu zmiennych: jeżeli zapisujemy coś na zmienną, a jednocześnie chcemy, żeby R nam wypisał to na ekran, owo przypisanie możemy wziąć w okrągłe nawiasy:
> ( a = 128 + pi ) [1] 131.1416
Po czwarte, co może być zapisane na zmienną?
Właściwie wszystko. Może to być liczba, TRUE / FALSE (wartość logiczna), wektor, macierz, funkcja, napis...
> zmienna_z_liczba = 4 > jakis_napis = "ala ma kota" > wektor = c( zmienna_z_liczba, 6, jakis_napis) > ( zmienna_z_wartoscia_logiczna = 3 > 2 ) [1] TRUE > zmienna_z_funkcja = sqrt > zmienna_z_funkcja(9) [1] 3 > zmienna_z_funkcja(zmienna_z_liczba) [1] 2 > zmienna_z_funkcja(jakis_napis) Error in zmienna_z_funkcja(jakis_napis) : non-numeric argument to mathematical function
...chciałam tylko sprawdzić, czy dalej umiem zdenerwować R-a. No tak, kazałam mu wykonać funkcję, zapisaną pod zmienną zmienna_z_funkcja, na czymś, co jest zapisane na zmiennej jakis_napis: pod zmienna_z_funkcja kryje się funkcja sqrt, czyli pierwiastek, a pod jakis_napis - napis "ala ma kota". Napisów pierwiastkować się nie da, i w ogóle żadnych "mathematical function" na nich wykonywać, bo są "non-numeric", o czym R mnie uprzejmie informuje.
Po piąte, czy zmienne mogą się zmieniać?
Oczywiście! Po to nazywają się zmienne. Jeśli programowałaś/eś kiedyś w innych językach być może spotkałaś/eś się z tym, że wprawdzie mogą się zmieniać, ale nie mogą zmieniać typów, tj. jeśli raz powiedziałam, że jakis_napis to zmienna trzymająca napis, to nie mogę teraz kazać mu zapisać na nią liczby. No więc w R-ze tak nie jest. Zmienne mogą się zmieniać jak im się żywnie podoba.
> a = 12 > a [1] 12 > a = 15 > a [1] 15 >( a = a + 3 ) [1] 18 > a = "a teraz to napis, paczcie" > a [1] "a teraz to napis, paczcie"
Coś godnego uwagi dzieje się w poleceniu "a = a + 3": zmieniamy wartość zmiennej a, a jednocześnie używamy tej zmiennej do wyliczenia nowej wartości. Czy to się R-owi nie pomiesza? Skądże - R najpierw obliczy sobie to, co jest z prawej strony, czyli weźmie zmienną a (wtedy wynoszącą 15), doda do niej 3, wyjdzie mu 18, i wtedy ten wynik podstawi pod zmienną a.
Warto wspomnieć, że jeśli na zmienną zapisujemy inną zmienną, R tak naprawdę zapisuje wartość. Na przykład w tym fragmencie kodu:
> a = 10 > b = a
R nie zapamiętuje, że b zawsze ma być równe tyle, co a. Kiedy przypisujemy jakąś wartość na zmienną, R wylicza, co to za wartość (kiedy piszemy a=10 wiele do liczenia nie ma, po prostu wychodzi mu 10) i zapamiętuje ją pod nazwą zmiennej. Jeśli za chwilę każemy mu zapamiętać, że b jest jednak równe 14, zmieni tylko wartość b, a pozostanie nieruszone. Podobnie odwrotnie - jeśli każemy mu teraz zapamiętać, że a jest równe 42, R nie pomyśli "acha, to teraz a to 42, a ponieważ b było warte a, to b teraz też musi być 42". b pozostanie jakie było.
> a [1] 10 > b [1] 10 > b = 14 > b [1] 14 > a [1] 10 > b = a > a [1] 10 > b [1] 10 > a = 42 > a [1] 42 > b [1] 10
Po szóste, co zrobić, jeśli zapomniałam/em, jak nazwałam/em moją zmienną?
Jeżeli pamiętasz chociaż jak zaczyna się jej nazwa, możesz zacząć ją pisać i nacisnąć tabulator; jeśli R zna w tej chwili tylko jedną rzecz, której nazwa się tak zaczyna po prostu uzupełni Ci linię nazwą Twojej zmiennej; jeśli zna kilka, uzupełni tylko tyle, ile jest pewien, a kiedy naciśniesz tabulator jeszcze raz podpowie wszystkie nazwy, które możesz wpisać. Możesz również użyć funkcji "ls()" - po wpisaniu "ls()" R wyświetli wektor z wszystkimi stworzonymi przez Ciebie obiektami. Jeśeli pracujesz w RStudio, możesz też zajrzeć do okna z prawym górnym rogu, gdzie znajdziesz spis wszystkich używanych obecnie zmiennych.
Po siódme, na co jeszcze warto zwracać uwagę nazywając zmienną?
Na to, czy zmienna lub funkcja o takiej nazwie już nie istnieje. I nie chodzi tu tylko o zmienne stworzone przez Ciebie. Przykładowo, w rozdziale o kalkulatorze używaliśmy zmiennej pi - nie musieliśmy wcześniej mówić R-owi, ile ona jest warta, R sam z siebie zna zmienną pi. Jeżeli naszą zmienną nazwiemy pi, stracimy to, co wcześniej było na pi zapisane.
Co ciekawe, nic wielkiego się nie stanie jeśli nazwiemy zmienną nazwą jakiejś funkcji - kiedy będziemy z niej korzystać jak ze zmiennej, R będzie brał jej wartość jako zmiennej, kiedy jak z funkcji - będzie używał funkcji.
> sum # tu jeszcze sum jest funkcja sumujaca elementy wektora function (..., na.rm = FALSE) .Primitive("sum") > sum + 1 # nie mozna dodawac funkcji i liczby, bedzie error Error in sum + 1 : non-numeric argument to binary operator > sum( c(1,2,3) ) # uzywamy sum jako funkcji [1] 6 > sum = sum( c(1,2,3) ) # a teraz zapisujemy na zmienna sum to co nam wyszlo > sum # sum to teraz przede wszystkim zmienna i R wypisze jej wartosc [1] 6 > sum + 1 # do zmiennej juz mozna dodawac [1] 7 > sum( c(12,13) ) # ...ale caly czas mozemy tez uzywac sum jako funkcji! [1] 25
Mimo wszystko nie polecam tego robić. Czasem zdarza się, że chcemy na zmienną zapisać jakąś funkcję. Na przykład mamy kawałek kodu, który wielokrotnie coś robi z liczbami, i do tej pory to coś polegało na dodawaniu ich, ale spodziewamy się, że może któregoś dnia zdecydujemy, że chcielibyśmy wyliczać z nich średnią. Zamiast podmieniać we wszystkich miejscach "sum" na "mean", możemy zrobić na samym początku tak:
Jeśli teraz stwierdzamy, że jednak chcemy liczyć średnią, a nie sumować, podmieniamy tylko na górze skryptu pierwszą linię na:
> co_robimy_z_liczbami = mean
Wszystkie pozostałe linie zostają takie same, i nagle zamiast sumować zaczynają liczyć średnią. Ale jeśli wcześniej nazwiemy jakąś zmienną sum, to nie zadziała :(
R zapisał sobie, że sum to 4. Wciąż można używać sum jako zwykłej funkcji, bo R swoje własne funkcje pamięta trochę gdzie indziej niż nasze własne zmienne i funkcje. Potem zapisał, że co_robimy_z_liczbami ma być równe sum, czyli 4 (najpierw sięgnął do naszych nazw zmiennych, żeby sprawdzić co to jest sum, nie do swoich funkcji). A potem każemy mu użyć co_robimy_z_liczbami jako funkcji, ale R nie rozumie o co nam chodzi, przecież nie ma takiej funkcji, co_robimy_z_liczbami to tylko nudna zmienna z liczbą.
Jeśli nazywamy zmienne polskimi nazwami jest raczej nieduża szansa, że cokolwiek w R-ze się tak nazywa, ale jeśli używamy nazw angielskich i/lub mocno skróconych takie ryzyko się pojawia. Jeśli mamy wątpliwości czy jakaś funkcja / zmienna już istnieje zawsze warto napisać ją i nacisnąć dwukrotnie tabulator - jeśli będzie wśród podpowiedzi to znaczy, że jest zajęta.
Po ósme, czy R zapamięta moje zmienne na zawsze?
Nie. Jeżeli zrobiłeś/aś zmienną liczba_kosiarek o wartości 23, a potem wyszedłeś/aś z R-a, R powinien zapytać Cię, czy chcesz zachować obszar roboczy (workspace): jeśli odpowiesz y (w terminalu) lub save (w RStudio), w katalogu, w którym R pracował stworzone zostaną ukryte pliki, w których zapisane będą wszystkie komendy, które wykonywałaś/eś, oraz wszystkie zmienne, które stworzyłeś/aś. Przy kolejnym uruchomieniu R-a wszystko to się załaduje i dalej będziesz miał(a) swoją zmienną liczba_kosiarek. Ale jeśli odpowiesz "n" lub "don't save", R oczywiście niczego nie zapisze i przy kolejnym uruchomieniu będzie znał tylko te nazwy, które ma zapisane na stałe, tj. swoje własne zmienne i funkcje.
Uwaga. Pliki z historią i danymi są zapisane w katalogu, w którym R akurat pracuje. Jeśli uruchamiasz R-a w terminalu, jest to po prostu katalog, w którym byłaś/eś, zanim wpisałaś/eś polecenie R. Jeśli uruchamiasz skrypt, będzie to katalog, w którym ów skrypt się znajduje. Zawsze możesz sprawdzić, gdzie R akurat pracuje, wpisując polecenie getwd() (jak get working directory). Jeśi nawet zapisałeś/aś obszar roboczy, ale następnym razem uruchomisz R-a z innego katalogu R nie znajdzie w nim żadnej historii i danych i uruchomi się nie załadowując niczego. Oczywiście to nie znaczy, że Twoje dane przepadły, po prostu musisz uruchomić R-a tak, żeby pracował w katalogu, gdzie są Twoje dane. Możesz też załadować dane i historię komend przy pomocy funkcji load() i loadhistory(), podając w nawiasach ścieżkę do plików z danymi.
> a # nie ma takiego obiektu, bedzie blad Error: object 'a' not found > load("/home/maciosz/katalog_w_ktorym_pracowal_R/.RData") > a # teraz juz jest, bo poprzednio go stworzylam, tylko zapisalo sie to w innym katalogu [1] 10
Jeżeli jakaś zmienna Cię denerwuje, bo zdefiniowałaś/eś ją przez pomyłkę, możesz ją usunąć z pamięci R-a poleceniem rm():
> wzorst = 1.53 # ups, literowka, niefajnie > wzorst [1] 1.53 > rm(wzorst) > wzorst Error: object 'wzorst' not found > imie = "florek" > oczy = "niebieskie" > waga = 66 > wzrost = 154 > ls() [1] "imie" "oczy" "waga" "wzrost" > rm(imie, oczy) > ls() [1] "waga" "wzrost" > rm( list = ls() ) > ls() character(0)
rm spodziewa się, że dostanie po prostu wypisane nazwy, jeśli są zapakowane w jakąś listę lub wektor (jak to jest w przypadku nazw zwróconych przez funkcję ls()), trzeba mu powiedzieć, że to lista, stąd owo "list = ls()" kiedy chcemy usunąć wszystko. Zauważmy, że przed character(0) nie ma napisu [1], co ma dużo sensu - character(0) to pusty wektor, a nie pierwszy element jakiegoś wektora.
Po dziewiąte, czy dużo jeszcze rzeczy muszę wiedzieć o zmiennych, żeby programować w R-ze?
Nie, to właściwie wszystko :) Zapraszamy do kolejnego rozdziału!
Często zdarza się, że dane, na których chcemy pracować są zapisane w pliku. Zamiast przeklejać je pracowicie do R-a możemy je wczytać bezpośrednio, np. za pomocą polecenia read.table():
> dane_z_pliku = read.table("plik_z_danymi")
Jeśli mój plik plik_z_danymi wyglądał tak:
0.4 11 19
432 432 101
11 1.1 0.43
28 82 10
To na dane_z_pliku zostało zapisane takie coś:
> dane_z_pliku V1 V2 V3 1 0.4 11.0 19.00 2 432.0 432.0 101.00 3 11.0 1.1 0.43 4 28.0 82.0 10.00
Wygląda trochę jak macierz, prawda? Ale to nie macierz, tylko data frame (ramka danych); o data frame'ach trochę więcej będzie później. Na razie możesz poeksperymentować i zobaczyć, jakie polecenia działają podobnie jak na macierzy, a jakie trochę inaczej (polecam np. sprawdzenie dane_z_pliku[2] i dane_z_pliku[2,3]). V1, V2 i V3 to nazwy, które R domyślnie nadał kolumnom tej tabeli, a 1, 2, 3 i 4 to domyślne nazwy wierszy.
Oczywiście plik plik_z_danymi znajdował się u mnie w tym samym miejscu, w którym R właśnie pracował. Może znajdować się gdzie indziej, ale wtedy trzeba podać całą ścieżkę do pliku:
> dane_z_pliku = read.table("C:/moje_dane/tabela_z_danymi.txt")
Jeśli nie jesteś pewien/pewna, gdzie R pracuje, można to sprawdzić za pomocą polecenia getwd() (jak get working directory):
> getwd() [1] "/home/maciosz/vinci/r-tutorial"
A jeśli chcesz zmienić miejsce pracy R-a, możesz to zrobić za pomocą funkcji setwd() (set working directory):
Przy wpisywaniu ścieżki możesz nacisnąć tabulator, żeby R podowiedział Ci, jakie katalogi / pliki możesz wpisać.
I jeszcze jedna pożyteczna funkcja, wylistowująca pliki w danym katalogu:
> list.files()
Funkcja read.table() ma trochę pożytecznych argumentów. Argumenty to to, co podajemy funkcji w nawiasach, wpływając na to, jak działa; czasem argument jest niezbędny (np. read.table() nie miałaby sensu bez podania nazwy pliku), czasem opcjonalny - domyślnie funkcja robi cośtam, a za pomocą opcjonalnego argumentu może robić coś trochę innego. Argumenty możesz przejrzeć wyświetlając helpa tej funkcji:
> ?read.table
lub korzystając z funkcji args(), wyświetlającej argumenty funkcji:
> args(read.table) function (file, header = FALSE, sep = "", quote = "\"'", dec = ".", numerals = c("allow.loss", "warn.loss", "no.loss"), row.names, col.names, as.is = !stringsAsFactors, na.strings = "NA", colClasses = NA, nrows = -1, skip = 0, check.names = TRUE, fill = !blank.lines.skip, strip.white = FALSE, blank.lines.skip = TRUE, comment.char = "#", allowEscapes = FALSE, flush = FALSE, stringsAsFactors = default.stringsAsFactors(), fileEncoding = "", encoding = "unknown", text, skipNul = FALSE)
Argumenty, z których sama najczęściej korzystam, to header, sep, row.names i col.names. Po kolei: header może być tylko wartością logiczną (TRUE / FALSE), i mówi R-owi o tym, czy pierwsza linia pliku to nagłówek. Domyślnie (jak widać) header jest ustawione na FALSE, czyli R uważa, że każdy wiersz to dane, a nazwy ma sobie sam wymyślić. Na przykład jeśli mój plik z danymi wyglądałby tak:
brutto netto
1234 1230
94.12 94
0.08 0.08
12 11.14
to wywołanie read.table() podając header = TRUE sprawi, że R zamiast domyślnych nazw V1 i V2 nazwie kolumny "brutto" i "netto":
> ( dane = read.table("plik_z_danymi", header=TRUE) ) brutto netto 1 1234.00 1230.00 2 94.12 94.00 3 0.08 0.08 4 12.00 11.14
Do kolumn, które mają nazwy możemy się odwoływać nie tylko indeksami, ale również ich nazwami:
> dane[,1] [1] 1234.00 94.12 0.08 12.00 > dane[,"brutto"] [1] 1234.00 94.12 0.08 12.00 > dane$brutto # nowy zapis! wiecej o tym w rozdziale o listach [1] 1234.00 94.12 0.08 12.00 > dane$"brutto" # moze byc z cudzyslowami, ale nie musi [1] 1234.00 94.12 0.08 12.00 > dane[,brutto] # a przy takim zapisie powinno byc w cudzyslowie Error in `[.data.frame`(dane, , brutto) : object 'brutto' not found
Kolejny argument, o którym wspomniałam, to "sep", czyli "separator", czyli coś, co oddziela kolejne kolumny. W moim pliku jest to tabulator; R domyślnie uważa, że separatorem będzie właśnie tabulator albo jakiś inny ciąg białych znaków. Jeśli mój plik wyglądałby tak:
1,2,3
4,5,6
dane wczytałyby się jako jedna kolumna z dwoma napisami: "1,2,3" i "4,5,6" zamiast z sześcioma liczbami:
> dane = read.table("plik_z_przecinkiem") > dane V1 1 1,2,3 2 4,5,6
Możemy to zmienić korzystając z argumentu sep i wprost mówiąc R-owi, że w naszym pliku kolumny oddzielone są przecinkiem:
> dane = read.table("plik_z_przecinkiem", sep=",") > dane V1 V2 V3 1 1 2 3 2 4 5 6
Kolejne argumenty, o których wspomniałam, to col.names i row.names, czyli argumenty pozwalające nadać kolumnom i wierszom nazwy. Mogą działać na przykład tak:
> oceny = read.table("plik_z_przecinkiem", sep=",", col.names = c("staszek","frania","genowefa"), row.names = c("historia", "matematyka")) > oceny staszek frania genowefa historia 1 2 3 matematyka 4 5 6
Można też zamiast wektora nazw podać liczbę, np. row.names = 1; wówczas R będzie wiedział, że to, co jest w pierwszej kolumnie to nazwy wierszy. Zauważmy, że podanie argumentu "col.names = 1" to to samo co podanie "header = TRUE".
Przykładowo, gdyby plik wyglądał tak:
biedronka;carrefour;sklepik_szkolny;produkt
1.10;2.05;;drozdzowka
2.20;2.50;3;woda gazowana
3.30;2.90;1.50;banany
moglibyśmy go wczytać tak:
> ( cennik = read.table("cennik", header = TRUE, row.names=4, sep = ";") ) biedronka carrefour sklepik_szkolny drozdzowka 1.1 2.05 NA woda gazowana 2.2 2.50 3.0 banany 3.3 2.90 1.5
Zauważmy, że nie ma w pliku danych o tym, po ile w sklepiku szkolnym są drożdżówki. R wpisał więc NA, czyli not available.
W tym rozdziale zapoznamy się z tworem zwanym instrukcją warunkową. Dzięki niemu możemy kazać programowi coś zrobić tylko pod pewnym warunkiem. Instrukcję warunkową tworzy się za pomocą słowa "if", czyli po prostu "jeśli".
Wygląda to następująco:
(Załóżmy, że gdzieś wcześniej w programie zdefiniowaliśmy już zmienne prawdopodobienstwo_wypadku_samochodem i prawdopodobienstwo_wypadku_samolotem. Pamiętaj, że żeby przetestować, czy ten if zadziała, te zmienne muszą istnieć.)
Całą powyższą instrukcję możemy przeczytać następująco: jeśli zmienna prawdopodobienstwo_wypadku_samochodem jest mniejsza od zmiennej prawdopodobienstwo_wypadku_samolotem, napisz "Jedzmy samochodem, tak bedzie bezpieczniej". R widząc instrukcję if oblicza wyrażenie w nawiasach - w tym wypadku, porównuje wartości dwóch zmiennych - i jeżeli wyszło mu TRUE, wykonuje wszystkie polecenia wewnątrz nawiasów klamrowych; jeśli wyszło mu FALSE, idzie dalej ignorując wszystkie polecenia, aż nie dojdzie do nawiasu klamrowego zamykającego tego if-a.
Zauważ, że kiedy napisaliśmy if (...) { i nacisnęliśmy enter, kolejna linia zaczęła się od plusa - R wie, że to jeszcze nie koniec, że napisana przez nas linia sama nie sensu, że jest wstępem do całego bloku instrukcji, i będzie uparcie zaczynał kolejne linie od plusa tak długo, jak długo nie skończymy bloku instrukcji poprzez zamknięcie nawiasu:instrukcją warunkową
> if (TRUE) { + + + + + + + # alez uparty z tymi plusami + + + } NULL
W tym przypadku warunek podany if-owi był na pewno spełniony - w końcu TRUE zawsze jest warte TRUE, ale wewnątrz ifa nie było żadnego polecenia, tylko jeden komentarz, więc wynikiem działania tego ifa okazał się NULL - nic.
W pierwszym przykładzie polecenie było tylko jedno, i tak naprawdę moglibyśmy sobie darować te nawiasy klamrowe, tak też by zadziałało:
ale kiedy poleceń jest więcej, R sam się przecież nie domyśli, które należą jeszcze do ifa, a które są po ifie i ma wykonać niezależnie od podanego ifowi warunku; musimy mu o tym powiedzieć otaczając nawiasami klamrowymi polecenia, które mają należeć do ifa. Spójrzmy na poniższy przykład, gdzie ifowi podajemy kolejny idiotyczny warunek, tym razem zawsze fałszywy:
Może rzuciło Ci się w oczy, że stawiam spacje przed poleceniami należącymi do ifa. Nie jest to konieczne, ale ułatwia to zdecydowanie czytanie programu; zwłaszcza, kiedy więcej ifów i innych konstrukcji zagnieździ się w sobie. Dzięki robieniu wcięć wiadomo, że wszystkie polecenia, które znajdują się w tym samym pionie należą do tego samego bloku. Wiele edytorów tekstowych (np. RStudio) automatycznie robią wcięcia po linijkach z ifem czy innymi instrukcjami, po których wcięcia by się przydały.
Oprócz "if" pożytecznym słowem kluczem jest "else", czyli "w przeciwnym wypadku":
R sprawdzi wartość wyrażenia w nawiasie, tj. sprawdzi, czy ryzyko_powodzi jest większe niż ryzyko_wybuchu_wulkanu; jeśli wyjdzie mu TRUE, wykona to, co jest w ifie, czyli wydrukuje "zamieszkajmy w islandii", a w przeciwnym wypadku (jeśli wyjdzie mu FALSE): wykona to, co jest w bloku komend po "else", czyli wydrukuje "zamieszkajmy nad morzem".
Ważna uwaga. Dopóki R wie, że nie skończyliśmy pisać instrukcji, możemy wciskać sobie enter ile razy nam się żywnie podoba; ale jeżeli zamkniemy nawias klamrowy po ifie i naciśniemy enter, R pomyśli, że skończyliśmy już, sprawdzi warunek w ifie i - zależnie od tego czy wyszedł mu prawdziwy czy fałszywy - wykona polecenia w nawiasach klamrowych lub nie. R nie ma pojęcia, czy chemy potem dopisać jakiegoś else'a. Dlatego jeśli piszemy else'a, musimy to zrobić w tej samej linii, w której znajduje się zamykający nawias klamrowy. Za tym jednym, dosyć zresztą logicznym, wyjątkiem entery w ifie możemy pacać gdzie nam się podoba:
No i tu pora przyznać, że właściwie pisząc "entery możemy pacać gdzie nam się podoba" mam w szczególności na myśli, że nie musimy ich pacać wcale. I to nie tylko w ifie, w ogóle wszędzie. Oczywiście R musi wiedzieć, że skończyliśmy jedno polecenie i zaczynamy następne, ale zamiast oddzielać enterem możemy je oddzielić średnikiem. Zdecydowanie tego nie polecam, bo wygląda zupełnie nieczytelnie, zresztą podobnie jak wciskanie enterów rozdzielających polecenia, jak wyżej. Zresztą, oceńcie sami:
No nie najlepiej to wygląda...
Ciekawym poleceniem jest ifelse(), który działa następująco: jako warunek dostaje wektor wartości TRUE / FALSE, a jako wynik zwraca wektor tej samej długości, z elementami wyliczonymi za pomocą jednego z dwóch podanych poleceń, pierwszego - gdy na danym miejscu było TRUE, lub drugiego w przeciwnym wypadku. Na przykład:
Trochę bardziej wyrafinowany przykład:
Na dwóch pierwszych miejscach mamy elementy wektora nazwanego wektor powiększone o dwa, na ostatnim - ostatni element zmniejszony o jeden. I jeszcze bardziej wyrafinowany przykład, bo tym razem warunku nie dostajemy na tacy, a obliczamy:
> ( wektor = 1:9 ) [1] 1 2 3 4 5 6 7 8 9 > ifelse( wektor %% 2 == 1, 0, wektor / 2 ) [1] 0 1 0 2 0 3 0 4 0
Warunkiem tu było wyrażenie "wektor %% 2 == 1", które zwróciło wektor wartości FALSE lub TRUE (zależnie od tego, czy liczba była parzysta, czy nie; przypominam, że %% oznacza resztę z dzielenia, a więc element wektora daje resztę z dzielenia przez 2 równą 1 gdy jest nieparzysty). Następnie zwraca wektor tej samej długości, który dla pozycji, gdzie były liczby nieparzyste ma zero, a dla pozostałych: tą liczbę podzieloną przez dwa.
Wyposażony w tę wiedzę możesz już spróbować zmierzyć się z zadaniem Operacje na plikach lub Instrukcje warunkowe.
Poznaliśmy już wektory i macierze i podstawowe operacje na nich, pora podnieść nieco poprzeczkę i nauczyć się bardziej zaawansowanych, a przy tym znacznie ciekawszych funkcji.
Kilka pożytecznych funkcji, które najlepiej poznać po prostu sprawdzając, jak działają:
> ceny = c(2.80, 4.60, 2.70, 8.40) > length(ceny) # dlugosc [1] 4 > sum(ceny) # suma [1] 18.5 > mean(ceny) # srednia [1] 4.625 > max(ceny) # chyba nie trzeba tlumaczyc... [1] 8.4 > min(ceny) [1] 2.7 > names(ceny) # sprawdzamy czy elementy w naszym wektorze maja nazwy NULL > # nie maja? no to je dodamy > names(ceny) = c("chleb", "czekolada", "sok", "maslo orzechowe") > ceny chleb czekolada sok maslo orzechowe 2.8 4.6 2.7 8.4 > > ceny["czekolada"] # teraz mozemy dostac sie do elementu po jego nazwie, nie tylko numerze! czekolada 4.6 > 2.8 %in% ceny # sprawdzamy czy wartosc 2.8 jest w wektorze [1] TRUE > "czekolada" %in% names(ceny) # a teraz czy wartosc "czekolada" jest w wektorze nazw wektora ceny [1] TRUE > "makaron" %in% names(ceny) [1] FALSE > ceny[ ceny > 3 ] # wyswietlimy tylko te elementy wektora, ktore sa wieksze od 3 czekolada maslo orzechowe 4.6 8.4
Ostatnie polecenie może na pierwszy rzut oka wyglądać mało intuicyjnie. Wpiszmy najpierw samo ceny > 3 i zobaczmy, co wyjdzie:
> ceny > 3 chleb czekolada sok maslo orzechowe FALSE TRUE FALSE TRUE
No tak, wektor TRUE / FALSE. Z tego wynika, że jeśli wektor wartości logicznych wpiszemy jako indeksy wektora, dostaniemy wektor z elementami odpowiadającymi wartościom TRUE. Na przykład:
> ( wektor = 1:5 ) [1] 1 2 3 4 5 > wektor[ c(TRUE, TRUE, FALSE, FALSE, TRUE) ] [1] 1 2 5
Podobne rzeczy można robić oczywiście z macierzami:
> ( macierz = matrix(1:9, ncol = 3) ) [,1] [,2] [,3] [1,] 1 4 7 [2,] 2 5 8 [3,] 3 6 9 > dim(macierz) # dimensions, czyli wymiary [1] 3 3 > colnames(macierz) = c("a", "b", "c") > rownames(macierz) = c("I", "II", "III") > macierz a b c I 1 4 7 II 2 5 8 III 3 6 9 > macierz["I",] a b c 1 4 7 > macierz["II","c"] [1] 8 > macierz[-1,"c"] # bez pierwszego wiersza, tylko kolumna "c" II III 8 9 > macierz[ macierz %% 2 == 1 ] # wszystkie wartosci, dla ktorych spelnione jest, ze reszta z dzielenia to 2 [1] 1 3 5 7 9 > sum(macierz) [1] 45
Jeśli nie tylko przeczytałeś/aś już wszystkie powyższe polecenia, ale też przepisałeś/aś je u siebie do R-a, przetestowałeś/aś i sądzisz, że rozumiesz już ich działanie, to możemy zanurkować jeszcze głębiej, poznawać kolejne ciekawe funkcje:
> which( c(TRUE, FALSE, TRUE, FALSE, FALSE) ) # na ktorych indeksach jest TRUE [1] 1 3 > which( c(1,2,3) ) # to nie zadziala, musza byc true / false Error in which(c(1, 2, 3)) : argument to 'which' methods/html/is.html">is not logical > which( 1:10 %% 3 == 0 ) # no to zrobmy true / false - sprawdzmy, gdzie sa w wektorze 1:10 liczby podzielne przez 3 [1] 3 6 9 > length( which( 1:100 %% 11 == 0 ) ) # ile jest liczb podzielnych przez 11 od 1 do 100? [1] 9 > wektor = c(4,3,0,5,11,2) > sort(wektor) # zwroc wektor posortowany (oczywiscie zmienna wektor pozostaje nietknieta) [1] 0 2 3 4 5 11 > order(wektor) # zwroc kolejnosc wektora, tj. na jakim miejscu bylyby poszczegolne elementy, gdyby byl posortowany [1] 3 6 2 1 4 5 > wektor[ c(2,4,6,1,3,5) ] # podajemy wektor indeksow - tu podalismy wszystkie, ale w dziwnej kolejnosci, wiec namieszalismy kolejnosc w wektorze [1] 3 5 2 4 0 11 > wektor[ order(wektor) ] # troche na okolo sposob na sortowanie wektora... [1] 0 2 3 4 5 11 > inny_wektor = c("a","b","c","d","e","f") > inny_wektor[ order(wektor) ] # sortujemy inny_wektor wg kolejnosci w wektorze wektor [1] "c" "f" "b" "a" "d" "e" > inny_wektor[ order(wektor) ][ wektor != 4 ] # i wywalamy wartosci z tych pozycji, na ktorych w wektorze wektor mamy liczbe 4 [1] "f" "b" "a" "d" "e"
Jeżeli pojęłaś/pojąłeś dokładnie wszystko, co dzieje się w powyższym fragmencie kodu, jesteś gotowa/y poznać jedną z najpożyteczniejszych funkcji R-a: apply. Jest to funkcja działająca na macierzach, która wykonuje jakąś żądaną przez nas operację na wszystkich wierszach / kolumnach danej macierzy. Przykład użycia:
Pierwszym argumentem jest macierz, na której chcemy coś policzyć. Drugi argument wynosi 1, jeśli chcemy zrobić coś na wierszach, albo 2 jeśli na kolumnach (można zapamiętać w ten sposób, że podając indeksy wierszy i kolumn zawsze pierwsze są wiersze, aczkolwiek można też nie pamiętać i skorzystać z helpa wpisując ?apply). Trzeci mówi, jaką funkcję chcemy zastosować. Wynikiem jest wektor: w pierwszym przypadku, dostaliśmy wektor dwuelementowy, bo operację wykonywaliśmy na wierszach, których było dwa: R zsumował pierwszy wiersz, wyszło mu 9, zsumował drugi, wyszło mu 12; zwrócił więc wektor c(9, 12). W drugim przypadku działaliśmy na kolumnach i dostaliśmy wektor sum kolejnych kolumn.
Oczywiście jest to ledwie garstka funkcji, które są dostępne w R-ze. Ciekawskim polecam poczytanie o innych funkcjach i przetestowanie ich działania, np. sample, rank, rev, diag czy outer, oraz sięgnięcie do rozmaitych zewnętrznych źródeł.
Poznałeś/aś już wektory i macierze, pora na odrobinę bardziej zaawansowane struktury danych. W tym rozdziale poznasz listy, ramki danych i typ czynnikowy.
Listy są trochę podobne do wektorów, ale ich elementy nie muszą mieć takiego samego typu. Nic nie stoi na przeszkodzie, żeby stworzyć listę zawierającą liczby, napisy i wartości logiczne jednocześnie. Tworzy się je za pomocą funkcji list(). Przykładowo:
> ( dane = list(imie = "walter", nazwisko = "white", wzrost = 171, czy_zonaty = TRUE ) ) $imie [1] "walter" $nazwisko [1] "white" $wzrost [1] 171 $czy_zonaty [1] TRUE > dane$imie # odwolujemy sie do nazwanych elementow listy za pomoca znaku dolara [1] "walter" > dane[["imie"]] # albo za pomoca podwojnych nawiasow kwadratowych, ale wtedy w cudzyslowie [1] "walter" > dane["imie"] # to tez zadziala, ale zwroci jednoelementowa liste $imie [1] "walter" > dane[[imie]] # to nie ma prawa zadzialac Error: object 'imie' not found > dane[1] # mozna tez odwolywac sie do indeksow $imie [1] "walter" > dane[2] $nazwisko [1] "white"
Przypominam, że zapis ze znakiem dolara pojawił się już przy wczytywaniu plików, gdzie okazało się, że plik wczytał nam się jako data.frame.
Ramki danych, czyli data frame'y (będę raczej posługiwać się nazwą angielską) są dla macierzy tym, czym listy dla wektorów - tzn. różnią się nieznacznie niektórymi funkcjami i zastosowaniami, a najbardziej rzucającą się w oczy różnicą jest to, że w przeciwieństwie do macierzy data frame potrafi trzymać wartości różnych typów:
> data.frame( jakies_liczby = c(12, 15, 128), wartosci_logiczne = c(TRUE, FALSE, TRUE), napisy = c("ala ma kota", "kot ma ale", "ola nie ma parasola") ) jakies_liczby wartosci_logiczne napisy 1 12 TRUE ala ma kota 2 15 FALSE kot ma ale 3 128 TRUE ola nie ma parasola
...ale uwaga, typy powinny się zgadzać w obrębie kolumn. Możesz spróbować do kolumny jakies_liczby dodać napisy, i nawet będzie wyglądało, że zadziałało, dopóki nie będziesz próbować na liczbach w tej kolumnie wykonać jakiegoś działania - wtedy zorientujesz się, że R tak naprawdę zdradziecko podmienił Ci Twoje wszystkie liczby na napisy.
Co ciekawego można robić na data frame'ach?
> data_frejm = data.frame(wzrost = c(123, 145, 167), waga = c(54, 43, 100) ) > rownames(data_frejm) = c("florek", "florcia", "matylda") # nadajemy nazwy wierszom > data_frejm wzrost waga florek 123 54 florcia 145 43 matylda 167 100 > data_frejm$wzrost # mozemy odwolac sie do kolumny za pomoca dolara [1] 123 145 167 > data_frejm$florek # do wiersza nie mozemy; bedzie NULL, bo nie ma kolumny florek NULL > data_frejm["florek", "waga"] [1] 54 > data_frejm[1] # to bedzie ciagle data frame wzrost florek 123 florcia 145 matylda 167 > > data_frejm[[1]] # a to jest wektor z pierwszej kolumny [1] 123 145 167 > data_frejm + 10 # dodawac mozna, czemu nie wzrost waga florek 133 64 florcia 155 53 matylda 177 110 > rbind(data_frejm, data_frejm) # zlaczyc dwa data frame'y w jeden; jesli nazwy wierszy sie powtarzaja, dodaje liczbe 1 wzrost waga florek 123 54 florcia 145 43 matylda 167 100 florek1 123 54 florcia1 145 43 matylda1 167 100 > cbind(data_frejm, data_frejm) # laczymy kolumnami wzrost waga wzrost waga florek 123 54 123 54 florcia 145 43 145 43 matylda 167 100 167 100 > data_frejm["zyta",] = c(198, 93) # dodajemy wartosci > data_frejm["franek","wzrost"] = 128 # tam, gdzie wartosci brakuje bedzie NA, not available > data_frejm wzrost waga florek 123 54 florcia 145 43 matylda 167 100 zyta 198 93 franek 128 NA
Zauważ, że nazwy kolumn mogą się powtarzać. Skąd R będzie wiedział, o którą z kolumn "wzrost" nam chodzi? Nie będzie wiedział :( Da nam po prostu pierwszą od lewej. Więc lepiej uważać z powtarzaniem nazw.
Specyficzną strukturą danych jest tzw. typ czynnikowy, po angielsku prostu factor. Wygląda on tak:
...czyli wygląda trochę jak zwyczajny wektor, tylko że linijkę niżej pada słowo "Levels" i w kolejności alfabetycznej wypisane są (bez powtórzeń) wszystkie wartości, które wystąpiły w naszym wektorze. W tym tutorialu factorom nie poświęcę zbyt wiele uwagi, ale warto je znać, chociażby żeby nie przestraszyć się, kiedy nagle okaże się, że kolumna w naszym data frame'ie ma jakieś Levele; a zdarzy się to, jeśli są w niej napisy:
> dane = data.frame( row.names = c("Danaerys", "Robb", "Joffrey"), nazwisko_rodowe = c("Targeryan", "Stark", "Baratheon"), wzrost = c(164, 169, 159)) > dane nazwisko_rodowe wzrost Danaerys Targeryan 164 Robb Stark 169 Joffrey Baratheon 159 > dane$wzrost # to bedzie zwykly wektor [1] 164 169 159 > dane$nazwisko_rodowe # a to juz bedzie factor! [1] Targeryan Stark Baratheon Levels: Baratheon Stark Targeryan
Mówi się, że R powstał po to, żeby nie używać pętli for. Oczywiście nie była to jedyna motywacja twórców R-a, ale faktycznie, większość rzeczy w R-ze można załatwić bez tego, korzystając np. z funkcji apply. Mimo wszystko czasem niektórym wygodniej skorzystać z fora, a przy tym jest to klasyczna konstrukcja występująca w chyba wszystkich sensownych językach programowania, więc trochę głupio byłoby jej nie wprowadzić.
Idea jest prosta - załóżmy, że chcemy zrobić tę samą rzecz (albo bardzo podobną) wielokrotnie. Na przykład napisać "spam" 20 razy. Możemy oczywiście 20 razy napisać w skrypcie:
print("spam")
ale pamiętajmy jedno z podstawowych przykazań programowania: nie powielajmy kodu. Po pierwsze, przeważnie to strata miejsca. Jak zaraz się przekonacie, można to napisać w dwóch linijkach zamiast w dwudziestu. Strata miejsca to oczywiście nic strasznego, nie mamy reglamentowanych kartek w edytorze tekstu, ale strata miejsca to i strata przejrzystości, a przejrzystość jest bardzo ważna w kodzie, jeśli chcemy rozumieć co się w nim dzieje nie tylko w momencie pisania, ale i parę tygodni / dni / godzin / minut później. Po drugie, jeżeli okaże się, że się pomyliliśmy przy kopiowaniu, albo po jakimś czasie się odmyśliliśmy i jednak chcemy pisać "spam!" zamiast "spam", albo okazało się, że program jednak nie robi tego, co chcemy - jeżeli powielamy kod trzeba potem poprawiać błąd w każdym miejscu, gdzie on występuje, zamiast w jednym. Pół biedy, jeśli jest to po prostu dwadzieścia linijek pod rząd tego samego, można to przeedytować w każdym edytorze co najmniej tak sprawnym jak notatnik zaznaczając dany fragment i używając jakiegoś polecenia w rodzaju "znajdź i zamień". Ale co jeśli beztroskie kopiowanie kodu tak nam wejdzie w krew, że zaczniemy kopiować fragmenty kodu w różne miejsca skryptu, czasem nieznacznie modyfikując, aż wytropienie wszystkich miejsc gdzie chcemy rzeczywiście poprawić "spam" na "spam!" stanie się niemal niemożliwe? Brr. Nie, nie kopiuje się kodu. W rozdziale o funkcjach nauczysz się jeszcze więcej, jak tego unikać, a tymczasem istotna myśl do zapamiętania, albo wygrawerowania i powieszenia nad łóżkiem: "nie powiela się kodu".
Napisanie "spam" dwadzieścia razy przy użyciu pętli for wyglądałoby tak:
Z rozdziału o wektorach wiesz już, że seq(20) to wektor liczb całkowitych od 1 do 20. Można by też niapisać 1:20 zamiast seq(20). R tworzy sobie ten wektor i zabiera się do roboty. Najpierw na zmienną licznik przypisuje pierwszą wartość z danego wektora, w tym wypadku 1. Potem robi wszystko, co jest wewnątrz pętli for, czyli wewnątrz klamrowych nawiasów. W tym wypadku po prostu drukuje napis "spam". Kiedy zrobi już wszystkie polecenia z pętli, czyli skończy się pierwszy obieg pętli, wraca na początek: tym razem pod zmienną licznik podstawia drugą wartość z wektora seq(20). I znowu wykonuje wszystkie polecenia w pętli. Po drugim obiegu pętli wraca na początek, pod zmienną licznik podstawia kolejną wartość z wektora, i tak dalej, i tak dalej. Kiedy wreszcie skończą mu się wartości w wektorze, kończy.
Wynikiem działania pętli jest więc wypisanie na ekranie 20 razy "spam". Ubocznym efektem jest stworzenie zmiennej licznik, która po zakończeniu wszystkich obiegów pętli ma teraz wartość 20. (Możesz wpisać "licznik" i nacisnąć enter, żeby R pokazał Ci wartość tej zmiennej, i sprawdzić czy nie oszukuję.)
Ta konstrukcja: jakiśtam napis, potem nawias {, potem seria instrukcji, a potem }, powinna coś przypominać. Konkretnie, powinna przypominać instrukcję warunkową if. Rzeczywiście, działa to podobnie jak przy ifie, analogicznie więc z instrukcją warunkową możemy napisać tę pętlę np. tak:
Albo tak:
Albo, aż głupio to pisać ale dla porządku napiszę, tak:
Ale *bardzo* odradzam pisania takich instrukcji w jednej linijce. Czytelność przede wszystkim!
W powyższym przykładzie zmienna licznik przyjmowała kolejne wartości od 1 do 20, ale w ogóle z niej nie korzystaliśmy. Była nam potrzebna tylko po to, żeby pętla wykonała się dokładnie 20 razy; licznik - jak sama nazwa wskazuje - tylko liczył, w którym obiegu pętli jesteśmy. Napiszmy teraz pętlę, w której zmienna "podstawa_potegowania" będzie przyjmować kolejne wartości z pewnego wektora, a potem będzie podnoszona do 3 potęgi.
Dla każdej wartości w wektorze po słowie "in" (czyli u nas c(1,3,5,10)) R wykona wszystkie polecenia w klamrowych nawiasach, przyjmując ową wartość jako wartość zmiennej przed słowem "in" (tutaj podstawa_potegowania). Stąd owa składnia "for [coś] in [zbior cosiów]".
Co ciekawe, nie zadziała w ten sposób:
Jest to podobna sytuacja do tej, na jaką zwracałam uwagę w rozdziale o skryptach. Kiedy piszemy R-owi polecenie, R drukuje nam jego wynik. W tym wypadku jednak z punktu widzenia R-a polecenie to cała pętla for, która wyniku żadnego nie ma; to, że po drodze dzieją się jakieś obliczenia (konkretnie podnoszenie do potęgi) niespecjalnie R-a obchodzi, chciałby nam tylko wydrukować wynik całego polecenia. A przecież zdaniem R-a polecenie skończyło się dopiero na zamykającym nawiasie klamrowym, o czym najlepiej świadczą plusy na początkach linii; linia z potęgowaniem nie była jeszcze gotowym poleceniem.
W R-ze mamy jeszcze do dyspozycji pętlę while. "while" oznacza "dopóki" i idea tej pętli jest prosta: R ma wykonywać jakiś ciąg instrukcji, dopóki spełniony będzie jakiś podany przez nas warunek. Przykładowo:
Co się po kolei tu dzieje: najpierw na zmienną licznik przypisujemy 1; będziemy za jej pomocą liczyć, ile razy pętla się wykonała. Zaczynamy pętlę: mówimy R-owi, że ma tak długo wykonywać w kółko instrukcje z pętli, jak długo spełniony jest warunek w nawiasach: czyli jak długo zmienna licznik jest mniejsza od lub równa 20. R więc zanim jeszcze zacznie wykonywać cokolwiek z pętli sprawdza, czy licznik jest mniejszy od 20. Jest, więc wchodzi w pętlę, drukuje "spam", a potem zwiększa zmienną licznik o jeden. Doszedł do końca instrukcji z pętli (wie o tym dzięki nawiasowi klamrowemu), więc sprawdza znowu warunek, żeby wiedzieć, czy dalej ma wykonywać pętlę: licznik dalej jest mniejszy niż 20, więc znowu drukuje "spam", znowu powiększa licznik o jeden etc. Wreszcie w dwudziestym obiegu pętli po raz dwudziesty wydrukuje "spam", po raz dwudziesty zwiększy licznik o 1 (teraz będzie równy 21) i po raz dwudziesty sprawdzi, czy ma zaczynać kolejny obieg pętli. Ale warunek już nie jest spełniony: 21 > 20, więc R przestanie pętlić.
Można zrobić głupią pętlę while, która nie wykona się nigdy, bo podany w nawiasie warunek będzie zawsze fałszywy, np.:
Albo:
Można też (aczkolwiek nie polecam) zrobić głupią pętlę while, która będzie się wykonywać w nieskończoność, bo warunek w nawiasie będzie zawsze prawdziwy:
Jeżeli zrobiłeś coś takiego i właśnie R wyświetla Ci nieskończoną liczbę napisów można to zatrzymać naciskając control + c.
W nawiasach pętli while może być absolutnie cokolwiek, co po wyliczeniu przez R-a da albo TRUE, albo FALSE: może to być prosty warunek na porównywanie liczb (jak licznik <= 20, albo trochę bezsensowne 1 < 0), ale może być jakaś skomplikowana funkcja, wyliczająca TRUE albo FALSE.
Pętle for, while oraz instrukcję warunkową if można oczywiście zagnieżdżać w sobie. Trzeba uważać wtedy z zamykaniem nawiasów klamrowych. Żeby się nie pogubić bardzo pomaga robienie wcięć na początku linii: wszystkie instrukcje z danego bloku powinny być tak samo wcięte, jeśli jakaś instrukcja zaczyna kolejny blok, będzie on wcięty o kolejną porcję spacji / tabulatorów. Przykład takich pozagnieżdżanych instrukcji poniżej:
for (liczba in 1:5) { + print( paste("liczba:", liczba) ) + licznik = 1 + while (licznik < liczba) { + print( paste( " Jestem w while'u, bo licznik wynosi", licznik, "czyli mniej od liczby, wynoszacej ", liczba) ) + licznik = licznik + 1 + print("sprawdze czy licznik jest podzielny przez dwa") + if (licznik %% 2 == 1) { + print(" licznik jest niepodzielny przez dwa") + } else { + print(" jest podzielny!") + } + } + print("wyszedlem z while'a, koncze ten obieg petli") + }
Funkcja paste skleja napisy w jeden, co jest pomocne, gdy chcemy do napisu dokleić wartość jakiejś zmiennej, jak tutaj. Ten kawałek kodu wiele mądrego nie robi, ale robiąc to przy okazji informuje, gdzie akurat w kodzie się znajduje i dlaczego. Kiedy piszę bardziej skomplikowane kawałki kodu i coś mi w nim przestaje działać, dopisywanie takich printów w różnych miejscach bardzo mi pomaga: dzięki nim wiem, ile wynosiły zmienne w różnych fragmentach skryptu, czy na pewno tyle, ile się spodziewałam, w którym konkretnie miejscu program się pomylił etc. Albo kiedy dostaję od kogoś kod napisany niezbyt czytelnie, a zależy mi na jego zrozumieniu, dopisuję takie charakterystyczne printy i uruchamiam go, dzięki temu program sam mnie informuje co robi. Polecam taki sposób analizy.
Funkcja for przerywa działanie, kiedy skończą jej się elementy w wektorze, funkcja while, kiedy podany jej warunek przestaje być spełniony; czy są inne sposoby przerwania funkcji? Są. Można przerwać funkcję poleceniem break:
Podobnie można przerwać pętlę while. Wydaje się dosyć intuicyjne, że raczej zawsze z instrukcji "break" będziemy korzystać wewnątrz jakiegoś ifa - jaki sens ma wprowadzać break bez instrukcji warunkowej, czyli takiego, który nie potrzebuje żadnego warunku, żeby się wykonać? W ten sposób pętla wykona się zawsze tylko raz, to po co w ogóle robić z niej pętlę.
Jest jeszcze jedna pętla w R-ze, chyba najrzadziej używana. Jest to pętla repeat, która działa jak while(TRUE), czyli wykonuje się w nieskończoność, i jedyny sposób, w jaki można ją przerwać, to za pomocą instrukcji break:
Zauważ, że w funkcji paste dopisałam argument sep, czyli separator: jest to napis, który funkcja paste wkleja pomiędzy wszystkie napisy, które daliśmy jej do posklejania. Domyślnie jest to spacja, ale głupio by wyglądała spacja między numerem obiegu a kropką, więc kazałam jej wstawiać pusty napis, czyli po prostu nic.
Teraz, kiedy wiesz wszystko, co warto wiedzieć o pętlach, spróbuj zmierzyć się z zadaniem Szachownica.
PS. Być może ciekawi Cię, jak napisać "spam" 20 razy nie korzystając z pętli for. O tak:
cat() to funkcja podobna do print, ale interpretuje znaki specjalne, czyli znaki, które znaczą trochę co innego, niż wyglądają: np. znak "\n" jest znakiem specjalnym oznaczającym nową linię. paste(), jak już było mówione służy do sklejania kilku napisów w jeden. Argument collapse informuje ją, co ma powciskać między kolejne elementy wektora rep("spam",20) - wciskamy tam znak specjalny "\n", który oznacza nową linię. Różnicę między collapse a sep widać na poniższych przykładach:
A różnicę między cat a print na poniższych:
W rozdziale o pętlach pisałam, że nie należy kopiować kodu. Być moż pomyślałaś/eś wtedy: "no dobrze, zamiast wklejać ten sam kod 10 razy pod rząd mogę użyć pętli, ale co jeśli chcę zrobić tą samą rzecz w różnych miejscach programu?" Do tego właśnie przydają się funkcje.
Co to jest funkcja? To nazwany jakoś zbiór instrukcji dla R-a (lub innego języka). Kiedy chcieliśmy wielokrotnie używać jakiejś liczby poradziłam, żebysmy ją jakoś nazwali i zrobili z niej zmienną; podobnie można zrobić z kawałkami naszego kodu, z tą różnicą, że fragmentu kodu nie nazywamy już zmienną, a właśnie funkcją.
Funkcję tworzy się następująco:
Najpierw pojawia się nazwa naszej funkcji. Następnie operator przypisania (= lub <-). Dalej pada słowo function i dwa okrągłe nawiasy - w ten sposób informujemy R-a, że to, co zaraz napiszemy i co ma przypisać na napiszInwokacje to funkcja (do okrągłych nawiasów jeszcze wrócimy). Następnie pojawia się otwarty nawias klamrowy, podobnie jak w ifie czy w pętlach - teraz R wie, że wszystko aż do zamykającego nawiasa klamrowego to nasza funkcja. Po otwartym nawiasie następuje jakiś zbiór instrukcji. Od teraz za każdym razem, jak napiszemy w R-ze napiszInwokacje(), czyli wywołamy naszą funkcję, R wykona wszystkie polecenia, które były zawarte w owych nawiasach klamrowych:
> napiszInwokacje() [1] "Litwo ojczyzno moja" [1] "Ty jestes jak zdrowie" [1] "Ile cie trzeba cenic"
Wracając więc do naszego problemu z rozdziału Pętla for, gdzie chcieliśmy wypisać 20 razy na ekranie "spam": możemy teraz zdefiniować sobie funkcję, która to zrobi, i wywoływać ją w różnych miejscach programu bez konieczności kopiowania pętli:
Jeśli po jakimś czasie uznamy, że funkcja zaspamuj() robi nie to, co byśmy chcieli, musimy ją poprawić tylko w jednym miejscu - tam, gdzie została zdefiniowana.
Funkcje nie muszą zawsze robić dokładnie tego samego. Zdarza się, że chcemy zrobić coś podobnego, ale na jakichś innych danych - na przykład w jednym miejscu programu chcemy napisać "spam" 20 razy, a w innym 30. Czy w takim razie możemy np. zrobić dwie funkcje, zaspamuj20razy() i zaspamuj30razy(), skopiować treść jednej do drugiej i podmienić tylko 20 w pętli na 30? Nie! ...to znaczy, oczywście - tak, teoretycznie możemy, ale mimo wszystko nie róbmy tego. Po pierwsze, wraca klasyczny argument - jeśli za dwa dni zdecydujemy, że spamować chcemy słowem "mielonka" a nie "spam" będziemy musieli zmieniać kod w dwóch miejscach zamiast w jednym; kiedy zmiany są bardziej szczegółowe zwiększa to szansę na to, że w którejś z wersji zmienimy przez pomyłkę inaczej. Po drugie, byłoby bez sensu definiować funkcje na każdą możliwą liczbę napisów. Dopóki chcemy mieć tylko dwie wersje da się to ogarnąć, ale co jeśli zapragniemy spamować na 50 różnych sposobów? Tworzyć 50 funkcji, zaspamuj1raz(), zaspamuj2razy(), zaspamuj3razy()...? Na szczęście nie musimy tego robić. Wyjście z sytuacji stanowią argumenty funkcji:
Argumenty to to, co wpisujemy do nawiasów okrągłych po słowie function podczas definiowania funkcji, i po nazwie funkcji podczas jej wywoływania. Jeżeli teraz napiszemy:
> zaspamuj(3)
R zadziała następująco: odnajdzie w pamięci co to jest to zaspamuj i znajdzie w ten sposób naszą funkcję. Zauważy, że definiując funkcję napisaliśmy w nawiasach ile_razy - to znaczy, że na zmienną ile_razy zapisze sobie to, co wpisaliśmy do nawiasów podczas wywoływania funkcji (czyli 3). I dalej prosto: pętla for, przy czym pod nazwę ile_razy podstawi sobie 3, tak jak mu kazaliśmy, i drukowanie napisu w każdym obiegu pętli. W efekcie na ekranie pojawią się trzy napisy "spam".
Ważna uwaga. Napisałam, że R zapisze sobie na zmienną ile_razy wartość 3. Jest to prawda, ale jeśli teraz napiszemy:
> ile_razy Error: object 'ile_razy' not found
...no właśnie, R będzie twierdził, że nie ma takiej zmiennej. To dlatego, że wykonując funkcję R tworzy sobie zupełnie osobne miejsce na różne obliczenia, odgrodzone od reszty. Na tym wyizolowanym kawałku zrobił sobie zmienną ile_razy, przypisał na nią 3, wykonał całą funkcję, po czym wylazł z tego kawałka i już do niego nie wróci. Jeśli wcześniej mielibyśmy zdefiniowaną zmienną ile_razy, zupełnie nijak nie wpłynie ona na przebieg funkcji: R na swoim odgrodzonym poletku i tak stworzy nową zmienną ile_razy, a stara pozostanie nietknięta:
> ile_razy = 5 > zaspamuj(2) [1] "spam" [1] "spam" > ile_razy [1] 5
Ten zapis, z nazwą funkcji, nawiasami i ewentualnie czymś w tych nawiasach powinien wydawać się znajomy. Tak, przecież wiele funkcji już poznałaś/eś. Przykładowo funkcja sqrt(): jako argument bierze jakąś liczbę i zwraca wynik pierwiastkowania jej. Albo funkcja matrix, tworząca macierz: matrix ma kilka argumentów, jako pierwszy podawaliśmy wartości, które mają być w macierzy, potem liczbę wierszy, a potem kolumn. Przypominam, że nie musieliśmy pamiętać kolejności argumentów: wystarczyło powiedzieć R-owi, jak argumenty, które podajemy się nazywają. Przykładowo:
Dopóki nie mówimy, jak nazywają się podawane argumenty R myśli, że podajemy je w takiej kolejności, w jakiej pojawiły się przy definiowaniu funkcji, ale kiedy już je nazywamy kolejność staje się obojętna.
Klasyczna zabawa - spróbujmy rozzłościć R-a:
> podniesDoPotegi() Error in podniesDoPotegi() : argument "podstawa" methods/html/is.html">is missing, with no default > podniesDoPotegi(10) Error in podniesDoPotegi(10) : argument "wykladnik" methods/html/is.html">is missing, with no default > podniesDoPotegi(10,11,12) Error in podniesDoPotegi(10, 11, 12) : unused argument (12) > podniesDoPotegi(3, "napis chyba nie moze byc wykladnikiem...") Error in podstawa^wykladnik : non-numeric argument to binary operator > funkcjaKtorejNaPewnoNieMa("jakis_argument") Error: could not find function "funkcjaKtorejNaPewnoNieMa"
R informuje nas, że nie umie wywołać funkcji podniesDoPotegi jak nie podamy argumentu podstawa, nie umie, jak nie podamy wykladnika, nie umie, jeśli podamy za dużo argumentów. Kiedy podaliśmy głupi argument (napis jako wykładnik) nawet spróbował uruchomić funkcję, nie zorientował się w pierwszej chwili, że coś jest nie tak - koniec końców wyglądało wszystko w porządku, w deklaracji funkcji było że mają być dwa argumenty, i podaliśmy dwa argumenty, nikt nigdzie nie twierdził, że muszą być liczbami. Ale kiedy spróbował rzeczywiście funkcję wykonać okazało się, że ma wykonać działanie podstawa^wykladnik, a pod zmienną wykladnik zapisaliśmy mu napis, co mu się nie spodobało. (Zauważ, że R informuje, w którym miejscu napotkał błąd: w trzech pierwszych przypadkach napisał "Error in podniesDoPotegi...", w czwartym: "Error in podstawa^wykladnik"; dzięki temu jeśli funkcja jest długa od razu widzimy, w której linijce mamy jakiś błąd). No i na koniec kazaliśmy mu wywołać funkcję, której nie ma, trudno było się spodziewać innej reakcji.
Być może zwróciłeś/aś uwagę na napis "with no default" w errorach. R pisze nam, że nie podaliśmy argumentu, a w deklaracji funkcji nie napisaliśmy, ile ten argument domyślnie ma wynosić. Zobaczmy jak to zrobić:
Jeżeli podamy coś w nawiasie, R pod zmienną imie podstawi to, co podaliśmy (oczywiście tylko na odgrodzonym kawałku na którym wywołuje naszą funkcję, nie pojawi się żadna globalna zmienna imie, którą później będziemy mogli użyć, a jeśli już taka była, to nie zostanie nadpisana); jeżeli natomiast nie podamy, R założy, że imie ma wartość "Florek". Przypominam, że funkcja paste() sklejała napisy, a jej argument sep mówił o tym, co ma powklejać między łączone napisy.
Kilkakrotnie używałam w tym tutorialu frazy "funkcja zwróciła [cośtam]". Pora się z niej wytłumaczyć. Spójrzmy na przykład poniższego skryptu:
Obie zdefiniowane przez nas funkcje obliczają sumę dwóch liczb, ale różnią się tym, co robią z wynikiem. Pierwsza go tylko i wyłącznie drukuje (razem z enterem: przypominam że '\n' to znak specjalny oznaczający nową linię), druga go *zwraca* za pomocą słowa "return" (czyli zwróć). Oznacza to, że wynikiem działania funkcji policzSumeIZwroc() jest to, co jest w nawiasach po funkcji return, czyli suma dwóch liczb podanych jako argumenty. Funkcja policzSumeIWydrukuj nie daje żadnego wyniku - tylko drukuje uzyskany wynik. Różnicę w ich działaniu można prześledzić uruchamiając skrypt:
> source("dwieFunkcje.R") 2 [1] "***" NULL [1] 4
Najpierw wykonała się funkcja policzSumeIWydrukuj(1,1), w związku z czym R wydrukował 2. Na zmienną a przypisał to, co zwróciła funkcja policzSumeIWydrukuj, a na b to, co zwróciła policzSumeIZwroc. Potem wydrukował trzy gwiazdki. Potem drukuje wartości a i b: jak się okazuje, pod a nie jest nic zapisane (NULL), zaś pod b - zgodnie z przewidywaniami - suma liczb 2 i 2.
Krótko mówiąc, żeby móc wynik działania funkcji przypisać na zmienną, musimy użyć w niej słowa kluczowego "return". Return może być tylko jedno na funkcję - kiedy R dociera w funkcji do słowa return, zwraca to, co jest w nawiasach i dalej się nawet nie kłopocze, ignoruje wszystko aż do zamykającego nawiasu klamrowego funkcji.
Uwaga dotycząca różnicy między funkcją print a cat. Poprzednio pisałam, że różnica między print i cat polega na tym, że inaczej interpretują znaki specjalne; przykładowo, gdybyśmy napisali print('\n') zamiast cat('\n') na ekranie wyświetliłoby się po prostu '\n'. Jest to prawda, ale nie cała. Print jest ciekawą funkcją, która nie tylko drukuje na ekran, ale jednocześnie zwraca to, co wydrukowała. To wyjaśnia różnice, które być może już zauważyłeś/aś - to, co drukuje się w wyniku funkcji cat, nie jest wcale wektorem i nie zaczyna się od [1], bo nie jest w ogóle żadnym stworzonym obiektem, R po prostu drukuje nam coś na ekran. Natomiast wydruki wywołane funkcją "print" są jednocześnie zwróconymi przez nią obiektami - czyli dla R-a wektorami. To również uzasadnia, dlaczego dla dobra przykładu w funkcji "policzSumeIWydrukuj" zastosowałam funkcję cat zamiast częściej do tej pory stosowanej w tym tutorialu funkcji print (częściej, bo po prostu nazywa się bardziej intuicyjnie). Z funkcją print przykład by mi nie wyszedł.