4.3 Plotting med matplotlib
Plotte én eller flere kurver med etiketter og legende — den faste del-b-en på ODE- og differenslikning-oppgaver.
Plotting er i 7 av de 8 nyere settene (88 %), nesten alltid som en del-b: «plott løsningen», «plott begge bestandene i samme figur», «lag et plott med tid langs førsteaksen». Det er aldri hele oppgaven, men det er nesten alltid noen poeng — og de er blant de billigste på hele eksamen, fordi koden er nesten identisk hver gang.
Prioritet: kunne (nivå 2 av tre: må sitte, kunne, kjenne til). Plottedelen henger sammen med to andre sjangre: differenslikninger (sjanger H, 75 %) og ODE-systemer (sjanger J, 100 %) har begge plottet som siste deloppgave.
Det er tre ting sensor ser etter, og alle tre er lette å glemme under tidspress:
1. at hver kurve har en label, og at plt.legend() faktisk kalles
2. at begge aksene er navngitt
3. at plt.show() står til slutt
Eksamen er hjelpemiddelfri, og du kan altså ikke kjøre koden for å se om figuren ble riktig. Derfor øver dette kapitlet også på det motsatte: å beskrive figuren i ord, slik at du selv kan kontrollere at koden gir det du mener den gir.
Forkunnskaper
- NumPy-tabeller, særlig np.linspace og elementvise operasjoner: kap. 4.1. Et plott er alltid to tabeller plottet mot hverandre.
- Kolonner i en tabell med to indekser (u[:, 0]), også fra kap. 4.1 — det er slik en ODE-løsning kommer til deg i Del 7.
- Verditabeller fra kap. 4.2.
- Løkker fra kap. 1.3, til den ene oppgaven der en kurve bygges opp punkt for punkt.
Kapitlet er delt i tre løkker.
Løkke 1 — malen med én kurve (~13 min)
En ball kastet rett opp har høyden meter. Vil du se banen, trenger du to tabeller: en tett rekke tidspunkter, og de tilsvarende høydene. Deretter ber du matplotlib tegne den ene mot den andre.
matplotlib er Pythons standardbibliotek for figurer, og delen vi bruker heter pyplot. Importlinjen er alltid den samme, og som med NumPy gir vi biblioteket et kort kallenavn.
Merk at figurene i denne boka er beskrevet i ord under koden, ikke vist som bilder. Det er med vilje: på eksamen skal du selv kunne si hva koden din tegner, uten å kjøre den.
Importlinjen som gjør plottefunksjonene tilgjengelige under kallenavnet plt. Den skal alltid stå med når oppgaven ber om kjørbar plottekode — glemt import er trekk selv om resten er riktig. Legg merke til at det er matplotlib.pyplot som importeres, ikke bare matplotlib: uten .pyplot finnes ikke plt.plot.
Tegner punktene og trekker rette linjer mellom dem. De to tabellene må ha like mange elementer — det er derfor np.linspace(a, b, N+1) og en verditabell laget fra samme x alltid passer sammen. Med mange punkter ser den brukne linja ut som en glatt kurve.
Kallet tegner ingenting på skjermen ennå; det legger kurven inn i figuren. Figuren vises først når plt.show() kalles.
Setter tekst på de to aksene: plt.xlabel('tid (s)') på førsteaksen (den vannrette) og plt.ylabel('høyde (m)') på andreaksen (den loddrette). Argumentet er en streng, og enheten hører med. Dette er ett av de tre punktene sensor ser etter, og det koster to linjer.
Åpner figurvinduet og viser alt som er lagt inn med plt.plot og aksekallene. Uten plt.show() regner programmet ut alt riktig, men du ser ingenting — og på eksamen er det trekk, fordi oppgaven ba om et plott. Kallet står alltid sist, etter alle plot- og label-kall, og har ingen argumenter.
Plott høyden til ballen for mellom 0 og 8 sekunder, med navngitte akser.
Først en tett tidstabell, så høydene, så plottet. 101 punkter gir en kurve som ser glatt ut.
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(0, 8, 101)
h = 4*t - 0.5*t**2
plt.plot(t, h)
plt.xlabel('tid (s)')
plt.ylabel('høyde (m)')
plt.show()Figur i ord: Førsteaksen viser tid fra 0 til 8 sekunder, andreaksen høyde i meter fra 0 til 8. Kurven er en nedadvendt parabel som starter i origo, stiger til toppunktet etter fire sekunder, og faller symmetrisk tilbake til 0 ved . Ingen legende, fordi det bare er én kurve.
Dette er hele malen. Alt annet i kapitlet er varianter av de fem linjene: flere kurver, navn på kurvene, tittel, eller andre akser.
Sensormerknad: de fire tingene som gir uttelling her er de to importene, at t og h har samme lengde, plt.plot(t, h) med tiden som første argument, og plt.show() til slutt. Bytter du om argumentene, får du en figur — bare speilvendt om diagonalen.
Nesten alle trekk på plotteoppgaver skyldes en av disse tre, og de har ingenting med matematikk å gjøre:
1. Glemt label= eller glemt plt.legend(). Har figuren to eller flere kurver uten legende, kan ingen se hvilken som er hvilken. Begge delene må med: label= inne i hvert plot-kall, og plt.legend() én gang etterpå.
2. Glemt plt.show(). Programmet kjører feilfritt og viser ingenting.
3. Glemt aksenavn. plt.xlabel og plt.ylabel er to linjer, og de er nevnt i oppgaveteksten omtrent hvert år.
En fjerde felle er å plotte feil kolonne av en løsningstabell — den kommer i neste løkke.
(Innstegsoppgave.) Programmet under skal plotte for 51 punkter mellom og 3, med navngitte akser. Tre linjer mangler. Skriv den ferdige koden.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 51)
y = x**2Løkke 2 — flere kurver i samme figur (~14 min)
Den vanlige eksamensvarianten er ikke én kurve, men to eller tre i samme figur: mottakelige, smittede og immune i en epidemimodell, eller byttedyr og rovdyr i en bestandsmodell. Da kaller du plt.plot én gang per kurve før du kaller plt.show(), og gir hver kurve et navn med label=.
I Del 7 kommer løsningen av et ODE-system til deg som en tabell med to indekser: én rad per tidspunkt og én kolonne per komponent. Da er u[:, 0] første komponent, u[:, 1] den andre og u[:, 2] den tredje. Det er derfor kolonneutsnittet fra kap. 4.1 er en forkunnskap her.
plt.plot(t, S, label='S') gir kurven navnet S, og plt.legend() tegner en liten boks i figuren som viser hvilken farge som hører til hvilket navn. De to hører sammen: label= uten legend() gir ingen boks, og legend() uten label= gir en tom eller nummerert boks. Kall plt.legend() én gang, etter alle plot-kallene.Plotter første komponent av en løsningstabell med to indekser mot tiden. u[:, 0] betyr «alle rader, kolonne 0», altså hele tidsforløpet til den første størrelsen i systemet. Antall rader i u er det samme som antall elementer i t, så de passer alltid sammen. Bommer du på kolonnenummeret, får du en helt annen kurve — figuren blir tegnet, men den viser feil størrelse.
Setter en overskrift over figuren: plt.title('Epidemiforløp'). Ikke påkrevd med mindre oppgaven ber om det, men det koster én linje og gjør figuren lettere å lese. Tittelen erstatter ikke aksenavnene og ikke legenden.
En epidemimodell gir en løsningstabell u med 61 rader (ett døgn per rad) og tre kolonner: mottakelige, smittede og immune. Plott alle tre kurvene i samme figur, med legende og navngitte akser.
Tabellen kommer normalt fra en ODE-løser (Del 7). Her lager vi den med tre kjente uttrykk, bare for å ha noe å plotte — plottekoden er identisk uansett hvor tallene kommer fra.
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(0, 60, 61)
u = np.zeros((61, 3))
u[:, 0] = 900*np.exp(-0.08*t) + 100
u[:, 1] = 300*(np.exp(-0.05*t) - np.exp(-0.15*t))
u[:, 2] = 1000 - u[:, 0] - u[:, 1]
plt.plot(t, u[:, 0], label='mottakelige')
plt.plot(t, u[:, 1], label='smittede')
plt.plot(t, u[:, 2], label='immune')
plt.xlabel('tid (døgn)')
plt.ylabel('antall personer')
plt.legend()
plt.show()Tallene bak kurvene er verdt å se på, siden de bestemmer hva figuren viser:
import numpy as np
t = np.linspace(0, 60, 61)
S = 900*np.exp(-0.08*t) + 100
I = 300*(np.exp(-0.05*t) - np.exp(-0.15*t))
R = 1000 - S - I
for j in [0, 10, 20, 40, 60]:
print(f'{t[j]:5.1f} {S[j]:8.1f} {I[j]:8.1f} {R[j]:8.1f}')Utskrift:
0.0 1000.0 0.0 0.0
10.0 504.4 115.0 380.6
20.0 281.7 95.4 622.9
40.0 136.7 39.9 823.5
60.0 107.4 14.9 877.7Figur i ord: Førsteaksen viser tid fra 0 til 60 døgn, andreaksen antall personer fra 0 til 1000. Kurven «mottakelige» starter i 1000 og faller bratt de første to ukene, deretter flatt ut mot rundt 107. Kurven «smittede» starter i 0, stiger til en topp på om lag 115 personer rundt dag 11, og avtar sakte mot 15. Kurven «immune» starter i 0 og vokser hele veien, brattest de første tjue døgnene, mot om lag 878. Summen av de tre er 1000 i hele tidsrommet. Legenden nede til høyre navngir de tre kurvene.
Sensormerknad: tre plot-kall med hver sin label, én legend(), to aksenavn og én show(). Plotter du bare to av tre kurver, mister du bare det ene delpoenget — strukturen er fortsatt riktig, og det belønnes.
Du har tidsarrayet t og løsningstabellen u med tre kolonner, som beskriver antall byttedyr, antall rovdyr og tilgjengelig plantebiomasse.
a) Skriv koden som plotter byttedyr og rovdyr i samme figur med legende og navngitte akser, uten å plotte plantebiomassen.
b) Hva blir galt hvis du skriver plt.plot(u[:, 0], t) i stedet?
(Midtveisnivå, sjanger E — «hvilken linje mangler?».) Programmet skal vise en figur med to navngitte kurver.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 4, 41)
plt.plot(x, np.sin(x), label='sinus')
plt.plot(x, np.cos(x), label='cosinus')
plt.xlabel('x')
plt.ylabel('funksjonsverdi')
plt.show()Figuren dukker opp, men navnene på kurvene vises ikke noe sted. Hvilken linje mangler, og hvor må den stå?
En målestasjon i en fjord måler overflatetemperaturen én gang i døgnet. De ti målingene ligger i lista
maalt = [4.1, 4.6, 5.2, 6.0, 6.9, 7.6, 8.1, 8.3, 8.2, 7.9]der den første er tatt på døgn 0. En enkel modell for den samme perioden er grader celsius.
a) Bygg tidstabellen dogn punkt for punkt med en løkke, slik at den får like mange elementer som maalt.
b) Skriv hele programmet som plotter målingene og modellkurven i samme figur. Modellkurven skal regnes ut i 91 punkter mellom 0 og 9, og figuren skal ha legende og navngitte akser.
c) Hvorfor regnes modellkurven ut i 91 punkter når måleserien bare gir 10?
Løkke 3 — fasekurve og figuren i ord (~13 min)
Noen ganger er ikke tiden det interessante. I en rovdyr–byttedyr-modell vil du kanskje se hvordan de to bestandene henger sammen: hvordan antall rovdyr utvikler seg som funksjon av antall byttedyr. Da plotter du de to størrelsene mot hverandre i stedet for mot tiden.
Det kalles en fasekurve, og koden er den samme malen — bare med andre argumenter: plt.plot(B, R). Tiden er ikke lenger en akse; den er «langs kurven». En syklisk modell gir da en lukket løkke, mens en modell som går mot likevekt gir en spiral inn mot ett punkt.
Til slutt i denne løkka: å beskrive figuren i ord. På hjelpemiddelfri eksamen er dette selvkontrollen din, og noen oppgaver ber deg eksplisitt forklare hva plottet viser.
Et plott der to størrelser fra samme simulering plottes mot hverandre i stedet for mot tiden: plt.plot(B, R) med byttedyr på førsteaksen og rovdyr på andreaksen. Tiden er ikke en akse, men rekkefølgen punktene tegnes i. En periodisk løsning gir en lukket kurve, en løsning som går mot likevekt gir en spiral inn mot ett punkt, og en løsning som vokser gir en kurve som beveger seg utover.
Aksenavnene må da være de to størrelsene, ikke tid — det er den vanligste feilen når man bytter fra tidsplott til fasekurve.
Standardsvaret på «forklar hva plottet viser» har fire deler, i denne rekkefølgen: hvilke akser (hva som står langs hver, med enhet og område), hvor kurvene starter, hvordan de beveger seg (stiger, faller, har topp, svinger med hvilken periode), og hva de nærmer seg til slutt. Har figuren flere kurver, sier du hvilken som er hvilken.
Fire til seks setninger er nok. Dette er samtidig kontrollen din på hjelpemiddelfri eksamen: klarer du å si hva koden tegner, har du oftest også skrevet den riktig.
To bestander svinger periodisk: byttedyr rundt 40 individer og rovdyr rundt 9, med samme periode men forskjøvet i tid. Plott først begge mot tiden, deretter rovdyr mot byttedyr som fasekurve.
Tallene her er valgt for å gi to jevne svingninger; i kap. 6.3 kommer de fra en simulering du skriver selv.
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(0, 60, 601)
B = 40 + 20*np.sin(0.3*t)
R = 9 + 4*np.cos(0.3*t)
plt.plot(t, B, label='byttedyr')
plt.plot(t, R, label='rovdyr')
plt.xlabel('tid (måneder)')
plt.ylabel('antall individer')
plt.legend()
plt.show()Figur i ord (tidsplottet): Førsteaksen viser tid fra 0 til 60 måneder, andreaksen antall individer fra om lag 5 til 60. Byttedyrkurven svinger mellom 20 og 60 med periode måneder, og starter i 40 på vei oppover. Rovdyrkurven svinger mellom 5 og 13 rundt middelverdien 9, og starter på sitt maksimum. De to toppene ligger en kvart periode fra hverandre.
Så fasekurven. Legg merke til at aksenavnene nå er de to bestandene:
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(0, 60, 601)
B = 40 + 20*np.sin(0.3*t)
R = 9 + 4*np.cos(0.3*t)
plt.plot(B, R)
plt.xlabel('antall byttedyr')
plt.ylabel('antall rovdyr')
plt.title('Fasekurve')
plt.show()Figur i ord (fasekurven): Førsteaksen viser antall byttedyr fra 20 til 60, andreaksen antall rovdyr fra 5 til 13. Kurven er en lukket, oval løkke som tegnes om og om igjen, med sentrum i punktet . At kurven er lukket betyr at bestandene gjentar samme forløp periodisk — hadde de gått mot likevekt, ville kurven spiralert innover mot ett punkt.
Etter en simulering har du tidsarrayet t og de to bestandsarrayene B og R, alle med 401 elementer.
a) Skriv koden som plotter fasekurven med byttedyr langs førsteaksen.
b) Fasekurven blir en lukket løkke. Hva forteller det om løsningen?
c) Hva ville en spiral innover mot ett punkt fortalt i stedet?
En oppgave ber om et plott av for 201 punkter i , sammen med den øvre innhyllingskurven , i samme figur med legende.
a) Skriv hele programmet.
b) Beskriv figuren i ord, med de fire delene: akser, start, forløp, sluttoppførsel.
c) Hvorfor kan du ikke bruke exp fra math her?
1. Glemt plt.legend() når kurvene har label=. Navnene vises da ikke i det hele tatt. Vanligste plottefeil i faget.
2. Glemt plt.show(). Ingen figur, selv om alt annet er riktig.
3. Glemt aksenavn. To linjer, nevnt i oppgaveteksten nesten hvert år.
4. Feil kolonne av løsningstabellen. u[:, 1] der u[:, 0] var ment gir en pen figur av feil størrelse. Sjekk mot rekkefølgen i høyresiden din.
5. Byttet argumentrekkefølge. plt.plot(y, x) gir en speilvendt figur uten feilmelding.
6. Tidsakse i en fasekurve. En fasekurve plotter de to størrelsene mot hverandre; tiden er ikke en akse, og aksenavnene må endres tilsvarende.
7. math-funksjoner i plottekode. Tabellene er arrays, så bruk np.exp, np.sin, np.sqrt.
Begrepsbank
Begrepsbanken er flashcard-/repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.
De fem linjene som løser nesten hver plotteoppgave: plt.plot(x, y, label='...') for hver kurve, plt.xlabel(...), plt.ylabel(...), plt.legend() når det er mer enn én kurve, og plt.show() til slutt. Rekkefølgen er fast, og show står alltid sist.
Nok til at kurven ser glatt ut. 101 til 201 punkter er rikelig for en funksjonskurve, og for en simulering bruker du de punktene du alt har. Med bare 5–10 punkter ser kurven kantet ut, men den er ikke feil — matplotlib trekker alltid rette linjer mellom punktene.
Et tidsplott har tid langs førsteaksen og størrelsen langs andreaksen: plt.plot(t, B). En fasekurve plotter to størrelser mot hverandre: plt.plot(B, R), uten tidsakse. Samme data kan altså vises på to måter, og oppgaveteksten sier hvilken som er ment.
En fasekurve som lukker seg til en løkke betyr at løsningen er periodisk: systemet kommer tilbake til samme tilstand etter én periode. En spiral inn mot ett punkt betyr dempede svingninger mot en likevekt, og en kurve som beveger seg utover betyr voksende svingninger.
I en løsningstabell fra en ODE-løser er hver rad ett tidspunkt og hver kolonne én komponent. Derfor er u[:, 0] tidsforløpet til første komponent (det du plotter), mens u[0, :] er alle komponentene ved starttidspunktet (ett punkt per komponent). Å plotte det siste mot t gir feil lengder og en feilmelding.
Fire deler: hvilke akser med enhet og område, hvor kurvene starter, hvordan de beveger seg, og hva de nærmer seg. Med flere kurver sier du også hvilken som er hvilken. Fire til seks setninger er nok, og det er selvkontrollen din på en hjelpemiddelfri eksamen.
Kall plt.plot én gang per kurve, alle før plt.show(). Hver kurve får sin egen label=, og plt.legend() kalles én gang etterpå. Matplotlib velger fargene selv, så du trenger ikke oppgi dem — og på eksamen kreves det ikke.
Første argument er størrelsen langs den vannrette aksen, andre argument langs den loddrette. plt.plot(t, h) gir høyde som funksjon av tid; plt.plot(h, t) gir det speilvendte, uten noen feilmelding. Feilen er stille, og den fanges bare ved å lese aksenavnene du selv har skrevet.
Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.
Skolesaga er en uavhengig læringsressurs og er ikke tilknyttet eller godkjent av Universitetet i Oslo. Dette er ikke offisielt studiemateriell. Les mer.