Del 1: Indeksering¶
Level2: Nedbørsoppgavene Del 1 til 5 er laget som støtte-oppgaver til oppgaven "Nedbør i Norge". Oppgaven "Nedbør i Norge" er en lengre oppgave som kommer innom mange forskjellige elementer i programering. Intensjonen er at hvis man først gjør deloppgavene 1 til 5 og så går gjennom eksempelet på starten av oppgaven "Nedbør i Norge" så vil alt det programeringstekniske i oppgaven "Nedbør i Norge" være repetisjon, slik at man kan fokusere på den naturvitenskaplige tolkningen av dataene. På denne måten får man både trening i programmering og erfaring med hvordan det kan brukes til å studere og forstå naturvitenskaplige prosesser.
I denne oppgaven begynner vi å jobbe med Indeksering i Python. Indeksering vil si hvilke rader og kolonner i et datasett du vil studere. Indekseringen kan være forvirrende i starten men er noe vi trenger hele tiden, så det er lurt å bruke litt ekstra tid på dette i starten.
I filen Huskeregler.ipynb i mappen "Kom_i_gang" finner du generelle regler for indeksering i Python.
Tips: I denne oppgaven går vi rett på med indeksering på et ekte datasett over årlig temperatur i Bergen. I oppgavene Del0: kom i gang og Del1: Intro til programmering i python som ligger i mappen Level1: temperaturoppgaven (vi kaller den siste av disse Del1_Intro fra nå av) begynner vi hakket roligere og går gjennom de viktigste indekseringsreglene steg for steg med enklere eksempler som ikke er basert på ekte datasett. Hvis Python er nytt for deg vil jeg anbefale å titte på denne oppgaven som tilhører kurset Level1: temperaturoppgaven først, for en roligere intro til python og indeksering.
Last inn pakkene som trengs¶
import numpy as np # for matematikk m.m.
import matplotlib.pyplot as plt # for figurer
import sys
# Bytt ut stien under slik at den peker på hvor på din PC du har
# lagret mappen "Funksjoner".
sys.path.append(r"W:\Work\Documents\EkteData\EkteData\Funksjoner")
from EkteDataFunk import loadData # for filinnlasting
Del 1: Last inn et datasett¶
Last inn datasettet "TempBergenYearlyNonan.txt". I oppgavene Del2: RyddDatasett og Funksjoner er det detaljer på hvordan filinnlastingen fungerer, men i denne oppgaven fokuserer vi på indeksering.
file='TempBergenYearlyNonan.txt'
data = loadData(file, rydd='N')
# Linjen under gir dimensjonene til datasettet. Man må skrive "print()"
# rundt fordi bare den siste linjen i en celle printes ut automatisk
print(data.shape)
data # Dette printer ut alle verdiene i "data"
Outputet av data.shape
er (160, 2). Det betyr at data
er en matrise med 160 rader og 2 kolonner. data
har altså to dimensjoner.
Del 2: Lag en figur av dataene¶
Det er alltid praktisk å starte med å lage en enkel figur for å visualisere hva du faktisk jobber med. Husk tittle og tekst på aksene.
Linjen under plotter alle dataene i kolone 2 av tabellen data
som en funksjon av kolonne 1.
data[:,0]
betyr:
"alle radene", i kolonnen med indeks0
, altså kolonne nr 1.data[:,1]
betyr:
"alle radene", i kolonnen med indeks1
, altså kolonne nr 2.
NOTE: Vi kaller en tabell som data
for et array
. Et array
med to dimensjoner (som data
) er det samme som en tabell. Generelt er et array
det samme som en matrise: den kan ha mange dimensjoner.
# Plot kolonne 1 mot kolonne 2 i arrayet "data"
plt.plot(data[:,0], data[:,1])
# Lag en tittel
plt.title('Gjennomsnittlig årsnedbør Bergen')
# Sett navn på x-aksen
plt.xlabel('År')
# Sett navn på y-aksen
plt.ylabel('Temperatur [\u2103]') # \u2103 er kode for "grader celcius"
# Linjen under gjør bare at en tekst-linje man ikke trenger ikke
# blir printet ut. Prøv selv å kjøre denne cellen uten denne linjen
# (kommenter den ut med "#") om du vil.
plt.show()
For å lage denne figuren har vi allerede brukt undeksering i Python. Da vi skrev data[:,0]
og data[:,1]
indekserte vi arrayet data
. På denne måten fikk vi tak i en kolonne av gangen som vi plottet mot hverandre.
Del 3: Lagre dataene til variabler¶
Hvis man skal bruke samme del av et array mange ganger er det lurt å lagre denne delen som en egen variabel. I data
er for eksempel kolonne 1 år, mens kolonne 2 er temperatur. Det er derfor nyttig å lagre disse kolonnene i hver sin variabel som er lettere å jobbe videre med.
data.shape
viste oss at datasettet over temperatur har 160 rader og 2 kolonner. Hvis vi vil ha tak i alle årene og lagre dem i en vektor som vi kaller "tid" skriver vi tid=data[:,0]
, altså alle radene :
, men kun den første kolonnen 0
.
Når vi skriver tid=data[:,0]
lager vi variabelen tid
. når vi i tillegg skriver tid
på linjen under skrives innholdet til variabelen tid
ut slik at du kan se hva variabelen inneholder.
tid=data[:,0]
tid
array([1861., 1862., 1863., 1864., 1865., 1866., 1867., 1868., 1869., 1870., 1871., 1872., 1873., 1874., 1875., 1876., 1877., 1878., 1879., 1880., 1881., 1882., 1883., 1884., 1885., 1886., 1887., 1888., 1889., 1890., 1891., 1892., 1893., 1894., 1895., 1896., 1897., 1898., 1899., 1900., 1901., 1902., 1903., 1904., 1905., 1906., 1907., 1908., 1909., 1910., 1911., 1912., 1913., 1914., 1915., 1916., 1917., 1918., 1919., 1920., 1921., 1922., 1923., 1924., 1925., 1926., 1927., 1928., 1929., 1930., 1931., 1932., 1933., 1934., 1935., 1936., 1937., 1938., 1939., 1940., 1941., 1942., 1943., 1944., 1945., 1946., 1947., 1948., 1949., 1950., 1951., 1952., 1953., 1954., 1955., 1956., 1957., 1958., 1959., 1960., 1961., 1962., 1963., 1964., 1965., 1966., 1967., 1968., 1969., 1970., 1971., 1972., 1973., 1974., 1975., 1976., 1977., 1978., 1979., 1980., 1981., 1982., 1983., 1984., 1985., 1986., 1987., 1988., 1989., 1990., 1991., 1992., 1993., 1994., 1995., 1996., 1997., 1998., 1999., 2000., 2001., 2002., 2003., 2004., 2005., 2006., 2007., 2008., 2009., 2010., 2011., 2012., 2013., 2014., 2015., 2016., 2017., 2018., 2019., 2020.])
Oppgave 1:¶
Skriv ut
- de fem første radene til
data
og begge kolonnene - de to siste verdiene til
tid
- år nummer 5 til 10 i tidsserien
Tips 1: Se på Huskeregel-filen for indekseringsreglene.
Tips 2: Sammenlign resultatene dine med verdiene i data
for å sjekke at du har gjort rett.
Oppgave 2:¶
Lag en variabel "temp" som inneholder den andre kolonnen med temperatur.
temp=data[:,1]
Del 4: Se på en utvalgt del av datasettet¶
Vi har 160 år med data. Det er ikke alltid man vil studere alle årene på en gang. I oppgaven (Del1_Intro) plukket vi ut de 10 første og siste årene, de 50 siste årene etc. Men hva om vi har en mistanke om at det skjedde noe veldig spennende mellom 1873 - 1947? Da kan vi plukke ut de radene som tilsvarer disse årene og kun plotte disse. For å plukke ut de rette radene må vi finne indeksen til 1873 og 1947 i vektoren tid
- altså: hvor i vektoren tid
har tid
verdi 1873, og hvor er verdien 1947? Det finnes alltid mange måter å gjøre slikt på, og vi skal se på to forskjellige måter.
Metode 1: Denne er kanskje mest intuitiv, men ikke så effektiv, og heller ikke så robust i kompliserte koder
Siden tidssteget i tid
er i hele år kan man finne antall år mellom det året man er ute etter og startåret i vektoren. Dette gir antall år i mellom, som da tilsvarer indeksen til året du er ute etter.
# Finn startåret
startår=tid[0]
startår
1861.0
# Tanken bak variabelnavnene er "id=index", "t=tid", "1/2=start/end"
idt1=1873-startår
idt2=1947-startår
# Med komma mellom to variabelnavn skriver man ut verdiene til begge variablene
idt1, idt2
(12.0, 86.0)
idt1
er 12 og idt2
er 86. Da skal tid[12]=tid[idt1]=1873
og tid[86]=tid[idt2]=1947
. Vi prøver:
tid[12]
1873.0
# Dette skal bli det samme som tid[12] rett over
tid[idt1]
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-12-4f27096af71a> in <module> 1 # Dette skal bli det samme som tid[12] rett over ----> 2 tid[idt1] IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
Dette gir en feilmelding. Problemet er at idt1=12.0
som er en float-verdi, altså et desimaltall, mens indekser kun kan være "integers, slices (:
), [...] ". En float-verdi har desimaler, mens integer-verdier er heltall. Hvis vi konverterer idt1 og idt2 fra float til integer løser vi problemet. Dette gjør vi med den innebygde kommandoen int
.
idt1=int(idt1)
idt2=int(idt2)
idt1, idt2
(12, 86)
Nå kan vi bruke indeksene tid1 og tid2:
tid[idt1], tid[idt2]
(1873.0, 1947.0)
Lag en figur som viser temperatur fra 1873 til og med 1947
Husk at indeksering [x:y]
ikke inkluderer y. Så for å dekke alle indeksene "fra tid1 til og med tid2" må man skrive [idt1:idt2+1]
plt.plot(tid[idt1:idt2+1], temp[idt1:idt2+1])
plt.title('Gjennomsnittlig årsnedbør Bergen')
plt.xlabel('År')
plt.ylabel('Temperatur [\u2103]')
plt.show()
Metode 2: Denne metoden er mer effektiv, veldig anvennelig i store datasett, og robust i kompliserte koder.
Men vi må introdusere en funksjon: np.where()
.
Denne funksjonen lar deg finne verdier som oppfyller et kriterie, for eksempel at tid
skal være 1873 eller større:
np.where(tid>=1873)
(array([ 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159], dtype=int64),)
Tips: Hvis du er usikker på hvordan en funksjon egentlig fungerer er det ofte nyttig å lage enkle eksempler som du kan teste funksjonen på. Lag for eksempel et lite og oversiktlig array, så er det lett å se hva resultatet av funksjonen er.
For eksempel kan vi finne alle stedene arrayet x
er 3:
x=np.array([3, 7, 5, 9, 3, 3])
# Bruk dobbel = når du skal sjekke om noe er rett eller galt (True eller False)
np.where(x==3)
(array([0, 4, 5], dtype=int64),)
Dette viser at på indeks 0, 4, og 5 er verdien til x
lik 3. Siden x
er et så lite array er det mulig å sjekke manuelt at dette stemmer.
Tilsvarende kan vi finne indeksene til alle verdiene i x
som er større eller lik 6:
np.where(x>=6)
(array([1, 3], dtype=int64),)
På samme måte kan vi finne indeksene der tid
er 1947 eller mindre:
np.where(tid<=1947)
(array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86], dtype=int64),)
Alle verdier i tid
som oppfyller begge disse kriteriene er de indeksene vi vil ha.
idt=np.where((tid>=1873) & (tid<=1947))
idt
(array([12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86], dtype=int64),)
Disse indeksene kan vi så gi tilbake til tid
.
tid[idt]
array([1873., 1874., 1875., 1876., 1877., 1878., 1879., 1880., 1881., 1882., 1883., 1884., 1885., 1886., 1887., 1888., 1889., 1890., 1891., 1892., 1893., 1894., 1895., 1896., 1897., 1898., 1899., 1900., 1901., 1902., 1903., 1904., 1905., 1906., 1907., 1908., 1909., 1910., 1911., 1912., 1913., 1914., 1915., 1916., 1917., 1918., 1919., 1920., 1921., 1922., 1923., 1924., 1925., 1926., 1927., 1928., 1929., 1930., 1931., 1932., 1933., 1934., 1935., 1936., 1937., 1938., 1939., 1940., 1941., 1942., 1943., 1944., 1945., 1946., 1947.])
Med denne metoden slipper vi også problemet med floats/integers.
Vi lager en figur for å vise at de to metodene for å finne indekser gir samme resultat. Det vises kun en oransje linje, selv om vi har plottet to. Det er fordi outputtet av begge indekseringsmetodene er det samme, slik at de to linjene ligger perfekt oppå hveradre.
# Indeksering basert på Metode 1
plt.plot(tid[idt1:idt2+1], temp[idt1:idt2+1])
# Indeksering basert på Metode 2
plt.plot(tid[idt], temp[idt])
plt.title('Gjennomsnittlig årsnedbør Bergen')
plt.xlabel('År')
plt.ylabel('Temperatur [\u2103]')
plt.show()
Man kan alså definere et intervall med indekser på to forskjellige måter:
x[2:8]
x[[2, 3, 4, 5, 6, 7]]
tid[2:8], tid[[2, 3, 4, 5, 6, 7]]
(array([1863., 1864., 1865., 1866., 1867., 1868.]), array([1863., 1864., 1865., 1866., 1867., 1868.]))
Oppgave 3¶
Skriv ut årene og temperatur for alle årene fra året du ble født frem til 2016. Bruk både metode 1 og 2.
# Metode 1
# Metode 2:
Oppgave 4¶
Skriv ut årene og temperatur for alle årene fra året du ble født frem til og med slutten av datasettet. Bruk den metoden du vil, men pass på at det siste året (2020) blir med.
Oppgave 5¶
Skriv ut temperatur på hele 1900-tallet. Skriv også ut årene.
Del 5: Velg ut datapunkt med et jevnt intervall¶
Iblant trenger man å hente ut data med jevne intervall, for eksempel hvis man har et datasett med daglig oppløsning og bare vil ha tak i onsdagene, eller man bare vil ha tak i bursdagen sin fra en tidsserie med daglig oppløsning som spenner over flere år. Eller kanskje man vil hente ut data fra kl 12 for å sjekke solstyrke midt på dagen.
Med indeksering kan man bruke to eller tre verdier. Bruker man to angir man kun start og stop: [start:stop]
. Bruker man tre angir man også intervallet av verdier man er interessert i: [start:stop:step]
. Skal man f.eks. ha annenhver verdi mellom indeks a
og b
skriver man [a:b:2]
. Husk at det fremdeles er a
til og ikke med b
. Skal man ha med hele tidsserien skriver man [::step]
. De to kolonene viser at både start og slutt skal med. Hvis a=5
og b=20
blir dette:
tid[5:20:2]
array([1866., 1868., 1870., 1872., 1874., 1876., 1878., 1880.])
Oppgave 6¶
Finn temperaturen for hvert tiende år fra starten til slutten av tidsserien. Skriv ut årene i tillegg (for å sjekke at indekseringen du har satt stemmer).
Oppgave 7¶
Finn temperaturen for hvert tiende år siden 1870 frem til og med 2016. Skriv igjen ut årene i tillegg (for å sjekke at indekseringen du har satt stemmer).
Her kan du igjen benytte Metode 1 eller 2 for å finne årene mellom 1870 og 2016. Du kan enten finne indeksen til 1870 og indeksen il 2016 separat og så printe ut hvert tiende år/hver tiende temperatur mellom disse indeksene, ELLER, du kan finne indeksen til alle år mellom 1870 og 2016 og plugge hver tidende av disse indeksene inn i "tid".
Du kan komme til å støte på dette problemet: du får en output av idt som er idt=(array([9, 10, 11, ...]))
. For å velge bestemte indekser av dette outputtet må den ytterste parantesen vekk. For å få til dette skriver man idt=idt[0]
. Da går man "et nivå ned" i itd
og unngår den ytterste parantesen. Du kan nå indeksere idt.
Et notat til cellen over: man trenger ikke alltid lage variabler for alt. Man trenger for eksempel ikke "omdøpe" idt[0]
til idt
, man kan bruke idt[0]
direkte.
Har du for eksempel en vektor med indekser via Metode 2, men vil bare ha annenhver verdi kan du skrive
tid[idt[0][0::2]]
.Dette er det samme som
tid[idt[0][0:-1:2]]
.idt[0]
gjør at du unngår den ytterste parantesen du får når du bruker np.where.idt[0][0::2]
gjør at at du unngår den første parantesen, og velger ut annenhver indeks fra start til slutt.tid[idt[0][0::2]]
gjør at du ikke bare får indeksene men de faktiske tid-verdiene.
Ved å skrive alt dette i en linje slipper man mange mellomvariabler, som kan gjøre koden ryddig og oversiktlig. Det finnes selvfølgelig en grense - blir koden FOR kompakt og linjene FOR lange blir det fort uoversiktlig. Men dette er litt smak og behag, og så lenge man kommenterer godt i koden sin og forklarer hva som skjer blir det meste bra.
Oppgave 8¶
Lag en figur som viser hele temperaturtidsserien, men plot også hvert tiende år på samme figur. Synes du tidsserien blir godt representert ved å bare se på hvert tiende år? I en seinere oppgave, f.eks. en om for-løkker (NAT624/Del8 eller Nedbørsoppgaven/Del5), ser vi på hvordan man kan representere datasettet ved å ta gjennomsnittet over tiårsperioder. Hvordan tror du denne tidsserien representerer datasettet i forhold til den figuren vi har her?
Del 6: Del et datasett inn i et gitt antall like lange perioder¶
I stedet for å se på f.eks. hvert tiende, tyvende, 55te, etc. år kan det være man vil dele tidsserien inn i f.eks. fire like lange tidsperioder. Da må man ta utgangspunkt i lengden til tidsseerien, og så finne indeksene som deler tidsserien inn i antallet bolker man er ute etter. Siden det ikke er sikkert at lengden til tidsserien er perfekt delelig på antall tidsperioder du vil dele den inn i må man passe på at inndelingen man finner kan brukes som indekser, altså være heltall.
Vi tar utganspunkt i at vi vil dele tidsserien vår i tre like bolker.
Et notat til cellen under: Noen ganger er det praktisk å dele linjer opp. Det er standard å ta linjeskift rett etter første parantes, da hopper man automatisk fire mellomrom inn på neste linje. Så fordeler man innholdet i parantesen på linjer slik man synes er logisk. Rett før den avsluttende parantesen lager man et nytt linjeskift. Denne parantesen hopper automatisk tilbake til margen. På den måten er det lett å se hvor bolken avsluttes.
ind=[
0, # startindeks
len(tid)/3, # indeksen som markerer den første tredelen
2*len(tid)/3, # indeksen som markerer den andre tredelen
len(tid) # sluttindeksen
]
ind
[0, 53.333333333333336, 106.66666666666667, 160]
Disse veridene kan ikke brukes som indekser. De må både rundes av til heltall og konverteres fra float til integer.
ind=[
0,
int(np.round(len(tid)/3)),
2*int(np.round(len(tid)/3)),
len(tid)
]
ind
[0, 53, 106, 160]
Disse KAN brukes som indekser. Vil man f.eks plotte kun siste tredel av tidperioden kan man nå skrive:
plt.plot(tid[ind[2]:ind[-1]], temp[ind[2]:ind[-1]])
plt.title('Gjennomsnittlig årsnedbør Bergen')
plt.xlabel('År')
plt.ylabel('Temperatur [\u2103]')
plt.show()
Man kunne også skrevet det mer kompakt, slik som under, men i blant har man behov for å lagre indeksen til alle inndelingene, for eksempel hvis man vil plotte alle tidsperiodene i samme figur. Dette gir mening hvis man f.eks. vil plotte standardavviket til hver periode, trendlinjer til hver periode, eller andre variabler basert på tidsserien man har.
I tillegg blir det fort rotete og vanskelig å lese koden når man gjør alt på en gang i få linjer.
plt.plot(
tid[2*int(np.round(len(tid)/3)):],
temp[2*int(np.round(len(tid)/3)):]
)
plt.title('Gjennomsnittlig årsnedbør Bergen')
plt.xlabel('År')
plt.ylabel('Temperatur [\u2103]')
plt.show()
Oppgave 9¶
- Del tidsserien inn i fire like lange bolker.
- Plot de to midterste bolkene i samme figur.
- For å vise tydeligere at dette er de to midterste bolkene: plot hele tidsserien i bakgrunnen.
Last ned og prøv selv: