2 - funksjoner

Å bruke funksjoner

Funksjoner er helt sentralt i all slags programmering. Et program uten egendefinerte funksjoner er bare en liste med kommandoer, og det er svært begrenset hva du kan få til med det. Heldigvis er det ekstremt enkelt å lage egne funksjoner i Python.

En vanlig nybegynnerfeil i programmering er å ikke utnytte muligheten til å lage egne funksjoner tilstrekkelig. Hovedregelen er at om det er noe av koden din som repeteres, så skal du lage en ny funksjon som håndterer dette. Målet er at antall linjer i hver funksjon skal være så lite som mulig. Men før vi lærer å lage egne funksjoner, skal vi se litt på hvordan funksjoner brukes.

Innebygde funksjoner

Python kommer med en rekke innebygde funksjoner. I tillegg kan du importere pakker som har en lang rekke funksjoner. Her er noen innebygde funksjoner i Python:

Eksempel 1:

print( max(2.4,2.0)   )
print( min(2.4, 2.0)  )
print( abs(-2.4)      )
print( len("abcde")   )
2.4
2.0
2.4
5

Legg merke til at funksjonene max, min, abs og len alle er omsluttet av funksjonen print som sørger for at resultatet skrives ut nedenfor. En funksjon kan altså returnere noe, slik som max, min, abs og len, eller gjøre noe slik som print, eller begge deler.

Funksjonen len angir antall bokstaver, men kan også angi lengde på en liste. En liste i python skriver du som [1,'hei',a]. En liste kan altså bestå av flere ulike typer variabler og konstanter. De to første elementene her er konstanter, og den tredje er en variabel som er satt lik et flyttal i eksemplet under.

Eksempel 2:

a=50.8
print([1,'hei',a])
len([1,'hei',a])
[1, 'hei', 50.8]
3

Eksponentialfunksjonene

I Python er det svært enkelt å importere ferdig pakker for som kan kjøre ferdige funksjoner. Vi skal her se på et eksempel med noe som heter eksponentialfunksjonen. Denne finnes blant annet i pakken numpy. Dette er en svært nyttig pakke for matematiske beregninger. Se seksjonen om “Installere Python-pakker” i “0 - installasjon og tips.ipynb” for å installere Numpy.

Som vi skal se lenger ned, så er eksponentialfunksjonen viktig innen økonomifaget fordi den brukes i vekstberegninger, for eksempel for befolkning eller produksjon.

Som nevnt tidligere så er potensen \(y^x\) et grunntall \(y\) opphøyet i en eksponent \(x\). Når vi snakker om eksponenten i bestemt form, så betyr det at grunntallet er et bestemt tall. Dette tallet kalles \(e\), etter matematikeren Euler, og er tilnærmet \(e\approx2.71828\) (det er egentlig en uendelig rekke med siffer etter komma)

Siden Python ikke har noen innebygd funksjon for eksponenten, importerer vi altså først numpy-pakken, som har en slik funksjon. For å få mindre skriving senere, er det vanlig å importere numpy som aliaset np. Dette gjør vi slik:

Eksempel 3:

import numpy as np

Navnet på eksponent-funksjonen i numpy er exp. Skal vi for eksempel finne hva \(e^2=2.71828^2\) er, skriver vi

Eksempel 4:

np.exp(2)
7.38905609893065

Om vi ønsker å vite hva eksponenten er med litt mer enn fem desimaler, kan vi finne eksponenten av 1, siden ethvert tall opphøyd i 1 er seg selv:

Eksempel 5:

np.exp(1)
2.718281828459045

Eksponenten til 5 er for eksempel 2.71828 opphøyet i 5:

Eksempel 6:

np.exp(1)**5
148.41315910257657
np.exp(5)
148.4131591025766

For å opphøye noe bruker vi altså dobbelt gangetegn **.

Logartimen er det eksakt motsatte av eksponentialfunksjonen. Tar vi logaritmen av eksponentialfunksjon, får vi argumentet til sistnevnte. Tar vi eksponentialfunksjonen av logartimen får vi også argumentet til logartimen. I en av oppgavene under skal du vise dette.

Lage egne funksjoner - renteregning

Vi skal her se hvordan vi kan lage egne funksjoner for renteregning.

La oss starte med å lage en funksjon for et lån uten avdrag. Om banken legger til renten én gang i året og renten er r, så er lånet på x om T år gitt som \(x\cdot (1+r)^{T}\). Dette kan vi programmere som følgende funksjon:

Eksempel 7:

def account_balance(x,r,T):
    return x*(1+r)**T

Nøkkelordet returnsørger for at det som står etter return returneres av funksjonen. Dersom funksjonen ikke har returni seg, returneres None, som representerer “ingenting” i Python.

Om renten på lånet er 20%=0.2 og innskuddet er 100, så er beløpet om to år dermed:

Eksempel 8:

account_balance(100,0.2,2)
144.0

La oss nå tenke oss at banken legger til renter n ganger i året. Om for eksempel n=12, så legger banken til renter måndedlig. Vi kan da regne ut lånet ved ulik antall forrentninger per år. Formelen for dette er \(x\cdot (1+\frac{r}{n})^{T\cdot n}\).

Formelen har en enkel og intuitiv forklaring: Vi deler rentesatsen på antall perioder, samtidig som renten legges til i hver periode. Renten i hver periode er dermed \(\frac{r}{n}\), og dette forrentes totalt \(T\cdot n\) ganger. Dette kan programmeres i pyton som

Eksempel 9:

def account_balance_n(x,r,T,n):
    return x*(1+r/n)**(T*n)

La oss nå sammenligne å legge til renten hver måned, med å legge den til årlig:

Eksempel 10:

print(account_balance_n(100,0.2,2,12))
print(account_balance_n(100,0.2,2,1))
148.69146179463576
144.0

Vi ser at det lønner seg en god del å legge til renten hver måned. La oss nå anta at rentene legges til hvert sekund i året. Det er 31 536 000 sekunder i et år. I så fall er innskuddet om to år verdt:

Eksempel 11:

account_balance_n(100,0.2,2,31536000)
149.18246858790133

Banken vil altså tjene litt på å gjøre det. I stedet for å regne ut med daglig forrentning kan vi bruke kontinuerlig, som vil si at vi bruker eksponentialfunksjonen

Eksempel 12:

def account_balance_exponential(x,r,T):
    return x*np.exp(r*T)

Dette gir oss verdien på lånet når renten legges til “kontinuerlig”, altså oftere enn hvert sekund. La oss nå regne ut verdien av gjelden om to år med kontinuerlig forrentning:

Eksempel 13

account_balance_exponential(100,0.2,2)
149.18246976412703

Vi ser at dette er omtrent identisk med når vi la til renten hvert sekund. Eksponentialfunksjonen er altså en måte å regne ut vekst når økningen legges til veldig hyppig. Forrentning hvert sekund og kontinuerlig forrentig gir altså omtrent det samme resultatet. “Kontinuerlig forrentning” er et abstrakt begrep, men det betyr i realiteten bare at vi legger til veksten “veldig hyppig”.

Årsaken til at vi ofte bruker logaritmen og eksponentialfunksjonen istedet for å legge til veksten hvert sekund, ar et det er veldig mye enklere å regne med logartimer enn vanlig prosentregning.

Mer om funksjoner

La oss definere en enkel funksjon som dobler et tall:

Eksempel 14:

def f(x):
    return 2*x

La oss nå sjekke følgende:

  1. Hvilket resultat gir funksjonen?
  2. Hva slags objekt er funksjonen?
  3. Hva slags objekt returnerer funksjonen?

Eksempel 15:

print(f(8))            # printer resultatet av funksjonen når argumentet er 8
print(type(f))         # printer hvilken type objekt det er
print(type(f(5)))      # printer hvilken type resultate av funksjonen er
16
<class 'function'>
<class 'int'>

Funksjonen dobler argumentet, så med 8 som argument blir resultatet 16. Symbolet f representerer nå en funksjon (function), som vi ser er resultatet av print(type(f)). Men dersom vi kaller funksjonen med for eksemple argumentet 5, får vi resultatet av funksjonen, som er av type int. Funksjoner trenger imidlertid ikke retunere noe. Dette er også en funksjon:

Eksempel 16:

def hello():
    print('Hello world!')

hello()
Hello world!

Vi ser at denne funksjonen ikke har return-nøkkelordet. Dermed returneres ingen verdi. I stedet for å returnere noe, så gjør denne funksjonen noe, nemlig å skrive ut ‘Hello world”’ til skjermen.

Du kaller funksjoner ved å skrive funksjonen med argumentet i parentes etter navnet. Som vi ser her, når det ikke er noen argumenter, skriver du tomme parenteser.

En funksjon er også en variabel og et objekt. Du kan for eksempel definere en ny variabel som en tidligere definert funksjon:

Eksempel 17:

hi=hello
hi()
Hello world!

Av og til ønsker du at argumentene til en funksjon skal være valgfrie. Da kan du definere en standard (“default”) verdi for argumentet:

Eksempel 18:

def f(x,a=1.02e+2):
    return a*x

print(f"Innehaveren krevde i utgangspunkltet {f(5)} kroner for fem epler.\n"
     f"Det var alt for mye, så jeg tilbydde {f(5,1.02e-2)} kroner.\n"
     f"Vi ble til slutt enige om {f(5,1.02e+1)} kroner.\n")
Innehaveren krevde i utgangspunkltet 510.0 kroner for fem epler.
Det var alt for mye, så jeg tilbydde 0.051000000000000004 kroner.
Vi ble til slutt enige om 51.0 kroner.

Legg merke til at argumentene til en funksjon settes inn i en parentes. For f(x,y) for eksempel, så er x og y argumenter til funksjonen f. Disse står inne i en parentes (x,y). Denne parentesen er et objekt i seg selv og kalles en tuple. Det går derfor an å definere alle argumentene først, og så la funksjonen evaluere dem. La oss for eksempel si at x=20 og y=50, slik at (x,y)=(20,50). Vi kan da definere variablene først og så evaluere funksjonen på følgende måte

args=(20,50)
f(*args)
1000

Dette er en mulighet som kan være veldig nyttig i mange sammenhenger. En tupleskiller seg fra en liste (list) ved at sistnevnte defineres med hakeparenteser [] i stedet for runde parenteser (). Vi skal senere komme tilbake til flere forskjeller på en liste og en tuple.

Tilbud og etterspørsel

La oss definere en tilbud- og etterspørselsfunksjon. Disse funksjonene skal vise hvilken pris konsumentene maksimalt er villige til å betale for en gitt mengde, og hvor mye produsentene minimum må ha for å selge varen.

Den høyeste prisen er det bare noen få konsumenter som er villige til å betale, så desto større kvantum x som skal selges, desto lavere må prisen være. Etterspørselsfunksjonen må derfor være fallende (lavere pris ved høyere kvantum).

Her er et forslag til etterspørselsfunksjon:

Eksempel 19:

#Creating a function that returns total demand given a price p
def demand(x):
    return 125/(5+x)

Den laveste prisen er det bare den mest effektive bedriften som klarer å produsere lønnsomt for. Ettersom kvantum x øker vil flere, men mindre effektive bedrifter, tilby varen. Desto større kvantum, desto høyere pris kreves per enhet. Tilbudsfunksjonen bør derfor være stigende (høyere pris ved høyere kvantum).

Her er forslag til tilbudsfunksjoner:

Eksempel 20:

#Creating a function that returns total supply given a price p
def supply(x):
    return x**2

Oppgaver:

Oppgave 1:

Sjekk med nummeriske eksempler at tilbudskurven faktisk er økende og etterspørselskurven faktisk er fallende.

print(supply(5))
print(supply(10))
25
100

Tilbudet er økende

print(demand(5))
print(demand(10))
12.5
8.333333333333334

Etterspørselen er synkende

Oppgave 2:

Finn med prøving og feiling eller på annen måte hvilket kvantum som gir likhet mellom tilbud og etterspørsel. Hvilken pris gir slik kvantum?

#printing out demand and supply for different quantities to see for which the mnarket clears, approximately
print([supply(i) for i in np.linspace(0.,7,10)])
print([demand(i) for i in np.linspace(0,7,10)])
[0.0, 0.6049382716049383, 2.419753086419753, 5.4444444444444455, 9.679012345679013, 15.123456790123456, 21.777777777777782, 29.641975308641978, 38.71604938271605, 49.0]
[25.0, 21.634615384615383, 19.06779661016949, 17.045454545454543, 15.41095890410959, 14.0625, 12.93103448275862, 11.96808510638298, 11.138613861386139, 10.416666666666666]

Markedet klarer rundt en etterspørsel på 14-15 enheter.

Oppgave 3:

  1. Lag en funksjon som viser hvor mange penger du har i gjen når du har brukt x penger av et budsjett på b
def rest(b, x):
    return b-x
b =1000
x = 700
f"Av et budjsett på {b} brukte jeg {x} og har igjen {rest(b,x)}"
'Av et budjsett på 1000 brukte jeg 700 og har igjen 300'
  1. Lag en funksjon som returnerer hvor mye parkeringen kostet når du har stått en tidsperiode angitt av argumentet time_period, og en pris per time angitt av argumentet p. Funksjonen skal forvente at time_period er på formatet “hh:mm:ss” (en streng). Du kan trekke ut timer, minutter og sekunder fra en tekstreng på det foreslåtte formatet, ved å bruke koden hh,mm,ss=int(time_period[0:2]),int(time_period[3:5]),int(time_period[6:]). Du må konvertere tiden til timer på desimalform for å regne ut kostnad. Det kan du gjøre med denne formelen: tt_dec=hh+mm/60+ss/(60*60).
def parking_cost(time_period, p):
    hh,mm,ss=int(time_period[0:2]),int(time_period[3:5]),int(time_period[6:])
    time_decimal = hh + (mm/60) + (ss/(60*60))
    return time_decimal*p

time_period = "02:22:31"
p = 120

print(f"Parkeringstid: {time_period}, pris per time: {p} kroner, parkeringskostnad: {parking_cost(time_period, p)} kroner")
Parkeringstid: 02:22:31, pris per time: 120 kroner, parkeringskostnad: 285.03333333333336 kroner
  1. Lag en funksjon som tar en liste med adjektiv som argument. Kall argumentet for adj_list. Funksjonen skal returnere et tilfeldig element fra listen. Du kan for eksempel teste funksjonen med denne listen ['dyr', 'billig', 'pareto optimal', 'høy', 'lav', 'ineffektiv']. For å plukke et tilfeldig element fra en liste adj_list med 4elementer, skriver du adj_list[np.random.randint(4)].
def get_adjective(adj_list):
    i = np.random.randint(len(adj_list))
    return adj_list[i]

get_adjective(['dyr', 'billig', 'pareto optimal', 'høy', 'lav', 'ineffektiv'])
'høy'

Oppgave 4:

  1. Bruk eksponentialfunksjonen og log-funksjonen i numpy til å vise at log-funksjonen er det motsatte av eksponentialfunksjonen.
number = 3.234
e = np.exp(number)
print(np.log(e))
3.234

Vi ser at vi får samme tall

  1. Regn ut hvor mange prosent 205,210,220,230 og 240 er av 200. Regn også ut logaritmen av 205/200, 210/200 … osv. og kommenter resultatet.
for i in [205,210,220,230, 240]:
    print(f"i %: {100*(i/200-1)}, i log: {100*np.log(i/200)}")
i %: 2.499999999999991, i log: 2.4692612590371414
i %: 5.000000000000004, i log: 4.879016416943204
i %: 10.000000000000009, i log: 9.531017980432493
i %: 14.999999999999991, i log: 13.976194237515863
i %: 19.999999999999996, i log: 18.232155679395458

Prosentene ligner, logarigmen er % ved kontinuerlig forrentning.

Oppgave 5:

Lag en funksjon som returnerer det høyeste av funksjonene account_balance_exponential og account_balance. Er dette en nyttig funksjon?

def higest_account_balance(x,r,T,n):
    return max((
        account_balance_n(x,r,T,n),
        account_balance_exponential(x,r,T)
    ))
    
print(higest_account_balance(100, 0.2, 4, 1))
account_balance_exponential(100, 0.2, 4)
222.55409284924679
222.55409284924679

Funksjonen vil alltid være lik account_balance_exponential, siden kontinuerlig forrentning alltid er høyest, så ikke så nyttig

Oppgave 6

  1. Bruk innebygde Python-funksjoner til å finne maksimum, minimum, absoluttverdi og lengden av teksten “1, -5, 10”. Bruk funksjonen eval til å konvertere verdiene til en tallrekke
values = eval("1, -5, 10")
print(max(values))
print(max(values))
print(np.abs(values))
print(len(values))
print(len("1, -5, 10"))
10
10
[ 1  5 10]
3
9

Lengden kan enten være lengden av strengen eller lengden av listen

  1. Nåverdien til et beløp er det en trenger å sette i banken i dag for å få beløpet i fremtiden. Formelen for dette er X/((1+r)**T). Lag en funksjon som retuner nåverdien av et beløp. Lag også en funksjon som returnerer nåverdien med kontinuerlig forrentning.
def present_value(x,r,T,n):
    return x*(1+r/n)**(-T*n)

def present_value_exponential(x,r,T):
    return x*np.exp(-r*T)
  1. Lag en funkson som returnerer nåverdi eller sluttverdi avhengig av hva argumentene er. Sluttverdi er det som er beregnet i avsnittet “Lage egne funksjoner - renteregning”. Funksjonen skal ha antall forrentninger per år som argument, og skal returnere kontinuerlig forrentning dersom antall forrentninger er høyere enn 100

Du kan bruke en if- betingelse til å velge hva en funksjon skal gjøre slik:

def pv_or_balance(x,r,T, return_pv ,n=None):
    if n is None:
        if return_pv:
            return present_value_exponential(x,r,T)
        else:
            return account_balance_exponential(x,r,T)
    else:
        if return_pv:
            return present_value(x,r,T, n)
        else:
            return account_balance_n(x,r,T,n)

        
print( pv_or_balance(100, 0.2, 4, True) )
print( pv_or_balance(100, 0.2, 4, False) )
print( pv_or_balance(100, 0.2, 4, True, 1) )
print( pv_or_balance(100, 0.2, 4, False, 1) )
44.932896411722155
222.55409284924679
48.22530864197532
207.35999999999999

Men vi kan også bruke at med negaativ tid T får vi nåverdi, slik at vi slipper et eget argument for det

def pv_or_balance(x,r,T ,n=None):
    if n is None:
        return account_balance_exponential(x,r,T)
    else:
        return account_balance_n(x,r,T, n)

    
print( pv_or_balance(100, 0.2, -4) )
print( pv_or_balance(100, 0.2,  4) )
print( pv_or_balance(100, 0.2, -4, 1) )
print( pv_or_balance(100, 0.2,  4, 1) )
44.932896411722155
222.55409284924679
48.22530864197532
207.35999999999999