Sitzung 12 APIs

12.1 Vorbereitung

Für diese Lektion werden die Pakete benötigt:

library(tidyverse)
library(jsonlite)

12.2 SWAPI

Die Star Wars API ist eine eigens für Übungszwecke eingerichtete API, und steht uns deshalb (anders als andere APIs) ohne Login zur Verfügung. Wir sollten bei der Benutzung darauf achten, sie nicht zu überladen.

Jede gute API kommt mit einer ausführlichen Dokumentation in der die Endpunkte und Abfrageoptionen erklärt sind.

Bei den hier besprochenen REST-APIs geht es eigentlich nur darum, die richtige Abfrage als URL zu formulieren. Die Antwort des Servers gibt uns dann die Daten, die wir brauchen, und zwar üblicherweise im JSON-Format.

Zum Beispiel fragen wir so Informationen über Han Solo ab:

han_solo <- read_json("https://www.swapi.tech/api/people/14/")$result

Die Antwort ist eine Liste, deren Elemente sich wie gewohnt mit $ ansprechen lassen und wiederum Hinweise auf API-Abfragen enthalten können:

han_solo$properties$eye_color
## [1] "brown"
han_solo$properties$homeworld
## [1] "https://www.swapi.tech/api/planets/22"

Diese Information ließe sich wiederum abfragen durch:

han_solo$properties$homeworld %>%
  read_json() %>%
  .$result %>%
  .$properties %>%
  .$name
## [1] "Corellia"

Eine (recht willkürlich gewählte) Herausforderung wäre es nun, die Namen aller Charaktere herauszufinden, die in Return of the Jedi vorkommen.

Zunächst können wir den richtigen Film suchen mit:

read_json("https://www.swapi.tech/api/films/")$result %>%
  map("properties") %>%
  map("title")
## [[1]]
## [1] "A New Hope"
## 
## [[2]]
## [1] "The Empire Strikes Back"
## 
## [[3]]
## [1] "Return of the Jedi"
## 
## [[4]]
## [1] "The Phantom Menace"
## 
## [[5]]
## [1] "Attack of the Clones"
## 
## [[6]]
## [1] "Revenge of the Sith"

Dann lässt sich die gewünschte Liste ziehen mit:

read_json("https://www.swapi.tech/api/films/3")$result$properties$characters -> return_characters

Für jeden dieser Charaktere ließe sich der Name herausfinden mit

read_json("https://www.swapi.tech/api/people/1/")$result$properties$name
## [1] "Luke Skywalker"
read_json("https://www.swapi.tech/api/people/4/")$result$properties$name
## [1] "Darth Vader"
# usw.

Können wir aber auch die Namen nicht einzeln, sondern automatisch Abfragen?

12.3 Exkurs: Funktionen schreiben

Funktionen sind überall in R. Funktionen haben eine Eingabe (parameters) und eine Ausgabe (return values). Z.B. hat die Funktion mean() als Eingabe einen numerischen Vektor, und als Ausgabe das arithmetische Mittel dieses Vektors:

data(diamonds)
mean(diamonds$carat)
## [1] 0.7979397

Wir können auch eigene Funktionen schreiben. Die Definition einer eigenen Funktionen hat immer diese Form:

FUNKTIONSNAME <- function(EINGABE) {
  ...
  AUSGABE
}

Wenn es die Funktion mean() nicht gäbe, könnten wir sie (bzw. so etwas ähnliches) selbst schreiben, mit:

my_mean <- function(verteilung) {
  sum(verteilung) / length(verteilung)
}

Wenn wir die Definition ausführen, erscheint die Funktion in unserem Environment, genauso wie andere Objekte. Wir können sie dann genauso anwenden wie andere Funktionen:

my_mean(diamonds$carat)
## [1] 0.7979397
my_mean(diamonds$depth)
## [1] 61.7494

12.4 Abfragefunktion

Eine Funktion zur automatischen Abfrage der Charakternamen bräuchte als Eingabe die URL der API, und als Ausgabe den Charakternamen. Eigentlich geht es nur um eine Abstraktion des konkreten Befehls:

read_json("https://www.swapi.tech/api/people/1/")$result$properties$name
## [1] "Luke Skywalker"

Die Definition der Funktion könnte so aussehen:

get_character_name <- function(url) {
  read_json(url)$result$properties$name
}

Testweise lässt sie sich anwenden:

get_character_name("https://www.swapi.tech/api/people/1/")
## [1] "Luke Skywalker"

Leider lässt sie sich nicht so einfach (wie andere Funktionen) auf den Vektor von URLS anwenden, da die Funktion keinen Vektor als Eingabe erwartet:

Abhilfe schafft der Befehl map() aus dem purrr-Paket (Teil von tidyverse). Hier lässt sich ein Vektor (oder eine Liste) angeben, sowie der Name einer Funktion, die dann auf jedes Element des Vektors angewendet wird:

map(return_characters, get_character_name)

Resultat ist eine Liste, die sich mit unlist() auch zu einem Vektor wandeln ließe… oder man benutzt direkt die Abwandlung map_chr(), die nach Möglichkeit immer einen character vector ausgibt:

map_chr(return_characters, get_character_name)
##  [1] "Luke Skywalker"        "C-3PO"                 "R2-D2"                
##  [4] "Darth Vader"           "Leia Organa"           "Obi-Wan Kenobi"       
##  [7] "Chewbacca"             "Han Solo"              "Jabba Desilijic Tiure"
## [10] "Wedge Antilles"        "Yoda"                  "Palpatine"            
## [13] "Boba Fett"             "Lando Calrissian"      "Ackbar"               
## [16] "Mon Mothma"            "Arvel Crynyd"          "Wicket Systri Warrick"
## [19] "Nien Nunb"             "Bib Fortuna"