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š stolpec individual-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 z 40007 / 360.
    • Podatke za drugi stolpecc dobiš iz location-long. Odšteti je potrebno zemljepisno dolžino Medvedjega Brda (14.1118016) in jo pomnožiti z 40075 * 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.

Nasveti:

  • Jaz bi se organiziral tako, da bi najprej ugotovil število vrstic v datoteki. Nato bi sestavil prazne tabele imena, datumi in xy ustreznih dimenzij. Nato bi bral datoteko in zapisoval podatke v ustrezne vrstice teh tabel. Znotraj zanke bi v xy zapisoval kar podatke iz datoteke in jih šele po zanki (v enem zamahu) preračunal, Tudi dnevi 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 čemer datetime uvoziš z from datetime import datetime.
  • Prvega dneva raje ne išči kot minimum datumi. Klic np.min(datumi) bo vrnil najmanjšo število v tabeli (1); klic np.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 v datumi.

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 klicati np.max in np.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 vrne np.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 v a.
  • np.mean(a) vrne povprečno vrednost v tabeli a. Funkcija sprejme tudi axis, 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]) vrne 4, 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 je povprecna_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. Klic tezisce_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 na np.unique in na to, da lahko funkciji np.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.

Zadnja sprememba: četrtek, 19. december 2024, 13.30