Wikisida, MMG200 H21 Matematik 1: Funktioner i Python, grafritning och numeriska beräkningar

Wikisida
2021-09-16T09:56:09+02:00

Denna tredje vecka ska vi först gå igenom ytterligare en viktig grund inom programmering, nämligen möjligheten att skapa egna funktioner. Därefter ska vi börja bekanta oss med en modul NumPy som innehåller metoder för att göra effektiva numeriska beräkningar. Denna modul innehåller allt som modulen math innehåller och mycket mer. Vi ska också bekanta oss med modulen matplotlib som innehåller funktioner för att rita grafer och diagram mm.

Jag kommer precis som första veckan ge länkar till w3schools.com (Länkar till en externa sida.) där informationen är tillgänglig också om man är nybörjare. Om man har en del vana att programmera sedan tidigare så kan den "officiella" introduktionen på python.org (Länkar till en externa sida.) vara värd att titta på som ett lite mer krävande och innehållsrikt material.

Det finns också exempel här och dessa testar man lämpligen att köra för att se vad som händer.

Funktioner

En grundläggande idé i programmering är att skriva (egna) funktioner för att lösa en uppgift som är återkommande . Ni har redan använt inbyggda funktioner såsom print, input, sum etc och flera funktioner i modulen math. Man kan skriva egna funktioner som då kommer att fungera exakt som de inbyggda funktionerna. Läs om syntaxen för en funktion och hur man anropar den (Länkar till en externa sida.) (sluta läsa när du kommer till "Arbitrary Arguments, *args") .

Som ett första exempel så tittar vi på hur en funktion som beräknar medelvärdet av värdena i en lista skulle kunna se ut.

def medelvärde(lista):      # Namnet på funktionen och den har en inparameter: lista
    längd = len(lista)      # Beräkna längden av listan
    summa = sum(lista)      # Beräkna summan av elementen i lista
    return summa/längd      # Returnerar medelvärdet av talen i lista

När man kört denna koden så finns det nu en funktion medelvärde som man kan anropa med en lista som argument. Om man t.ex. kör koden

m = medelvärde([1, 5, 7, 10, 8])
print("Medelvärdet av talen i listan är", m)

så tilldelas variabeln m det värde som funktionen medelvärde returnerar, dvs. 6.2, och man får utskriften Medelvärdet av talen i listan är 6.2.

Om man tittar i variabelutforskaren efter att man kört koden (gör det!) så ser man att variablerna längd och summa som definierades inuti funktionen inte finns med. Det beror på att variabler som definieras inuti en funktion är lokala. Det betyder att de bara är aktiva medan funktionen körs och inte kan användas utanför funktionen. Det är en god vana att inuti en funktion (normalt) bara använda de variabler som skickas in som parametrar (lista i exemplet ovan) och de lokala variabler som definieras inuti funktionen (längd och summa i exemplet ovan).

Man kan förkorta koden för funktionen genom att strunta i mellanstegen att spara längden och summan. Följande kod ger samma resultat som den ovan:

def medelvärde(lista):                # Namnet på funktionen och den har en inparameter: lista
    return sum(lista)/len(lista)      # Returnerar medelvärdet av talen i lista

Vi gör ett ytterligare exempel med den lite mer avancerade funktionen standardavvikelse som ni sett på gymnasiet. Denna mäter hur mycket tal i en lista x₁, x₂,...,xn avviker från sitt medelvärde m och definieras av att

.

Först i exemplet visas också ett sätt att enbart importera de funktioner man behöver från en modul, i det här fallet sqrt från modulen math. Dessa kan sedan anropas utan att man anger modulens namn, så man kan här skriva sqrt istället för math.sqrt.

from math import sqrt  # Detta importerar enbart funktionen sqrt från math och man
                       # kan anropa den som sqrt istället för det längre math.sqrt

def standardavvikelse(lista):  # Namnet på funktionen och den har en inparameter: lista
    n = len(lista)
    m = medelvärde(lista)     # Använder funktionen vi definierade ovan

    # Beräkna summan i formeln för standardavvikelsen
    summa = 0
    for x in lista:
        summa += (x - m) ** 2

    return sqrt(summa/n)  # Returnera standardavviklesen

Vi anropar nu denna funktion med samma lista som vi beräknade medelvärdet för ovan och i följande körning

sigma = standardavvikelse([1, 5, 7, 10, 8])
print("Standardavvikelsen av talen i listan är", round(sigma,1))

så tilldelas variabeln sigma värdet 3.0594117081556713 och man får utskriften Standardavvikelsen av talen i listan är 3.1.

Vi nämnde ovan att en variabel inuti en funktion sägs vara lokal och att den är aktiv bara inuti funktionen. En variabel utanför funktionen sägs vara global. Globala variabler kan användas inuti en funktion, men inte ändras. (Det går att ändra på detta genom att använda nyckelordet global, men man bör undvika det då det kan ge mycket svårspårade fel.) Läs om globala variabler (Länkar till en externa sida.).

Vi illustrerar konceptet med lokala/globala variabler med följande enkla exempel med en funktion som adderar ett till inparametern.

def addera_ett(a):
    a += 1
    return a

a = 7
b = addera_ett(a)
print("a =", a, "b =", b)

När man kör koden kommer man att få utskriften a = 7 b = 8. Variabeln a inne i funktionen är lokal och när man ändrar den så ändras inte den globala variabeln med samma namn.

NumPy

NumPy (förkortning av Numerical Python) är en modul för att göra snabba numeriska beräkningar. Den innehåller allt som modulen math innehåller och mycket mer. Den största skillnaden är att NumPy är mycket snabbare om man gör beräkningar med stora mängder data, vilket man som matematiker eller statistiker troligen kommer att göra. Grunderna i NumPy finns beskrivna på w3schools.com, (Länkar till en externa sida.)men det finns också en utmärkt introduktion för nybörjare på den officiella webbplatsen för modulen (Länkar till en externa sida.) som rekommenderas varmt.

Det är standard att importera modulen under aliaset np (jobbigt att skriva fem bokstäver när det räcker med två om man måste göra det många gånger) och den första raden i exemplet nedan visar hur man importerar en modul och ger den ett annat namn (alias).

Grunden i NumPy är en datatyp som heter ndarray. På ytan fungerar den ungefär som listor i Python, men implementeringen är annorlunda vilket gör att beräkningar går ca 50 gånger snabbare om man använder ndarray istället för listor. Följande exempel visar hur man kan skapa en variabel av typen ndarray och hur man använder samma kommandon som för listor för att välja ut och ändra variabler.

import numpy as np  # Importerar modulen numpy och ger den namnet np så att
                    # om man vill anropa t.ex. funktionen sin i modulen så gör man det
                    # som np.sin

lista = [1,4,9,16,25,36]
arr = np.array(lista) # Skapa en variabel arr av typen np.ndarray
print(arr[1:3])       # Skriv ut andra och tredje elementet
arr[1] = 0            # Ändra det andra elementet
print(arr)
arr2 = np.arange(1, 11) # Analog till range som skapar en ndarray med alla heltal från 1 till 10
print(arr2)

En viktig skillnad mot listor är att det inte går att lägga till och plocka bort element på samma sätt med append, pop etc. Detta är priset man får betala för den effektivare implementeringen.

En av de stora finesserna i NumPy är de universella funktionerna. Detta är inbyggda varianter på standardfunktioner som addition, sinus, logaritmer etc som kan appliceras på alla element i en ndarray på en gång istället för att skriva en loop som går igenom elementen. Man kan också utföra addition, subtraktion, multiplikation och division med ett fixt tal på alla talen. Följande exempel illustrerar hur dessa fungerar.

import numpy as np

arr = np.array([1,4,9,16,25,36])
arr2 = np.arange(1, 7)
print(arr + arr2)  # Alternativt np.add(arr, arr2)
print(arr + 5)
print(arr * arr2)  # Alternativt np.multiply(arr, arr2)
print(np.log(arr))
print(np.arctan(arr))
print( 1 / arr)    # Inverterar alla elementen i arr

Detta är ett väldigt kompakt och effektivt sätt att beräkna många värden på en gång t.ex. om man vill plotta en funktion. Man kan också skriva egna funktioner som blir universella (Länkar till en externa sida.). Läs om universella funktioner (Länkar till en externa sida.)och titta också på ett par av sidorna efter t.ex. om aritmetiska operatorer (Länkar till en externa sida.) och trigonometriska funktioner (Länkar till en externa sida.).

Vi avslutar med ett exempel där vi skapar en universell funktion som beräknar värdet av f(x)=x3−x+1 f ( x ) = x 3 − x + 1 av ett tal eller en lista av tal.

import numpy as np

# Skapa en "vanlig" funktion min_f som beräknar värdet av f
def min_f(x):
    return x**3-x+1

# Skapa en universell funktion av denna med samma namn dvs min_f
min_f = np.frompyfunc(min_f,1,1)

x_lista = np.array([-2, -1, 0, 1, 2])  # En lista (array) av x-värden
y_lista = min_f(x_lista)               # Ger lista med  f(x) för alla värden
                                       # i x_lista, dvs [-5, 1, 1, 1, 7]
print(y_lista)

Grafer och diagram

Till slut ska vi bekanta oss med modulen matplotlib som innehåller möjligheten att illustrera funktioner och data på varje tänkbart sätt vilket man inser om man scrollar igenom denna långa lista med exempel (Länkar till en externa sida.). Det finns också flera olika sätt att åstadkomma samma sak, så det kan lätt bli lite förvirrande om man tittar på koden för olika exempel. Här kommer vi att fokusera på att använda delmodulen matplotlib.pyplot vilket är det enklaste att starta med. (Om man använt Matlab tidigare så kommer man att känna igen sig för det finns stora likheter mellan kommandona för att plotta i Matlab och pyplot.)

Plotta värden i listor

Grundkommandot för att plotta i pyplot är plot(x,y) där x är en lista med "x-värden" och y är en lista med "y-värden". (Lista kan vara i princip vilken listtyp som helst så t.ex. även ndarray i NumPy.) Man kan utelämna listan med "x-värden", plot(y), och då sätts x-värdena till range(0,len(y)). Ett par exempel:

import matplotlib.pyplot as plt       #
plt.plot([1, 2, 3, 4], [1, 4, 9, 16]) # Plotta de 4 första jämna kvadraterna
plt.show()                            # Visa figuren (behövs ej i Spyder som automatiskt visar den i panelen Plots)

import matplotlib.pyplot as plt
plt.plot([0,1,1,2,3,5,8,13,21,34]) # Plotta de 10 första Fibonacci-talen
plt.show()

Man kan specificera om det ska vara linjer eller punkter (eller både och) i diagrammet och man kan även ställa in olika färger. (Om man inte gör det så blir det blå linjer som i exemplen ovan.) Man kan också välja form på linjen samt hur punkterna ska vara. Detta anges med en sträng som ett tredje argument till plot. De grundläggande möjligheterna beskrivs längst ned på introduktionssidan för kommandot plot (Länkar till en externa sida.)och illustreras av följande modifieringar av exemplen ovan.

import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4], [1, 4, 9, 16], 'k--o' ) # De 4 första jämna kvadraterna som svart ('k') streckad linje ('--') och runda ringar ('o')

import matplotlib.pyplot as plt
plt.plot([0,1,1,2,3,5,8,13,21,34], 'rh') # De 10 första Fibonacci-talen som röda ('r') hexagoner ('h')

Man kan plotta mer än en lista i samma fönster genom att antingen anropa plot flera gånger eller lägga in deras argument efter varandra i samma anrop. Följande två varianter ger samma resultat och ritar de båda exemplen ovan i samma fönster.

import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4], [1, 4, 9, 16], 'k--o' ) # De 4 första jämna kvadraterna som svart streckad linje och runda ringar
plt.plot([0,1,1,2,3,5,8,13,21,34], 'rh') # De 10 första Fibonacci-talen som röda hexagoner

import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4], [1, 4, 9, 16], 'k--o',[0,1,1,2,3,5,8,13,21,34], 'rh') # Båda i samma anrop

Ett annat alternativ är att plotta två (eller fler) listor i olika fönster i samma figur. För att åstadkomma det använder man kommandot subplot. Detta har i sin grundform tre parametrar som anger antalet rader, antalet kolumner respektive vilket av fönstren som ska vara aktivt. I följande exempel skapas två fönster ovanför varandra och aktiveras ett i taget och fylls med de nu välkända plottarna.

import matplotlib.pyplot as plt
plt.subplot(2, 1, 1) # Skapa två rader av fönster (i en kolumn) och aktivera första
plt.plot([1, 2, 3, 4], [1, 4, 9, 16], 'k--o' ) # Plottar i första fönstret
plt.subplot(2, 1, 2) # Aktivera andra fönstret
plt.plot([0,1,1,2,3,5,8,13,21,34], 'rh')# Plottar i andra fönstret

Det går också att jobba med flera figurer parallellt genom att använda funktionen figure. Följande exempel skapar två olika figurer som visas separat.

import matplotlib.pyplot as plt
plt.figure(1) # Skapa och aktivera den första figuren
plt.plot([1, 2, 3, 4], [1, 4, 9, 16], 'k--o' ) # Plottar i första figuren
plt.figure(2) # Skapa och aktivera den andra figuren
plt.plot([0,1,1,2,3,5,8,13,21,34], 'rh') # Plottar i andra figuren

Man kan också lägga in diverse förklarande text med funktioner i pyplot. Exemplet nedan illustrerar några av de mest använda. Alla funktioner i pyplot, inklusive de som används i exemplet nedan, och andra kommandon än plot för att skapa plottar finns dokumenterade här (Länkar till en externa sida.). Vi kommer att återkomma senare till hur man gör histogram och andra diagram.

import matplotlib.pyplot as plt
plt.plot([1, 2, 3, 4], [1, 4, 9, 16], 'k--o', label='Jämna kvadrater' )
plt.plot([0,1,1,2,3,5,8,13,21,34], 'rh', label='Fibonacci')
plt.title('Kvadrater och Fibonacci') # Sätt en titel
plt.axis([-1,10,-1,40]) # Ange gränser för x och y i fönstret
plt.xlabel('x-värdena') # Text längs x-axeln
plt.ylabel('y-värdena') # Text längs y-axeln
plt.legend() # Förklaring till de olika listorna med texten som angavs som 'label' i kommandot plot

Plotta funktioner

Vi ska nu titta på hur man enkelt kan plotta reella funktioner genom att kombinera universella funktioner i NumPy med kommandot plot.

Vi använde tidigare funktionen arange(start, slut, steg) i NumPy för att skapa ett fält (lista/array) av tal från start till slut (ej inkluderande slut) med avstånd steg. Det finns en snarlik funktion linspace som är enklare att jobba med när man ska plotta (och även i många andra lägen). Den har formatet linspace(start, slut, antal punkter) och skapar ett fält av längd antal punkter med jämnt fördelade tal från start till slut (inkluderande slut). Vi visar med ett exempel hur man kan använda detta för att plotta sinus från 0 till 2 π .

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0,2 * np.pi, 200) # Skapa lista med 200 jämnt fördelade tal från 0 till 2(pi)
y = np.sin(x) # Beräkna sinus av alla värden i x
plt.plot(x, y, 'r-.') # Plotta sinus från 0 till 2pi med röda streck/punkt-linje
print(x[-1] - 2 * np.pi) # Sista värdet i x är 2pi

Vi kombinerar nu detta med tekniken för att ha flera fönster i en figur för att plotta sinus, cosinus och tangens. För tangens ritas två plottar för att se exempel på hur man kan ta hand om funktioner som har singulariteter (går mot oändligheten).

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0,2 * np.pi, 200) # Skapa lista med 200 jämnt fördelade tal från 0 till 2(pi)

plt.subplot(2,2,1) # Skapa 4 fönster (2 rader och 2 kolumner) och aktivera första
plt.plot(x, np.sin(x), 'r-.') # Plotta sinus från 0 till 2pi med röda streck/punkt-linje
plt.title('sin')

plt.subplot(2,2,2) # Aktivera andra fönstret
plt.plot(x, np.cos(x), 'g--') # Plotta cosinus från 0 till 2pi med gröna streck
plt.title('cos')

plt.subplot(2,2,3) # Aktivera tredje fönstret
plt.plot(x, np.tan(x), 'y-') # Plotta tangens från 0 till 2pi med gul linje
plt.title('tan')

plt.subplot(2,2,4) # Aktivera fjärde fönstret
x = np.linspace(np.pi/2 + 1e-5,3 * np.pi/2 - 1e-5, 200) # Skapa lista med 200 jämnt fördelade tal från pi/2 till 3pi/2 (men undvik ändpunkter)
plt.plot(x, np.tan(x), 'y-') # Plotta tangens från pi/2 till 3pi/2 (undviker ändpunkter och skär av höga värden) med gul linje
plt.axis([1,5,-10,10])              # Skär av höga och låga värden genom att bara visa mellan -10 och 10
plt.title('tan')

Diagram

Vi avslutar med att se hur man kan skapa diagram av olika slag. Det kommer inte att vara några övningar på denna delen, så den kan sparas till slutet och gås igenom i mån av tid.

Det finns ett antal funktioner i pyplot för att göra olika diagram (fullständig lista (Länkar till en externa sida.) och många exempel). Här kommer ett par enkla exempel. Det finns massor av olika inställningar man kan göra som man hittar på respektive funktion i den fullständig listan (Länkar till en externa sida.).

Man kan göra ett cirkeldiagram med funktionen pie: (Länkar till en externa sida.)

import matplotlib.pyplot as plt

x = [10, 15, 25, 5, 45]  # Värdena som ska illustreras i diagrammet
namn = ['Fysik', 'Kemi', 'Biologi', 'Annat', 'Matematik']  # Kategori för de olika värdena
plt.pie(x, labels=namn)   # Rita upp cirkeldiagrammet
plt.title('Mitt favoritämne')  # Lägg till en titel

Ett sätt att illustrera frekvens av element i listor är med så kallade histogram (Länkar till en externa sida.). Följande exempel generar 50 slumpheltal från 0 till 9 och visar sedan ett histogram som visar frekvensen av de olika talen.

import matplotlib.pyplot as plt
from numpy import random # Funktioner för att arbeta med slumptal

x = random.randint(10, size=50) # Skapa ett fält med 50 slumpheltal n med 0 <= n <10
plt.hist(x) # Rita histogram av värdena i listan med slumptal

För att illustrera tvådimensionell data finns en flexibel funktion som alternativ till den vanliga plot som heter scatter (Länkar till en externa sida.). Med den kan man enkelt ha individuell storlek och färg på alla datapunkter vilket illustreras med detta exempel.

import matplotlib.pyplot as plt
from numpy import random # Funktioner för att arbeta med slumptal

x = random.rand(500) # Skapa ett fält med 500 slumpflyttal n med 0 < n < 1
y = random.rand(500) # Skapa ett fält med 500 slumpflyttal n med 0 < n < 1
farg = np.arctan(x/y) # Skapar lista med vinkeln mellan x-axeln och linjen till respektive punkt
storlek = 50 * np.sqrt(x**2 + y**2) # Skapar lista med avstånden till origo
plt.scatter(x, y, c = farg, s = storlek, alpha = 0.5) # Plotta cirklar med färg baserat på vinkel
                                                   # och storlek baserat på avstånd från origo
                                                   # alpha = 0.5 gör dem transparenta

Övningar

1. Egna funktioner

Skriv en funktion korsar_x_axeln som har två inparametrar k och m. Den ska returnera värdet på x där linjen y=kx+m korsar x-axeln om linjen korsar x-axeln. Om linjen inte korsar x-axeln ska den istället returnera strängen "Inget svar". Bland fallen att linjen inte korsar x-axeln finns det ett speciellt fall, vilket då?

Skriv kod som låter användaren mata in k och m och sedan anropar funktionen korsar_x_axeln med dessa som inparametrar och sparar det returnerade värdet i en variabel svar.

Om linjen korsade x-axeln så skriv ut x-värdet där den korsar med förklarande text, annars skriv ut texten Linjen korsar inte x-axeln.

Tips: type(svar) is float (eller type(svar) == float) ger sant om och endast om variabeln svar är av typen float så koden för sista delen skulle kunna ha formen

if type(svar) is float:
    print(....)
else:
    print(....)

2. NumPy

Skriv kod som använder funktioner i NumPy som:

Skapar en variabel arr av typen ndarray som innehåller alla positiva heltal från ett till en miljon. (Tips: använd np.arange.)

Beräknar genom att använda en universell funktion i NumPy följande summa . (Tips: använd np.sin och np.sum) Max två rader kod!

Beräknar summan . Återigen max två rader kod.

3. Rita funktionsgrafer

Ni ska rita grafen av funktionerna på intervallet 0 till 2. Rita alla i samma fönster men med olika färg och en ska vara streckad, en punktlinje, en heldragen och en med omväxlande linje och punkt. Skapa också en nyckel som anger vilka funktionerna är. Y-axeln ska brytas vid y=4 så att bara y-värden från 0 till 4 visas.

Lämpliga kommandon att använda är

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2, 100)
plt.axis([0, 2, 0, 4])