Jobbe med data

Når man oppretter et dapla-team så får vi tildelt et eget området for lagring av data. For å kunne lese og skrive data fra Jupyter til disse områdene må vi autentisere oss, siden Jupyter og lagringsområdet er to separate sikkerhetsoner.

Figur 1 viser dette klarer skillet mellom hvor vi koder og hvor dataene ligger på Dapla1. I dette kapitlet beskriver vi nærmere hvordan du kan jobbe med dataene dine på Dapla.

Bilde som viser forskjellen mellom kodemiljø og hvor data lagres.
Figur 1: Tydelig skille mellom kodemiljø og datalagring på Dapla.

SSB-biblioteker

For å gjøre det enklere å jobbe data på tvers av Jupyter og lagringsområdet er det laget noen egne SSB-utviklede biblioteker for å gjøre vanlige operasjoner mot lagringsområdet. Siden både R og Python skal brukes på Dapla, så er det laget to biblioteker, en for hver av disse språkene. fellesr er biblioteket for R, og dapla-toolbelt er biblioteket for Python.

dapla-toolbelt

dapla-toolbelt er en en pakke som lar deg enkelt lese og skrive til lagringsområdet uten å måtte autentifisere deg manuelt. Den har en Pandas-aktig syntaks som forhåpentlig er gjenkjennbar for de fleste. Pakken er installert i alle Python-kernels på Dapla, så du trenger ikke å installere den selv hvis du åpner en notebook med Python3 for eksempel. For å importere hele biblioteket i en notebook skriver du bare

notebook
import dapla as dp

dapla-toolbelt bruker en pakke som heter gcsfs for å kommunisere med lagringsområdet. gcsfs er en pakke som lar deg bruke Google Cloud Storage (GCS) som om det var en filsystem. Det betyr at du kan bruke samme syntaks som du bruker for å lese og skrive til filer på din egen maskin. Du kan lese mulighetene i gcsfs her. Et eksempel på hvordan de to pakkene kan brukes sammen ser du her:

notebook
from dapla import FileClient
fs = FileClient.get_gcs_file_system()

# Example of how you can use gcsfs and dapla-toolbelt together
fs.touch("gs://my-bucket/my-folder/")

I koden over brukte jeg kommandoen touch fra gcsfs og FileClient fra dapla-toolbelt for å opprette en mappe i lagringsområdet.

I kapitlene under finner du konkrete eksempler på hvordan du kan bruke dapla-toolbelt til å jobbe med data i SSBs lagringsområdet.

fellesr

R-pakken fellesr er under utvikling og gir mye av den samme funksjonaliteten som dapla-toolbelt gir for Python. I tillegg til å kunne lese og skrive til lagringsområdet på Dapla, så har fellesr også funksjoner for å jobbe med metadata på Dapla.

På Dapla Lab kan man bruke vanlige R-funksjoner for mappene du har tilgjengelig i filsystemet, dvs. bøtten du har tilgang til via Dapla-teamet ditt. Det betyr at funksjonene for å lese (read_SSB) og skrive (write_SSB) data i fellesr ikke lenger er nødvendige og vil ikke lenger vedlikeholdes.

fellesr er installert på Dapla og funksjoner kan benyttes ved:

notebook
library(fellesr)

Hvis du benytter et renv-miljø, må pakken installeres før bruk. Dette kan gjøres ved:

notebook
renv::install("statisticsnorway/ssb-fellesr")

Vanlige operasjoner

I denne delen viser vi hvordan man gjør veldig vanlige operasjoner når man koder et produksonsløp for en statistikk. Flere eksempler på nyttige systemkommandoer finner du her.

Liste ut innhold i mappe

Eksempeldata i Dapla Felles

Dapla Felles er et team der alle i SSB er med i developers-gruppa. Dvs. at alle har lese- og skrivetilgang til følgende områder:
gs://ssb-dapla-felles-data-produkt-prod/ i prod-miljøet på Dapla, og
gs://ssb-dapla-felles-data-produkt-test/ i test-miljøet. Eksemplene under bruker førstnevnte i koden, slik at alle kan kjøre koden selv.

Kode-eksemplene finnes for både R og Python, og du kan velge hvilken du skal se ved å trykke på den arkfanen du er interessert i.

Å liste ut innhold i et gitt mappe på Dapla er ganske enkelt. Under ser du hvordan du kan liste ut innholdet i følgende mappe:

gs://ssb-dapla-felles-data-produkt-prod/datadoc/brukertest/10/sykefratot/klargjorte-data

Vi bruker modulen FileClient fra dapla-toolbelt for å liste ut innholdet i en mappe.

notebook
from dapla import FileClient

# Set path to folder
file_path = "gs://ssb-dapla-felles-data-produkt-prod/datadoc/brukertest/10/sykefratot/klargjorte-data"

FileClient.ls(file_path)

Med kommandoen over får du listet ut alle filnavn i mappen. Hvis du vil ha mer informasjon om filene så kan du bruke ls-kommandoen med detail = True, som under:

notebook
FileClient.ls(file_path, detail = True)

Syntaksen med ls er veldig lik det man kjenner fra en Linux-terminal. Men når vi bruker detail = True blir metadata om filene returnert som en Python-liste med dictionaries. Det kan være svært nyttig når du f.eks. trenger å vite dato og tidspunkt for når en fil ble opprettet, eller når den sist ble oppdatert.

notebook
# Path to folder
file_path = "/buckets/produkt/datadoc/brukertest/10/sykefratot/klargjorte-data"

# List files in folder 
list.files(file_path)

Skrive ut filer

Å skrive filer til et lagringsområde på Dapla er også ganske enkelt. Det ligner mye på den syntaksen vi er kjent med fra vanlige R- og Python-pakker, med noen små unntak.

Parquet

Under lager vi en dataframe i en notebook og skriver den ut til en parquet-fil.

Når vi leser en Parquet-fil med dapla-toolbelt så bruker den pyarrow i bakgrunnen. Dette er en av de raskeste måtene å lese og skrive Parquet-filer på.

notebook
import dapla as dp
import pandas as pd
import numpy as np

# Set path to folder
file_path = "gs://ssb-dapla-felles-data-produkt-prod/dapla-manual-examples"

# Create pandas dataframe
purchases = pd.DataFrame(np.random.randn(10, 5),
                        columns=["A", "B", "C", "D", "E"])

# Write pandas dataframe as parquet to GCS bucket
dp.write_pandas(df = purchases,
                gcs_path = f"{file_path}/data.parquet",
                file_format = "parquet",)

Når vi kalte write_pandas over så spesifiserte vi at filformatet skulle være parquet. Dette er default, så vi kunne også ha skrevet det slik:

notebook
dp.write_pandas(df = purchases,
                gcs_path = f"{file_path}/data.parquet")

Men for de andre filformatene må vi altså spesifisere dette.

Når vi jobber med Parquet-fil i R, bruker vi pakken arrow.

notebook
# Set stien til hvor data skal lagres
file_path = "/buckets/produkt/dapla-manual-examples/purchases.parquet"

# Lag eksempeldata
purchases = data.frame(A = runif(10), B= runif(10), C=runif(10))

# Skrive data til bøtte som parquet
arrow::write_parquet(purchases, file_path)

Tekstfiler

Noen ganger ønsker vi å lagre data i andre formatter slik som CSV, JSON og XML.

dapla-toolbelt kan skrive ut json, csv og posisjonsfiler (fixed-width-files/fwf) til lagringsområdet. Måten den gjør det på er å bruke Pandas sine funksjoner read_json, read_csv, read_fwf under panseret. Dette kan være nyttig å vite for skjønne hvordan dapla-toolbelt håndterer ulike strukturer i (spesielt hierarkiske) tekstfiler. Under ser du hvordan du kan skrive ut en dataframe til en json-fil.

notebook
import numpy as np
import pandas as pd
from dapla import FileClient

# Set path to folder
file_path = "gs://ssb-dapla-felles-data-produkt-prod/dapla-manual-examples"

# Create a dataframe with Pandas
df = pd.DataFrame(np.random.randn(10, 5), columns=["A", "B", "C", "D", "E"])

# Save dataframe as json with dapla-toolbelt
dp.write_pandas(df = df,
                gcs_path = f"{file_path}/test.json",
                file_format = "json")

Som vi ser at syntaksen over så kunne vi skrevet ut til noe annet enn json ved å endre verdien i argumentet file_format.

Funksjonen write.csv2 kan brukes til å skrive csv-filer til bøtter.

notebook
# Sett stien til hvor data skal lagres
file_path = "/buckets/produkt/dapla-manual-examples/purchases.csv"

# Lag eksempeldata
purchases = data.frame(A = runif(10), B= runif(10), C=runif(10))

# Skriv til csv
write.csv2(purchases, 
           file_path, 
           row.names = FALSE)

xlsx

Det er ikke anbefalt å bruke xlsx-formatet, men her ser du hvordan det kan skrives ut. Mer kommer.

notebook
import pandas as pd
from dapla import AuthClient

# Henter token for å kunne lese fra Dapla
token = AuthClient.fetch_google_credentials()

# Filsti
file_path = "gs://ssb-dapla-felles-data-produkt-prod/dapla-manual-examples"

df.to_excel(f"{file_path}/test.xlsx",
           storage_options={"token": token})

Funksjonen write.xlsx fra pakken openxlsx kan brukes til å skrive .xlsx-filer til bøtter i R.

notebook

# Sett stien til hvor data skal lagres
file_path = "/buckets/produkt/dapla-manual-examples/purchases.xlsx"

# Lag eksempeldata
purchases = data.frame(A = runif(10), B= runif(10), C=runif(10))

# Skriv til xlsx
openxlsx::write.xlsx(purchases, 
                     file = file_path,
                     rowNames = FALSE,
                     showNA = FALSE,
                     overwrite=T)

Lese inn filer

Under finner du eksempler på hvordan du kan lese inn data til en Jupyter Notebooks på Dapla.

Parquet

notebook
import dapla as dp

# Set path to folder
file_path = "gs://ssb-dapla-felles-data-produkt-prod/datadoc/brukertest/10/sykefratot/klargjorte-data/person_testdata_p2021_v1.parquet"

# Read path into pandas dataframe 
dp.read_pandas(gcs_path= file_path,
               file_format = "parquet",
               columns = None,)

Som vi så med write_pandas så er file_format default satt til parquet, og default for columns = None, så vi kunne også ha skrevet det slik:

dp.read_pandas(gcs_path= file_path)

columns-argumentet er en liste med kolonnenavn som vi ønsker å lese inn. Hvis vi ikke spesifiserer noen kolonner så vil alle kolonnene leses inn.

Funksjonen read_parquet fra pakken arrow kan brukes til å lese inn parquet-filer i R.

Her er et eksempel av å lese inn parquet-filen “person_testdata_p2021_v1.parquet”:

notebook
library(arrow)

file_path = "/buckets/produkt/datadoc/brukertest/10/sykefratot/klargjorte-data/person_testdata_p2021_v1.parquet"

person_testdata <- arrow::read_parquet(file_path)

Vi kan også filtrere hvilke variabler vi ønsker å lese inn ved å spesifisere parameter col_select. For eksempel:

person_testdata <- arrow::read_parquet(file_path,
                                       col_select = c("fnr", "sivilstand"))

Kartdata lagret som .parquet kan leses inn ved å kombinere funksjonen open_dataset fra pakken arrow og read_sf_dataset fra pakken sfarrow.

library(arrow)
library(sfarrow)
library(tidyverse)

data <- arrow::open_dataset("/buckets/produkt/GIS/Kart/2023/ABAS_grunnkrets_flate_2023/ABAS_grunnkrets_flate_2023.parquet") %>%
  dplyr::filter(KOMMUNENR == "0301") %>%
  sfarrow::read_sf_dataset()

Tekstfiler

Kommer mer snart. Python-koden under bygger på eksempelet over.

notebook
import dapla as dp

# Path to write to
file_path = "gs://ssb-dapla-felles-data-produkt-prod/dapla-manual-examples/test.json"

# Read in json-file from dapla-storage
df = dp.read_pandas(gcs_path = file_path,
               file_format = "json")
notebook
# Filsti
file_path = "/buckets/produkt/dapla-manual-examples/purchases.csv"

# Lese inn CSV-fil
dt_1987 <- read.csv2(file_path)

For å lese inn en json-fil kan skrive følgende:

notebook
library(jsonlite)

# Filsti
file_path = "/buckets/produkt/dapla-manual-examples/test.json"

# Lese inn JSON-fil
data <- jsonlite::fromJSON(file_path)

xlsx

notebook
import pandas as pd
from dapla import AuthClient

# Hent token
token = AuthClient.fetch_google_credentials()

# Les inn fil
df = pd.read_excel("gs://ssb-dapla-felles-data-produkt-prod/dapla-manual-examples/test.xlsx",
    storage_options={"token": token})

XLSX-filer kan lese inn med funksjonen read.xlsx fra pakken openxlsx.

notebook
library(openxlsx)

file_path = "/buckets/produkt/dapla-manual-examples/purchases.xlsx"

data <- openxlsx::read.xlsx(file_path)

SAS

Her er et eksempel på hvordan man leser inn en sas7bdat-fil på Dapla som har blitt generert i prodsonen.

notebook
import dapla as dp

file_path = "gs://ssb-dapla-felles-data-produkt-prod/dapla-manual-examples/statbank_ledstill.sas7bdat"

dp.read_pandas(file_path, file_format="sas7bdat", encoding="latin1")

For å lese sas7bdat-filer i R kan man bruke funksjonen read_sas fra pakken haven (som ligger i ´tidyverse´).

notebook
library(tidyverse)

# Filsti
file_path = "/buckets/produkt/dapla-manual-examples/statbank_ledstill.sas7bdat"

data <- haven::read_sas(file_path)

Slette filer

Å slette filer fra lagringsområdet kan gjøres på flere måter. I kapitlet om sletting av data viste vi hvordan man gjør det med pek-og-klikk i Google Cloud Console. Under ser du hvordan du kan slette filer med dapla-toolbelt og gcsfs eller fellesr.

notebook
from dapla import FileClient
fs = FileClient.get_gcs_file_system()

# Skriv inn full filsti til filen som skal slettes
file_path = ""

fs.rm(file_path)

Funksjonen file.remove kan brukes til å slette data på lagringsområdet.

notebook
# Skriv inn full filsti til filen som skal slettes
file_path = ""

file.remove(file_path)

Kopiere filer

Å kopiere filer mellom mapper på et Linux-filsystem innebærer som regel bruke cp-kommandoen. På Dapla er det ikke så mye forskjell. Vi bruker en ligende tilnærming nå vi skal kopiere mellom bøtter eller mapper på lagringsområdet til SSB. Under ser du hvordan du kan kopiere en fil fra en mappe til en annen.

La oss begynne med et eksempel der vi kopierer en fil fra en mappe til en annen i samme bøtte.

notebook
from dapla import FileClient
fs = FileClient.get_gcs_file_system()

# Path to folders
bucket = "gs://ssb-dapla-felles-data-produkt-prod"
from_folder = "felles/veiledning/python/eksempler/purchases"
to_folder = "felles/veiledning/python/eksempler"

# Copy file
fs.cp(f"{bucket}/{from_folder}/data.parquet",
      f"{bucket}/{to_folder}/data_copy.parquet")

Dette fungerer også for å kopiere filer mellom bøtter.

Et annet scenario vi ofte vil støte på er at vi ønsker å kopiere en fil fra vårt Jupyter-filsystem til en mappe på lagringsområdet. Her kan vi bruke fs.put-metoden.

notebook
from dapla import FileClient
fs = FileClient.get_gcs_file_system()

# Create a new file in your home directory called test.txt
with open('/home/jovyan/test.txt', 'w') as f:
    f.write('Create a new text file!')

#Path to folder
bucket = "gs://ssb-dapla-felles-data-produkt-prod"
folder = "felles/veiledning/python/eksempler"

# Copy file from local to remote file system
fs.put(lpath=f"/home/jovyan/test.txt", rpath=f"{bucket}/{folder}/test.txt")

Ønsker vi å kopiere en hel mappe fra lagringsområdet til Jupyter-filsystemet, kan vi bruke fs.get-metoden, med opsjonen recursive=True.

notebook
from dapla import FileClient
fs = FileClient.get_gcs_file_system()

# Copy file
fs.get(<from_bucket>,
      "/home/jovyan/sesongjustering/",
      recursive=True)
Console
# Skriv inn filnavn på filen som skal flytte og navn på bøtta hvor filen ligger. 
file_name = "purchases.csv"
bucket_path_gammel = "/buckets/produkt/dapla_manual-examples/"

#Skriv inn ny (hvis ikke bøtta finnes må den opprettes - se Opprette mapper under)
bucket_path_ny = "/buckets/produkt/dapla_manual-examples/ny_mappe/"

#Hvis du bare vil endre plassering skriver du:
file.copy(from = paste0(bucket_path_gammel,file_name), to = paste0(bucket_path_ny,file_name))

#Hvis du også vil endre navnet på filen kan du skrive:
new_name = "innkjoep.csv"
file.copy(from = paste0(bucket_path_gammel,file_name), to = paste0(bucket_path_ny,new_name))

Merk at funksjonen file.copy() bevarer den opprinnelige filen. Hvis du heller ønsker å flytte filen (dvs å kopiere og slette den opprinnelige filen) velg file.rename().

Flytte filer

notebook
from dapla import FileClient
fs = FileClient.get_gcs_file_system()

bucket = "gs://ssb-dapla-felles-data-produkt-prod"
from_folder = "felles/veiledning/python/eksempler/purchases"
to_folder = "felles/veiledning/python/eksempler"

fs.mv(f"{bucket}/{from_folder}/data.parquet", f"{bucket}/{to_folder}/data.parquet")
Console
# Skriv inn filnavn på filen som skal flyttes og navn på bøtta hvor filen ligger nå. 
file_name = "purchases.csv"
bucket_path_gammel = "/buckets/produkt/dapla_manual-examples/"

#Skriv inn navn på den nye plasseringen (hvis ikke bøtta finnes må den opprettes - se Opprette mapper under)
bucket_path_ny = "/buckets/produkt/dapla_manual-examples/ny_mappe/"

#Hvis du bare vil endre plassering skriver du:
file.rename(from = paste0(bucket_path_gammel,file_name), to = paste0(bucket_path_ny,file_name))

#Hvis du også vil endre navnet på filen kan du skrive:
new_name = "innkjoep.csv"
file.rename(from = paste0(bucket_path_gammel,file_name), to = paste0(bucket_path_ny,new_name))

Merk at funksjonen file.rename() sletter den opprinnelige filen. Hvis du bare vil kopiere uten å slette den opprinnelige filen velg file.copy().

Opprette mapper

Selv om bøtter ikke har mapper med en hierarkisk struktur slik man er kjent med fra klassike filsystemer, så kan man opprette det som ser ut som mapper i objektnavnet. I realiteten blir bare / oppfattet som en del av navnet på objektet. Skulle du likevel ønske å opprette dette så kan du gjøre det følgende måte:

notebook
from dapla import FileClient
fs = FileClient.get_gcs_file_system()

#Path to folder
bucket = "gs://ssb-dapla-felles-data-produkt-prod"
folder = "felles/veiledning/python/eksempler"

# Create folder
fs.touch(f"{bucket}/{folder}/testmappe/")
Console
# Skriv inn navnet på den nye mappen. 
ny_mappe = "/buckets/produkt/dapla_manual-examples/testmappe/"
dir.create(ny_mappe)

Fotnoter

  1. I de tidligere systemene på bakken så var det ikke nødvendig med autentisering mellom kodemiljø og datalagringen↩︎