Pandas

Introduksjon

cute sticker

(designed by Nerdy Vogue)

Mennesker produserer enorme mengder data hver dag. Hver gang noen trykker på en skjerm, kjøper noe på butikken eller besøker en nettside lager du data som lagres i et gitt format. Den enkleste måten å forestille seg data på er i tabeller: hver rad representerer en observasjon og hver kolonne represerer en attribut/ kategori for den gitte observasjonen.

Når det gjelder å jobbe med datatabeller på datamaskiner, er Excel, Sheets eller R de første verktøyene man tenker på. De er alle gode, men ingen av dem gir like stor skalerbarhet, støtte fra brukersamfunnet og allsidige muligheter for videre arbeid med dataene på samme tid. Hva om vi ønsker å analysere tabelldata i Python? Svaret er biblioteket Pandas! Det er egnet for ulike formater, som for eksempel:

Grunnleggende

Datatabeller i pandas kalles DataFrames. Det er en 2-dimensjonal, størrelsesmuterbar, potensielt hetrogen tabell. Man kan tenke på det som et oppslagsverk der nøklene er variabelnavnene, og verdiene er data i dataframen. Her er hvordan man kan lage en dataframe fra bunn av:

# importrer pakken (hvis ModuleNotFoundError: pip install pandas)
import pandas as pd

## mulighet 1: fra et oppslagsverk med nøkler som kolonnenavn og verdi som kolonneverdier
gene_data = {
    "Gene": [       # kolonnenavn
        "BRCA1",    # kolonneverdier
        "TP53",
        "MYC"
    ],
    "Level": [150, 320, 210]
    }

df = pd.DataFrame(gene_data)
print(df, "\n\n", type(df)) #  <class 'pandas.core.frame.DataFrame'>
                            #     Gene  Level
                            # 0  BRCA1    150
                            # 1   TP53    320
                            # 2    MYC    210 


## mulighet 2: fra en 2D-liste med data pluss en vanlig liste med kolonnenavn
colnames = ["Gene", "Level"]
values = [
    ["BRCA1", 150],
    ["TP53", 320],
    ["MYC", 210]]
df = pd.DataFrame(values, columns = colnames)
print(df, "\n\n", type(df)) #  <class 'pandas.core.frame.DataFrame'>
                            #     Gene  Level
                            # 0  BRCA1    150
                            # 1   TP53    320
                            # 2    MYC    210

Når det er sagt, jobber vi ofte med dataframes som allerede er lagd. De kan være lagret i ulike formater. De mest vanlige er .csv, .xls og .json. En av de mest populære datasettene for trening er iris. Nå skal vi undersøke det litt nærmere:

import csv
import requests
import io
csv_url = 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv'

## mulighet 1: last ned en CSV-fil fra internettet, konverter den til et oppslagsverk og sett det som en DataFrame

# Last ned CSV-filen fra GitHub
response = requests.get(csv_url) # last ned CSV-filen
response.raise_for_status()  # valgfritt: Sikre at filen ble hentet
data_string = response.text

# Les csv-fil akkurat som tidligere!
reader = csv.DictReader(io.StringIO(data_string), delimiter=',', quotechar="'")
headers = reader.fieldnames
data = list(reader)

iris = pd.DataFrame(data)
print(iris.head(5)) # vis de første N radene 

## mulighet 2: ybruk read_csv-metoden (ikke bruk denne på eksamen!)

iris2 = pd.read_csv(csv_url,
                    dtype={'sepal_length': float,
                           'sepal_width': float,
                           'petal_length': float,
                           'petal_width': float,}) # , index_col=0
print(iris2.head()) # (5 er default)

## begge vil skrive ut:

#    sepal_length  sepal_width  petal_length  petal_width species
# 0           5.1          3.5           1.4          0.2  setosa
# 1           4.9          3.0           1.4          0.2  setosa
# 2           4.7          3.2           1.3          0.2  setosa
# 3           4.6          3.1           1.5          0.2  setosa
# 4           5.0          3.6           1.4          0.2  setosa

iris = iris2

Som med oppslagsverk, kan man lage nye kolonner i dataframen ved å henvise til nye kolonnenavn.

import random
print('Før ny kolonne ble lagt til:', iris.shape) # (150, 5) - 150 rows, 5 columns
random_values = [round(random.random(), 2) for i in range(len(iris))] # passer på at den nye listen er like lang som antall rader i dataframen

# lage ny kolonne
iris['nonsence_random_floats'] = random_values 
print('Etter kolonne ble lagt til:', iris.shape) # (150, 6) - ny kolonne vises

# slette eksisterende kolonne
iris = iris.drop(columns=['nonsence_random_floats']) 
print('Etter drop:', iris.shape) # (150, 5) - ha det kolonne!         

I tillegg til .head() og .tail() for å se de første og siste N radene, kan man også se dataframens kolonner og konvertere de tilbake til numpy arrays:

print(iris.head(2), '\n\n') # viser de første 2 radene
print(iris.tail(2), '\n\n') # viser de siste 2 radene
print(iris.columns, '\n\n') # viser kolonnenes navn: ['sepal_length', 'sepal_width', 'petal_length', 'petal_width','species']
print(iris.index, '\n\n')   # viser radens indekser (kun en range hvis ikke annet er spesifisert)
print(iris.to_numpy()[0:3]) # konverterer dataframen til et numpy array

Indeks og subset

Akkurat som dataholdere i Python, tillater dataframes oss å hente spesifikk data. For pandas dataframes kan man bruke både nøkler (kolonnenavn) og indekser (radnummer) til å hente data:

# hente kolonner
species_column = iris['species']  # akkurat som med oppslagsverk
species_column2 = iris.species    # eller som en attributt
assert species_column.equals(species_column2)  # True
print(species_column, "\n\n", type(species_column))  # <class 'pandas.core.series.Series'>
                                                    # 0       setosa
                                                    # 1       setosa
                                                    # 2       setosa
                                                    # ...
                                                    # 149    virginica
                                                    # Name: species, dtype: object

# hente rader
row = iris.loc[2]  # 3rd obs/rad (med indeks 2)
print(row, "\n\n", type(row))  # <class 'pandas.core.series.Series'>
                               # sepal_length       4.7
                               # sepal_width        3.2
                               # petal_length       1.3
                               # petal_width        0.2
                               # species         setosa
                               # Name: 2, dtype: object

Ved begge metoder får man en pandas serie - et 1D array. Den første printer hele art-kolonnen, mens den andre kun henter ut en spesifikk rad.

Hvis man vil hente ut eller endre en spesifikk celles verdi, kan man gjøre følgende:

print(iris.iloc[5,2]) # 1.7
iris.iat[5,2] = 1.8 # endrer verdien på 6. rad og 3. kolonne til 1.8 (pass på at det er samme datatype)

Man kan også lage subset av en dataframe. Dette gjøres på 2 måter:

example of filtering operation
fra Pandas tutorial

# lage subset uten filter
irises_sepals_data = iris[['sepal_length', 'sepal_width' , 'species']]  # velg kun kolonnene om sepals og art
half_of_irises_sepals_data = irises_sepals_data.sample(frac=0.5)        # bonus: hent tilfeldig utvalg av 50% av observasjonene
print(half_of_irises_sepals_data.head())                                # vis N rader
#      sepal_length  sepal_width     species
# 127           6.1          3.0   virginica
# 84            5.4          3.0  versicolor
# 130           7.4          2.8   virginica
# 114           5.8          2.8   virginica
# 100           6.3          3.3   virginica...
# Filtrer rader der sepal_width er større enn 3
more_than_3_mask = iris['sepal_width'].astype(float) > 3   # filtrer rader etter en betingelse
print(more_than_3_mask, '\n')  # Boolean maske
# 0       True
# 1      False
# 2       True
# ...

# Behold kun radene der betingelsen er True
print(iris[more_than_3_mask], '\n')  # Filtrerte rader
# # [67 rows x 5 columns] 

# Alternativt i en komprimert linje (motsatt betingelse):
print(iris[iris['sepal_width'].astype(float) <= 3])  # Filtrer radene etter motsatt betingelse
# [83 rows x 5 columns]

Sett sammen to dataframes

Noen ganger trenger man å merge to dataframes. Her er ulike måter å gjøre det på:

La oss splitte iris dataframen og sette den sammen igjen:

# del iris dataframen i 2
iris1 = iris.iloc[:75]  # første halvdel
iris2 = iris.iloc[75:] # andre halvdel

# concatenate to dataframes
iris_combined = pd.concat([iris1, iris2], axis=0)  # axis=0 betyr rader

# lag en ny Serie med størrelse på blomstene basert på observasjonene i dataframen
sizes = pd.Series({'virginica': 'big', 'versicolor': 'medium', 'setosa': 'small'}, name='size')
iris = iris.merge(sizes, left_on='species', right_index=True, how='left')  # merge iriser med den nye dataen etter kolonnen 'species' 
print(iris.sample(3))                                                      # vis 3 tilfeldige rader

Opperasjoner med dataframes

Dataframe-objekter arver noen attributter fra numpy array-klassen. Man kan altså sjekke ting som -size, .shape og .dtypes.

Når det kommer til metoder (pandas-funksjoner som brukes etter dataframen som df.head()), er det mye nyttig man kan gjøre.

# hent formen på dataframen
print("\nForm på dataframen:", iris.shape)    # (150, 5)

# hent datatypen for hver kolonne
print("\nDatatype for hver kolonne:\n", iris.dtypes, sep = "") 
# sepal_length    float64
# sepal_width     float64
# petal_length    float64
# petal_width     float64
# species          object

Den største fordelen er likevel at man kan se oppsummerings-statistikk. Her er det viktig at alle kolonner har korrekt datatype

iris.info()
summary = iris.describe() # oppsummerigs-statistikk
print("Oppsummerings-statistikk:\n", summary.transpose(), sep = "") # for lesbarhet

# Oppsummerings-statistikk:
#               count      mean       std  min  25%   50%  75%  max
# sepal_length  150.0  5.843333  0.828066  4.3  5.1  5.80  6.4  7.9
# sepal_width   150.0  3.057333  0.435866  2.0  2.8  3.00  3.3  4.4
# petal_length  150.0  3.758000  1.765298  1.0  1.6  4.35  5.1  6.9
# petal_width   150.0  1.199333  0.762238  0.1  0.3  1.30  1.8  2.5

Gruppering

Denne statistikken kan også kalkuleres for hver art - dette kalles gruppering, eller group by. Dette referer normal til en prosess som involverer ett eller flere av de følgende stegene:

example of groupping operation
fra Pandas tutorial

grouped_medians = iris.groupby('species')['petal_length'].median()  # gruppere etter art og finne medianen
print("Grouped Summary Statistics by Species:\n", grouped_medians)