Das Ziel dieses Tutorials ist, dir zu zeigen, wie du Daten zu H‑1B‑Visa per Web Scraping mit R sammelst. Anschließend lernst du, wie du JSON-Objekte parsest sowie die Daten speicherst und weiterverarbeitest, um eine grundlegende explorative Datenanalyse (EDA) des großen Datensatzes an H‑1B‑Anträgen durchzuführen.
Vielleicht findest du so heraus, wie du dich als Kandidat am besten positionierst – oder lernst neuen R‑Code!
Einführung
Letzte Woche hat DataCamps Blog „Can Data Help Your H-1B Visa Application“ einige Ergebnisse einer Analyse der H‑1B‑Daten über die Jahre vorgestellt. Jetzt bist du dran: Schnapp dir die Daten, analysiere sie selbst und finde heraus, was noch drinsteckt! Ted Kwartler führt dich mit einer Reihe von R‑Tutorials Schritt für Schritt durch.
Ein Freund von mir arbeitet in einer Kanzlei in Texas, die H‑1B‑Visa beantragt. Das H‑1B ist ein Nichteinwanderungsvisum in den USA, das es US‑Arbeitgebern erlaubt, ausländische Fachkräfte vorübergehend in Spezialberufen zu beschäftigen. Die Annahme ist offenbar extrem schwierig, da die Zahl der Visa begrenzt ist, sich aber Tausende bewerben. Das ist zwar anekdotisch, aber ich wollte mir die Daten selbst ansehen – in der Hoffnung, qualifizierten Kandidaten zu zeigen, dass die USA ein gastfreundlicher Ort sind!
Daten beschaffen: Web Scraping und Parsing
Ein DataCamp-Kollege hat mich auf diese Seite hingewiesen: eine einfache Website mit H‑1B‑Daten von 2012 bis 2016. Die Seite gibt an, 2 Mio. H‑1B‑Anträge in einer einzigen Tabelle zu organisieren.Ich habe mich entschieden, die Daten programmatisch zu sammeln (lies: per Web Scraping), denn Copy & Paste bis ans Lebensende war keine Option! Wie du siehst, zeigt das Bild unten einen Ausschnitt mit Bostons H‑1B‑Daten:
In diesem Tutorial nutzen wir unter anderem folgende Bibliotheken: jsonlite zum Parsen von JSON-Objekten, rvest zum „Ernten“ von HTML, pbapply – mein Favorit, weil es Fortschrittsbalken zu den Basis-apply‑Funktionen hinzufügt – und data.table, um R bei großen Data Frames Beine zu machen.
library(jsonlite)library(rvest)library(pbapply)library(data.table)Bereitgestellt von
Die Seitenstruktur erkunden
Wenn du die Seite erkundest, fällt dir auf, dass das Suchformular Vorschläge zum Autovervollständigen anbietet. Tippst du zum Beispiel ein „B“ in das City-Feld, erscheint ein Modal mit Vorschlägen wie unten gezeigt. Das folgende Bild zeigt die Optionen, wenn ich „B“ tippe:
Das bedeutet: Du kannst die Autovervollständigung als effiziente Möglichkeit nutzen, die Seite abzufragen. In Chrome kannst du neu laden, dann per Rechtsklick „Untersuchen“ wählen, im Entwicklertool zu „Network“ wechseln und schließlich auf der Seite „B“ tippen, um das Modal zu laden. Beim Durchsehen der Netzwerk-Links findest du eine PHP‑Abfrage, die ein JSON‑Objekt mit Städten zurückgibt – etwa diese. Ziel ist es, zunächst alle vorgeschlagenen Städte zu sammeln und diese Liste dann zu nutzen, um viele Seiten mit H‑1B‑Daten zu scrapen. Beim Blick auf die URL oben fällt dir auf, dass sie mit einem Buchstaben endet. Du kannst also paste0() mit der Basis‑URL http://h1bdata.info/cities.php?term= und letters verwenden. Die Basis wird für jeden Wert in letters recycelt. Das Objekt letters ist ein eingebauter R‑Vektor von „a“ bis „z“. Das Objekt json.cities ist ein Vektor von URLs von a bis z, die alle Autovervollständigungs‑Vorschläge als JSON enthalten.
json.cities<-paste0('http://h1bdata.info/cities.php?term=', letters)Bereitgestellt von
JSON-Objekte parsenDas json.cities-Objekt ist ein Vektor aus 26 Links, die R einlesen muss. Mit lapply() oder pblapply() zusammen mit fromJSON parst R jedes JSON‑Objekt, um all.cities zu erstellen. Du verschachtelst das Ergebnis in unlist, sodass die Ausgabe ein einfacher String‑Vektor ist. Mit diesem Code hast du alle vorgeschlagenen Städte in einem Vektor, den du zum Aufbau der eigentlichen Datenseiten verwenden kannst.
Um die Ladezeit einzelner Seiten zu verringern, kannst du zwei Parameter – Stadt und Jahr – in jede Seitenabfrage geben. Zum Beispiel Boston‑Daten 2012, dann Boston 2013 und so weiter. Eine großartige Funktion für Faktorkombinationen ist expand.grid(). Im Code unten siehst du, dass die Stadtinformationen all.cities übergeben werden und das Jahr per seq() von 2012 bis 2016. Die Funktion erzeugt über 5.000 Stadt‑Jahr‑Kombinationen. expand.grid() erzeugt programmatisch Boston 2012, Boston 2013, Boston 2014 usw., da jede Stadt und jedes Jahr eine eindeutige Kombination darstellen.
city.year<-expand.grid(city=all.cities,yr=seq(2012,2016))Bereitgestellt von
Einige Städte wie Los Angeles bestehen aus zwei Wörtern und müssen für URLs kodiert werden. Die Funktion url_encode() ändert „Los Angeles“ in Los%20Angeles, damit die Adresse gültig ist. Du übergibst den gesamten Vektor, und url_encode() arbeitet zeilenweise:
city.year$city<-urltools::url_encode(as.character(city.year$city))Bereitgestellt von
Zum Schluss nutzt du erneut paste0(), um die Basis‑URL mit den Stadt‑ und Jahres‑Kombinationen in city.year zu verketten. Ein Beispiel‑Link hier.
all.urls<-paste0('http://h1bdata.info/index.php?em=&job=&city=', city.year[,1],'&year=', city.year[,2])Bereitgestellt von
Informationen von Seiten extrahieren
Nachdem du die vorherigen Schritte erledigt hast, kannst du eine eigene Funktion namens main schreiben, um die Daten jeder Seite zu sammeln. Der Workflow ist mit rvest ziemlich geradlinig. Zuerst wird eine URL übergeben und mit read_html() der Seiteninhalt geparst. Dann wird die einzige html_table der Seite aus dem restlichen HTML herausgegriffen. Die Funktion main konvertiert das Objekt x in eine data.table, damit es speichereffizient vorliegt. Bevor main endet, kannst du mit Sys.sleep noch eine Pause einbauen, damit du nicht als DDOS‑Angriff wahrgenommen wirst.
main<-function(url.x){
x<-read_html(url.x)
x<-html_table(x)
x<-data.table(x[[1]])return(x)
Sys.sleep(5)}Bereitgestellt von
Jetzt holen wir uns die Daten! Ich nutze gern den Fortschrittsbalken von pblapply(), um den Scraping‑Fortschritt im Blick zu behalten. Du übergibst einfach all.urls und die Funktion main an pblapply(). R lädt sofort eine Seite, sammelt die Tabelle und hält die jeweilige data.table im Speicher. Jede URL wird der Reihe nach abgearbeitet und im Speicher gehalten.
Uff! Das hat Stunden gedauert! An diesem Punkt ist all.h1b eine Liste von Data Tables, eine pro Seite. Um die Liste in eine einzige Data Table zu vereinen, kannst du rbindlist verwenden. Das ist ähnlich zu do.call(rbind, all.h1b), nur deutlich schneller.
all.h1b<-rbindlist(all.h1b)
Speichere die Daten zum Schluss ab, damit du das nicht nochmal machen musst. Zum Glück habe ich hier eine Kopie gesichert.
write.csv(all.h1b,'h1b_data.csv', row.names=F)
Daten bereinigen
Auch wenn du die Daten selbst gescraped hast, sind ein paar zusätzliche Schritte nötig, um sie in ein gut handhabbares Format zu bringen.
Du nutzt lubridate, um Datumsangaben zu organisieren. Außerdem kommt stringr zum Einsatz, das komfortable Wrapper für String‑Manipulation bereitstellt.
Beim Scraping sind 1,8 Mio. der 2 Mio. H‑1B‑Datensätze erfasst worden. Aus meiner Sicht sind 1,8 Mio. gut genug. Also laden wir die Daten mit fread(): Diese Funktion ist wie read.csv, nur als „fast & friendly file finagler“ wesentlich effizienter.
Die Spaltennamen der gescrapten Daten sind in Großbuchstaben und enthalten Leerzeichen.
Sie per Namen zu referenzieren ist mühsam, daher benennst du sie zuerst um.
Für das Umbenennen brauchst du Funktionen auf beiden Seiten des Zuweisungsoperators (<-). Links verwendest du colnames() und übergibst den Data Frame. Rechts übergibst du einen String‑Vektor.
In diesem Beispiel nimmst du zunächst die Originalnamen und wandelst sie mit tolower() in Kleinbuchstaben um. In der zweiten Zeile wendest du gsub() an, eine Funktion für globale Ersetzungen.
Wenn gsub() ein Muster erkennt – hier ein Leerzeichen –, ersetzt es alle Vorkommen durch den zweiten Parameter, den Unterstrich. Abschließend sagst du gsub(), die Ersetzungen in names(h1b.data) vorzunehmen, also in den jetzt kleingeschriebenen Spaltennamen.
colnames(h1b.data)<-tolower(names(h1b.data))colnames(h1b.data)<-gsub(' ','_',names(h1b.data))Bereitgestellt von
Einer meiner ersten Schritte bei der Datenerkundung ist tail(). Diese Funktion gibt die letzten Zeilen zurück. Hier liefert tail() die letzten 8 Zeilen.
So bekommst du schnell ein Gefühl für Form und Vektoren der Daten.
Als Nächstes prüfe ich immer die Klassen der Vektoren. Bei gescrapten Daten können numerische Werte oder Faktoren zu Text werden. Wenn du die Klassen jetzt korrigierst, ersparst du dir später Ärger!
Mit der Funktion apply() übergibst du h1b.data, dann 2 und die Funktion class. Da du 2 gewählt hast, prüft R die Klasse jeder Spalte und gibt sie in der Konsole aus. Mit apply() und 1 würdest du zeilenweise arbeiten – hier nicht hilfreich.
Alle Spalten sind „character“ und müssen korrigiert werden. Ich zeige dir, wie du eine der Datumsspalten änderst, den Rest überlasse ich dir. Mit tail() siehst du dir die letzten 6 Zeilen der falsch typisierten Daten an.
Um die Datumsangaben zu korrigieren, müssen die Schrägstriche / in Bindestriche - geändert werden. Wieder hilft gsub(): Suche nach / und ersetze durch -.
tail(h1b.data$submit_date)
h1b.data$submit_date<-gsub('/','-', h1b.data$submit_date)Bereitgestellt von
Mit den Bindestrichen kannst du mdy() anwenden – das steht für „month, day, year“. So sind die Daten angeordnet. Wäre die Reihenfolge anders, würdest du die Buchstaben entsprechend anpassen.
Um sicherzugehen, dass die Spalte korrekt geändert wurde, prüfst du erneut das Tail und die Klasse. tail() sollte Daten wie „2016-03-11 UTC“ zeigen, und die Vektorklasse sollte „POSIXct“ statt „character“ sein.
h1b.data$submit_date<-mdy(h1b.data$submit_date)tail(h1b.data$submit_date)class(h1b.data$submit_date)Bereitgestellt von
Für diese Analyse ist es sinnvoll, nur Monat und Jahr in neue Spalten zu extrahieren. Im folgenden Code siehst du zwei neue Spalten: $submit_month und $submit_yr.
In lubridate kannst du mit month() auf eine ganze Spalte den Monat aus dem Datum extrahieren. year() akzeptiert ebenso die Datumsspalte und erzeugt h1b.data$submit_yr. Mit head() solltest du jetzt zwei neue Spalten sehen.
h1b.data$submit_month<-month(h1b.data$submit_date, label=T)
h1b.data$submit_yr<-year(h1b.data$submit_date)head(h1b.data)Bereitgestellt von
Als Nächstes sehen wir uns $base_salary an. Dort steht ein Komma als Tausendertrennzeichen, weshalb R die Werte als Text betrachtet – das muss geändert werden. Wieder hilft gsub(), um das Komma zu entfernen und durch ein leeres Zeichen zu ersetzen. Danach wandelst du mit as.numeric()h1b.data$base_salary offiziell in numerische Werte um.
Mit head() kannst du dir einen Ausschnitt des neuen Vektors ansehen.
h1b.data$base_salary<-gsub(',','',h1b.data$base_salary)
h1b.data$base_salary<-as.numeric(h1b.data$base_salary)head(h1b.data$base_salary)Bereitgestellt von
Eine weitere sinnvolle Aufteilung ist nach Bundesstaat. In h1b.data$location sind Stadt und Staat durch ein Komma getrennt. Der Code unten nutzt str_split_fixed(), um die Ortsangabe am ersten Komma zu teilen. Übergebe einfach die Spalte, das Trennzeichen und die Anzahl der zurückzugebenden Spalten. Das resultierende Objekt state ist eine große Matrix mit derselben Zeilenzahl wie h1b.data und 2 Spalten.
Die nächsten zwei Zeilen binden die einzelnen Vektoren als $city und $state an h1b.data. Die Vektoren sind nicht perfekt, da Schreibweisen variieren können – etwa „Winston Salem“ versus „Winston‑Salem“. Für einfache EDA ist die Methode insgesamt gut genug, behalte aber im Hinterkopf, dass du in anderen Analysen Begriffe eventuell zusammenführen musst.
Wenn du dich für ein H‑1B‑Visum bewerben würdest, wolltest du wissen, in welchen Staaten die Chancen am größten sind. Die Funktion table() zählt kategoriale Variablen und lässt sich leicht auf h1b.data$state anwenden. In der zweiten Zeile erstellst du einen kleinen Data Frame, der die Staatskürzel und die gezählten H‑1B‑Werte enthält.
state.tally<-table(h1b.data$state)
state.tally<-data.frame(state=names(state.tally), h1b=as.vector(state.tally))Bereitgestellt von
Mit state.tally und barplot() erzeugst du ein einfaches Balkendiagramm der H‑1B‑Werte je Staat. Der zweite Parameter names.arg definiert die Balkenbeschriftungen, und las=3 weist R an, die Labels vertikal zu platzieren. Einige unsaubere Ortsangaben durch das Komma‑Split sind zu sehen, aber die Aussage ist klar: Bewerber landen sehr wahrscheinlich in CA, NJ, NY oder TX.
barplot(state.tally$h1b,
names.arg =names(table(h1b.data$state)),
las=3)Bereitgestellt von
Du siehst die H‑1B‑Summen nach Staat von 2012 bis 2016.
Als Nächstes versuchen wir, den Zusammenhang zwischen H‑1B‑Visa und einer externen Kennzahl zu verstehen. Der Einfachheit halber hat R einen eingebauten Datensatz namens state.x77. Das ist eine Matrix mit 50 Zeilen – je Staat eine – und Angaben wie Bevölkerung und Lebenserwartung aus dem US‑Zensus 1977.
Tipp: Verwende in deiner eigenen Analyse eine aktuellere Datenquelle.
Fürs Lernen ist state.x77 jetzt ein gutes Beispiel. Sieh dir den Datensatz mit head() an.
Verschmelzen wir diese Informationen mit den state.tally‑Daten zu einem größeren Data Frame, um Beziehungen zu verstehen. Erstelle dazu einen Data Frame state.data mit den Staatskürzeln state.abb und den alten Zensusdaten von 1977. Dann führst du mit mergestate.tally und state.data zusammen.
Du kannst die Spalte state explizit als Join‑Schlüssel angeben. Im dritten Schritt inspizierst du per Index die Zeilen 15 bis 20.
state.data<-data.frame(state=state.abb,state.x77)
state.data<-merge(state.tally,
state.data,
by='state')
state.data[15:20,]Bereitgestellt von
Eine grundlegende EDA‑Funktion ist cor: Sie gibt die Korrelation zwischen zwei Variablen aus.
Denk daran: Korrelationen reichen von −1 bis 1. 0 bedeutet keinen Zusammenhang. Werte nahe 1 bedeuten eine positive Korrelation (hoffentlich) wie R‑Programmierung und Einkommen. Negative Werte deuten auf einen gegenläufigen Zusammenhang hin – zum Beispiel R‑Programmierung und Sozialleben!
Dieser Code wendet cor auf die Bevölkerungszahl 1977 und die aktuellen H‑1B‑Summen an. Der zeitliche Versatz ist uns bewusst – der Code soll die Vorgehensweise illustrieren. Du kannst $Population natürlich gegen einen anderen Vektor im Data Frame tauschen.
Eine weitere Möglichkeit, Zusammenhänge zu untersuchen, ist ein Scatterplot – besonders eine Scatterplot‑Matrix. Mit pairs() erstellst du diese schnell. Der Code unten nutzt eine Formel zur Definition der Beziehungen. Jede Spalte wird einzeln mit Pluszeichen verknüpft. Der Parameter data nimmt den Data Frame, und main setzt den Titel.
pairs(~ h1b + Population + Income,
data = state.data,
main='h1b relationships')Bereitgestellt von
Du siehst, wie diese Scatterplot‑Matrix die H‑1B‑Summen den Variablen Population und Income gegenüberstellt.
Zwischen Population und h1b ist eine Beziehung zu erkennen. Das ist intuitiv, da bevölkerungsreichere Staaten mehr Jobchancen – und damit mehr Bedarf an H‑1B‑Visa – haben.
Um in einen einzelnen Plot aus der Matrix „hineinzuzoomen“, ruf einfach plot() mit zwei Variablen auf.
plot(state.data$Income,state.data$h1b,
main ='Income to H1B')Bereitgestellt von
Nächste Schritte mit deinen H‑1B‑Daten
Wir haben die H‑1B‑Daten gerade erst angerissen! Der Datensatz ist sehr reichhaltig, und im nächsten Beitrag analysierst du die Gehaltsdaten, entfernst Ausreißer und erstellst aussagekräftigere Visualisierungen mit ggplot2.
Außerdem baust du auf diesen EDA‑Konzepten auf, indem du den H‑1B‑Status über die Zeit und die Top‑Arbeitgeber untersuchst. Eine der coolen Visualisierungen, die ich dir schon jetzt zeigen kann, ist ein Boxplot mit Gehaltsverteilungen nach H‑1B‑Status. Der Code dazu:
ggplot(h1b.data)+geom_boxplot(aes(factor(case_status),base_salary,fill=as.factor(case_status)))+ylim(0,100000)+theme_gdocs()+scale_fill_gdocs()+theme(axis.text.x=element_blank())Bereitgestellt von
Mach dich mit Python-Datenstrukturen vertraut: Lerne mehr über Datentypen und primitive sowie nicht-primitive Datenstrukturen wie Strings, Listen, Stapel usw.
Gewinne Vertrauen in die OLS-Regression, indem du ihre theoretischen Grundlagen beherrschst. Erforsche, wie du einfache Implementierungen in Excel, R und Python durchführst.
Lerne, wie du Python-Listen mit Techniken wie Slicing, List Comprehensions und itertools aufteilen kannst. Finde heraus, wann du welche Methode für die beste Datenverarbeitung nutzen solltest.
Erforsche Pythons match-case: eine Anleitung zu seiner Syntax, Anwendungen in Data Science und ML sowie eine vergleichende Analyse mit dem traditionellen switch-case.
Das Ziel dieses Tutorials ist, dir zu zeigen, wie du Daten zu H‑1B‑Visa per Web Scraping mit R sammelst. Anschließend lernst du, wie du JSON-Objekte parsest sowie die Daten speicherst und weiterverarbeitest, um eine grundlegende explorative Datenanalyse (EDA) des großen Datensatzes an H‑1B‑Anträgen durchzuführen.
Vielleicht findest du so heraus, wie du dich als Kandidat am besten positionierst – oder lernst neuen R‑Code!
Einführung
Letzte Woche hat DataCamps Blog „Can Data Help Your H-1B Visa Application“ einige Ergebnisse einer Analyse der H‑1B‑Daten über die Jahre vorgestellt. Jetzt bist du dran: Schnapp dir die Daten, analysiere sie selbst und finde heraus, was noch drinsteckt! Ted Kwartler führt dich mit einer Reihe von R‑Tutorials Schritt für Schritt durch.
Ein Freund von mir arbeitet in einer Kanzlei in Texas, die H‑1B‑Visa beantragt. Das H‑1B ist ein Nichteinwanderungsvisum in den USA, das es US‑Arbeitgebern erlaubt, ausländische Fachkräfte vorübergehend in Spezialberufen zu beschäftigen. Die Annahme ist offenbar extrem schwierig, da die Zahl der Visa begrenzt ist, sich aber Tausende bewerben. Das ist zwar anekdotisch, aber ich wollte mir die Daten selbst ansehen – in der Hoffnung, qualifizierten Kandidaten zu zeigen, dass die USA ein gastfreundlicher Ort sind!
Daten beschaffen: Web Scraping und Parsing
Ein DataCamp-Kollege hat mich auf diese Seite hingewiesen: eine einfache Website mit H‑1B‑Daten von 2012 bis 2016. Die Seite gibt an, 2 Mio. H‑1B‑Anträge in einer einzigen Tabelle zu organisieren.Ich habe mich entschieden, die Daten programmatisch zu sammeln (lies: per Web Scraping), denn Copy & Paste bis ans Lebensende war keine Option! Wie du siehst, zeigt das Bild unten einen Ausschnitt mit Bostons H‑1B‑Daten:
In diesem Tutorial nutzen wir unter anderem folgende Bibliotheken:
jsonlitezum Parsen von JSON-Objekten,rvestzum „Ernten“ von HTML,pbapply– mein Favorit, weil es Fortschrittsbalken zu den Basis-apply‑Funktionen hinzufügt – unddata.table, um R bei großen Data Frames Beine zu machen.Die Seitenstruktur erkunden
Wenn du die Seite erkundest, fällt dir auf, dass das Suchformular Vorschläge zum Autovervollständigen anbietet. Tippst du zum Beispiel ein „B“ in das City-Feld, erscheint ein Modal mit Vorschlägen wie unten gezeigt. Das folgende Bild zeigt die Optionen, wenn ich „B“ tippe:
Das bedeutet: Du kannst die Autovervollständigung als effiziente Möglichkeit nutzen, die Seite abzufragen. In Chrome kannst du neu laden, dann per Rechtsklick „Untersuchen“ wählen, im Entwicklertool zu „Network“ wechseln und schließlich auf der Seite „B“ tippen, um das Modal zu laden. Beim Durchsehen der Netzwerk-Links findest du eine PHP‑Abfrage, die ein JSON‑Objekt mit Städten zurückgibt – etwa diese. Ziel ist es, zunächst alle vorgeschlagenen Städte zu sammeln und diese Liste dann zu nutzen, um viele Seiten mit H‑1B‑Daten zu scrapen. Beim Blick auf die URL oben fällt dir auf, dass sie mit einem Buchstaben endet. Du kannst also
paste0()mit der Basis‑URLhttp://h1bdata.info/cities.php?term=undlettersverwenden. Die Basis wird für jeden Wert inlettersrecycelt. Das Objektlettersist ein eingebauter R‑Vektor von „a“ bis „z“. Das Objektjson.citiesist ein Vektor von URLs von a bis z, die alle Autovervollständigungs‑Vorschläge als JSON enthalten.json.cities-Objekt ist ein Vektor aus 26 Links, die R einlesen muss. Mitlapply()oderpblapply()zusammen mitfromJSONparst R jedes JSON‑Objekt, umall.citieszu erstellen. Du verschachtelst das Ergebnis inunlist, sodass die Ausgabe ein einfacher String‑Vektor ist. Mit diesem Code hast du alle vorgeschlagenen Städte in einem Vektor, den du zum Aufbau der eigentlichen Datenseiten verwenden kannst.expand.grid(). Im Code unten siehst du, dass die Stadtinformationenall.citiesübergeben werden und das Jahr perseq()von 2012 bis 2016. Die Funktion erzeugt über 5.000 Stadt‑Jahr‑Kombinationen.expand.grid()erzeugt programmatisch Boston 2012, Boston 2013, Boston 2014 usw., da jede Stadt und jedes Jahr eine eindeutige Kombination darstellen. Einige Städte wie Los Angeles bestehen aus zwei Wörtern und müssen für URLs kodiert werden. Die Funktionurl_encode()ändert „Los Angeles“ in Los%20Angeles, damit die Adresse gültig ist. Du übergibst den gesamten Vektor, undurl_encode()arbeitet zeilenweise: Zum Schluss nutzt du erneutpaste0(), um die Basis‑URL mit den Stadt‑ und Jahres‑Kombinationen incity.yearzu verketten. Ein Beispiel‑Link hier.Informationen von Seiten extrahieren
Nachdem du die vorherigen Schritte erledigt hast, kannst du eine eigene Funktion namens
mainschreiben, um die Daten jeder Seite zu sammeln. Der Workflow ist mitrvestziemlich geradlinig. Zuerst wird eine URL übergeben und mitread_html()der Seiteninhalt geparst. Dann wird die einzigehtml_tableder Seite aus dem restlichen HTML herausgegriffen. Die Funktionmainkonvertiert das Objektxin einedata.table, damit es speichereffizient vorliegt. Bevormainendet, kannst du mitSys.sleepnoch eine Pause einbauen, damit du nicht als DDOS‑Angriff wahrgenommen wirst.Jetzt holen wir uns die Daten! Ich nutze gern den Fortschrittsbalken von
pblapply(), um den Scraping‑Fortschritt im Blick zu behalten.Du übergibst einfach
all.urlsund die Funktionmainanpblapply(). R lädt sofort eine Seite, sammelt die Tabelle und hält die jeweiligedata.tableim Speicher. Jede URL wird der Reihe nach abgearbeitet und im Speicher gehalten.Daten zu einer Data Table zusammenführen
Uff! Das hat Stunden gedauert! An diesem Punkt ist
all.h1beine Liste von Data Tables, eine pro Seite. Um die Liste in eine einzige Data Table zu vereinen, kannst durbindlistverwenden. Das ist ähnlich zudo.call(rbind, all.h1b), nur deutlich schneller.all.h1b<-rbindlist(all.h1b)Speichere die Daten zum Schluss ab, damit du das nicht nochmal machen musst. Zum Glück habe ich hier eine Kopie gesichert.
write.csv(all.h1b,'h1b_data.csv', row.names=F)Daten bereinigen
Auch wenn du die Daten selbst gescraped hast, sind ein paar zusätzliche Schritte nötig, um sie in ein gut handhabbares Format zu bringen.
Du nutzt
lubridate, um Datumsangaben zu organisieren. Außerdem kommtstringrzum Einsatz, das komfortable Wrapper für String‑Manipulation bereitstellt.Das ist Geschmackssache, aber ich setze gern
scipen=999. Pflicht ist das nicht, aber so verschwindet die wissenschaftliche Notation.Beim Scraping sind 1,8 Mio. der 2 Mio. H‑1B‑Datensätze erfasst worden. Aus meiner Sicht sind 1,8 Mio. gut genug. Also laden wir die Daten mit
fread(): Diese Funktion ist wieread.csv, nur als „fast & friendly file finagler“ wesentlich effizienter.Die Spaltennamen der gescrapten Daten sind in Großbuchstaben und enthalten Leerzeichen.
Sie per Namen zu referenzieren ist mühsam, daher benennst du sie zuerst um.
Für das Umbenennen brauchst du Funktionen auf beiden Seiten des Zuweisungsoperators (
<-). Links verwendest ducolnames()und übergibst den Data Frame. Rechts übergibst du einen String‑Vektor.In diesem Beispiel nimmst du zunächst die Originalnamen und wandelst sie mit
tolower()in Kleinbuchstaben um. In der zweiten Zeile wendest dugsub()an, eine Funktion für globale Ersetzungen.Wenn
gsub()ein Muster erkennt – hier ein Leerzeichen –, ersetzt es alle Vorkommen durch den zweiten Parameter, den Unterstrich. Abschließend sagst dugsub(), die Ersetzungen innames(h1b.data)vorzunehmen, also in den jetzt kleingeschriebenen Spaltennamen.Einer meiner ersten Schritte bei der Datenerkundung ist
tail(). Diese Funktion gibt die letzten Zeilen zurück. Hier lieferttail()die letzten 8 Zeilen.So bekommst du schnell ein Gefühl für Form und Vektoren der Daten.
Als Nächstes prüfe ich immer die Klassen der Vektoren. Bei gescrapten Daten können numerische Werte oder Faktoren zu Text werden. Wenn du die Klassen jetzt korrigierst, ersparst du dir später Ärger!
Mit der Funktion
apply()übergibst duh1b.data, dann2und die Funktionclass. Da du2gewählt hast, prüft R die Klasse jeder Spalte und gibt sie in der Konsole aus. Mitapply()und 1 würdest du zeilenweise arbeiten – hier nicht hilfreich.Oh oh!
Alle Spalten sind „character“ und müssen korrigiert werden. Ich zeige dir, wie du eine der Datumsspalten änderst, den Rest überlasse ich dir. Mit
tail()siehst du dir die letzten 6 Zeilen der falsch typisierten Daten an.Um die Datumsangaben zu korrigieren, müssen die Schrägstriche
/in Bindestriche-geändert werden. Wieder hilftgsub(): Suche nach/und ersetze durch-.Mit den Bindestrichen kannst du
mdy()anwenden – das steht für „month, day, year“. So sind die Daten angeordnet. Wäre die Reihenfolge anders, würdest du die Buchstaben entsprechend anpassen.Um sicherzugehen, dass die Spalte korrekt geändert wurde, prüfst du erneut das Tail und die Klasse.
tail()sollte Daten wie „2016-03-11 UTC“ zeigen, und die Vektorklasse sollte „POSIXct“ statt „character“ sein.Für diese Analyse ist es sinnvoll, nur Monat und Jahr in neue Spalten zu extrahieren. Im folgenden Code siehst du zwei neue Spalten:
$submit_monthund$submit_yr.In
lubridatekannst du mitmonth()auf eine ganze Spalte den Monat aus dem Datum extrahieren.year()akzeptiert ebenso die Datumsspalte und erzeugth1b.data$submit_yr. Mithead()solltest du jetzt zwei neue Spalten sehen.Als Nächstes sehen wir uns
$base_salaryan. Dort steht ein Komma als Tausendertrennzeichen, weshalb R die Werte als Text betrachtet – das muss geändert werden. Wieder hilftgsub(), um das Komma zu entfernen und durch ein leeres Zeichen zu ersetzen. Danach wandelst du mitas.numeric()h1b.data$base_salaryoffiziell in numerische Werte um.Mit
head()kannst du dir einen Ausschnitt des neuen Vektors ansehen.Eine weitere sinnvolle Aufteilung ist nach Bundesstaat. In
h1b.data$locationsind Stadt und Staat durch ein Komma getrennt. Der Code unten nutztstr_split_fixed(), um die Ortsangabe am ersten Komma zu teilen. Übergebe einfach die Spalte, das Trennzeichen und die Anzahl der zurückzugebenden Spalten. Das resultierende Objektstateist eine große Matrix mit derselben Zeilenzahl wieh1b.dataund 2 Spalten.Die nächsten zwei Zeilen binden die einzelnen Vektoren als
$cityund$stateanh1b.data. Die Vektoren sind nicht perfekt, da Schreibweisen variieren können – etwa „Winston Salem“ versus „Winston‑Salem“. Für einfache EDA ist die Methode insgesamt gut genug, behalte aber im Hinterkopf, dass du in anderen Analysen Begriffe eventuell zusammenführen musst.Deine Daten erkunden: Erste Schritte
Wenn du dich für ein H‑1B‑Visum bewerben würdest, wolltest du wissen, in welchen Staaten die Chancen am größten sind. Die Funktion
table()zählt kategoriale Variablen und lässt sich leicht aufh1b.data$stateanwenden. In der zweiten Zeile erstellst du einen kleinen Data Frame, der die Staatskürzel und die gezählten H‑1B‑Werte enthält.Mit
state.tallyundbarplot()erzeugst du ein einfaches Balkendiagramm der H‑1B‑Werte je Staat. Der zweite Parameternames.argdefiniert die Balkenbeschriftungen, undlas=3weist R an, die Labels vertikal zu platzieren. Einige unsaubere Ortsangaben durch das Komma‑Split sind zu sehen, aber die Aussage ist klar: Bewerber landen sehr wahrscheinlich in CA, NJ, NY oder TX.Du siehst die H‑1B‑Summen nach Staat von 2012 bis 2016.
Als Nächstes versuchen wir, den Zusammenhang zwischen H‑1B‑Visa und einer externen Kennzahl zu verstehen. Der Einfachheit halber hat R einen eingebauten Datensatz namens
state.x77. Das ist eine Matrix mit 50 Zeilen – je Staat eine – und Angaben wie Bevölkerung und Lebenserwartung aus dem US‑Zensus 1977.Tipp: Verwende in deiner eigenen Analyse eine aktuellere Datenquelle.
Fürs Lernen ist
state.x77jetzt ein gutes Beispiel. Sieh dir den Datensatz mithead()an.Verschmelzen wir diese Informationen mit den
state.tally‑Daten zu einem größeren Data Frame, um Beziehungen zu verstehen. Erstelle dazu einen Data Framestate.datamit den Staatskürzelnstate.abbund den alten Zensusdaten von 1977. Dann führst du mitmergestate.tallyundstate.datazusammen.Du kannst die Spalte
stateexplizit als Join‑Schlüssel angeben. Im dritten Schritt inspizierst du per Index die Zeilen 15 bis 20.Eine grundlegende EDA‑Funktion ist
cor: Sie gibt die Korrelation zwischen zwei Variablen aus.Denk daran: Korrelationen reichen von −1 bis 1. 0 bedeutet keinen Zusammenhang. Werte nahe 1 bedeuten eine positive Korrelation (hoffentlich) wie R‑Programmierung und Einkommen. Negative Werte deuten auf einen gegenläufigen Zusammenhang hin – zum Beispiel R‑Programmierung und Sozialleben!
Dieser Code wendet
corauf die Bevölkerungszahl 1977 und die aktuellen H‑1B‑Summen an. Der zeitliche Versatz ist uns bewusst – der Code soll die Vorgehensweise illustrieren. Du kannst$Populationnatürlich gegen einen anderen Vektor im Data Frame tauschen.Eine weitere Möglichkeit, Zusammenhänge zu untersuchen, ist ein Scatterplot – besonders eine Scatterplot‑Matrix. Mit
pairs()erstellst du diese schnell. Der Code unten nutzt eine Formel zur Definition der Beziehungen. Jede Spalte wird einzeln mit Pluszeichen verknüpft. Der Parameterdatanimmt den Data Frame, undmainsetzt den Titel.Du siehst, wie diese Scatterplot‑Matrix die H‑1B‑Summen den Variablen
PopulationundIncomegegenüberstellt.Zwischen
Populationundh1bist eine Beziehung zu erkennen. Das ist intuitiv, da bevölkerungsreichere Staaten mehr Jobchancen – und damit mehr Bedarf an H‑1B‑Visa – haben.Um in einen einzelnen Plot aus der Matrix „hineinzuzoomen“, ruf einfach
plot()mit zwei Variablen auf.Nächste Schritte mit deinen H‑1B‑Daten
Wir haben die H‑1B‑Daten gerade erst angerissen! Der Datensatz ist sehr reichhaltig, und im nächsten Beitrag analysierst du die Gehaltsdaten, entfernst Ausreißer und erstellst aussagekräftigere Visualisierungen mit
ggplot2.Außerdem baust du auf diesen EDA‑Konzepten auf, indem du den H‑1B‑Status über die Zeit und die Top‑Arbeitgeber untersuchst. Eine der coolen Visualisierungen, die ich dir schon jetzt zeigen kann, ist ein Boxplot mit Gehaltsverteilungen nach H‑1B‑Status. Der Code dazu:
Bleib dran für den nächsten Teil der Tutorialserie „Exploring H‑1B Data with R“! Schau dir in der Zwischenzeit unser R‑Data‑Frame‑Tutorial, den Importing Data in R‑Kurs oder unseren Data Manipulation in R with dplyr‑Kurs an.