9  Teikning með Matplotlib

9.1  Inngangur

Matplotlib er teiknipakki fyrir Python sem er byggður á tölvugrafík í Matlab-kerfinu. Með Matplotlib er hægt að teikna (eða birta, sýna) myndir (images), hæðarlínur (countours), punktarit (scatter plot), línurit og gröf (line plots) og þrívíðar upplýstar myndir. Pakkinn er þannig að framendinn (frontend) eða skilgreining teikningarinnar er aðskilinn frá bakendanum (backend) sem birtir hana. Þannig er hægt að skrifa forrit sem býr til teikningu, og svo er hægt að sýna teikninguna inni í Jupyter-bók, sem sjálfstæðan glugga á skjá, á vef, í pdf-skjali, í Latex-skjali, á prentara o.s.frv. án þess að breyta teikniforritinu sjálfu. Auk þess er hægt að fá alls kyns viðbætur við Matlplotlib, t.d. fyrir kortagerð, þrívíða teikningu, samskipti við Pandas og samskipti við Excel, svo fáeinar séu nefndar. Af þessum viðbótum er er líklega helstan að telja pakkann Seaborn, sem tengist Pandas nánum böndum. Markmið höfunda Seaborn er að teikna sem „nútímalegust“ tölfræðileg gröf á sem sjálfvirkastan máta.

Eftirfarandi mynd sýnir dæmi um myndir teiknaðar með Matplotlib.

../_images/matplotlib-dæmi.jpeg

Mynd 9.1: Matplotlib myndir

Athugasemd: Skipun eða fall

Hér verður talað um teikniskipanir þegar strangt tiltekið ætti kannski frekar að tala um teikniföll: scatter-fallið, plot-fallið o.s.frv. Einn kostur við að segja frekar skipun er að það veldur síður ruglingi þegar verið er að teikna föll.

9.2  Einfaldar myndir

9.2.1  Undirbúningur teikningar

Venjulegasta notkun Matplotlib er að nota undirpakkann pyplot og í Matplotlib notendahandbókinni er mælt með að flytja hann inn sem plt, svo það er fyrir vikið alsiða. Teiknaðar myndir birtast sjálfkrafa inni í bókinni, neðan við forritsreitinn. Hér er kafli sem hægt er að setja fremst í vinnubók sem teiknar, þar sem bætt hefur verið við skipunum sem láta rúðunet teiknast undir öðrum teiknuðum hlutum, breyta sjálfgefinni stærð myndar, og koma í veg fyrir að fyrirsagnir og ásamerkingar yfirskrifi hver aðra.

# Frumstilling teikningar
import matplotlib.pyplot as plt
plt.rc('axes', axisbelow=True)
plt.rc('figure', figsize=(8,4))  # (6,4) er sjálfgefið
plt.rc('figure.constrained_layout', use=True)  # forðast skörun myndhluta

9.2.2  Punktarit og línurit

Ein mikilvægasta notkunin á Matplotlib er að teikna myndir af talnalistum, sem geta hvort sem er verið venjulegir Python listar eða NumPy vigrar eins og við kynnumst í 10. kafla.

Hér er forrit sem teiknar punktana \((x,y)\) þar sem \(y = \sqrt x\) fyrir \(x = 0, 1, 2, 3, 4, 5\) og tengir þá með beinum línustrikum. Skipunin plt.figure býr til nýja mynd og tilgreinir breidd hennar og hæð, scatter-skipunin teiknar punktana sjálfa með flatarmál u.þ.b. 40 ferpunkta (punktur ≈ 1/3 mm) og plot-skipunin teiknar strikin á milli þeirra. Að lokum teiknar grid rúðunet. Í skipuninni er hægt að tilgreina lit punkta, sbr. fyrra sýnidæmið í kafla 9.3.2 og þeir geta líka verið hver með sínum lit sbr. dæmið um besta plan í kafla 13.5.

from math import sqrt
x = list(range(6))
y = [sqrt(xi) for xi in x]
plt.figure(figsize=(7,3))
plt.scatter(x,y,s=50)
plt.plot(x,y)
plt.grid(True)
../_images/matplotlib-kynning.png

Athugasemd: Einingar í figsize

Stærð myndarinnar er gefin í tommum og (6,4) er sjálfgefið. Myndin er sköluð niður í ca. 2/3 þegar hún birtist á venjulegum fartölvuskjá, en er (a.m.k. nokkurnvegin) ósköluð ef bókin er prentuð sem pdf.

Æfing:

Afritið skipanirnar að ofan inn í vinnubók og keyrið. Prófið að breyta:

  • fjölda punkta

  • stærð myndarinnar

  • stærð punktanna (aftasti stikinn í scatter)

  • sleppa rúðunetinu

9.2.3  Súlurit

Hér er búið til súlurit (histogram) af normaldreifðum slembigögnum. Stikinn bins gefur fjölda súlna og range gefur svæðið á x-ás sem súluritið nær yfir. Góð regla er að láta súlurnar mætast í rúnnuðum (round) tölum t.d. heilum eða hálfum (hér mætast þær í hálfum tölum). Kallið gauss(mu,sigma) skilar slemitölu úr normaldreifingu með meðaltal mu og staðalfrávik sigma (Gauss-dreifing er annað nafn á normaldreifingu)

import matplotlib.pyplot as plt
from random import gauss
x = [gauss(0,1) for i in range(500)]
plt.hist(x, bins=12, range=(-3,3))
plt.xlabel('x')
plt.ylabel('fjöldi gilda á hverju bili (af 500)')
plt.show()
../_images/sulurit.png

Skipunin hist reiknar sjálf hæð hverrar súlu en það er líka hægt að láta hæð súlnanna koma úr lista eða vigri með því að nota skipunina bar. Um það er sýnt dæmi í næsta kafla.

Æfing:

Prófið skipanirnar að ofan. Prófið að fækka og fjölga punktum. Prófið líka að nota 6 súlur (það þarf að breyta bæði bins og range).

Athugasemd: Kyrrstæðar og lifandi myndir

Myndirnar í þessum 9. kafla eru kyrrstæðar (static) en Matplotlib getur líka búið til bæði hreyfimyndir (animated) og lifandi (interactive) myndir með stýrihlutum (widgets), t.d. hnöppum og sleðum, hvort sem er í úttaksglugga í vinnubók eða í sérstökum sjálfstæðum myndaglugga (amk. með JupyterLab). Þá má láta myndina hreyfast eins og í teiknimynd eða breytast með músastýringu. Lítið dæmi um lifandi mynd með stýrisleða er í kafla 16.2.4 og dæmi um hreyfimynd er í kafla 16.3.2.

Skipunin plt.show() er valkvæð þegar teiknað er beint í vinnubók, en hana þarf ef sérstakur gluggi er opnaður. Notkun hennar hefur þann viðbótarkost að losna við aukaupplýsingar sem sumar teikniskipanir skrifa ef þær eru aftast í vinnubók.

9.3  Teikningar af gögnum í skrám

9.3.1  Innlestur talnagagna í dálkum

Algengt er að maður vilji teikna upplýsingar sem eru geymdar í skrám, og oft eru það dálkar í skránni sem þarf að teikna. Í sýnidæminu í kafla 8.2.4 var sýnt hvernig hægt er að lesa dálka í slíkri skrá inn í lista af strengjum. Ef dálkarnir geyma tölur þarf beita float-fallinu á innihaldið. Til að lesa inn skrá xy.txt með tveimur talnadálkum má nota:

x = []
y = []
with open("xy.txt") as f:
   for lína in f:
       (xs, xy) = lína.split()
       x.append(float(xs))
       y.append(float(xy))
plt.plot(x,y)

Ef skráin er á vefnum þarf í fyrsta lagi að nota urlopen í stað open og svo þarf líka að nota decode() á línuna, eins og gert var í kafla 8.2.4. Annar möguleiki er svo að nota NumPy sem er á dagskrá í 10. kafla.

9.3.2  Dæmi um teikningu talnagagna

Hér eru tvö sýnidæmi um teikningu gagna sem eru fengin úr gagnaskrám. Það fyrra teiknar punktarit (scatter plot) og það síðara súlurit.

Sýnidæmi: Eðlisþyngd og bræðslumark

Í kafla 8.3.4 var skoðuð skrá með bræðslumarki og eðlisþyngd fjögurra málma (sbr. líka verkefni 17. Hér er forrit sem teiknar punktarit af skránni.

from urllib.request import urlopen
import matplotlib.pyplot as plt
with urlopen("https://cs.hi.is/python/malmar.txt") as f:
  next(f)  # sleppa fyrirsögnum
       nafn = []
  eðlisþ = []
  bræðslum = []
  for lína in f:
    (n,e,b) = lína.decode().split()
    nafn.append(n)
    eðlisþ.append(float(e))
    bræðslum.append(float(b))

plt.scatter(eðlisþ, bræðslum, s=40, color='Crimson')
plt.grid(True)
plt.xlim(0,20)
plt.ylim(800,1800)
for (x,y,n) in zip(eðlisþ, bræðslum, nafn):
  plt.text(x, y, n + " ", fontsize=16, color='DarkBlue',
           ha="right", va="center")
plt.xlabel('Eðlisþyngd g/ml')
plt.ylabel('Bræðslumark °C')
plt.show()

Hér fylgir myndin sem birtist, og tækifærið hefur verið notað til að sýna hvernig hægt er að skrifa texta inn á mynd með plt.text (sjá töflu 9.2)

../_images/malmar.jpg

Sýnidæmi: Kosningasúlurit

Hér er dæmi sem teiknar súlurit af kosningaúrslitunum 2021. Þessi úrslit eru aftur á dagskrá í verkefnum verkefni 16) og 24; í því síðara er fínna súlurit búið til.

from urllib.request import urlopen
import matplotlib.pyplot as plt
with urlopen("https://cs.hi.is/python/kosningar-2021.txt") as f:
  next(f)
  listi = []
  atkvæði = []
  for lína in f:
    (l, a, þs) = lína.decode().split()
    listi.append(l)
    atkvæði.append(float(a))

x = range(len(listi))
plt.figure(dpi=90)
plt.bar(x, atkvæði, color="tomato")
plt.xticks(x, listi);
plt.ylabel('Atkvæði')
plt.title('Úrslit alþingiskosninga 2021')
plt.show()

Hér er verður x listinn [0,1,2…10] (það voru 11 framboðslistar). Hér er listi yfir liti. Forritið teiknar svo þessa mynd:

../_images/kosningar-2021.jpg

Æfing: Súlurit af einkunnum

Notið hist til að teikna súlurit af einkunnunum í skránni https://cs.hi.is/python/einkunn.txt.

9.4  Töflur yfir Matplotlib-skipanir

Hér eru töflur yfir helstu teikniskipanirnar, stýristika og stýriskipanir. Aftast í seinni dálki taflanna er víða í svigum tilvísun í kafla sem útskýrir notkun viðkomandi skipunar eða stika. Eins og framar í þessum fyrirlestrarnótum eru töflurnar fyrst og fremst hugsaður til uppflettingar og ekki til að læra utanað.

Fyrst er tafla yfir aðal teikniskipanirnar og svo sérstök tafla bara um hvernig hægt er að setja texta inn á myndir með text-skipuninni.

Tafla 9.1: Helstu teikniskipanir

figure

Býr til mynd (kafli 9.2.2 og 9.6.2)

scatter

Teiknar punkta í tilgreindum x- og y-hnitum (9.2.2)

plot

Teiknar línustrik sem tengja saman punkta 9.5.1 og 9.6.1)

hist

Teiknar súlurit yfir tíðni – hæð súlna fæst með talningu (9.2.3 og 9.5.3)

bar

Teiknar súlurit með gefinni súluhæð (9.3.2)

savefig

Vistar teikningu í png-skrá (savefig("skrá.png"))

text

Skrifar texta inn á mynd (sjá töflu ref:%s<text-fallið>) (9.3.1)

show

Birtir mynd (þarf ekki ef teiknað er í glugga í vinnubók (9.2.3)

Tafla 9.2: Texti settur inn á mynd

text(x,y,"texti")

skrifar texta aftan við punkt (x,y)

text(..., fontsize=n)

– með n punkta letri (9.3.1)

text(...,va="top")

– neðan við punkt (vertical alignment) (9.3.1)

text(...,va="bottom")

– ofan við punkt (eða center/baseline)

text(...,ha="left")

– aftan við punkt (horizontal algn)

text(...,ha="center")

– með punkt í miðjum texta (eða right)

Teikniskipanirnar bjóða svo upp á fjölda stýristika (control parameters) til að stjórna lit teikninga, breidd súlna, leturstærð, merkingum ása o.s.frv. Auk stýristikanna eru svo notaðar ýmsar hjálpar- eða stýriskipanir til breyta mörkum ása, bæta við skýringartextum, rúðuneti o.fl. Næstu töflur gefar yfirlit yfir helstu stýristika og stýriskipanir.

Tafla 9.3: Helstu stikar í plot, hist og bar

color

Litur línurits (grafs) eða súlurits. Má skammstafa r, g, b, y, w, k
(black), c (cyan), m (magenta) eða nota streng með litanafni;
color má skammstafa c í plot en ekki í hist og bar (9.3.2 og 9.5.1).

alpha

gagnsæi, 0 alveg gagnsætt, 1 alveg ógagnsætt (sjálfgefið)

linewidth eða lw

Breidd línu í línuriti eða súluramma í súluriti. Eining punktar
(~1/3 mm), sjálfgefið lw=1.5 (9.5.1)

linestyle eða ls

Línutegund, getur t.d. verið '' (engin lína) ':'
(punktalína), '-' (heil lína, sjálfgefið) (9.5.1)

marker

Merki fyrir punkta í plot. Algengur marker er 'o' (en líka má nota
'.' '+' 'x' og fleiri); plot-skipun með marker = 'o', ls=''
gefur svipaða niðurstöðu og scatter með sjálfgefnum stikum (9.5.1)

markersize eða ms

Stærð markers í plot, sjálfgefið 6 punktar (~2 mm) (k. 9.5.1)

edgecolor eða ec

Litur á rönd súlna eða merkja. Ein tegund súlurita notar
color='w', ec='k' (9.5.3)

label

notað með legend-skipun, (sjá töflu 9.5) (9.5.2)

bins

fjöldi súlna (9.2.3 og 9.5.3)

range

ytri mörk súlurits (gott að velja bins í samræmi við range) (k. 9.2.3 og 9.5.3)

rwidth

„relative width“ (sjálfgefið 1.0) (9.5.3)

Tafla 9.4: Helstu stikar í scatter

marker

Merki fyrir punkta, sjá marker í töflu 9.3

s

stærð punkta, flatarmál í ferpunktum (punktur ≈ 1/3 mm). Má vera vigur og
þá fær hver punktur sína stærð (9.5.1)

color

litur (allir punktar í sama lit), sjá color í töflunni að ofan (9.3.2)

c

vigur af litum (hver punktur í sínum lit). Má vera vigur af tölum og þá litast
punktarnir með litaskala (color map; sjá athugasemd hér fyrir neðan) (13.5)

alpha

gagnsæi, sjá töfluna að ofan

edgecolor

litur á rönd punkta

Athugasemd: Um punktastærð

Sjálfgefin punktastærð er 36 = 6^2, sem gefur sömu stærð og o-merki í plot-skipun með markersize 6 (sem er sjálfgefna stærðin í plot). Ath. að það er ekki stutt að skrifa size í stað s

Athugasemd: Um litaskala :class: athugid

Þessar fyrirlestrarnótur skauta framhjá umfjöllun um litaskala, en um þá er fjallað allítarlega í Viðauka A4 í *Valin efni í

stærðfræði og reiknifræði*. Um litaskala er líka fjallað í 4. kafla í

Python Data Science Handbook.

Tafla 9.5: Helstu hjálparskipanir fyrir teikningar

plt.xlim(min,max)

stillir neðri og efri mörk x-áss (kafli 9.5)

plt.ylim(min,max)

Stillir neðri og efri mörk y-áss (9.5)

plt.clim(cmin, cmax)

Stillir neðri og efri mörk "litaáss" innan litaskala sem er
í notkun – sjá athugasemd hér næst á undan (13.5)

plt.title(strengur)

setur fyrirsögn á teikningu (9.3.2)

plt.xlabel(strengur)

setur textaskýringu við x-ás (9.2.3, 9.5.1 og 9.5.3)

plt.ylabel(strengur)

setur textaskýringu við y-ás (9.2.3, 9.5.1 og 9.5.3)

plt.xticks(listi)

setur merkingar á tilgreindar staðsetningar á x-ás
(listi gæti t.d. verið range(0,6)) (9.3.2)

plt.yticks(listi)

setur merkingar á tilgreindar staðsetningar á y-ás

plt.xticks(listi, merki)

setur bæði staðsetningar og merkingar (eins fyrir y-ás)

plt.gca().set_xticklabels
(['A', 'B', 'C'])

lætur merkingar á x-ás verða A, B og C (eins fyrir y-ás)

plt.tick_params(length=l,
width=w, direction="in")

stillir strikin við ásamerkingarnar (l og w mælt
í punktum (1/3 mm)); í stað "in" má nota "out")

plt.grid(True)

teiknar rúðunet (nota má grid(True, axis='y') fyrir
bara láréttar línur) (9.2.2, 9.5.2 og 9.5.3)

plt.box(False)

fjarlægir ramma utanum teikningu (9.6.2)

plt.axvline(x)

bætir við lóðréttri línu (sjálfgefið er x = 0) (9.6.2 og 9.6.4)

plt.axhline(y)

bætir við láréttri línu (sjálfgefið er y = 0) (9.6.2 og 9.6.4)

plt.legend()

bætir við kassa með skýringum á línum/skatter-punktum
/ súlum sem búnar voru til með label; sjá töflu 9.3 (9.5.2)

Loks er hér hlekkur á yfirlit yfir allar Matplotlib-skipanir.

9.5  Þrjú dæmi um teikningar

Til frekari glöggvunar eru hér þrjú viðbótardæmi og nokkrar æfingar.

9.5.1  Dæmi um plot

from math import sqrt
x = range(5)
y = [sqrt(t) for t in x]
plt.figure(figsize=(4, 1.8), dpi=90) # dpi=72 sjálfgefið
plt.plot(x, y, lw=3, ls=':', c='r', marker='o', ms=8)
plt.xlabel('x')
plt.ylabel('y = √x')
../_images/rauttgraf.png

Æfing: Stikar í plot

Prófið þessar skipanir. Prófið að breyta:

  • dpi (dots-per-inch; myndin stækkar)

  • ls (line style, prófið '-')

  • lw (line width)

  • c (color prófið t.d. 'b')

  • marker (prófið '+' og 'x')

  • ms (marker size)

9.5.2  Dæmi um legend

from math import pi, sin
from random import random
t = [k*2*pi/20 for k in range(21)]
s = [sin(x) for x in t]
y = [random() for k in range(21)]
plt.figure(figsize=(5,2))
plt.plot(t, s, label='sin(x)')
plt.plot(t, y, label='slembitölur')
plt.grid(True)
plt.legend();
../_images/legend-daemi.png

Æfing: Legend

Prófið þessar skipanir. Prófið svo að bæta við cos(x) með tilheyrandi label. Prófið líka að fjölga punktunum (það þarf að breyta bæði 20 og 21 í stærri tölur).

9.5.3  Dæmi um hist

from random import gauss
x = [gauss(0,1) for i in range(500)]
plt.figure(figsize = (6,3))
plt.hist(x, bins=12, range=(-3,3), rwidth=0.8, color='c', ec='k');
plt.xlabel('x-gildi');
plt.ylabel('fjöldi');
plt.grid(True, axis='y')
../_images/sulur2.png

Æfing: Súlurit

Prófið skipanirnar og einhverjar breytingar á þeim. Stikunum er lýst í töflu 9.3

9.6  Teikning af gröfum falla

Við byrjum þennan kafla á æfingu, sem snýst bæði um efni undanfarandi kafla og þessa. Lesendur eru hvattir til að spreyta sig á henni áður en þeir lesa áfram því hér á eftir koma svör við ýmsum atriðum í henni.

Til að teikna graf er byrjað á að núllstilla með því að keyra:

import matplotlib.pyplot as plt
plt.rc('axes', axisbelow=True)
def linspace(a,b):
   return [a + (b-a)*i/200 for i in range(201)]

Með linspace(a,b) fæst listi með 200 jafnt dreifðum þétt liggjandi \(x\)-gildum á bilinu frá a til b, og svo reiknum við fallsgildin \(y = f(x)\) í öllum þessum \(x\)-um og nálgum graf fallsins með því að teikna línustrik sem tengja alla punktana.

9.6.1  Einfalt graf

Við getum teiknað graf sínus-fallsins á bilinu \([0, 4\pi]\) með:

Forrit:
from math import pi, sin, cos, exp
x = linspace(0, 4*pi)
y = [sin(xi) for xi in x]
plt.plot(x, y);
Úttak:
../_images/output_3_0.png

9.6.2  Fínna graf

Hægt er að bæta ýmsu við forritsbúinn í kafla 9.6.1. Byrjum á að afrita hann og endurbætum hann svo:

  • teygjum á grafinu (breikkum myndina) með skipuninni plt.figure(figsize=(12,4)) (á undan plot).

  • Svo má bæta við rúðuneti með plt.grid

  • Það er hægt að teikna x- og y-ása með plt.axhline og plt.axvline. Notið með stika c='k' til að fá svarta ása.

  • Við getum látið x-ásinn ná t.d. frá -0.2 til \(4\pi\) með plt.xlim([-0.2, 4*pi]).

  • Það má fjarlægja rammann með plt.box(False)

  • Síðasta tötsið fæst með plt.tick_params(length=0)

Forrit (sjá úttak í svari við lið 3):
plt.figure(figsize=(15,4))
plt.grid(True)
plt.axvline(c='k')
plt.axhline(c='k')
plt.box(False)
x = linspace(0, 4*pi)
y = [sin(xi) for xi in x]
plt.plot(x, y)
plt.xlim([-0.2, 4*pi])
plt.tick_params(length=0);

9.6.3  Kósínus bætt við

Teiknum í framhaldi \(y = \cos x\) inn á sömu mynd.

Forrit:
yc = [cos(xi) for xi in x]
plt.plot(x, yc)
Úttak (úr 9.6.2 og 9.6.3):
../_images/output_6_0.png

9.6.4  Graf vísisfallsins exp(x)

Teiknum nú nýja mynd með \(y = e^x\) á bilinu \([-5, 2]\).

Forrit:
(a,b) = (-5,2)
x = linspace(a,b)
plt.figure(figsize=(9,6)); plt.grid(True)
plt.axvline(c='k'); plt.axhline(c='k')
plt.box(False)
plt.plot(x, [exp(t) for t in x])
plt.xlim([a,b])
plt.tick_params(length=0)
Úttak:
../_images/output_7_0.png

Æfing: Fallateikning.

Æfið ykkur í að teikna föll með því að nota hugmyndir í þessum kafla. Teiknið t.d. gröf eftirfarandi falla og prófið ykkur áfram í hverju tilviki til að finna hæfilegt bil á x-ás svo helstu eiginleikar fallanna komi fram. Þið getið byrjað á að afrita forritið hér næst á undan.

  1. \(x\sin x\)

  2. \(e^{-2x+1}\)