-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy path05_1_datenimport-exkurs-api.Rmd
273 lines (215 loc) · 19.6 KB
/
05_1_datenimport-exkurs-api.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
## **Exkurs 1: APIs**
### APIs in R-Packages
Der Umgang mit APIs ist für Programmieranfänger:innen oft eine Herausforderung. Viele APIs sind aber bereits in passende **Packages** eingebettet, über die der Zugriff auf die Daten wesentlich leichter funktioniert. Über das Package `WDI` könnt Ihr beispielsweise **Daten der World Bank** laden, die Euch helfen können, Eure gesellschaftliche Herausforderung besser zu verstehen und zu kontextualisieren. In der Datenbank gibt es den Indikator "Terrestrial and marine protected areas (% of total territorial area)", der für die Planung von zukünftigen Aktivitäten und die Kommunikation mit Freiwilligen und Fördernden genutzt werden soll. Für "Break Free From Plastics" ist dieser Indikator spannend, weil in **Naturschutzgebieten** Flora und Fauna besser vor Plastikmüll geschützt sind. In Ländern, wo neben der hohen Müllmenge und der niedrigen Recyclingquoten, besonders wenige Gebiete als Naturschutzgebiete ausgezeichnet sind, könnte der Bedarf nach gemeinnützigen Organisationen wie "Break Free From Plastic", die die Natur von schädlichen Plastikmüll befreien, also besonders hoch sein.
*Anmerkung: Genau wissen wir das natürlich nicht, die Daten geben uns hier lediglich einen Hinweis darauf, wo der Bedarf groß sein könnte. Deshalb ist es wichtig die Annahmen, auf denen Entscheidungen basieren genau zu definieren und sich darüber Gedanken zu machen, ob die Datengrundlage für eine Entscheidung überhaupt ausreichend ist. Unsere Thesen können wir dann ggf. in Interviews mit Expert:innen (hier z.B. Naturschützer:innen) verifizieren.*
```{r wb, exercise = TRUE}
#install.packages("WDI")
# Daten der World Bank mit R-Package ziehen
wb_areas <- WDI::WDI(
country = "all", # Auswahl der Länder
indicator = "ER.PTD.TOTL.ZS", # Spezifikation des Indikators (Tipp: siehe Link in der Datenbank)
start = 2018, # Auswahl Zeithorizont: Anfang
end = 2018, # Auswahl Zeithorizont: Ende
language = "en" # Sprachauswahl
)
head(wb_areas)
```
Ladet nun den Datensatz zum Indikator ["Fish species, threatened" (EN.FSH.THRD.NO)](https://data.worldbank.org/indicator/EN.FSH.THRD.NO?view=chart){target="_blank"} für das Jahr 2018. Kopiert dazu den Code von oben und fügt den richtigen Indikatorschlüssel ein.
```{r exercise_wb, exercise = TRUE}
# Euer Code hier
```
```{r exercise_wb-solution}
# Daten der World Bank mit R-Package ziehen
WDI::WDI(
country = "all", # Auswahl der Länder
indicator = "EN.FSH.THRD.NO", # Spezifikation des Indikators (Tipp: siehe Link in der Datenbank)
start = 2018, # Auswahl Zeithorizont: Anfang
end = 2018, # Auswahl Zeithorizont: Ende
language = "en" # Sprachauswahl
)
```
```{r exercise_wb-check}
grade_this_code()
```
Daten mit APIs, die in R-Packages eingebettet sind, zu importieren, ist sehr einfach und wir sparen uns viel Zeit für die Datenbereinigung. Neben dem `WDI`-Package für die World Bank, gibt es noch viele **weitere, nützliche R-Packages**:
- `acled.api`-Package: Daten zu **bewaffneten Konflikten** von ACLED (zur [Doku](https://cran.r-project.org/web/packages/acled.api/index.html){target="_blank"}//mehr [Infos](https://acleddata.com/#/dashboard){target="_blank"})
- `datenguideR`-Package: Daten der **amtlichen Amtstatistik** in Deutschland (zum [Repo](https://github.com/CorrelAid/datenguideR){target="_blank"}//mehr [Infos](https://datengui.de/){target="_blank"})
- `DWD`-Package: Daten des **Deutschen Wetter Dienstes** (zur [Doku](https://github.com/CorrelAid/datenguideR){target="_blank"}//mehr [Infos](https://www.dwd.de/DE/Home/home_node.html){target="_blank"})
- `eurostat`-Package: Open Data von **Eurostat** (zur [Doku](https://cran.r-project.org/web/packages/eurostat/index.html){target="_blank"}//mehr [Infos](https://ec.europa.eu/eurostat/de/home){target="_blank"})
- `GermaParl`-Package: Plenarprotokolle des **Bundestags** (zur [Doku](https://cran.r-project.org/web/packages/gesisdata/index.html){target="_blank"}//mehr [Infos](https://www.gesis.org/home){target="_blank"})
- `gesisdata`-Package: Daten des **Leibniz-Instituts** (zur [Doku](https://cran.r-project.org/web/packages/gesisdata/index.html){target="_blank"}//mehr [Infos](https://www.gesis.org/home){target="_blank"})
- `googleAnalyticsR`-Package: Daten von **Google Analytics** (zur [Doku](https://cran.r-project.org/web/packages/googleAnalyticsR/index.html){target="_blank"}//mehr [Infos](https://analytics.google.com/analytics/web/provision/#/provision){target="_blank"})
- `gtrendsR`-Package: **Google Trends** Daten (zur [Doku](https://cran.r-project.org/web/packages/gtrendsR/gtrendsR.pdf){target="_blank"}//mehr [Infos](https://trends.google.com/trends/?geo=FR){target="_blank"})
- `nasadata`-Package: Daten von **NASA** (zur [Doku](https://cran.r-project.org/web/packages/nasadata/index.html){target="_blank"}//mehr [Infos](https://data.nasa.gov/){target="_blank"})
- `pangaear`-Package: Daten zu Erde und **Umwelt** (zur [Doku](https://cran.r-project.org/web/packages/pangaear/pangaear.pdf){target="_blank"}//mehr [Infos](https://www.pangaea.de/){target="_blank"})
- `rtweet`-Package: **Twitter**-Daten (zur [Doku](https://cran.r-project.org/web/packages/rtweet/rtweet.pdf){target="_blank"}//mehr [Infos](https://www.twitter.com/){target="_blank"})
- `salesforcer`-Package: Interaktion mit Daten in Salesforce (zur [Doku](https://cran.r-project.org/web/packages/salesforcer/index.html){target="_blank"})
- `dieZeit`-Package: Online-Veröffentlichungen der **Zeit** (zur [Doku](https://cran.r-project.org/web/packages/diezeit/index.html){target="_blank"}//mehr [Infos](https://www.zeit.de/){target="_blank"})
- uvm...
Schaut Euch gerne um - entweder über eine Suchmaschinenrecherche dazu, ob Eure Lieblingsdatenquelle ein zugehöriges R-Package hat, oder auf dieser Liste aller R-Packages, die über [CRAN](https://cran.r-project.org/web/packages/available_packages_by_name.html){target="_blank"} (Comprehensive R Archive Network, zu dt. Umfassendes R-Archivnetzwerk), die zentrale Stelle für geprüfte Package-Distribution, installiert werden können. <br>
*Hinweis: Manche Packages wurden (noch) nicht in die Liste der von RStudio geprüften CRAN-Packages aufgenommen. Ihre Installation erfolgt zumeist über den Befehl `remotes::install_github("Link zum Github-Repository")`.*
### Eigene API Anfragen
[![License: CC BY 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/deed.de)
*"Eigene API-Anfragen (Exkurs)" von Frie Preu in "R Lernen - der Datenkurs von und für die Zivilgesellschaft" von CorrelAid e.V. Lizensiert unter [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/deed.de).*
![*Video: Eigene API-Anfragen (10min)*](https://youtu.be/DePm6F_6YMA)
#### **Interaktive Übung**
Eine API (_Application Programming Interface_, de: Schnittstelle zur Programmierung von Anwendungen) ist eine Schnittstelle, die ein System bereitstellt, um anderen Programmen die Interaktion zu ermöglichen.
Eine Interaktion sieht so aus:
1. Der **Client** macht eine Anfrage (engl. Request) an die API
2. Die API verarbeitet die Anfrage und gibt eine Antwort (engl.: **Response**) zurück.
3. Der Client verarbeitet die Antwort.
APIs verfügen zumeist über eine **Dokumentation**, die enthält, welche Funktionalitäten verfügbar sind und wie Anfragen gestellt werden müssen.
**Analogie**: Wenn Ihr (als _Client_) im Restaurant seid, stellt Euch das Restaurant eine:n Kellner:in (Eure _API_) und eine Speisekarte (Eure _API-Dokumentation_) bereit. Der:die Kellner:in nimmt Eure Bestellungen (_Anfragen_) entgegen, die Küche verarbeitet diese und der:die Kellner:in bringt Eure Bestellung (_Antwort_).
Die allermeisten APIs heutzutage verwenden das HTTP-Protokoll, welches fünf sogenannte _Methoden_ umfasst: GET, POST, PUT, PATCH und DELETE. Da wir in unserem Fall auf Interaktionen schauen, welche sich auf den Datenaustausch fokussieren, ergeben sich folgende Entsprechungen:
- `GET` --> Daten lesen
- `POST` --> Neue Daten erstellen
- `PUT` --> Daten ersetzen
- `PATCH` --> Daten aktualisieren
- `DELETE` --> Daten löschen
(siehe Folie 10 von ["Datenzugriff im World Wide Web"](https://projektzyklus.correlaid.org/07_datenmanagement-webdaten/2021-05-09_Datenzugriff_im_WWW.pdf), Jan Dix, lizensiert unter [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/legalcode.de).)
[Hier (en)](https://github.com/public-apis/public-apis) findet Ihr eine Liste von öffentlichen APIs, die Ihr kostenfrei nutzen könnt.
#### **GET-Anfragen**
Wenn Ihr nur Daten laden möchtet, reicht `GET` meistens aus. Je nach API können allerdings auch `POST` Anfragen notwendig sein. `GET`-Anfragen können als normale URL (das was Ihr in euren Browser eingebt) abgebildet werden. Diese URLs setzen sich aus drei Teilen zusammen:
`{BASIS_URL}/{ROUTE}?{QUERY_PARAMETER}`.
Das kennt Ihr zum Beispiel von einer Google-Suche: `https://www.google.com/search?q=CorrelAid`.
- `BASIS_URL`: `https://www.google.com/`
- `ROUTE`: `search`
- `QUERY_PARAMETER`:
- `q`: `CorrelAid`
**Analogie**: Im Restaurant bestellt (--> `GET`) Ihr bei Elmo (Eure API mit der Basis-URL `https://elmo.correlandfriends.de/`) auf der Route "Essen" (`essen`) das Gericht Risotto (Query-Parameter `gericht=risotto`). Die komplette Anfrage-URL wäre also: `https://elmo.correlandfriends.de/essen?gericht=risotto`. Das Fragezeichen signalisiert das Ende der Route und den Anfang der Query-Parameter.
#### **Statuscodes**
Fast alle APIs geben in Ihrer Antwort einen Code zurück, anhand dem man schnell sehen kann, ob die Anfrage erfolgreich war oder nicht. Dieser sogenannte _Statuscode_ ist sehr hilfreich, da er Aufschluss gibt, was schief gegangen sein könnte.
Wenn die Anfrage erfolgreich war, gibt die API einen `200` Statuscode zurück. Darüber hinaus gibt es viele Statuscodes, die einen Fehler anzeigen. Häufige Fälle sind:
- `404`: Nicht gefunden ("Not found"). Z.B. existiert der Endpunkt / die Route gar nicht in der API
- `401`: Nicht autorisiert ("Not authorised"): Ihr seid nicht autorisiert auf die API zuzugreifen, z.B. weil Ihr keinen *Token* übergeben habt.
- `403`: Nicht erlaubt ("Forbidden"): Ihr seid zwar im Prinzip für die API autorisiert, aber nicht für die Route, auf die ihr zugreifen wollt (z.B. sensitive Daten oder Administration).
- `422`: Nicht verarbeitbare Anfrage ("Unprocessable Entity"): Eure Anfrage wurde nicht richtig gestellt
- `500`: Interner Server-Fehler ("Internal Server Error"): Eure Anfrage ist zwar richtig gestellt worden, innerhalb der API kam es aber zu einem Fehler
**Analogie**: Nach Eurem Besuch im Restaurant "Correl and Friends" geht Ihr noch in die Bar "AidBar". Doch irgendwie seid Ihr nicht so richtig satt geworden vorhin und bestellt, ohne die Karte (_API -Dokumentation_) zu konsultieren, eine Pommes (`/essen?gericht=Pommes`). Leider muss Euch die Kellnerin enttäuschen, "Aid's Tavern" verkauft nur Getränke: 404!
Auf Folien 13-16 [dieses Foliensatzes](https://projektzyklus.correlaid.org/07_datenmanagement-webdaten/2021-05-09_Datenzugriff_im_WWW.pdf) findet Ihr noch mehr Erklärungen zu wichtigen Statuscodes.
#### **Beispiel**
Wir verwenden die [Sustainable Development Goals (SDG) API](https://unstats-undesa.opendata.arcgis.com/#api) der Vereinten Nationen (en: _United Nations_), welche Daten über den Fortschritt der [Sustainable Development Goals](https://sdgs.un.org/) bereitstellt. Die Dokumentation der API findet Ihr [hier](https://unstats.un.org/SDGAPI/swagger/). Der Indikator "[Municipal Solid Waste collection coverage by cities (percent)](https://www.sdg.org/datasets/undesa::indicator-11-6-1-municipal-solid-waste-collection-coverage-by-cities-percent/about)" (Seriencode: `EN_REF_WASCOL`) passt gut zu unseren bisherigen Analysen zum Thema Plastikverschmutzung. Um die Daten zu diesem Indikator zu laden, verwendet Ihr den GET-Endpunkt [`/v1/sdg/Series/Data`](https://unstats.un.org/SDGAPI/swagger/#!/Series/V1SdgSeriesDataGet). Zudem habt Ihr nun mehrere Möglichkeiten, Query-Parameter anzugeben, unter anderem den Code des Indikators (`seriesCode`) und den Zeitrahmen, für den Ihr Daten benötigt.
Um Anfragen an APIs zu machen, nutzen wir das [`httr`](https://httr.r-lib.org/)-Package (Doku).
```{r series-req, exercise=TRUE}
# library(httr)
basis_url <- "https://unstats.un.org/" # Haupt-URL
initiale_anfrage <- httr::GET( # Initialisierung
basis_url, # URL verlinken
path = "/SDGAPI/v1/sdg/Series/Data", # Route definieren
query = list(
seriescode = "EN_REF_WASCOL"
)
)
```
Wenn wir uns die Anfrage-URL anschauen, erkennen wir wieder das Schema `{BASIS_URL}/{ROUTE}?{QUERY_PARAMETER}`.
```{r waste-url, exercise=TRUE}
initiale_anfrage$url
```
```{r quiz-route, exercise = FALSE, echo=FALSE}
quiz(caption = NULL,
question("Was ist die Basis-URL der Anfrage?",
answer("https://unstats.un.org/SDGAPI/v1/sdg/Series/Data"),
answer("https://unstats.un.org/SDGAPI/v1/sdg/Series/"),
answer("https://unstats.un.org/", correct = TRUE),
correct = "Richtig!",
incorrect = "Leider falsch: Schaue dir den Code nochmal an!",
allow_retry = TRUE,
try_again_button = "Nochmal versuchen"),
question("Was ist die Route der Anfrage?",
answer("v1/sdg/Series/Data"),
answer("SDGAPI/v1/sdg/Series/Data/", correct = TRUE),
answer("sdg/Series/"),
correct = "Richtig!",
incorrect = "Leider falsch: Schaue dir den Code nochmal an!",
allow_retry = TRUE,
try_again_button = "Nochmal versuchen"
),
question("Wie viele Parameter hat unsere Anfrage?",
answer("0"),
answer("1", correct = TRUE),
answer("2"),
answer("3"),
answer("4"),
correct = "Richtig!",
incorrect = "Leider falsch: Schaue dir den Code nochmal an!",
allow_retry = TRUE,
try_again_button = "Nochmal versuchen"
)
)
```
Zuerst checken wir nun den **Statuscode**, um sicher zu gehen, dass unsere Anfrage erfolgreich war. In `httr` gibt es hierzu auch die `stop_for_status` Funktion, die einen Fehler schmeißt, wenn die Anfrage nicht erfolgreich war, sonst aber nichts tut.
```{r waste-status, exercise=TRUE}
initiale_anfrage$status_code # 200
httr::stop_for_status(initiale_anfrage) # nichts passiert, alles gut!
```
Nun fragen wir uns: **Wie viele Seiten Dokument** müssen überhaupt geladen werden? APIs geben Euch nämlich meistens nicht von Haus aus alle Resultate zurück.
```{r apicontent, exercise = TRUE}
# Vorläufig Inhalt der Inititalabfrage zur Prüfung speichern
content <- httr::content(initiale_anfrage)
# Einlesen der Seitenanzahl durch das Attribut "totalPages", auf das wir mithilfe von "$" zugreifen
total_pages <- content$totalPages
print(total_pages)
pages <- c(1:total_pages)
```
Diese Information merken wir uns und schauen uns nun an, was genau die Antwort unserer Anfrage beinhaltet. Diese erhaltet Ihr, indem Ihr die Funktion `httr::content()` (de: Inhalt) verwendet. Output dieser Funktion ist eine **vielschichtige (eng. nested) Liste**.
```{r content, exercise=TRUE}
# Inhalt der Response herausziehen
httr::content(initiale_anfrage)
```
Wir erhalten von der Anfrage also eine Liste mit mehreren Elementen zurück, die die gesamte _Antwort_ darstellt. Als Anwender:innen seid Ihr hauptsächlich an den **übermittelten Daten** interessiert, die wir nun ziehen ("$data") . Mit `purrr::map_df` können wir nun die Datenliste (ebenfalls vielschichtig) entpacken.
```{r goals-extract, exercise=TRUE}
# Für verschiedene Städte in verschiedenen Ländern erhalten wir so zu verschiedenen Jahren Ihre Müllsammlungsquoten
waste_list <- waste_data$data
# Mit dem purrr-Package ziehen wir nun die Daten in einen Dataframe
library(purrr)
waste_geo <- waste_list %>%
purrr::map_df(`[`, c("geoAreaCode", "geoAreaName", "dimensions", "value")) %>%
filter(dimensions != "G") # Duplikate entfernen, die aus der Datenstruktur resultieren
# Datensatz betrachten
dplyr::glimpse(waste_geo)
```
Abschließend schauen wir uns nochmal genauer eines der Datenelemente an:
```{r inspect-data, exercise=TRUE}
# Wir schauen uns eines der Daten Elemente an. Die doppelten Klammern extrahieren das x-te Element aus der Liste, hier das zehnte.
str(waste_data$data[[10]])
```
Das Daten-Listenelement enthält ziemlich viele Informationen. Für Euch relevant ist der Wert (`value`) sowie die Informationen über das "Wo": `geoAreaCode` und `geoAreaName`.
Diese Informationen nutzen wir nun - gemeinsam mit der Seitenanzahl - um für jede Seite die benötigten Informationen zu ziehen und diese in einem Dataframe zusammenzuführen. Wie genau Ihr mit solchen komplexeren Datenstrukturen umgehen könnt und diese Daten aus _allen_ Elementen extrahieren könnt, lernt Ihr zu einem späteren Zeitpunkt in Eurer Karriere als Datenwissenschaftler:innen der Zivilgesellschaft - hier schon einmal die Preview:
```{r listofdataframes, exercise=TRUE}
# Funktion, mit der alle Seiten geladen werden, indem die Seitenanzahl "Page" durch eine Nummer zwischen 1 und 11 (= Total Pages) ersetzt wir
list_of_dataframes <- purrr::map(pages, function(page) {
antwort <- httr::GET(
basis_url,
path = "/SDGAPI/v1/sdg/Series/Data",
query = list(
seriescode = "EN_REF_WASCOL",
page = page # Hier wird die Seitenzahl eingesetzt
)
)
# Von jeder Seite die Daten laden
waste_list <- httr::content(antwort)$data
# Generierung eines Dataframes mit den ausgewählten Variablen
waste_geo <- waste_list %>%
purrr::map_df(`[`, c("geoAreaCode", "geoAreaName", "dimensions", "value"))
return(waste_geo)
})
# Erstellung eines großen Dataframes mit allen Daten
waste_geo_df <- dplyr::bind_rows(list_of_dataframes)
```
#### **Authentifizierung**
In unserem Beispiel konnten wir ohne Authentifizierung die UN-SDG API verwenden. Die meisten APIs erfordern allerdings eine Authentifizierung, d.h. Ihr müsst beweisen, dass Ihr berechtigt (*autorisiert*) seid, auf die API und ihre Daten zuzugreifen.
Hierzu gibt es viele verschiedene Modelle. Im einfachsten Fall erstellt ihr euch einen sogenannten Token in der entsprechenden Website in den Benutzereinstellungen. Ein **Token** ist ein zufällig erstellter String, quasi euer API-"Passwort". Tokens sehen meistens so aus `eyJpc3MiOiJodHRwczovL2V4YW1...`.
```{r authorization-ex, exercise=FALSE, eval=FALSE, echo=TRUE}
# Liest die Umgebungsvariable API_TOKEN und speichert sie im Objekt Token
basis_url <- "https://beispielapi.org/"
token <- Sys.getenv("API_TOKEN")
# Füge unserer Anfrage einen Autorisierungs-Header hinzu, sodass die API weiß, dass wir autorisiert sind
antwort <- httr::GET(basis_url,
httr::add_headers(Authorization = paste("Authorization", token)))
```
Die Details zur Authentifizierung sind von API zu API unterschiedlich. Die genauen Instruktionen solltet ihr in der jeweiligen API-Dokumentation finden.
Es gibt spezifische Statuscodes, die in Verbindung mit Authentifizierung besonders häufig auftauchen: 401 und 403. Diese Fehlercodes tauchen häufig auf, wenn man eigentlich den richtigen Token hat, aber ihn in der Anfrage nicht richtig übergeben hat. Lest deshalb immer genau in der Dokumentation nach, wie die Authentifizierung der API im Detail funktioniert.
Aus Sicherheitsgründen ist es zudem sehr wichtig, Euren Token (oder sonstige **sicherheitsrelevante Informationen**) **nicht direkt im Code zu speichern**. Anstatt dessen nutzt ihr hierzu am besten **Umgebungsvariablen** (engl. environment variables). Wie man Umgebungsvariablen zu R hinzufügt, ist in [diesem Blogpost](https://www.roelpeters.be/what-is-the-renviron-file/) gut beschrieben. Auch wir üben das zu einem späteren Zeitpunkt nochmal.
### **Zusätzliche Ressourcen**
- [APIs in R](https://app.dataquest.io/course/apis-in-r){target="_blank"} auf DataQuest (engl.)
- Ihr wollt mehr darüber lernen, wie man API-Anfragen selbst schreibt? Dataquest bietet den Kurs ["APIs in R"](https://app.dataquest.io/course/apis-in-r){target="_blank"} an.