Del 7: forløkker og feilsøking¶
Ønsketimen: feilsøking, dokumentasjon, for-løkker og scatterplot¶
Denne oppgaven er dedikert til ting NAT624-studentene ønsket å se på den siste dagen av kurset: feilsøking, bruk av dokumentsjon, løkker og en (nesten) ny type figur, scatterplot.
Vi bruker tid spesifikt på feilsøking og å bruke dokumentasjon fordi dette er en av de siste kursdagene vi har, og dette er to ting det er veldig nyttig å være litt trygg på når dere skal bruke programmering i Python på egenhånd seinere. I denne biten er det flere deloppgaver med feil i som dere skal finne. I andre deloppgaver i denne biten må dere google dere frem for å løse en oppgave.
Løkker beskriver mange forskjellige operasjoner. Vi skal først og fremst se på for-løkker. Det heter for-løkker fordi syntaksen er "for disse scenarioene, gjør dette". Dette er praktisk når man skal gjøre nesten det samme mange ganger, f.eks. hvis vi skal finne trendlinjen til hver 20-årsperiode til en tidsserie. Eller plotte alle disse trendlinjene. Eller beregne gjennomsnittet til perioden. Vi ser også så vidt på if-løkker: "hvis dette er tilfellet, gjør dette", f.eks., "hvis verdien er større enn 1000, skriv ut verdien".
Scatterplot bruker vi tid på fordi dette er en enkel og veldig beskrivende metode for å se etter sammenhenger mellom to eller tre variabler. Flere av NAT624-studentene sammenlignet hverandres datasett og værmeldingen sine data i innleveringene sine. Noen av sammenlignet også med datasett over andre variabler enn temperatur, lastet ned fra f.eks. seklima.met.no. Å sammenligne to tidsserier kan gi mye informasjon, men scatterplot konsenterer sammenhenger på en litt annen måte som kan være nyttig.
Del 1: Feilsøking og bruk av dokumentasjon¶
import numpy as np # for regneoperasjoner
import matplotlib.pyplot as plt # for figurer
from matplotlib import cm # for å lage fargeskalaer
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
SMALL_SIZE = 10
MEDIUM_SIZE = 12
BIGGER_SIZE = 14
plt.rc('font', size=SMALL_SIZE) # controls default text sizes
plt.rc('axes', titlesize=MEDIUM_SIZE) # fontsize of the axes title
plt.rc('axes', labelsize=MEDIUM_SIZE) # fontsize of the x and y labels
plt.rc('xtick', labelsize=SMALL_SIZE) # fontsize of the tick labels
plt.rc('ytick', labelsize=SMALL_SIZE) # fontsize of the tick labels
plt.rc('legend', fontsize=SMALL_SIZE) # legend fontsize
plt.rc('figure', titlesize=BIGGER_SIZE) # fontsize of the figure title
file='TempBergenYearlyNonan.txt'
temp,tid = loadData(file)
tid.shape, np.squeeze(temp).shape
((160,), (160,))
temp=np.squeeze(temp)
Plottingen av figuren under ser litt annerledes ut enn slik vi har plottet figurer tidligere, men forskjellene er egentlig veldig små. Hovedforskjellen er at vi først definerer aksene vi plotter i og kaller dem ax
. Dette er lignende noen av figurene vi lagde i Del 5. Dette er praktisk hvis man f.eks. vil ha to y-akser som i Del 5, eller flere panel, men også som i figuren under: i stedet for å skrive fem linjer som starter plt.title, plt.xlabel, plt.(...)
kan man skrive ax.set()
og definere alt av tittel, aksenavn etc inni samme parantes. Det ser ryddigere ut. Men resultatet er det samme.
ax=plt.axes() # Lag akser å plotte i
# Plot tidsserien
ax.plot(tid, temp)
# Definer grid, tekst etc.
ax.set(
title='Temperatur 1860-2020',
xlabel='År',
ylabel='Temperatur [\u2103]',
xlim=[1860,2020], # Sett grense for x-aksen
ylim=[5.5,10.5] # Sett grense for y-aksen
) # Sett grense for y-aksen
ax.grid() # Legg til rutenett (grid) i bakgrunnen
plt.show()
Del 1, oppgave 1¶
Finn gjennomsnittstemperaturen til hele perioden
# tips: np.mean()
Finn standardavviket til hele perioden
# tips: google "numpy standard deviation"
Finn indeksen til år 1900. Sjekk at du har riktig indeks ved å skrive ut verdien på denne indeksen i tidsvektoren.
# tips1: husk funksjonen np.where().
# tips2: husk at vi har brukt denne funksjonen før. Du kan altså enten bruke
# google til å finne dokumentasjonen på hvordan denne funksjonen brukes ELLER
# du kan finne filen der du jobbet med dette tidligere og se hva du gjorde da.
Lag en figur som viser temperatur fra år 1900 til slutten av tidsperioden. Bruk dokumentasjonen til matplotlib.pyplot til å velge linjefarge, markør og markørfarger.
# tips: Nedbørsoppgaven_del3_plotting_av_tidsserier
I figuren under har jeg lagt til "minor" grid-linjer og spesifisert farge, linjestil og gjennomsiktighet. Gjør de små grid-linjene oransje og gjør hoved-grid-linjene tykkere.
ax=plt.axes()
ax.plot(tid, temp)
ax.set(
title='Temperatur 1860-2020',
xlabel='År',
ylabel='Temperatur [\u2103]',
xlim=[1860,2020],
ylim=[5.5,10.5]
)
ax.minorticks_on()
ax.grid(which='minor', color=[.5, .6, .3], alpha=.3, linestyle='-')
ax.grid(which='major', linestyle='-')
plt.show()
Hva er feil med kodecellen under? Svaret finnes i feilmeldingen under. Tips: husk at den mest nyttige informasjonen i en feilmelding sjelden finnes midt i feilmeldingen.
ax=plt.axes()
ax.plot(tid, temp)
ax.set(
title='Temperatur 1860-2020',
xlabel='År',
ylabel='Temperatur [\u2103]',
xlim=[1860,2020],
ylim=[5.5,10.5]
)
ax.minorticks_on()
ax.grid(which='minor', color=[.4, .5, 2], alpha=.3, linestyle=':')
ax.grid(which='major', linestyle='-')
plt.show()
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) ~\Anaconda3\envs\EkteData\lib\site-packages\IPython\core\formatters.py in __call__(self, obj) 339 pass 340 else: --> 341 return printer(obj) 342 # Finally look for special method names 343 method = get_real_method(obj, self.print_method) ~\Anaconda3\envs\EkteData\lib\site-packages\IPython\core\pylabtools.py in <lambda>(fig) 246 247 if 'png' in formats: --> 248 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs)) 249 if 'retina' in formats or 'png2x' in formats: 250 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs)) ~\Anaconda3\envs\EkteData\lib\site-packages\IPython\core\pylabtools.py in print_figure(fig, fmt, bbox_inches, **kwargs) 130 FigureCanvasBase(fig) 131 --> 132 fig.canvas.print_figure(bytes_io, **kw) 133 data = bytes_io.getvalue() 134 if fmt == 'svg': ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\backend_bases.py in print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs) 2191 else suppress()) 2192 with ctx: -> 2193 self.figure.draw(renderer) 2194 2195 bbox_inches = self.figure.get_tightbbox( ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\artist.py in draw_wrapper(artist, renderer, *args, **kwargs) 39 renderer.start_filter() 40 ---> 41 return draw(artist, renderer, *args, **kwargs) 42 finally: 43 if artist.get_agg_filter() is not None: ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\figure.py in draw(self, renderer) 1861 1862 self.patch.draw(renderer) -> 1863 mimage._draw_list_compositing_images( 1864 renderer, self, artists, self.suppressComposite) 1865 ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\image.py in _draw_list_compositing_images(renderer, parent, artists, suppress_composite) 129 if not_composite or not has_images: 130 for a in artists: --> 131 a.draw(renderer) 132 else: 133 # Composite any adjacent images together ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\artist.py in draw_wrapper(artist, renderer, *args, **kwargs) 39 renderer.start_filter() 40 ---> 41 return draw(artist, renderer, *args, **kwargs) 42 finally: 43 if artist.get_agg_filter() is not None: ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\cbook\deprecation.py in wrapper(*inner_args, **inner_kwargs) 409 else deprecation_addendum, 410 **kwargs) --> 411 return func(*inner_args, **inner_kwargs) 412 413 return wrapper ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\axes\_base.py in draw(self, renderer, inframe) 2745 renderer.stop_rasterizing() 2746 -> 2747 mimage._draw_list_compositing_images(renderer, self, artists) 2748 2749 renderer.close_group('axes') ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\image.py in _draw_list_compositing_images(renderer, parent, artists, suppress_composite) 129 if not_composite or not has_images: 130 for a in artists: --> 131 a.draw(renderer) 132 else: 133 # Composite any adjacent images together ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\artist.py in draw_wrapper(artist, renderer, *args, **kwargs) 39 renderer.start_filter() 40 ---> 41 return draw(artist, renderer, *args, **kwargs) 42 finally: 43 if artist.get_agg_filter() is not None: ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\axis.py in draw(self, renderer, *args, **kwargs) 1167 1168 for tick in ticks_to_draw: -> 1169 tick.draw(renderer) 1170 1171 # scale up the axis label box to also find the neighbors, not ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\artist.py in draw_wrapper(artist, renderer, *args, **kwargs) 39 renderer.start_filter() 40 ---> 41 return draw(artist, renderer, *args, **kwargs) 42 finally: 43 if artist.get_agg_filter() is not None: ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\axis.py in draw(self, renderer) 289 for artist in [self.gridline, self.tick1line, self.tick2line, 290 self.label1, self.label2]: --> 291 artist.draw(renderer) 292 renderer.close_group(self.__name__) 293 self.stale = False ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\artist.py in draw_wrapper(artist, renderer, *args, **kwargs) 39 renderer.start_filter() 40 ---> 41 return draw(artist, renderer, *args, **kwargs) 42 finally: 43 if artist.get_agg_filter() is not None: ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\lines.py in draw(self, renderer) 765 gc.set_url(self.get_url()) 766 --> 767 lc_rgba = mcolors.to_rgba(self._color, self._alpha) 768 gc.set_foreground(lc_rgba, isRGBA=True) 769 ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\colors.py in to_rgba(c, alpha) 187 rgba = None 188 if rgba is None: # Suppress exception chaining of cache lookup failure. --> 189 rgba = _to_rgba_no_colorcycle(c, alpha) 190 try: 191 _colors_full_map.cache[c, alpha] = rgba ~\Anaconda3\envs\EkteData\lib\site-packages\matplotlib\colors.py in _to_rgba_no_colorcycle(c, alpha) 275 c = c[:3] + (alpha,) 276 if any(elem < 0 or elem > 1 for elem in c): --> 277 raise ValueError("RGBA values should be within 0-1 range") 278 return c 279 ValueError: RGBA values should be within 0-1 range
<Figure size 432x288 with 1 Axes>
Under finner jeg indeksene som deler tidsperioden inn i fire like deler. Men det blir feil når jeg prøver å bruke resultatet til å indeksere tid (for å sjekke at det funket). Hvorfor blir det feil?
indeks=[
0,
len(tid)*1/4,
len(tid)*2/4,
len(tid)*3/4,
len(tid)
]
tid[indeks]
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-13-561914ae2bd6> in <module> 7 ] 8 ----> 9 tid[indeks] IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
int(indeks[3])
120
# Hva om jeg vil konvertere alle elementene i "indeks" til integers?
# Dette går ikke
int(indeks)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-15-41273d50e908> in <module> 1 # Hva om jeg vil konvertere alle elementene i "indeks" til integers? 2 # Dette går ikke ----> 3 int(indeks) TypeError: int() argument must be a string, a bytes-like object or a number, not 'list'
# En verdi av ganger funker
indeks[3]=int(indeks[3])
indeks
[0, 40.0, 80.0, 120, 160]
# Dette går, men det er ganske tungvindt.
# Hva om "indeks" var 20 elementer lang?
indeks[1]=int(indeks[1])
indeks[2]=int(indeks[2])
indeks[3]=int(indeks[3])
indeks[4]=int(indeks[4])
indeks
[0, 40, 80, 120, 160]
Del 2: For-løkker¶
Vi tar utgangspunkt i problemet over (å finne indeksene som deler tidsperioden vår inn i fire like deler) til å introdusere for-løkker.
Den letteste måten å endre alle elementene i vektoren indeks
til integers er ved hjelp av en for-løkke. Dette er nyttig når man skal gjøre samme operasjon flere ganger.
Det finnes en hel oppgave om for-løkker i Ekte Data-filene: Level 2: Nedbørsoppgaven Del5 forløkke
Aller først et par enkle eksempler:
for ind in range(6): # range(6) er alle heltall fra 0 til 5 (6 heltall)
print(ind)
0 1 2 3 4 5
list(range(6))
[0, 1, 2, 3, 4, 5]
Prøv med ekte data:
file='TempBergenYearlyNonan.txt'
temp,tid = loadData(file)
temp=np.squeeze(temp)
# Skriv ut de 6 første verdiene i "temp"
for i in range(6):
print(temp[i])
8.336363636363636 7.716666666666666 7.758333333333334 6.866666666666667 7.2250000000000005 7.416666666666668
# Sjekk at det stemmer:
temp[:6]
array([8.33636364, 7.71666667, 7.75833333, 6.86666667, 7.225 , 7.41666667])
# for hver indeks [0,1,2,3] print ut temperaturen pluss temperaturen på
# neste indeks
for k in range(4):
print(temp[k]+temp[k+1])
16.0530303030303 15.475 14.625 14.091666666666669
# Sjekk at det stemmer:
print(temp[0]+temp[0+1])
print(temp[1]+temp[1+1])
print(temp[2]+temp[2+1])
print(temp[3]+temp[3+1])
16.0530303030303 15.475 14.625 14.091666666666669
Del 2, oppgave 2¶
Bruk en for-løkke til å printe ut temperatur + gjennomsnittstemperatur til de 10 første temperatur-verdiene
Tilbake til problemstillingen med indeksene:
Vi lager "indeks" på nytt og konverterer alle elementene til integers ved hjelp av en for-løkke
indeks=[
0,
len(tid)*1/4,
len(tid)*2/4,
len(tid)*3/4,
len(tid)
]
indeks
[0, 40.0, 80.0, 120.0, 160]
int(indeks[2])
80
for-løkker gjør en operasjon mange ganger etter hverandre. Hvor mange ganger bestemmes i den første linjen: for i in range(5)
betyr feks at operasjonen skal gjøres fem ganger. range(5)
iterer fra 0 til og med 4, altså 5 iterasjoner. i
blir da 0
i første iterasjon, 1
i neste iterasjon osv.
for i in range(len(indeks)):
indeks[i]=int(indeks[i])
print('i=',i,', indeks[i]=',indeks[i])
indeks
i= 0 , indeks[i]= 0 i= 1 , indeks[i]= 40 i= 2 , indeks[i]= 80 i= 3 , indeks[i]= 120 i= 4 , indeks[i]= 160
[0, 40, 80, 120, 160]
Nå kan vi f.eks. sammelingne de fire 40-årsperiodene ved å beregne gjennomsnittstemperaturen i hver periode.
for i in range(4):
mean=np.mean(temp[indeks[i]:indeks[i+1]])
print('Gj.snitt temp for periode nr.', i, '=', np.round(mean,2))
Gj.snitt temp for periode nr. 0 = 7.26 Gj.snitt temp for periode nr. 1 = 7.37 Gj.snitt temp for periode nr. 2 = 7.65 Gj.snitt temp for periode nr. 3 = 8.23
Gjennomsnittstemperaturen for den siste 40-årsperioden er en hel grad varmere enn gjennomsnittstemperaturen for den første 40-årsperioden.
Vi kan også sammenligne de fire 40-årsperiodene ved å se på en figur:
plt.plot(temp[indeks[0]:indeks[1]], label='1')
plt.plot(temp[indeks[1]:indeks[2]], label='2')
plt.plot(temp[indeks[2]:indeks[3]], label='3')
plt.plot(temp[indeks[3]:indeks[4]], label='4')
plt.legend(ncol=4)
plt.ylabel('Temperatur')
plt.xlabel('Indeks')
plt.show()
Dette kan vi også gjøre med en for-løkke, så slipper vi så mange nesten like kodelinjer:
for i in range(4):
plt.plot(temp[indeks[i]:indeks[i+1]], label=str(i+1))
plt.legend(ncol=4)
plt.ylabel('Temperatur')
plt.xlabel('Indeks')
plt.show()
Fordelen med for-løkker er at man slipper å skrive så mange nesten helt like kode-linjer.
I eksemplene over har vi brukt for in in range(x)
. Da er i
indeksen til hver iterasjon. Men man kan også la i
være faktiske verdier slik som under:
for val in indeks:
print(val)
# Sjekk at det er det samme:
indeks
0 40 80 120 160
[0, 40, 80, 120, 160]
Ved å bruke dette kan man gjøre for-løkken for plotting en del penere:
diff=int(len(tid)/4) # Fordi vi vet at vi har delt tidsserien inn i fire like deler
diff
40
for ind in indeks[:-1]:
plt.plot(temp[ind:ind+diff], label=str(ind))
plt.legend(ncol=4)
plt.ylabel('Temperatur')
plt.xlabel('Indeks')
plt.show()
Del 2, oppgave 4¶
- Bruk en forløkke til å dele tidsserien inn i 5 like lange deler.
- Bruk er for-løkke til å plotte alle de 5 periodene i samme figur.
I blant trenger man både en faktisk verdi og indeksen. Da kan man bruke enumerate
istedet for range
:
for count, val in enumerate(indeks):
print('count:', count, 'val:', val)
count: 0 val: 0 count: 1 val: 40 count: 2 val: 80 count: 3 val: 120 count: 4 val: 160
Ved å bruke dette kan man gjøre begge de to siste stegene vi har gjort over (1: konvertere til integers og 2: plotte) inne i samme løkke.
indeks=[
0,
len(tid)*1/4,
len(tid)*2/4,
len(tid)*3/4,
len(tid)
]
indeks
[0, 40.0, 80.0, 120.0, 160]
for count, val in enumerate(indeks[:-1]):
ind=int(val) # konverter indeksen til integer
plt.plot(temp[ind:ind+diff], label=str(ind))
plt.legend(ncol=4)
plt.ylabel('Temperatur')
plt.xlabel('Indeks')
plt.show()
For å gjøre poenget med for-løkker enda tydeligere: Hva om vi vil beregne gjennomsnitt og standardavvik for ti-årsperiode, og så lage en figur over alle disse punktene?
# lag ny indekseringsvektor:
diff=10 # Fordi vi vet at vi vil se på tiårsperioder
# Vi bruker np.arange fordi vi vet tidssteget vi vil ha
# syntaksen er: np.arange(start, stop, step)
indeks=np.arange(0, len(tid), diff)
indeks
array([ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150])
for count, val in enumerate(indeks):
# Når f.eks. val=30 blir linjen under: np.mean(temp[30:30+10]), som er
# tiårsperiode nr 4.
mean=np.mean(temp[val:val+diff])
std=np.std(temp[val:val+diff])
# Vi vil plotte punktene midt i perioden de representerer. Derfor finner vi
# verdien til "tid" ikke ved f.eks. 30 eller 40, men på indeks nr 35.
tid_mean=tid[int(val+diff/2)]
plt.plot(tid_mean, mean, 'rp')
plt.plot(tid_mean, mean+std, 'cp')
plt.plot(tid_mean, mean-std, 'cp')
# TIPS: Hvis vi satte label-navn i plottelinjene over ville legend blitt
# veldig fordi hvert eneste punkt ville fått en linje i legend. I slike
# tilfeller kan man plotte NaN-punkt og angi markørstil som matcher de
# faktiske punktene man vil ha i legend.
plt.plot(np.nan, np.nan, 'rp', label='mean')
plt.plot(np.nan, np.nan, 'cp', label='mean +\- std')
plt.legend()
plt.ylabel('Temperatur')
plt.xlabel('År')
plt.show()