Vaje
Polona in Ančka nista sami. Danes bomo delali z originalnimi podatki (za prajšnjo domačo nalogo so bili predelani v prijaznejšo obliko), kakršne dobimo na spletni strani Movebank, če poiščemo medvede in kliknemo na študijo, izvedeno v Sloveniji. Za potrebe domače naloge so podatki seveda že priloženi nalogi - so pa enaki, kot bi jih sami dobili na spletni strani.
Podatke in teste najdete na spletni učilnici pod temi vajami v datoteki podatki-in-testi.zip.
Vse funkcije morajo biti napisane tako, kot da bi tabela lahko vsebovala druge podatke (drugačno število vrstic, drugačno število medvedov z drugimi imeni, datumi in lokacijami), smejo (in morajo) pa predpostaviti, da se stolpci imenujejo tako, kot v podani datoteki. Testi bodo tvojemu programu podtikali tudi drugačne podatke.
Kjer ni eksplicitno zahtevan numpy, lahko nalogo rešuješ, kakor želiš.
Branje podatkov
Napiši funkcijo, preberi_podatke()
, ki prebere podatke iz datoteke "Brown bear Slovenia 1993-1999.csv"
, ki se nahaja v istem direktoriju kot program. Za branje podatkov lahko napišeš funkcijo ali ne; lahko uporabljaš np.genfromtxt
(ali, kot se bo izkazalo, raje) primerno Pythonovo funkcijo za branje csv datotek.
Funkcija mora vrniti terko četverko s štirimi tabelami. Vse imajo toliko vrstic (oz. elementov, če so 1D), kolikor je meritev v datoteki.
imena
je vsebuje imena medvedov; konkretno, v tabelo prebereš stolpecindividual-local-identifier
.datumi
ima tri stolpce, ki vsebujejo leto, mesec in dan posamične meritve, npr.[1994, 4, 29]
za peto vrstico.dnevi
ima en stolpec, ki vsebuje isti podatek v drugi obliki: namesto datumov vsebuje število dni od začetka študije. Pri tem ignorirajte ure dneva: 23. maj ob 00:01 je en dan kasneje kot 22. maj ob 23:59. Element, ki ustreza vrstici z najstarejšim datumom (4. maj 1993) ima vrednost 0; vrstica, ki se nanaša na 11. maj 1993 ima vrednost 7. Pri tem ne smeš predpostaviti, da je 4. maj 1993 najstarejši datum - program mora to odkriti sam.xy
vsebuje vsebuje izmerjene koordinate medveda. Koordinatni sistem ima središče v Medvedjem Brdu (N45.9709794, E14.1118016), osi x in y tečeta na sever in vzhod, enota je kilometer.- Podatke za prvi stolpec dobiš iz
location-lat
. Od prebrane vrednosti moraš odšteti zemljepisno širino Medvedjega Brda (45.9709794
) in razliko pomnožiti z40007 / 360
. - Podatke za drugi stolpecc dobiš iz
location-long
. Odšteti je potrebno zemljepisno dolžino Medvedjega Brda (14.1118016
) in jo pomnožiti z40075 * np.cos(np.radians(45.9709794)) / 360
.
40007 km je obseg Zemlje, merjen čez pole, torej ena stopinja zemljepisne širine predstavlja 40007 / 360 km. [Obseg Zemlje čez ekvator je 40075](https://en.wikipedia.org/wiki/Earth%27s_circumference=; to pomnožimo s cos zemljepisne širine, da dobimo obseg vzporednika, ki teče čez Medvedje Brdo. Ob tej pretvorbi predpostavljamo, da je področje študije dovolj majhno, da nam ni potrebno upoštevati ukrivljenosti Zemlje. Brez te predpostavke bi morali v vseh funkcijah, ki sledijo, delati s koti in računati razdalje na krogli (oz. elipsoidu ali celo geoidu), kar bi bilo precej bolj zoprno.
- Podatke za prvi stolpec dobiš iz
Nasveti:
- Jaz bi se organiziral tako, da bi najprej ugotovil število vrstic v datoteki. Nato bi sestavil prazne tabele
imena
,datumi
inxy
ustreznih dimenzij. Nato bi bral datoteko in zapisoval podatke v ustrezne vrstice teh tabel. Znotraj zanke bi vxy
zapisoval kar podatke iz datoteke in jih šele po zanki (v enem zamahu) preračunal, Tudidnevi
bi pripravil po zanki, ko že vem, kateri dan je prvi. - Koliko dni je minilo od začetka študijskega leta 2023 do 17. decembra 2023 izveš z
(datetime(2023, 17, 12) - datetime(2023, 10, 01)).days
, pri čemerdatetime
uvoziš zfrom datetime import datetime
. - Prvega dneva raje ne išči kot minimum
datumi
. Klicnp.min(datumi)
bo vrnil najmanjšo število v tabeli (1); klicnp.min(datumi, axis=0)
bo vrnil najmanjše leto, najmanjši mesec in najmanjši dan, torej 1. 1. 1993. Ob branju datoteke raje spremljaj datume, si zapomni najmanjšega in ga potem odštej od vseh datumov vdatumi
.
Funkcije, ki utegnejo priti prav (ali pa ne, kakor se lotiš):
np.unique
; poznamo s predavanj, tudi z dodatnimi argumenti. -np.ptp
; poišči dokumentacijo; uporabna, da ni treba klicatinp.max
innp.min
.- Poglej, kaj vrne
dict(zip(["ana", "berta", "cilka"], [1, 2, 3]))
. np.genfromtext
ne bo prišla preveč prav; namesto nje uporabi drugo funkcijo, ki jo poznamo že dolgo.
Globalne spremenljivke
V program dodaj vrstico
imena, datumi, dnevi, xy = preberi_podatke()
Vse podatke zdaj dobiš v teh štirih spremenljivkah, tako da ti ni potrebno klicati preberi_podatke
.
Inventura
Napiši še funkcije
medvedi()
vrne abecedno urejen seznam imen medvedov, ki so prijazno sodelovali v študiji.n_meritev
vrne slovar, katerega ključi so imena medvedov, vrednosti pa število meritev vsakega medveda.razponi()
vrne slovar, katerega ključi so imena medvedov, vrednosti pa število dni med prvo in zadnjo meritvijo medveda. Medvedu, ki bi ga začeli spremljati 4. maja 1996 in bi raziskovalcem pokazal srednji krempelj že 13. maja 1996, bi pripadal razpon 9.
Poskusi narisati naslednja grafa.
Oba sta barh
. Z desnim ne bi smelo biti posebnih težav. Levi je bolj zanimiv. Črte tečejo od minimalnega do maksimalnega dneva; to dosežemo z argumentom left
, kot dolžino pa podamo razliko med min
in max
(točno to, kar vrača razpon
). Za nastavljanje številk na osebh uporabimo xticks
. Predlagam, da se potrudiš z desnim grafom, levega pa pokažem na predavanjih, če bo čas.
Z zakrivljeno palico v roki (pohaja medved za tropom ovčic)
Najbolj zanimive so meritve v dveh zaporednih dnevih - torej, ko so nekega medveda videli na primer, 13. maja in potem 14. maja.
Pazi: v fake-data.csv se datuma 2001-01-03 in 2001-01-04 ne nanašata na istega medveda, torej ne gre za zaporedno meritev!
n_zaporednih_meritev(medved)
prejme ime medved in pove, kolikokrat se je zgodilo, da je za medveda zabeležena pozicija na dva zaporedna dneva. (Recimo: koliko je takšnih dni, za katere velja, da so taistega medveda videli tudi dan predtem.)zaporedne_meritve()
vrne slovar, katerega ključi so imena medvedov, pripadajoče vrednost pa število zaporednih meritev za te medvede.dnevna_razdalja(medved)
vrne povprečno dnevno razdaljo, ki jo je prehodil medved. Izračunamo jo tako, da za vse dneve, za katere obstajajo zaporedne meritve, izračunamo razdaljo med izmerjenima koordinata. Funkcija vrne povprečje teh razdalj. Če za kakega medveda ni zaporednih meritev, naj funkcija vrnenp.nan
oz.math.nan
.dnevne_razdalje()
vrne slovar, katerega ključi so medvedi, pripadajoče vrednosti pa njihove povprečne dnevne razdalje.popotnik()
vrne ime medveda z največjo povprečno dnevno razdaljo.izlet()
poišče največjo dnevno razdaljo (torej: par najbolj oddaljenih pozicij izmerjenih na zaporedna dneva za istega medvega). Vrniti mora trojko: ime medveda, ki je opravil tako pot, datum potovanja (drugi dan, ne prvi) in pretacano razdaljo. Datum je lahko shranjen kot Pythonov seznam s tremi elementi ali ekvivalentna tabela v numpyju.
Opomba: dnevne razdalje so seveda podcenjene. Pravilne bi bile, če bi šel medved po tem, ko mu odčitajo pozicijo, v ravni črti na točko, kjer bi mu odčitali pozicijo prihodnji dan. To seveda ni nujno res, saj gre vmes mogoče še na stranišče ali pa strašit kolesarje.
Naloge se lahko lotite po starem, vendar za vajo priporočam, da n_zaporednih_meritev
, dnevna_razdalja
in izlet
sprogramirate z numpy (ali celo z in brez). Pri ostalih funkcijah numpy nima smisla, saj delajo le s kratkimi slovarji.
- Ta naloga je ena velika vaja iz mask, ki jih včasih le seštevamo, včasih pa očitno bo potrebno uporabljati maske in
np.sum
. np.sqrt(a)
vrne tabelo s koreni vseh elementov va
.np.mean(a)
vrne povprečno vrednost v tabelia
. Funkcija sprejme tudiaxis
, vendar tega tu ne potrebujemo.
Tudi tu lahko za neobvezno vajo dorišeš kak graf, recimo bar
ali barh
z dnevno razdaljo, ki jo prehodi medved.
Zimsko spanje
Napiši funkcijo
mesecna_razdalja
, ki vrne tabelo ali seznam z 12 elementi, ki vsebujejo povprečno dnevno razdaljo, ki jo medvedi prehodijo v posameznem mesecu. Pri računanju torej upoštevaš razdalje med lokacijami, zabeleženi na zaporednih dneh (pri čemer mora iti seveda za istega medveda). Meritve med zadnjim dnem enega in naslednjim dnem naslednjega meseca beleži pod prvi mesec; če gre za meritev med, na primer, 31. januarjem in 1. februarjem, jo beleži pod januar.Če za kak mesec sploh ni zaporednih meritev (to se zgodi v lažnih podatkih), naj bo na pripadajočem mestu
np.nan
.Napiši funkcijo
leni_meseci(s)
, ki prejme seznam(!)) z povprečnimi dnevnimi razdaljami po mesecih. Poiskati trimesečno obdobje, v katerem je vsota dnevnih povprečij najmanjša in vrniti indeks prvega meseca tega obdobja. Upoštevaj, da bo vsaj do konca sveta vsakemu decembru sledil januar.Če je enako najbolj lenih obdobij več, vrni začetek prvega od njih.
Predpostaviti smeš, da podatek za noben mesec ni
np.nan
.Klic
leni_meseci([5, 3, 5, 1, 2, 0, 3, 4, 1, 6, 1, 5])
vrne4
, ker se leno obdobje (1, 2, 0) začne pri aprilu.Klic za resnične podatke vrne 12, ker medvedi lenarijo od decembra do februarja.
Napiši funkcijo
lenoba(s)
, ki prejme enak argument kot prejšnja funkcija in vrne razmerje med povprečno dnevno razdaljo v lenih mesecih in povprečno dnevno razdaljo čez vse leto.
Nasveti:
- v
mesecna_razdalja
se splača kombinirati numpy (maske za imena medvedov in zaporedne dneve, računanje razdalj) ter zanke v Pythonu. Konkretno, v nekem trenutku bo najbrž potrebno narediti zanko čez vse zaporedne dnevne meritve. Lahko pa narediš tudi zanko čez vse podatke, če ti je lažje. - Trik: mogoče se bo kdaj splačalo napisati
s + s
. Na ta način se bo januar znašel (tudi) za decembrom, - Z nekaj iznajdljivosti se drugo funkcijo elegantno reši z
np.cumsum
. Če ti ne potegne, kako, pa naredi v čistem Pythonu. Tretjo pa itak.
Za več vaje iz grafov, poskusi narisati še tole:
Amigos para siempre
Napiši naslednje funkcije.
povprecna_razdalja(medved1, medved2)
vrne povprečno razdaljo med vsemi pari točk, na katerih so opazili medveda s podanima imenoma.povprecne_razdalje()
vrne slovar, katerega ključi so vsi pari imen medvedov (na primer("ancka", "jana")
), pripadajoče vrednosti pa razdalje med tema medvedoma. Vsak par naj se pojavi le enkrat: pare sestavi tako, da bo prvo ime po abecedi pred drugim.prijatelji()
izpiše deset parov (ali manj, če ni toliko medvedov) z najmanjšo razdaljo. Spisek naj bo urejen po naraščajočih razdaljah in naj bo oblikovan do presledka natančno tako (število ima 5 mest, od tega dve decimalni; na koncu vrstice ni "nevidnih" presledkov za imeni!):
dusan : 2.88 : vanja
vanja : 2.96 : vera
maja : 3.22 : vanja
dusan : 3.39 : vera
dusan : 3.63 : maja
lucia : 3.73 : vanja
maja : 3.76 : vera
clio : 3.89 : lucia
clio : 3.94 : vanja
lucia : 4.04 : vera
bffl()
vrne najboljša prijatelja. V gornjem primeru sta to Dušan in Vanja. (Opomba: brez namigovanj. Poiskal sem članek: gre za enoletnega medvedka in medvedko.)
Nasvet:
povprecne_razdalje()
se bo izvajala par sekund; funkcija, ki je dejansko pocasna jepovprecna_razdalja
. Pri njej se ti res splača nabrati vse koordinate obeh medvedov z numpyjem in maskami. Potem pa pozabi na numpy in napiši dve zanki v Pythonu.Rešitev z uporabo numpyja je stokrat hitrejša (in bistveno krajša), vendar zahteva malo več znanja. Ta, ki ga zanima, naj se zazre v tole in in potem razmisli, kako mu to pomaga. Namig: tole je potem potrebno narediti s koordinatama ločeno.
>>> np.array([1, 2, 3]) + np.array([[100], [200]]) array([[101, 102, 103], [201, 202, 203]]) >>> a = np.array([100, 200]) >>> a[:, None] array([[100], [200]])
Pri ostalih funkcijah nimate kaj početi z numpyjem.
Medvedi na obisku
V zadnjih funkcijah bomo preverili, kako družabni so medvedi in kam zahajajo medvedi, ko se jim zahoče človeške družbe. Funkcije bodo prejele seznam koordinat krajev, na primer [[45.962375, 14.293736], [45.916703, 14.229728], [45.775864, 14.213661], [45.9709794, 14.1118016]]
(koordinati "središč" Vrhnike, Logatca, Postojne in Cerknice).
Pazi, koordinate bo potrebno iz zemljepisne širine in dolžine pretvoriti v koordinatni sistem s središčem v Medvedjem Brdu, kot to počnemo v prvi funkciji (preberi_podatke()
)
druzabnost(medved, kraji, k)
vrne število meritev, v katerih je bil podani medved največk
kilometrov daleč od enega od podanih krajev.tezisce_delovanja(medved, kraji)
za vsako meritev ugotovi, kateremu izmed krajev je najbližje. Vrniti mora tabelo ali seznam, ki ima toliko elementov kolikor je krajev; vsak element vsebuje delež primerov, ko je bil medved najbližje temu kraju.Klic
tezisce_delovanja("maja", kraji)
vrne[0.59, 0.31, 0, 0.1]
, ker je bila Maja v 59 % primerov najbližje Vrhniki, v 31 % najbližje Logatcu, nikoli najbližje Postojni in v 10 % primerov najbližje Cerknici. Klictezisce_delovanja("polona", kraji)
pa vrne[0, 0, 0, 1]
. Polona je Cerkničanka.obiskovalci(kraji)
prejme seznam krajev in vrne seznam množic. Za vsakega medveda pogleda, kateremu kraju je najpogosteje nejbližje. Če prvemu, da doda v prvo množico; če drugemu v drugo in tako naprej.
Nasveti
- V teh funkcijah zdravo mešaj numpy in Pythonove zanke. Te bodo šle vedno čez kraje ali čez medvede.
- Pazi, da ne boš slučajno pretvoril v medvedji koordinatni sistem koordinat, ki so morda že v medvedjem koordinatnem sistemu.
- Najlepša (z vidika numpyja) je
tezisce_delovanja
: spomni se nanp.unique
in na to, da lahko funkcijinp.argmin
lahko podaš os.
Študenti z močnejšim statističnim ozadjem lahko poskusijo narisati tole.
Slika se nanaša na Jano (rdeča), Polono (modra) in Majo (zelena). Graf je vrste scatter
, z add_patch
pa so dodane elipse. Njihovo središče je v povprečni koordinati medveda, osi gresta v smereh lastnih vektorjev, polmeri pa so veliki 1, 3 in 5 deviacij. Z drugimi besedami, slika ilustrira dvodimenzionalno Gaussovo porazdelitev, izračunano iz opaženih koordinat medvedov.