Data#

Det første kapitelet handler om data og variabler. Det er viktig å tenke på hva slags variabler data inneholder for å forberede og analysere de.

Det er et velkjent ordtak at folk som jobber med data science bruker 80% av tiden sin for å rense og forberede data i stedet for å faktisk hente ut innsikt. Det finnes ingen god kilde for dette og det riktige tallet er nok lavere enn 80%, men det stemmer at data forberedelse kan være en stor del av et data science prosjekt. Derfor er det viktig å tenke på hva som er målet ved å forberede data hva slags verktøy vi kan bruke for dette.

Strukturerte og ustrukturerte data#

Grovt underdeler vi data i strukturerte og ustrukturerte data. Strukturerte data er all data som kan skrives i en tabell, eller et excel ark. Det kan for eksempel være data om personer og inkludere kolonner som navn, adresse, kjønn, osv. I så fall tenker vi på hver kolonne som en egenskap og hver rad som å representere et datapunkt.

import pandas as pd
pd.set_option("display.max_columns", 16)
df = pd.DataFrame(dict(navn=['Nora', 'Lucas', 'Isak', 'Ella'], alder=[25, 19, 23, 22]))
df
navn alder
0 Nora 25
1 Lucas 19
2 Isak 23
3 Ella 22

Her er et eksempel der hver rad representerer en person og kolonnene er egenskapene av personen, i dette tilfelle navn og alder.

I motsetning til strukturerte data har vi ustrukturerte data som tekst, bilder, audio, video og mange flere. For å analysere ustrukturerte data har vi to muligheter. Vi kan omgjøre de til strukturerte data eller vi kan bruke spesielt tilpassete metoder.

Her er et eksempel med tekstdata. Hvis vi har to tilbakemeldinger av studenter fra data science kurset og prøver å finne ut om de som skrev det likte kurset. Data er tekst og kan ikke enkelt skrives i tabellformat.

Student A: Jeg likte kurset godt og jeg lærte mye, men det gikk litt raskt.

Student B: Tempoet i kurset var altfor raskt, jeg lærte ingenting.

Men en måte å oversette det til tabellformat er å ta alle ord som kolonner og bare telle hvor mange ganger ordet dukker opp i tilbakemeldingen. Det har fordelen at det blir strukturerte data, men ulempen at vi mister informasjon om rekkefølge av ordene. Så vi mister en viktig del av informasjonen fra de opprinnelige data.

Student

Jeg

likte

kurset

godt

A

2

1

1

1

B

1

0

1

0

Her er et annet eksempel. Et bilde som dette kan selvfølgelig lages om som en rad i en tabell, der hver kolonne representerer en piksel. Men hvis vi analyserer det på denne måten så har vi mistet informasjonen om hvilke piksler som er ved siden av hverandre. Hvis vi flytter objektet i bildet litt, så endrer piksler seg mye selv om det ikke var en visuelt stor endring.

import numpy as np 
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt

# get image
image = load_digits()['data'][0].reshape(8, 8)

# shift image
shifted = np.roll(image, (1, 1), axis=(0, 1))

# Plot images
f, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
plt.gray()
ax1.matshow(image)
ax2.matshow(shifted)
plt.show()
_images/data_7_0.png

Her er de to bildene: originalet og den som er flyttet litt.

df_image = pd.DataFrame(dict(original=image.flatten(), shifted=shifted.flatten())).T
df_image
0 1 2 3 4 5 6 7 ... 56 57 58 59 60 61 62 63
original 0.0 0.0 5.0 13.0 9.0 1.0 0.0 0.0 ... 0.0 0.0 6.0 13.0 10.0 0.0 0.0 0.0
shifted 0.0 0.0 0.0 6.0 13.0 10.0 0.0 0.0 ... 0.0 0.0 2.0 14.0 5.0 10.0 12.0 0.0

2 rows × 64 columns

Og her er hva som skjer i tabellen, der hver kolonne representerer en piksel.

Den andre måten å analysere ustrukturerte data på er å bruke spesielt tilpassete metoder. Det er som oftest en bedre løsning fordi den ikke mister informasjon. Men denne løsningen er utenfor omfang av dette kurset. Derfor kommer vi i dette kurset til å se kun på strukturerte data.

Variabeltyper#

Da er vi klare for å klassifisere strukturerte data.

structured_data

På den ene siden har vi numeriske data, som underdeles igjen i kontinuerlige og diskrete variabler. Kontinuerlige variabler er data som kan ta hvilken som helst verdi innen et gitt interval. Typiske eksempler av kontinuerlige variabler kan være høyde eller alder. Diskrete variabler er numeriske data som bare kan ha et utvalg av mulige verdier. For eksempel antall barn, eller mål i en fotballkamp. Det gir ingen mening å snakke om 1.3 barn, det er enten ett eller to. Denne skillelinjen er ikke så skarp som man kanskje skulle trodd. Hvis jeg måler høyde, så kan jeg kanskje bare måle det med en presisjon av 5 millimeter. I så fall er det bare en diskret antall verdier jeg faktisk kan ha i data. Men vi modellerer høyde likevel som kontinuerlig, fordi det gir mening å si at noen er 175,28 centimeter og høy selv om vi ikke kan måle det.

På den andre siden har vi kategoriske variabler. Det er data som består av kategorier i stedet for tall. Typiske eksempler er kjønn eller utdanningsnivå. Blant kategoriske variabler finnes det nominale og ordinale variabler. Nominale variabler er variabler der det ikke finnes noe rekkefølge av kategoriene. For eksempel kjønn eller yrke.

Ordinale variabler er kategoriske variabler som har en opplagt rekkefølge i kategoriene. For eksempel hvis jeg lager en spørreundersøkelse om hvor godt forelesningsnotatene er likt blant studentene. I så fall er det kanskje svarkategoriene «veldig dårlig», «dårlig», «nøytral», «bra» eller «veldig bra». I så fall gir det mening å si at «veldig bra» er bedre enn «bra», som er bedre enn «nøytral» og så videre.

Nå har vi hørt litt om forskjellige typer variabler. Men hvorfor bryr vi oss egentlig om dette?

La oss si at vi har en undersøkelse som vurderer kundetilfredshet med svaralternativ «svært misfornøyd», «misfornøyd», «nøytral», «fornøyd» og «svært fornøyd». Dette er en ordinal variabel fordi det er en naturlig rekkefølge i svarkategoriene, men avstanden mellom dem er ikke nødvendigvis lik. Her er litt eksempeldata:

Kategori

Frekvens

Svært misfornøyd (1)

10

Misfornøyd (2)

20

Nøytral (3)

25

Fornøyd (4)

30

Svært fornøyd (5)

15

Hvis vi behandler denne ordinalvariabelen som en nominal kategorisk variabel, vil vi ignorere rekkefølgen. Hvis vi skal oppsummere variablen, så konkluderer vi kanskje at kategorien som ble svart mest (mode) er fornøyd (30% av svar).

Hvis vi behandler denne ordinalvariabelen som en numerisk variabel, antar vi at avstanden mellom kategoriene er lik. Vi bruker altså for eksempel koden 1 for «svært misfornøyd», koden 2 for «misfornøyd», og så videre til koden 5 for svært fornøyd. For å oppsummere variablen vil vi kanskje ta gjennomsnittet (mean). Vi finner altså det er

\[\text{mean}=\frac{(1⋅10)+(2⋅20)+(3⋅25)+(4⋅30)+(5⋅15)}{100}=3.2\]

Denne gjennomsnittsverdien gir oss et tall, men det gir ikke meningsfull informasjon om kundetilfredsheten, spesielt siden avstanden mellom kategoriene ikke er lik.

Når vi behandler variabelen som ordinal, tar vi hensyn til rekkefølgen, men ikke nødvendigvis avstanden mellom kategoriene. For eksempel kan vi bruke medianen i stedet for mean for å oppsummere kundetilfredshet. Medianen tar hensyn til rekkefølgen, men ikke avstanden mellom kategoriene.

Medianen er verdien som deler dataene i to like store deler. I vårt tilfelle, hvis vi sorterer dataene og finner vi at medianen er verdien ved den 50. observasjonen, som i dette tilfellet er «nøytral».

Nå har vi allerede sett på et eksempel av ordinale data. Hvis vi har nominale data i stedet, så gir det ikke mening si at en kategori er større eller mindre enn en annen.

La oss ta et eksempel der vi har en dataset med informasjon om studenter og deres studieprogrammer.

StudentID

Studieprogram

abc123

Informatikk

def456

Matematikk

ghi789

Fysikk

nbl630

Kjemi

jbo006

Biologi

ppa040

Matematikk

fze128

Informatikk

nga042

Kjemi

I dette tilfellet pleier vi å lage flere såkalte dummy-variabler ut av den kategoriske variablen. Vi bruker så mange variabler som det var kategorier og hver av disse nye kategorivariablene er 1 hvis radet er fra denne kategorien og 0 ellers. Her er hvordan datasetet ser ut etter dummy-koding:

StudentID

Informatikk

Matematikk

Fysikk

Kjemi

Biologi

abc123

1

0

0

0

0

def456

0

1

0

0

0

ghi789

0

0

1

0

0

nbl630

0

0

0

1

0

jbo006

0

0

0

0

1

ppa040

0

1

0

0

0

fze128

1

0

0

0

0

nga042

0

0

0

1

0

Fordeler med dummy-variabler er at vi enkelt kan inkludere studieprogrammet i vår statistiske modell og få innsikt i hvordan for eksempel karakterer varierer mellom de forskjellige programmene. Dette lar oss også utføre hypotesetester for å se om det er forskjeller mellom studieprogrammene.

Ryddige data#

Hva mener vi med ryddig data? Prinsippene for ryddig data er egentlig veldig enkle, men det er ofte verdifullt å tenke på de prinsippene. Vi kan spare mye tid hvis vi vet akkurat hvordan vi har lyst å sette opp data. I ryddige data har hver variabel sin egen kolonne og hver observasjon sin egen rad. Det finnes mange eksempler av uryddige data. I så fall må vi først lage data om til et ryddig format før vi kan analysere de. Her finnes det mange eksempler.

Et eksempel der en kolonne inneholder flere variabler er følgende, der både karakter og antall poeng er i samme kolonnen.

StudentID

Matematikk

Fysikk

abc123

A (95 poeng)

B (85 poeng)

def456

B (82 poeng)

D (68 poeng)

ghi789

C (76 poeng)

A (92 poeng)

En mer ryddig variant ville delt det opp:

StudentID

Matematikk-Karakter

Fysikk-Karakter

Matematikk-Poeng

Fysikk-Poeng

abc123

A

B

95

85

def456

B

D

82

68

ghi789

C

A

76

92

Det er kanskje mindre åpenbar hva det betyr at hver observasjon skal ha sin egen rad. Hva som er en observasjon avhenger av målet med analysen vår. Hvis vi prøver å predikere eksamenskarakteren til studenter i data science, så er følgende ryddige data:

StudentID

Alder

Studieprogram-Informatikk

Studieprogram-Statistikk

Karakter-Programmering

Karakter-Kalkulus

abc123

24

1

0

3

6

def456

21

0

1

5

4

ghi789

32

1

0

5

5

Her prøver vi å predikere ut data science karakter per student, så vi vil ha en rad per student. I tillegg er hver kolonne en variabel som kan hjelpe oss med prediksjonen.

Et eksempel av samme data på et uryddig format for spørsmålet vi prøver å svare på er følgende:

StudentID

Alder

Studieprogram

Fag

Karakter

abc123

24

Informatikk

Programmering

3

def456

21

Statistikk

Programmering

5

ghi789

32

Informatikk

Programmering

5

abc123

24

Informatikk

Kalkulus

6

def456

21

Statistikk

Kalkulus

4

ghi789

32

Informatikk

Kalkulus

5

Det viser akkurat samme data, men her er det to rad per student. Sånn er ikke det enkelt å predikere én karakter per student. I underkapittel Pandas ser vi hvordan å omformatere data slikt.

Numpy#

Denne delen handler om numpy-biblioteket, som vi bruker veldig ofte når vi jobber med data i python. Først er det viktig å huske at man ikke blir flink til å bruke numpy ved å lese om bruken av numpy. Den eneste måten man lærer å programmere er ved å faktisk programmere.

Numpy er et bibliotek som brukes for å lage vektorer og matriser. Her lager vi en vektor av som inneholder tall 1 og 2.

vec = np.array([1, 2])

Hvis vi vil se på vektoren, så kan vi skrive den ut med print.

print(vec)
[1 2]

Hvis vi vil sjekke hvor mange elementer vektoren inneholder, så kan vi spørre etter shape egenskapen som i det tilfelle gir oss at den har lengde 2.

vec.shape
(2,)

Vi kan også lage matriser. Her lager vi identitetsmatrisen i to dimensjoner.

mat = np.eye(2)
print(mat)
[[1. 0.]
 [0. 1.]]

Når vi spør etter shape egenskapen får vi vite at matrisen har størrelsen 2 gang 2.

mat.shape
(2, 2)

Lage vektorer og matriser#

Det finnes mange måter å lage en numpy array. Her viser vi noen eksempler.

Numpy-funksjonen arange er det samme for numpy arrays som range er i standard python.

np.arange(5)
array([0, 1, 2, 3, 4])

Numpy-funksjonen linspace tillater at vi lager en array med fast start, slutt og en fast entall elementer som er jevnlig fordelt mellom starten og slutten.

np.linspace(start=2, stop=4, num=5)
array([2. , 2.5, 3. , 3.5, 4. ])

Vi kan bruke ones eller zeros for å lage en numpy array med bare enere eller bare nullere.

np.ones(5)
array([1., 1., 1., 1., 1.])
np.zeros([2, 3])
array([[0., 0., 0.],
       [0., 0., 0.]])

Så finnes det også mange måter å lage tilfeldige tall på. Her er eksempler på hvordan å lage uniform og normal fordelte tall. Vi skall snakke om forskjellige fordelinger i Sannsynlighet.

np.random.uniform(low=0, high=2, size=5)
array([0.47477833, 1.49162381, 1.35030741, 0.59560222, 1.08072494])
np.random.normal(loc=0, scale=1, size=(2, 4))
array([[-0.16357365, -0.54792586,  1.69838215,  0.94723717],
       [ 0.19561738,  1.74770547,  0.05872884, -1.05994023]])

Data i numpy arrays kan ha forskjellige data type. Vi kan for eksempel lage integer, float eller boolean numpy arrays. Men hele numpy array har alltid samme data type. Her er noen eksempler på forskjellige datatyper:

np.ones(4, dtype=int)
array([1, 1, 1, 1])
np.ones(4, dtype=float)
array([1., 1., 1., 1.])
np.ones(4, dtype=bool)
array([ True,  True,  True,  True])
np.ones(4, dtype=str)
array(['1', '1', '1', '1'], dtype='<U1')

Regning i numpy#

Vanligvis når vi regner med numpy arrays, så gjør vi elementvise regninger. Vi kan for eksempel addere to matriser eller multiplisere en matrise med et tall.

mat = np.arange(1, 7).reshape(2, 3)
mat
array([[1, 2, 3],
       [4, 5, 6]])
mat + mat
array([[ 2,  4,  6],
       [ 8, 10, 12]])
2*mat + 1
array([[ 3,  5,  7],
       [ 9, 11, 13]])

Det finnes mange funksjoner som man kan bruke elementvis i numpy, for eksempel sin, log, negative:

np.sin(mat)
array([[ 0.84147098,  0.90929743,  0.14112001],
       [-0.7568025 , -0.95892427, -0.2794155 ]])
np.log(mat)
array([[0.        , 0.69314718, 1.09861229],
       [1.38629436, 1.60943791, 1.79175947]])
np.negative(mat)
array([[-1, -2, -3],
       [-4, -5, -6]])

Det finnes også mange muligheter for å oppsummere matriser, for eksempel max, mean, og sum.

np.max(mat)
6
np.mean(mat)
3.5
np.sum(mat)
21

Hvis vi vil multiplisere en matrise med en vektor, så må vi være litt forsiktige. Her har vi lagd en identitetsmatrise mat og en vektor vec.

mat = np.eye(2)
vec = np.array([1, 2])

For å regne ut multiplikasjonen må vi bruke dot-metoden. Da får vi som forventet ut den opprinnelige vektoren v.

mat.dot(vec)
array([1., 2.])

Hvis vi bare bruker mutliplikasjonstegnet, så gjør vi elementvis multiplikasjon. Når dimensjonene ikke er like, så gjør numpy en kolonnevise multiplikasjon. Den første kolonnen av identitetsmatrisen blir altså multiplisert med 1 og den andre kolonnen med 2.

mat * vec
array([[1., 0.],
       [0., 2.]])

Undermatriser#

Når vi har en matrise, så vil vi ofte bare se på en del eller undermatrise av den. Det kan vi gjøre ved å spesifisere et enkelt element. Her viser vi matrisen A i rad én og kolonne to. Husk at vi begynner å numerere med 0.

A = np.arange(12).reshape(3, 4)
A
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
A[1, 2]
6

Vi kan også se på en hel rad eller kolonne ved å bruke kolon-tegnet på den andre dimensjonen.

A[1, :]
array([4, 5, 6, 7])
A[:, 1]
array([1, 5, 9])

Vi kan også angi lister for rad og kolonner og kan bruke en forkortet notasjon i kolon j, som tar med alle radene eller kolonnene fra og inkludert i til men ekskludert j.

A[[0, 2], :]
array([[ 0,  1,  2,  3],
       [ 8,  9, 10, 11]])
A[:, 1:3]
array([[ 1,  2],
       [ 5,  6],
       [ 9, 10]])

Kombinere data#

For å kombinere data, kan vi bruke hstack og vstack funksjonene. hstack setter data sammen horisontalt, fungerer altså kun hvis de to numpy arrays vi vil kombinere har samme antall rad. vstack setter data sammen vertikalt, fungerer altså hvis de to numpy arrays vi vil kombinere har samme antall kolonner.

A = np.eye(4)
B = np.ones([3, 4])
C = np.zeros([4, 2])
np.hstack((A, C))
array([[1., 0., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0., 0.],
       [0., 0., 0., 1., 0., 0.]])
np.vstack((A, B))
array([[1., 0., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.],
       [1., 1., 1., 1.],
       [1., 1., 1., 1.],
       [1., 1., 1., 1.]])

Pandas#

For å jobbe med tabulære data er det praktisk å bruke pandas.

import pandas as pd

Lese og beskrive data#

Vi begynner ved å lese inn data. Her bruker vi pandas sin read_csv-funksjonen. Det er kanskje den mest brukte måten å lese inn data. Men det finnes veldig mange forskjellige typer filer og for de fleste typer finnes det en pandas funksjon for å lese de inn, se i dokumentasjonen. Avhengig av hvordan data ser ut, så må vi også velge ut hvilke rad og kolonner vi vil lese inn, angi hvilke verdier som skal ansees som manglende verdier, hva slags kodering tekstdata har, og mange andre egenskaper av data. Alt dette er mulig med de riktige argumentene til read_csv-funksjonen.

df = pd.read_csv('data/informatikkemner_uib.csv')

Her leste vi inn data om alle informatikkemner ved Universitetet i Bergen (tilstand 9. august 2024). La oss se litt på det datasettet.

df.shape
(101, 6)

Først ser vi igjen på shape. Datasettet har altså 101 rad og 6 kolonner.

La oss se litt på de første radene ved hjelp av head-metoden. Det er ofte nyttig for å få et inntrykk av data.

df.head()
emnekode tittel semester nivå studiepoeng vurdering
0 BINF100 Grunnleggande bioinformatikk Vår Lågaregradsemne 10.0 Skriftleg skuleeksamen
1 BINF200 Analyse av biologiske sekvensar og strukturar Haust Lågaregradsemne 10.0 Mappevurdering
2 BINF201 Innføring i omics Haust Lågaregradsemne 10.0 Mappevurdering
3 BINF301 Genom-skala algoritmar Vår Masteremne 10.0 Skriftleg skuleeksamen
4 BINF305 Systembiologi Haust Masteremne 10.0 Mappevurdering

Her ser vi at det er informasjon om emnekode, tittel, hvilket semester det blir undervist i, hvilket nivå emnet er, hvor mange studiepoeng studentene for ved bestått emne og vurderingsformen.

Så skal vi finne ut av hva slags data hver variabel inneholder. I motsettning til numpy, så kan pandas inneholde flere datatyper. Men hver kolonne består av bare én datatype. Egenskapen dtypes gir oss informasjon om data er integer, float eller noe annet. For tekstdata står det object.

df.dtypes
emnekode        object
tittel          object
semester        object
nivå            object
studiepoeng    float64
vurdering       object
dtype: object

Vi kan bruke describe-metoden for å få litt statistikk om de data vi har lest inn. Her for vi antall målinger, gjennomsnitt, standardavvik, minimum, 25%-kvantil, median, 75%-kvantil og maksimum. Vi går gjennom de begrepene i kapitel Statistikk.

df.describe()
studiepoeng
count 101.000000
mean 10.153465
std 6.307037
min 0.000000
25% 10.000000
50% 10.000000
75% 10.000000
max 60.000000

Men her ser vi bare på numeriske data. Vi kan også bruke describe-metoden for å se på kategoriske data, men i så fall må vi bruke opsjonen include=object. Da får vi selvfølgelig litt forskjellige informasjon fordi mange av de statistikkene ikke ville gitt mening. Vi får opp antall observasjoner, antall observasjoner som er forskjellige fra hverandre, den mest vanlige observasjonen og hvor vanlig den er.

df.describe(include=object)
emnekode tittel semester nivå vurdering
count 101 101 101 101 77
unique 101 85 4 3 14
top BINF100 Videregåande emner/seminar i kryptografi Vår Masteremne Skriftleg skuleeksamen
freq 1 3 35 60 30

En annen viktig ting å se på er hvor mye av data som mangler. Vi kan i utgangspunktet se det delvis på count i describe, men det er greit å rege det ut direkte.

df.isnull().sum()
emnekode        0
tittel          0
semester        0
nivå            0
studiepoeng     0
vurdering      24
dtype: int64

Undermengder av data#

Samme som i numpy array vil vi ofte se på undermengder av data. Her bruker vi .loc metoden.

Vi kan velge ut kolonner ved å angi kolonnenavn.

df.loc[:, ['tittel', 'studiepoeng']]
tittel studiepoeng
0 Grunnleggande bioinformatikk 10.0
1 Analyse av biologiske sekvensar og strukturar 10.0
2 Innføring i omics 10.0
3 Genom-skala algoritmar 10.0
4 Systembiologi 10.0
... ... ...
96 Metaheuristikkar 10.0
97 Metodar for analyse av forsyningskjeder 10.0
98 Diskrete strukturar 10.0
99 Geometrisk djup læring 3.0
100 Masteroppgåve i Programvareutvikling samarbeid... 0.0

101 rows × 2 columns

Vi kan da spesifisere rad på samme måten.

df.loc[[20, 40, 60], :]
emnekode tittel semester nivå studiepoeng vurdering
20 INF112 Innføring i systemutvikling Vår Lågaregradsemne 10.0 Skriftleg eksamen
40 INF234 Algoritmer Haust Masteremne 10.0 Skriftleg skuleeksamen
60 INF272 Ikkje-lineær optimering Haust, Vår Masteremne 10.0 NaN

Men det er kanskje mer nyttig å angi en betingelse for radene. La oss for eksempel velge ut alle fag med tittel som starter på «Innføring».

df.loc[df.loc[:, 'tittel'].str.startswith('Innføring'), :]
emnekode tittel semester nivå studiepoeng vurdering
2 BINF201 Innføring i omics Haust Lågaregradsemne 10.0 Mappevurdering
17 INF100 Innføring i programmering Haust, Vår Lågaregradsemne 10.0 Skriftleg skuleeksamen (Ny eksamen)
20 INF112 Innføring i systemutvikling Vår Lågaregradsemne 10.0 Skriftleg eksamen
21 INF113 Innføring i operativsystem Haust Lågaregradsemne 10.0 Mappevurdering
27 INF161 Innføring i data science Haust Lågaregradsemne 10.0 Mappevurdering
37 INF225 Innføring i programomsetjing Haust Masteremne 10.0 Munnleg eksamen
39 INF227 Innføring i logikk Vår Masteremne 10.0 Skriftleg skuleeksamen
50 INF247 Innføring i kryptoanalyse av symmetriske chiffer Vår Masteremne 10.0 Skriftleg skuleeksamen
56 INF264 Innføring i maskinlæring Haust Masteremne 10.0 Mappevurdering
85 INF620 Innføring i programmering modul 1 Haust, Vår Lågaregradsemne 5.0 NaN
86 INF621 Innføring i programmering modul 2 Haust, Vår Lågaregradsemne 5.0 NaN
87 INF624 Innføring i datasikkerheit Haust Lågaregradsemne 5.0 NaN
88 INF626 Innføring i kunstig intelligens Vår Lågaregradsemne 5.0 NaN

Legg merke til at vi først lager en boolsk variabel og så bruker vi den til å indeksere. Her ser vi først på dette mellomsteget.

inforings_emner = df.loc[:, 'tittel'].str.startswith('Innføring')
inforings_emner
0      False
1      False
2       True
3      False
4      False
       ...  
96     False
97     False
98     False
99     False
100    False
Name: tittel, Length: 101, dtype: bool

Så kan vi enkelt bruke denne boolsken variabelen til å indeksere datasettet vårt.

df.loc[inforings_emner, :]
emnekode tittel semester nivå studiepoeng vurdering
2 BINF201 Innføring i omics Haust Lågaregradsemne 10.0 Mappevurdering
17 INF100 Innføring i programmering Haust, Vår Lågaregradsemne 10.0 Skriftleg skuleeksamen (Ny eksamen)
20 INF112 Innføring i systemutvikling Vår Lågaregradsemne 10.0 Skriftleg eksamen
21 INF113 Innføring i operativsystem Haust Lågaregradsemne 10.0 Mappevurdering
27 INF161 Innføring i data science Haust Lågaregradsemne 10.0 Mappevurdering
37 INF225 Innføring i programomsetjing Haust Masteremne 10.0 Munnleg eksamen
39 INF227 Innføring i logikk Vår Masteremne 10.0 Skriftleg skuleeksamen
50 INF247 Innføring i kryptoanalyse av symmetriske chiffer Vår Masteremne 10.0 Skriftleg skuleeksamen
56 INF264 Innføring i maskinlæring Haust Masteremne 10.0 Mappevurdering
85 INF620 Innføring i programmering modul 1 Haust, Vår Lågaregradsemne 5.0 NaN
86 INF621 Innføring i programmering modul 2 Haust, Vår Lågaregradsemne 5.0 NaN
87 INF624 Innføring i datasikkerheit Haust Lågaregradsemne 5.0 NaN
88 INF626 Innføring i kunstig intelligens Vår Lågaregradsemne 5.0 NaN

Databeskrivelse med pandas#

Typiske ting vi kan gjøre med pandas er å se på beskrivende statistikk, som mean, max for numeriske variabler.

df.loc[:, 'studiepoeng'].mean()
10.153465346534654
df.loc[:, 'studiepoeng'].max()
60.0

Vi kan også for eksempel se på antall forskjellige verdier.

df.nunique()
emnekode       101
tittel          85
semester         4
nivå             3
studiepoeng      8
vurdering       14
dtype: int64

For enkle variabler er det også nyttig å se på antall ganger hver verdi forekommer.

df.loc[:, 'studiepoeng'].value_counts()
10.0    84
5.0      6
2.5      5
30.0     2
20.0     1
60.0     1
3.0      1
0.0      1
Name: studiepoeng, dtype: int64

Ofte er det også nyttig å gruppere etter en variabel og gjøre utregninger per gruppe. Her ser vi for eksempel på hvor mange unique verdier det er for alle variabler avhengig av semesteret.

df.groupby("semester").nunique()
emnekode tittel nivå studiepoeng vurdering
semester
Haust 31 30 3 2 4
Haust, Vår 31 25 3 6 9
Uvisst 4 3 2 3 0
Vår 35 31 3 4 6

Kombinere data#

Først trenger vi et datasett som kan kombineres med dataene vi har. Vi kan også lage data frames selv. Den vanligste måten er ved bruk av pandas DataFrame init-metoden og en dictionary som inneholder kolonnene. La oss lage et dataframes som inneholder noen av undervisere for høst 2024.

df_undervisere_2024 = pd.DataFrame(dict(emne=['INF161', 'INF264', 'AIKI100'], undervisere=['Blaser', 'Malde', 'Pedersen'])) 
df_undervisere_2024 
emne undervisere
0 INF161 Blaser
1 INF264 Malde
2 AIKI100 Pedersen

Nå skal vi kombinere de to datasett. Først må vi sette den kolonnen som skal være lik som indeks-kolonnen. Det gjør at vi kan nå bruke id direkte for å få data rad med denne id-en.

df_undervisere_2024.set_index('emne', inplace=True)
df.set_index('emnekode', inplace=True)

Nå er vi klar til å kombinere data.

For å kombinere data må vi først spesifisere de to datasett. Så må vi angi hva å gjøre når de to datasett ikke inneholder de samme studentene. Ved bruk av ’right’, så får vi med alle emner der vi har informasjon om undervisere.

df.join(df_undervisere_2024, how='right')
tittel semester nivå studiepoeng vurdering undervisere
emne
INF161 Innføring i data science Haust Lågaregradsemne 10.0 Mappevurdering Blaser
INF264 Innføring i maskinlæring Haust Masteremne 10.0 Mappevurdering Malde
AIKI100 NaN NaN NaN NaN NaN Pedersen

Hvis vi bruker ’inner’ i stedet, så får vi bare informasjon om de radene som er med i begge datasett.

df.join(df_undervisere_2024, how='inner')
tittel semester nivå studiepoeng vurdering undervisere
INF161 Innføring i data science Haust Lågaregradsemne 10.0 Mappevurdering Blaser
INF264 Innføring i maskinlæring Haust Masteremne 10.0 Mappevurdering Malde

Vi kan også bruke ’outer’ og ’left’. Se selv hva som skjer i så fall. Hvilken av de typene joins (altså ’left’, ’right’, ’inner’, eller ’outer’) vi skal bruke avhenger av hva vi vil ha som radene - husk vår diskusjon om Ryddige data.

Reformatere data#

Nå kommer det noen funksjoner som kan være praktisk for å reformatere data på en ryddig måte. Her lager vi eksemplet av uryddig data fra Seksjon Ryddige data.

df_uryddig = pd.DataFrame(dict(StudentID=['abc123', 'def456', 'ghi789'], 
                               Matematikk=['A (95 poeng)', 'B (82 poeng)', 'C (76 poeng)'], 
                               Fysikk=['B (85 poeng)', 'D (68 poeng)', 'A (92 poeng)']))
df_uryddig
StudentID Matematikk Fysikk
0 abc123 A (95 poeng) B (85 poeng)
1 def456 B (82 poeng) D (68 poeng)
2 ghi789 C (76 poeng) A (92 poeng)

I dette eksempelet trenger vi igjen str-funksjonen for å jobbe med tekst.

df_ryddig = df_uryddig.copy()
df_ryddig[['Matematikk-Karakter', 'Matematikk-Poeng']] = df_ryddig['Matematikk'].str.extract(r'([A-F]) \((\d+) poeng\)')
df_ryddig[['Fysikk-Karakter', 'Fysikk-Poeng']] = df_ryddig['Fysikk'].str.extract(r'([A-F]) \((\d+) poeng\)')
df_ryddig = df_ryddig.loc[:, ['StudentID', 'Matematikk-Karakter', 'Fysikk-Karakter', 'Matematikk-Poeng', 'Fysikk-Poeng']]
df_ryddig
StudentID Matematikk-Karakter Fysikk-Karakter Matematikk-Poeng Fysikk-Poeng
0 abc123 A B 95 85
1 def456 B D 82 68
2 ghi789 C A 76 92

Her endret vi kun på kolonnene, men radene har fortsatt samme betydning som før. I det neste eksempelet bytter vi på betydning av radene også. Her lager vi først datasettet.

df_original = pd.DataFrame({
    'StudentID': ['abc123', 'def456', 'ghi789'],
    'Alder': [24, 21, 32],
    'Studieprogram-Informatikk': [1, 0, 1],
    'Studieprogram-Statistikk': [0, 1, 0],
    'Karakter-Programmering': [3, 5, 5], 
    'Karakter-Kalkulus': [6, 4, 5]})
df_original
StudentID Alder Studieprogram-Informatikk Studieprogram-Statistikk Karakter-Programmering Karakter-Kalkulus
0 abc123 24 1 0 3 6
1 def456 21 0 1 5 4
2 ghi789 32 1 0 5 5

Som første steg fjerner vi dummy-kolonnene for studieprogramet.

df = df_original.copy()
dummie_columns = ['Studieprogram-Informatikk', 'Studieprogram-Statistikk']
studieprogram_df = pd.from_dummies(df.loc[:, dummie_columns], sep='-')
df = pd.concat([df.drop(dummie_columns, axis=1), studieprogram_df], axis=1)
df
StudentID Alder Karakter-Programmering Karakter-Kalkulus Studieprogram
0 abc123 24 3 6 Informatikk
1 def456 21 5 4 Statistikk
2 ghi789 32 5 5 Informatikk

Så lager vi en ny datasett, som inneholder en rad per karakter. Vi må angi hvilke kolonnene som skal til sammen identifisere en rad (id_vars) og hva den nye variablen (var_name) og verdien (value_name) skal hete.

df_melted = pd.melt(df, id_vars=['StudentID', 'Alder', 'Studieprogram'], var_name='Fag', value_name='Karakter')
df_melted
StudentID Alder Studieprogram Fag Karakter
0 abc123 24 Informatikk Karakter-Programmering 3
1 def456 21 Statistikk Karakter-Programmering 5
2 ghi789 32 Informatikk Karakter-Programmering 5
3 abc123 24 Informatikk Karakter-Kalkulus 6
4 def456 21 Statistikk Karakter-Kalkulus 4
5 ghi789 32 Informatikk Karakter-Kalkulus 5

Til slutt kan vi fjerne ’Karakter-’ fra faget.

df_melted.loc[:, 'Fag'] = df_melted.loc[:, 'Fag'].str.split('-', n=1).str.get(1)
df_melted
StudentID Alder Studieprogram Fag Karakter
0 abc123 24 Informatikk Programmering 3
1 def456 21 Statistikk Programmering 5
2 ghi789 32 Informatikk Programmering 5
3 abc123 24 Informatikk Kalkulus 6
4 def456 21 Statistikk Kalkulus 4
5 ghi789 32 Informatikk Kalkulus 5

Nå har vi omformattert datasettet til et annet format. Kan vi komme tilbake igjen?

Her bruker vi akkurat de samme stegene men baklengs. Først legger vi til ’Karakter-’ til faget igjen.

df_melted.loc[:, 'Fag'] = 'Karakter-' + df_melted.loc[:, 'Fag']
df_melted
StudentID Alder Studieprogram Fag Karakter
0 abc123 24 Informatikk Karakter-Programmering 3
1 def456 21 Statistikk Karakter-Programmering 5
2 ghi789 32 Informatikk Karakter-Programmering 5
3 abc123 24 Informatikk Karakter-Kalkulus 6
4 def456 21 Statistikk Karakter-Kalkulus 4
5 ghi789 32 Informatikk Karakter-Kalkulus 5

Så bruker vi funksjonen pivot til å lage flere kolonner ut av en kolonne. Legg merke til at det som var id_vars for melt-funksjonen blir til index nå. Det som var var_name blir til columns og det som var value_name ble til values. Her ser godt at pivot og melt er inverse funksjoner.

df_pivoted = df_melted.pivot(index=['StudentID', 'Alder', 'Studieprogram'], columns='Fag', values='Karakter').reset_index()
df_pivoted
Fag StudentID Alder Studieprogram Karakter-Kalkulus Karakter-Programmering
0 abc123 24 Informatikk 6 3
1 def456 21 Statistikk 4 5
2 ghi789 32 Informatikk 5 5

Til slutt må vi også lage dummy-variablene igjen.

df_tilbake = pd.get_dummies(df_pivoted, columns=['Studieprogram'], dtype=int, prefix_sep='-')
df_tilbake
StudentID Alder Karakter-Kalkulus Karakter-Programmering Studieprogram-Informatikk Studieprogram-Statistikk
0 abc123 24 6 3 1 0
1 def456 21 4 5 0 1
2 ghi789 32 5 5 1 0

Nå har vi de samme data som vi begynte med. Vi kan sjekke det ved gjelp av equals-funksjonen. Siden kolonne-rekkefølgen har forandret seg bruker vi sort_index på begge dataframes.

df_original.sort_index(axis=1).equals(df_tilbake.sort_index(axis=1))
True