4 - lister, oppslag og numpy
En liste er en datastruktur, som igjen er en organisering av objekter. Slike datastrukturer er helt sentralt i alle programmeringsspråk, og alle språk har ulike typer strukturer til ulike formål. I noen datastrukturer kan du putte alle typer objekter sammen, slik som tekst, funksjoner og tall og nye datastrukturer.
I andre datastrukturer kan du kun putte tall. Slike datastrukturer er vanligvis det vi i matematikken kaller vektorer og matriser. Disse kommer vi tilbake til når vi skal snakke om Numpy.
Det vi skal se på først er de innebygde datastrukturene til Python. Alle de innebygde strukturene kan inneholde alle typer objekter, men de har noen viktige egenskaper som skiller dem:
list
(liste): Hvert element har en bestemt plassering i listen, Tilgang fås ved å referere til plasseringen (indeksen) i form av et heltall int
. Tuple
: Lik lister, men kan ikke forandres når den er skapt. Hovedsakelig til bruk i forbindelse med funksjoner.dict
(dictionary, oppslag): Elementene har ikke en bestemt plassering. Tilgang fås ved å referere til en nøkkel (vanligvis en streng str
)Vi starter med å se på lister.
Lister lages med klammeparenteser. Her er noen lister med navn, forkortelse (Ticker), aksjekurs, markedsverdi, PE og gjeldsgrad for noen aksjer på Oslo Børs. Tallene er fra 27. november 2020 (kilde https://tilon.uit.no).
PE er "Price/Earnings", som er forholdet mellom selskapenes inntjening og prisen. Gjeldsgrad er hvor stor andel av total kapital som er gjeld. Det som ikke er gjeld er egenkapital.
equinor=['Statoil', 'EQNR', 167.554919090282, 376883380048.5, 6.14609823076753, 0.617893838660362]
dnb=['DNB', 'DNB', 164, 259169427140, 11.1121822724349, 0.914999939276725]
telenor=['Telenor', 'TEL', 148.737572948101, 227115017138.4, 13.0211568133471, 0.715300723576896]
marine_harvest=['Marine Harvest', 'MHG', 225.589705235372, 118004750966.2, 21.6878893009616, 0.440427599611273]
aker_bp=['Aker BP', 'AKERBP', 272.499264741749, 103712690592, 26.7636558040214, 0.722568840028356]
yara_international=['Yara International', 'YAR', 333.328922090651, 99481443032.4, 86.741957363663, 0.465057636887608]
gjensidige_forsikring=['Gjensidige Forsikring', 'GJF', 172.830508474576, 92125000000, 24.9404407385348, 0.847890127134592]
Vi kan nå få tilgang til listene ved å referere til plasseringen med klammeparentes. Om vi vil referere navnet til equinor, som ligger på plass 0, kan vi for eksempel skrive equinor[0]
(første element er alltid på plass 0 i Python):
equinor[0]
Vi ser at navnet er feil, dette er det gamle navnet til Equinor. Dette kan vi enkelt endre ved å sette element 0 i equinor
-listen lik 'Equinor'
:
print(equinor)
equinor[0]='Equinor'
print(equinor)
Men vi kan legge hvilke som helst objekter inn i en liste, så vi kan også lage en liste av listene over.
stocks=[equinor,dnb,telenor,marine_harvest,aker_bp,yara_international,gjensidige_forsikring]
stocks
Aker BP-aksjen er element nummer fem i denne listen. Om vi nå skal referere til den, så bruker vi indeks 4, siden vi starter på null.
stocks[4]
Om du vil referere til siste element, bruker du indeks -1. Vil du referere til nest siste, bruker du indeks -2, og så videre:
print(stocks[-1])
print(stocks[-2])
Du kan også referere til flere elementer som står ved siden av hverandre med en såkalt slice
, eller skjære som vi kan kalle det på norsk:
#Fra element to til og med tre:
stocks[2:4]
#Fra og med nest siste element:
stocks[-2:]
#Til og med nest siste element:
stocks[:-1]
Og vi kan referere til en liste inne i en liste:
print(f"Det tredje selskapet er {stocks[2][0]}")
Det tredje selskapet, altså Equinor, har indeks to, siden Python starter indekseringen på 0.
Det er enkelt å utvide lister, vi bruker bare +
. Om vi for eksempel vil legge til selskapet ['Tomra Systems', 'TOM', 276.409619134278, 41208789715.2, 52.8996016883184, 0.454299114121939]
, kan vi gjøre det slik:
tomra_systems=['Tomra Systems', 'TOM', 276.409619134278, 41208789715.2, 52.8996016883184, 0.454299114121939]
stocks=stocks+[tomra_systems]
stocks
En veldig vanlig nybegynnerfeil i Python er å glemme at selv om et objekt, slik som en liste, har fått nytt navn, så er det fortsatt det samme objektet. Dette er spesielt lett å glemme når du objektet er et argument i en funksjon. Her er for eksempel en funksjon som opererer på argumentet:
#This function removes element i
def pop_and_print(a,i):
last_element=a.pop(-1)
print(last_element)
pop_and_print(stocks,-1)
stocks
Over brukes pop
-metoden til listeobjektet. En "metode" er en funksjon som kan henges på et bestemt objekt. Liste-objektet har altså en metode pop
, som vi kan henge på listeobjektet med et punktum. pop
-metoden fjerner elementet angitt av argumentet (siste element -1
i eksemplet under), og returnerer det fjernede elementet.
Kjører du koden over mange nok ganger, vil du se at alle elementene til slutt er borte og du får feilmelding.
Av og til vil du at funksjonen skal operere på argumentet, men om det ikke er meningen at argumentet skal være endret når funksjonen er ferdigkjørt, så kan du ta en kopi inne i funksjonen. Det gjøres enklest ved å bruke typen til objektet som funksjon. Du danner da et nytt objekt av samme type. For eksempel for en liste som stocks
bruker du list
som funksjon
#This function removes element i it
def pop_and_print(a,i):
#making a copy:
a=list(a)
#doing the rest:
last_element=a.pop(-1)
print(last_element)
pop_and_print(stocks,-1)
stocks
Denne koden kan kjøres så mange ganger du vil, uten at stocks endres
Én måte å lage lister på, er med en enkel løkke. DU definerer først listen, og så legger du til elementer:
a = [] #Defining the list
for i in range(4):
a.append(5*i + 20)
a
Alternativt kan vi bruke "list comprehension", som er en enklere måte å lage lister på, dersom operasjonen ikke er for komplisert:
a = [5*i+20 for i in range(4)]
a
En tuple
fungerer ganske likt som en liste når du skal hente noe fra den. Men i motsetning til en liste, så går det ikke an å endre på en tuple
etter at den er skapt. Du kan lage en tuple enten ved å bruke den innebygde funksjonen tuple()
eller ved å lage en liste med en vanlig parentes:
a=tuple(equinor)
b=('Equinor', 'EQNR', 146.85, 315357973561)
print(type(a))
print(type(b))
Vi kan få tak i innholdet, men det går ikke an å endre på innholdet i en tuple
etter at den er skapt:
print(a[0])
a[0]='Statoil'
Tuple er mest brukt i forbindelse med funksjoner, og er ikke noe vi kommer til å bruke mye tid på i dette kurset.
Numpy er en pakke som kan gjøre matematiske beregninger på store datasett svært effektivt. En matrise er en liste der alle rader har like mangee elementer, og det er jo tilfelle med vår stocks
-liste.
Vi starter med å definere en liste av liste med informasjon om aksjene:
stocks=[
['Equinor', 'EQNR', 167.554919090282, 376883380048.5, 6.14609823076753, 0.617893838660362] ,
['DNB', 'DNB', 164, 259169427140, 11.1121822724349, 0.914999939276725] ,
['Telenor', 'TEL', 148.737572948101, 227115017138.4, 13.0211568133471, 0.715300723576896] ,
['Marine Harvest', 'MHG', 225.589705235372, 118004750966.2, 21.6878893009616, 0.440427599611273] ,
['Aker BP', 'AKERBP', 272.499264741749, 103712690592, 26.7636558040214, 0.722568840028356] ,
['Yara International', 'YAR', 333.328922090651, 99481443032.4, 86.741957363663, 0.465057636887608] ,
['Gjensidige Forsikring', 'GJF', 172.830508474576, 92125000000, 24.9404407385348, 0.847890127134592] ,
['Orkla', 'ORK', 86.5857852597003, 89087299091.2, 26.5615083754323, 0.350968405416214] ,
['Norsk Hydro', 'NHY', 31.4389402413895, 67532103728.64, 15.6215830970715, 0.439195576287418] ,
['SalMar', 'SALM', 449.3, 50905689550.7, 14.222545257898, 0.396134627028104] ,
['Tomra Systems', 'TOM', 276.409619134278, 41208789715.2, 52.8996016883184, 0.454299114121939] ,
['Aker', 'AKER', 514.200494811093, 40393931997, 43.4343354806452, 0.548092886866901] ,
]
Som du ser, så består listen av tolv rader og fire kolonner. Dette er altså en 12x6-matrise. Den kan enkelt konverteres til en numpy-matrise med funksjonen np.array()
slik som her:
import numpy as np
stocks_np=np.array(stocks)
Listen er nå lagret som en numpy 12x6-matrise i objektet stocks_np
. Vi kan imidlertid ikke utføre noen matematiske beregninger slik denne matrisen står, fordi alle tall er i tekstformat. Som vi har sett tidligere går det bare an å regne med float
, int
og bool
. At det er tekst ser vi ved at det er enkle anførselstegn '
rundt alle elementene.
Heldigvis er det veldig enkelt å hente ut informasjon fra numpy-matriser, fordi mulighetene til å skjære utsnitt er langt større enn for vanlige lister. Numpy-lister kan nemlig skjæres i flere dimensjoner.
I motsetning til vanlige lister, kan vi for eksempel velge ut kolonner. I en liste av lister velger du, som vi har sett, rad 2 og kolonne 0 med stocks[2][0]
. Med numpy-matriser separerer du de to indeksene med et komma, stocks[2,0]
.
Men enda viktigere, du kan velge alle elementer ved å sette inn kolon :
i stedet for tall. Dermed kan du velge hele kolonne 0 med stocks_np[:,0]
.
Her er vi mest interessert i de to siste kolonnene. Det er disse som inneholder tall. For å plukke ut disse velger vi alle radene (:
) og alle kolonnene fra og med kolonne 2 (2:
). Da får vi
stocks_np[:,2:]
Sist i matrisen over, står det dtype='<U21'
. Det betyr at dette er tekstrenger på 21 eller færre tegn. Vi må imidlertid ha dette over i tallformat for å kunne jobbe med det. Det gjør vi slik:
stocks_numbers=np.array(stocks_np[:,2:],dtype=float)
stocks_numbers
Når vi konverterte listen til en ndarray
, så brukte vi altså funksjonen np.array()
, slik som i Eksempel 22. Men denne gangen legger vi til det valgfrie argument dtype=float
for å eksplisitt gi beskjed om at vi ønsker strengene konvertert til flyttal.
Nå kan vi begynne å bruke tallene. For eksempel kan vi se grafisk om det er en sammenheng mellom gjeldsgrad og pris-inntjeningstraten (PE). Dette er de to siste kolonnene i datamatrisen:
from matplotlib import pyplot as plt
fig,ax=plt.subplots()
ax.set_ylabel('PE')
ax.set_xlabel('gjeldsrate')
ax.scatter(stocks_numbers[:,-1], stocks_numbers[:,-2])
Ser du en sammenheng?
Det er også enkelt å regne med Numpy. Vi kan bruke alle de vanlige regneartene med numpy-matriser:
a=np.array([1,5,6,3])
b=np.array([23,15,2,10])
print(a+b)
print(a-b)
print(a*b)
print(a/b)
print(a**3)
print(a**0.5)#Kvadratroten
Et oppslag, eller dictionary har symbol dict
i Python. Dette er en datastruktur der hvert element ikke identifiseres med hvor det er plassert, men med et nøkkelord (key
). Å bruke oppslag i stedet for lister gjør ofte koden mer lesbar. Det finnes to måter å lage oppslag på; enten ved å bruke funksjonen dict()
:
a=dict()
a['gjeldsrate']=stocks_numbers[:,-1]
a['PE']=stocks_numbers[:,-2]
print(type(a))
Som vi ser, blir dette et objekt av type dict
. Alternativt kan vi definere elementer inne i en krølleparentes på formen {nøkkel: objekt}
, slik som dette:
b={
'gjeldsrate':stocks_numbers[:,-1],
'PE':stocks_numbers[:,-2],
}
print(type(b))
Her er det altså to elementer med nøkler 'gjeldsrate'
og 'PE'
, og tilhørende objekter fra stocks_numbers
.
Vi legger merke til at dette også er en objekt av type dict
. Med oppslag blir det enklere å bruke variablene, siden vi kan referere til dem med navn i stedet for indeks. Her er plottet i Eksempel 23 gjort med oppslag:
plt.ylabel('PE')
plt.xlabel('gjeldsrate')
plt.scatter(a['gjeldsrate'], a['PE'])
Når du først har laget en dict, er det enkelt å legge til nye elementer. Det gjør du enkelt ved å bruke en nøkkel som ikke finnes, for eksempel 'Pris'
, og dette elementet lik ønsket objekt:
a['Pris']=stocks_numbers[:,0]
plt.ylabel('Pris')
plt.xlabel('gjeldsrate')
plt.scatter(a['gjeldsrate'], a['Pris'])
dict
-objektet er helt sentralt i selve byggeklossene til Python. Alle variabler du lager er faktisk elementer i to dict
som du kan få frem med funksjonene locals()
og globals()
, som kaller de lokalt og globalt definerte variablene i miljøet du befinner deg. Dette eksemplet viser at objektet stocks
som vi har definert i Eksempel 23, er identisk med elementet 'stocks'
i locals()
.
locals()['stocks']==stocks
Vi ser at disse objektene egentlig er det samme, ved at den logiske testen om disse er like, returnerer sann (True
).
Du kan automatisere å lage oppslag på samme måte som du kan automatisere lister. Med løkke kan du for eksempel gjøre slik:
d = {}
for name, ticker, price, mcap, pe,debt in stocks:
d[name] = [ticker, price, mcap, pe,debt]
d['Telenor']
Men du kan også gjøre noe tilsvarende av "list comprehension":
d = { name:[ticker, price, mcap, pe,debt] for name, ticker, price, mcap, pe,debt in stocks}
d['Telenor']
Det er enkelt å konvertere en pandas dataramme til numpy. Vi skal bruke skolegangeksemplet fra forelesning "3 - matplotlib". Vi henter inn dataene og konverterer dem til numpy slik:
import numpy as np
import pandas as pd
df=pd.read_csv('./data/schooling-gdp.csv')
schooling=np.array(df)
schooling
schooling
(ligger i kolonne med indeks 2)
schooling
men hent kolonnen ved å refere til tredje siste kolonne.
schooling
, som representerer BNP per capita, skolegang og befolkning, og konverter dem til datatype float
.
schooling_dict
.
plt.barh
på denne måten:
plt.barh(names,values)
Hvor names
er en liste med navnene og values
er de tilhørende verdiene.
argsort
får du rangeringen til etter en sortering. Her er rangeringen for "GDP per schooling"
[::-1]
til slutt sørger for å reversere sorteringen. Legg referansen til soreringen inn i variabelen sorted
, og lag diagrammet på nytt med sorterte verdier. Om du har en numpy-liste a, kan du velge de sorterte verdiene med a[sorted]
Lag
a) et nytt diagram med alle landene og
b) et diagram med de 20 øverste.
c) et diagram med de 20 nederste