Dataanalyse
Etter en rask intrukduksjon til numpy og pandas, kan man begynne på sitt første dataanalyse-prosjekt.
Den beste definisjonen av dataanalayse vil være å prøve å besvare spørsmål ved å bruke data. I bunn og grunn er det nettopp dette som er poenget. Dette er likevel en forenklet sannhet da det er vanskelig å gi en generell definisjon for et så bredt fagfelt.
Her er noen eksempler der dataanalyse benyttes til å besvare spørsmål, store og små:
- Bioinformatikk: Kan vi identifisere ulike gener hos pasienter med og uten lungekreft?
- Personlig tidsstyring: Hvilke daglige vaner korrelerer med høy produktivitet?
- Miljøvitenskap: Hvordan kan vi forutsi jordskjelv bedre?
- Eiendom: Hva trender for tiden i Bergens boligmarked?
- Vinteridrettsmedisin: Hvilke biomekaniske faktorer bidrar til ACL-skader hos skiløpere?
- Romforskning: Kan gravitasjonsbølger avdekke sort hulls opprinnelse?
- Pokémoner: Hva er de ulike Pokémon-typene?
Vår data
Nå skal vi jobbe med en dataframe om hauker. Dataene ble samlet under fuglefangsten av studenter ved Cornell College, USA. Datasettet inkluderer fugleartene: Rødhalevåk, Tverrhalehauk og Trostehauk:

Image credit: wikipedia and allaboutbirds.org
import pandas as pd
import numpy as np
hawks = pd.read_csv('https://raw.githubusercontent.com/vincentarelbundock/Rdatasets/refs/heads/master/csv/Stat2Data/Hawks.csv', index_col=0)
hawks.head()
Første steg i en skikkelig dataanalyse er å sette et mål, eller et spørsmål som skal besvares. Dette bestemmes normalt av prosjektet man jobber med. Her ønsker vi å besvare dette spørsmålet:
Hvordan skilles de ulike haukearter basert på fysiske egenskaper?
For å analysere dette spørsmålet effektivt, må det brytes ned til mindre, håndterbare steg. Det vil si:
- Utforske data
- Se etter mønster
- Avgjøre hvilke variabler som skiller artene mest
Man kan dele variabene i ulike kategorier:
- Haukens art er i denne analysen en avhengig variabel. Vi skal undersøke hvordan de ulike artene skilles basert på fysiske egenskaper
- Art er også en kategorisk variabel (1 av 3 arter). Denne analysen kan for eksempel innebære gruppering eller klassifisering
- Alle andre variabler er forklaringsvariabler/ uavhengige variabler som vi ønsker å analysere for å kunne svare på spørsmålet vårt
Det er viktig å forstå hva dataene man skal analysere faktisk handler om. En beskrivelse av variablene kan hjelpe oss med dette (kilde)
Kolonne Beskrivelse
Month: 8 september til 12 desember
Day: Dag i måneden
Year: År: 1992-2003
CaptureTime: Tid fanget (HH:MM)
ReleaseTime: Tid løslagg (HH:MM)
BandNumber: ID-kode bånd
Species: CH=Cooper’s, RT=Red-tailed, SS=Sharp-shinned
Age: A=Adult or I=Immature
Sex: F=Female or M=Male
Wing: Lengde (i mm) av hovedvingefjær fra ende til ledd
Weight: Kroppsvekt (i gram)
Culmen: Lengde (i mm) av overnebbet der fra tuppen til der det treffer ansiktet
Hallux: Lengde (i mm) av rovkloen
Tail: Mål (i mm) i forindelse med halens lengde (oppfunnet ved MacBride rovfuglsenter)
StandardTail: Standardmål av halelengden (i mm)
Tarsus: Lengde av fotbeinet (i mm)
WingPitFat: Fettmengde i vingespissen
KeelFat: Fettmengde på brystbeinet (measured by feel)
Crop: Kroens fyllingsgrad, justert fra 1=full til 0=tom
Dataoversikt
Første steg når man jobber med et datasett er å få et godt overbill over dataenes struktur og innhold:
hawks.info()
# <class 'pandas.core.frame.DataFrame'>
# Index: 908 entries, 1 to 908
# Data columns (total 19 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 Month 908 non-null int64
# 1 Day 908 non-null int64
# 2 Year 908 non-null int64
# 3 CaptureTime 908 non-null object
# 4 ReleaseTime 907 non-null object
# 5 BandNumber 908 non-null object
# 6 Species 908 non-null object
# 7 Age 908 non-null object
# 8 Sex 332 non-null object
# 9 Wing 907 non-null float64
# 10 Weight 898 non-null float64
# 11 Culmen 901 non-null float64
# 12 Hallux 902 non-null float64
# 13 Tail 908 non-null int64
# 14 StandardTail 571 non-null float64
# 15 Tarsus 75 non-null float64
# 16 WingPitFat 77 non-null float64
# 17 KeelFat 567 non-null float64
# 18 Crop 565 non-null float64
# dtypes: float64(9), int64(4), object(6)
# memory usage: 141.9+ KB
Det er viktig å undersøke oppsummerende statistikk (som gjennomsnitt, median og spredning) gruppert etter art for å oppdage viktige forskjeller. Dette kan hjelpe oss med å se mønstre, for eksempel hvis en art har en tendens til å være større eller tyngre enn en annen.
Vi starter med å telle antall observasjoner av hver art. NB: samme hauk kan ha blitt observert flere ganger.
# teller hvor mange ganger hver fugl ble observert i snitt (båndnummer identifiserer en fugl)
print((hawks['BandNumber'].value_counts().sort_values(ascending=False)), "\n")
# beholder kun distinkte båndnummer (fjerner duplikate fugler)
hawks_unique = hawks.drop_duplicates(subset=['BandNumber'], keep='last') # beholder siste observasjon
# teller etter art
print(hawks_unique['Species'].value_counts().sort_values())
# Species
# CH 69
# SS 261
# RT 577
La oss se på litt oppsummerende statistikk etter art - først for numeriske data, så for faktorer. For enkelhets skyld konverteres noen av kolonnene til annen enhet.
hawks['Weight_kg'] = hawks['Weight'] / 1000 # konverterer vekt til kg
hawks['Culmen_cm'] = hawks['Culmen'] / 10 # konverteres til cm
hawks['Hallux_cm'] = hawks['Hallux'] / 10 # konverteres til cm
hawks['Wing_cm'] = hawks['Wing'] / 10 # konverteres til cm
print(hawks[['Wing_cm', 'Weight_kg', 'Culmen_cm', 'Hallux_cm', 'Species']].groupby('Species').mean().round(2))
# Wing_cm Weight_kg Culmen_cm Hallux_cm
# Species
# CH 24.41 0.42 1.76 2.28
# RT 38.33 1.09 2.70 3.20
# SS 18.49 0.15 1.15 1.50
For å slippe å regne alt manuelt, kan man bruke describe()-metoden:
print(hawks.describe())
Oppsummerende statistikk er nyttig, men det forteller ikke alltid hele historien:
- Noen kolonner trenger det ikke. For eksempel, Month, Day og Year representerer datoer, så det gir egentlig ingen mening å beregne et gjennomsnitt. På samme måte er BandNumber en ID, og trenger ikke analyseres med resten av dataene
- Outliers kan ødelegge. I Weight-kolonnen er den minste verdien 56g og den største 2030g - det er en enorm forskjell. Kun noen få ekstreme verdier som dette kan drastisk endre gjennomsnittet, og få det til å se høyere eller lavere ut enn det egentlig er. Derfor brukes ofte median (den midterste verdien) for å gi en bedre idé om hva som er typisk. Det er alltid lurt å se etter unormale data før man trekker en konklusjon.
Men hva med kategoriske variabler?
# printer kolonner med dtype objekt
print(hawks.select_dtypes(include='object').sample(10))
Hvilke konklusjoner kan man gjøre fra dette?
Rensing av data
Det er særlig to ting å legge merke til fra disse resultatene:
-
Dataene ble konvertert til riktig datatype, som gjør analyseprosessen litt enklere.
-
Det er 908 observasjoner totalt, men noen av variablene har manglende verdier (NaN, inneholder ingen verdier)
Punkt nr 2 kan bli et problem. Generelt ekslkuderer operasjoner manglende data, men dette kan gi uønskede resultater:
try:
hawks['Weight_int'] = hawks['Weight'].astype('int')
except ValueError as e:
print("IntCastingNaNError:", e)
# IntCastingNaNError: Cannot convert non-finite values (NA or inf) to integer
Vi må altså behandle disse manglende verdiene. Her har vi flere muligheter:
- La de være…
- fjerne manglende verdier - fjern hele rader/ kolonner. Dette fungerer fint når mange verdier mangler for en bestemt trekk eller observasjon. Hvis ikke mister man for mye data.
- Fylle med satt verdi, ofte gjennomsnitt, median eller noe annet som gir mening. Ok hvis variasjonen i dataene ikke er veldig stor og hvis det kun gjelder få manglende verdier.
- Interpolasjon - fyller inn med en gjettet verdi basert på tilgjengelige data
- Predikere manglende verdier (med for eksempel lineær regresjon)
columns_to_fill = ['Weight', 'Wing', 'Tail'] # viktig data med lite NaN
columns_to_drop = ['Tarsus', 'WingPitFat'] # ikke så viktig data med mye NaN
columns_to_interpolate = ['StandardTail', 'KeelFat', 'Crop'] # en mellomting
# 1. fjerne verdier
hawks = hawks.drop(columns=columns_to_drop) # fjerner kolonnene fra analysen
# eller
bad_rows = hawks[hawks.isna().sum(axis=1) > 3].index # identifiserer observasjoner med mer enn 3 manglende verdier
hawks = hawks.drop(index=bad_rows) # fjerner disse observasjonene
# 3. interpolasjon
for column in columns_to_interpolate:
hawks[column] = hawks[column].interpolate(method='linear') # interpolering av manglende verdier
# 2. fylling med faste verdier
for column in columns_to_fill:
columns_mean = hawks[column].mean() # calculate mean of the columns
hawks[column] = hawks[column].fillna(columns_mean) # fill missing values with mean
# mer korrekt enn å fylle inn med gjennomsnitt kunne vært
# å finne gjennomsnitt for hver art separat - spesifikt gjennomsnitt
hawks = pd.read_csv('https://raw.githubusercontent.com/vincentarelbundock/Rdatasets/refs/heads/master/csv/Stat2Data/Hawks.csv', index_col=0)
for column in columns_to_fill:
species_means = hawks.groupby('Species')[column].transform('mean') # kalkulerer art-spesifikke gjennomsnitt
hawks[column] = hawks[column].fillna(species_means) # fyller manglende verdier med de arts-spesifikke gjennomsnittene
Korrelasjon
Korrelasjon måler både styrke og retning til den lineære sammenhengen mellom to numeriske variabler. I sin enkleste form, refererer det til sammenhengen mellom to variablene. Korrelasjon blir oppgitt som et tall mellom -1 og 1. Her er noen eksempler:
- Trening og kalorier: Når man trener vil man forbrenne kalorier. Man kan derfor si at trening og forbrenning av kalorier korrelerer positivt. Når den ene variabelen øker, vil den andre også øke.
- Jo høyere man klatrer i fjellet, jo lavere blir oksygennivået. Oksygennivå og høyde korrelerer altså negativt.
- To variabler som ikke påvirker hverandre i det hele tatt vil ha null korrelasjon
La oss beregne korrelasjoner mellom de numeriske variablene i dataframen:
hawks.select_dtypes(include='float').corr().round(2)
Her er det viktig å huske at korrelasjon ikke betyr kausal sammenheng.
