Rohdaten
{
"locations" : [ {
"timestampMs" : "1417727161925",
"latitudeE7" : 469345574,
"longitudeE7" : 74339415,
"accuracy" : 31
}, {
"timestampMs" : "1417727100712",
"latitudeE7" : 469345603,
"longitudeE7" : 74339025,
"accuracy" : 32
}, {
"timestampMs" : "1417727030427",
"latitudeE7" : 469345829,
"longitudeE7" : 74339350,
"accuracy" : 30
}, {
"timestampMs" : "1417726978911",
...
Einlesen in R
library(jsonlite)
library(plyr)
raw <- fromJSON('LocationHistory.json')
Datenansicht in R
> head(raw$locations)
timestampMs latitudeE7 longitudeE7 accuracy velocity altitude activitys heading
1 1446466295025 469344810 74340787 20 NA NA NULL NA
2 1446466154216 469344810 74340787 20 NA NA NULL NA
3 1446466096080 469344859 74340783 20 NA NA NULL NA
4 1446465976033 469344859 74340783 20 NA NA NULL NA
5 1446465853219 469344859 74340783 20 NA NA NULL NA
> lapply(locs,class)
$timestampMs
[1] "character"
$latitudeE7
[1] "integer"
...
# -> kein ordentliches Datenobjekt sondern eine riesige Liste
# -> Datentypen/Skalen noch nicht richtig
Aufbereitung
locs = raw$locations
# Einen data.frame bauen, der die Daten enthält:
ldf = data.frame(t=rep(0,nrow(locs)))
# Zeitformat ist als Character (POSIX) in ms gespeichert.
# Umwandeln in eine Zahl (in Sekunden)
ldf$t = as.numeric(locs$timestampMs)/1000
class(ldf$t) = 'POSIXct'
# lat/lon sind gemessen in "10^7""
ldf$lat = as.numeric(locs$latitudeE7/1E7)
ldf$lon = as.numeric(locs$longitudeE7/1E7)
# zum Spass noch ein paar Variablen bauen:
ldf$Jahr = substring(as.Date(ldf$t),1,4)
ldf$Sommer = substring(as.Date(ldf$t),6,7)%in%c("06","07","08","09")
fertige Daten
> head(ldf)
t lat lon Jahr Sommer
1 2015-11-02 13:11:35 46.93448 7.434079 2015 FALSE
2 2015-11-02 13:09:14 46.93448 7.434079 2015 FALSE
3 2015-11-02 13:08:16 46.93449 7.434078 2015 FALSE
4 2015-11-02 13:06:16 46.93449 7.434078 2015 FALSE
5 2015-11-02 13:04:13 46.93449 7.434078 2015 FALSE
6 2015-11-02 13:03:23 46.93449 7.434096 2015 FALSE
Plotten
bern = get_map('Bern, CH',13,scale=2,source="google",maptype="hybrid")
ldf.bern = mapclip(ldf,bern)
png("bernmap.png", width=1200, height=1200)
ggmap(bern) + geom_point(data=ldf.bern,aes(x=lon,y=lat,colour=Sommer),size=2,alpha=0.1)
dev.off()
# für jede Website die csv Links grabben
for(url in urls) {
doc <- read_html(url)
links <- html_nodes(doc, "a") |> html_attr("href")
csvlinks <- paste0("https://www.football-data.co.uk/",
links[grep(pattern = ".csv",
x = links)])
# Was bedeuten die Variablen?
# Div = League Division
# Date = Match Date (dd/mm/yy)
# HomeTeam = Home Team
# AwayTeam = Away Team
# FTHG = Full Time Home Team Goals
# FTAG = Full Time Away Team Goals
# FTR = Full Time Result (H=Home Win, D=Draw, A=Away Win)
# Alle Links parsen und zusammentackern
for(i in 1:length(csvlinks)) {
tempdata <- as.data.table(read.csv(csvlinks[i]))
try(dsocc <- rbind(dsocc,tempdata[, c("Div", "Date", "HomeTeam", "AwayTeam", "FTHG",
"FTAG", "BbAvH", "BbAvD", "BbAvA"),
with=FALSE], fill=TRUE, use.names=TRUE))
}
}
# Variablen definieren
dsocc[,win:=ifelse(FTHG>FTAG,1,ifelse(FTHG<FTAG,0,0.5))] # Sieg
dsocc[,numDate:=as.numeric(as.Date(Date,format="%d/%m/%y"))] # Datum numerich
dsocc[,HomeTeam:=as.character(HomeTeam)] # Datentyp fixen
dsocc[,AwayTeam:=as.character(AwayTeam)] # Datentyp fixen
Algorithmus (vereinfachter Ausschnitt)
# Ähnlich wie die Elo-Zahl
# bekannt durch Schach, mittlerweile in Variationen aber sehr verbreitet:
# 1. Startwerte setzen für alle Mannschaften
# 2. Beim ersten Spiel im Datensatz anfangen -> Algorithmus anwenden, Ratings updaten
# 3. Durch die Daten Zeile für Zeile gehen und Algorithmus anwenden, Ratings updaten
# 4. Historie der Ratings (Mannschaftsstärken) speichern/mitprotokollieren
# Kernstück:
# Erwartungswerte für Tore und Gegentore
E_a <- lambda * (off_a + h1) / def_b
E_b <- lambda * (off_b - h2) / def_a
# Tatsächlicher Spielstand (Tore/Gegentore):
S_a <- dbl$FTHG[i]
S_b <- dbl$FTAG[i]
# neues Rating definieren auf Basis vom bisherigen Rating und den
# Abweichungen von der Erwartung
neu_off_a <- off_a + K * (S_a - E_a)
neu_def_a <- def_a - K * (S_b - E_b)
neu_off_b <- off_b + K * (S_b - E_b)
neu_def_b <- def_b - K * (S_a - E_a)
# Ein paar Resultate (Stand Oktober 2025)
| off | def | team | mean |
|---|---|---|---|
| 4263.695 | 3398.618 | Bayern Munich | 3831.156 |
| 3262.307 | 2637.602 | Dortmund | 2949.955 |
| 3130.993 | 2860.953 | Leverkusen | 2995.973 |
| 2688.872 | 2320.150 | Ein Frankfurt | 2504.511 |
| 2553.544 | 2447.270 | Stuttgart | 2500.407 |
| 2405.461 | 1933.691 | Hoffenheim | 2169.576 |
| 2305.780 | 1998.042 | M’gladbach | 2151.911 |
| 2173.959 | 2111.056 | Werder Bremen | 2142.507 |
| 2133.925 | 2288.238 | Wolfsburg | 2211.081 |
| 2087.344 | 2424.374 | Mainz | 2255.859 |
| 1814.109 | 2166.448 | Augsburg | 1990.278 |
| 1788.618 | 2168.801 | FC Koln | 1978.709 |
| 1786.803 | 1840.454 | Hertha | 1813.628 |
| 1745.362 | 2117.526 | Ingolstadt | 1931.444 |
| 1638.118 | 1807.842 | Hannover | 1722.980 |
| 1544.824 | 1758.929 | Schalke 04 | 1651.876 |
| 1498.660 | 1622.374 | Darmstadt | 1560.517 |
| 1489.422 | 2136.361 | Hamburg | 1812.891 |
getwd(): Zeigt das Arbeitsverzeichnis ansetwd(): Definiert ein neues Arbeitsverzeichnisdir(): Zeigt den Inhalt des aktuellen
Arbeitsverzeichnises\ für Pfadangaben sondern
/ oder \\a <- 10 oder a = 10: Erzeugt oder
überschreibt das Objekt a mit dem Inhalt rechts (10)a: Zeigt den Inhalt des Objekts a anrm(): Löscht Objekte aus dem Workspacesave(a, b, file = "example.RData"): Speichert die
angegebenen Objekte (a,b) in das aktuelle Arbeitsverzeichnisload("example.RData"): Lädt alle im angegebenen File
gespeicherten Objekte#: Beginnt eine auskommentierte Zeile die nicht
interpretiert wirdls(): Zeigt alle Objekte des aktuellen Workspace
an# Kommentare beginnen mit #
# Alles in der Zeile nach # wird von R ignoriert
# 1. R starten
# 2. Arbeitsverzeichnis (mit Schreibrecht) anlegen
# und R auf dieses Verzeichnis setzen: setwd()
# 3. Code ausführen mit STRG + Enter (Oder Icon "Run" oben rechts)
5 + 5
getwd() # Arbeitsverzeichnis anzeigen
# setwd() # Arbeitsverzeichnis definieren
setwd("meinpfad")
dir() # Arbeitsverzeichnis anzeigen
a <- 50 # Erzeugt Objekt a (Vektor der Länge 1) mit dem einzelnen Wert 50
a
# Objekt erzeugen, dass die Vornamen der Beatles enthält
die.beatles <- c("John", "Paul", "George", "Ringo")
die.beatles
# Mit c() - concatenate lässt sich auch ein Zahlenvektor bauen:
b <- c(1, 2, 3, 4)
# oder kürzer
b <- seq(1, 4)
# oder noch kürzer
b <- 1:4
# Objektnamen dürfen keine Leerzeichen haben. Ferner empfiehlt es sich - und _ zu meiden
# siehe Google R Style Guide
# Namen sollten aussagekräftig sein. Namensgebung sollte im ganzen Code-File konsistent
# sein (Punkte, Gross-/Kleinschreibung)
ls() # Workspace anzeigen
# Objekte a, b und die.beatles speichern in "beispiel1.RData"
save(a, b, die.beatles, file = "beispiel1.RData")
# Objekte löschen
rm(a, b, die.beatles) #oder:
rm(list=ls()) # löscht den gesamten Workspace
ls() # was ist jetzt noch im Workspace?
die.beatles # nicht mehr da
load("beispiel1.RData") # gespeichertes Objekt laden
die.beatles # wieder da
library(packagename): installiertes Paket ladenlibrary(): alle installierten Pakete anzeigenlibrary(help=packagename): Einige
Paketinformationensearch(): aktuell geladene Pakete anzeigendetach("package:packagename"): Paket wieder
“ausladen”ls("package:packagename"): alle Objekte innerhalb einer
Pakets zeigenpackagename::bar: ein einzelnes Objekt aus einem Paket
laden statt das ganze Paketinstall.packages("packagename"): Paket
installierenremove.packages("packagename"): Paket
deinstallieren# Nehmen wir an wir möchten eine XLSX Datei einlesen.
# Ein Package, dass dies sehr gut macht ist "readxl" mit der Funktion read_excel()
install.packages("readxl") # Install package
?read_excel
# geht erst wenn das Package auch geladen ist
library(readxl)
?read_excel
+ - * / ^& | == != > < >= <=?function)
exp(x)=e^x log(x) log10(x) sin(x) cos(x) tan(x)abs(x) sqrt(x) ceiling(x) floor(x) trunc(x) round(x, digits=n)# Rechnen
ergebnis <- (23+24)*11/(18+15)*5
ergebnis
# Funktionen
log(2)
cos(2)
# Vergleich
x <- -3:3
x
# sind die Elemente von x gleich 0?
x == 0
# grösser 0?
x > 0
# kleiner 0?
x < 0
# grösser gleich 0?
x >= 0
# kleiner gleich 0?
x <= 0
# ungleich 0?
x != 0
# grösser als -1 aber kleiner als 1
x > -1 & x < 1
# grösser als 1 und kleiner als -1
x > 1 & x < -1
# grösser als 1 oder kleiner als -1
x > 1 | x < -1
mean() Funktion kann ein zusätzlicher Parameter
trim angegeben werden. Finden Sie heraus, was mit diesem
Parameter getan werden kann indem Sie die Hilfe von mean()
lesen und halten Sie diese Info in ihrem Scriptfile fest.# 2.
(3 + 4)^2
-99/33 + 42
log(1)
sqrt(2)^2
# 3.
5 == 7
5 * 5 >= 6 * 4
sqrt(3) != cos(17)
# 4.
# help(mean)
# ?mean
# the fraction (0 to 0.5) of observations to be trimmed from each end of x
# before the mean is computed.
class(): Offenbart die Klasse eines Objektsas.numeric() oder as.character()list(1, "Hallo", TRUE)# Homogen
# Integer Vektor
x <- 1:9
class(x)
x
# Numerischer Vektor
x <- c(1.3, 2.4, 3.5)
class(x)
typeof(x)
x
# Logischer Vektor
x <- -3:3
y <- x >= 0
class(y)
y
# String/Character Vektor
x <- c("a", "b", "c", "d", "f")
class(x)
x
# Matrix
matrix.2mal3 <- matrix(c(1,2,11,12,20,30), nrow = 2, ncol=3)
class(matrix.2mal3)
typeof(matrix.2mal3)
matrix.2mal3
# Array (z.B. 3 Matrizen)
arraybsp <- array(1:50, c(5,5,2)) # Zahlen von 1 bis 50 einem Array mit 2 5x5 Matrizen
class(arraybsp)
arraybsp
# Heterogen
# Liste
liste <- list(a= c(4:8), b = c(1:3), c = c(2:10))
class(liste)
liste
# Data frame: Kann man anschauen durch Aufruf von swiss oder fix(swiss)
class(swiss)
swiss
# Faktoren
sex <- c(0, 0, 1, 1)
factor(sex, labels=c("Mann", "Frau"))
# warum ist das nuetzlich?
a <- rep(c("Haus", "Strasse"), 10^5)
object.size(a)
object.size(as.factor(a))
a<-rep(c("Haus", "Strasse"), 5)
object.size(a)
object.size(as.factor(a))
# Funktionen: z.B. cos(); mean()
class(mean)
mean
# Datum
# https://stat.ethz.ch/R-manual/R-devel/library/base/html/strptime.html
daten.unformatiert<-c("19990123","20110324","20100412")
daten.als.daten<-as.Date(daten.unformatiert,format = "%Y%m%d")
tage.seit<-c(17000,14000,13000)
tage.als.daten<-as.Date(tage.seit,origin="1970-01-01")
Inf und -Inf: Positiv und negativ
unendlichNaN: “Not a number”, z.B.
0/0NA: fehlender Wert (Missing)# Wichtiger Hinweis zu fehlenden Werten:
x <- c(1, 2, NA, 4)
#falsch:
x == NA
x == "NA"
#richtig:
is.na(x)
# siehe hierzu auch:
is.infinite(x)
1:100
1:100 * 3
# Vektor erzeugen und speichern
x <- 3 * 1:100
x
# oder über concatenate
x <- c(1, 2, 3, 4, 5)
x
# Vektor mit 15 Elementen. 5 Wiederholungen von 1,2,3
x <- rep(1:3, time=5) #default ist times: verkettet den Vektor 1 bis 3 5 mal
x
x <- rep(1:3, each=5) #each wiederholt jedes Element 5 mal
x
# Vektor mit Zahlen von 10 bis 100 in Zehnerschritten
x <- seq(10, 100, 10)
x
# Verknüpfen
x <- c(a = 10, b = 20, c = 30, d = 40)
x
x <- 11:200
x
x[1] # erstes Element von x
x[1:10] # die ersten 10 Elemente von x
x[-(11:100)] # Alle Elements von x ausser die Positionen 11 bis 100
# Allgemeiner gibt es drei Möglichkeiten:
x <- c(a = 10, b = 20, c = 30, d= 40) # vergibt direkt Namen, alternativ:
# names(x) <- c("a", "b", "c", "d")
# Möglichkeit 1
x[1:2] # über die Position
# Möglichkeit 2
x[c("a", "c")] # über den Namen
# Möglichkeit 3
x[x < 20 | x >= 30] # über eine Bedingung
# head / tail
x <- 1:100
head(x)
tail(x)
x der Länge 50, der die
Zahlen von 1 bis 5 zehn mal wiederholt.y (Länge 3), der die Elemente
von x an den Positionen 12, 20 und 50 enthält.freunde mit drei Namen Ihrer
Wahl.# 2.
x <- rep(1:5, 10)
x
length(x)
# 3.
y <- x[c(12, 20, 50)]
y
# 4.
freunde <- c("Bernd", "Frank", "Franz")
"factor"factor(): Erzeugt einen Faktorlevels(): Zeigt die Kategorien eines Faktorsas.numeric(): Zwingt den Faktor in einen numerischen
Vektor"list"list(): Erzeugt eine Listelist$switzerland: Greift auf das Element
switzerland der Liste list zulist[2]: Zweites Element von list# Faktor erzeugen
sex <- factor(c(rep(0, 50), rep(1, 50)), labels = c("Mann", "Frau"))
# Hinweis zu as.numeric(): manchmal hat man folgendes Problem:
jahrgang <- factor(c("2000", "2000", "2001", "2002")) # falscher Datentyp "character"
# umwandeln in numerische Werte liefert aber nicht, was wir wollen
as.numeric(jahrgang)
# besser:
as.numeric(as.character(jahrgang))
# Eine Liste erzeugen:
kursteilnehmer <- list(Maenner = c("Simon", "Peter", "usw."),
Frauen = c("Daniela", "Johanna"))
kursteilnehmer
# Zuriff auf Elemente der Liste
kursteilnehmer$Maenner
kursteilnehmer[1]
kursteilnehmer[[1]]
kursteilnehmer["Maenner"]
kursteilnehmer[["Maenner"]]
# was ist der Unterschied?
class(kursteilnehmer[["Maenner"]])
class(kursteilnehmer["Maenner"])
# d.h. wenn man mit den Elementen aus einem Listenelement arbeiten will,
# braucht es doppelte Klammern oder Zugriff über "$"
length(kursteilnehmer)
kursteilnehmer$Frauen[2]
kursteilnehmer[[2]][2]
data.frame(): erzeugt einen data frameas.data.frame(): konvertiert in einen data frameorder(): sortiert Datensummary() und str(): Überblick über data
frameshead() and tail(): erste/letzte Zeilen
inspizierennames(): zeigt Spaltennamenobject$var1: Greift direkt auf die Spalte
var1 im data frame object zuna.omit(): Zeilenweise Ausschluss von fehlenden Werten,
d.h. Zeilen die mindestens 1 Missing beinhalten# fertige Daten sind oft data frames:
titanic <- read.dta("http://www.stata-press.com/data/kkd/titanic2.dta")
is.data.frame(titanic)
# man kann sich auch leicht selber einen bauen
obst <- c("Apfel", "Apfel", "Birne")
gemuese <- c("Tomate", "Karotte", "Karotte")
id <- 1:3
df <- data.frame(id, obst, gemuese)
df
# Ansteuern von Zeilen und Spaltenpositionen
df$obst
df[, "obst"]
df[3, "gemuese"]
df[3, 3]
# d.h. es gibt wie bei Vektoren diverse Möglichkeiten: Position, Name ($ oder "") oder Bedingung
# Spalte hinzufügen
df$drinks <- c("Milch", "Cola", "Bier")
df
# Spalte löschen
df$gemuese <- NULL
df
swiss.
Verschaffen Sie sich ggf. einen Überblick mit ?swiss.Education und
Catholic ausgeben, jedoch nur für Fälle, deren
Kindersterblichkeit zwischen 20 und 22 liegt.swiss[10:12, ]
swiss[swiss$Infant.Mortality > 20 & swiss$Infant.Mortality < 22, c("Education", "Catholic")]
for(i in Sequenz){anweisungen}i ist die Zählervariable/PlatzhalterSequenz ist ein Vektor von Werten (muss nicht zwingend
ein Zahlenvektor sein)iwhile(bedingung==TRUE){anweisungen}anweisungen werden wiederholt ausgeführt, bis die
Bedingung FALSE wird, dann wird die Schleife verlassenif (bedingung) {anweisung1} else {anweisung2}argument1 gleich TRUE dann tue
anweisung1, wenn nicht dann anweisung2if ist ifelse vektorisiert und
eignet sich dadurch für bedingte Wertzuweisungen: ```variable <-
ifelse(bedingung, wert1, wert2)apply, lapply, sapply, sind
oft sinnvollere Alternativen zu Schleifen# For Schleife
for (x in 1:10) {
print(sqrt(x))
}
# aber besser:
sqrt(1:10) # da Funktionen in R i.d.R. sowieso vektorisiert arbeiten
# Schleifen machen aber Sinn, wenn es Abhängigkeiten zwischen den Durchläufen gibt:
x <- 0
for(i in 1:10) {
x <- x+i
print(x)
}
# x wird immer weiter inkrementiert
# mit ein bisschen Überlegen gibt es aber auch hier eine Lösung die performant ist und vektorisiert arbeitet:
cumsum(1:10)
# es braucht also schon etwas kompliziertere Abhängigkeiten
# Sequenzen in For-Schleifen können auch Character sein:
namen <- c("Alfred", "Jakob", "Peter")
for (name in namen) {
print(paste("Hallo", name))
}
# einfacher aber:
paste("Hallo", namen) # weil vektorisiert
#sinnvolleres Beispiel:
for (dataset in c("data1.csv", "data2.csv", "data3.csv")) {
read.csv(pfad/dataset)
# Anweisungen,
# z.B. Datenbereinigung, Appending (rbind), Modellschätzungen, etc.
}
# das Beispiel könnte aber auch mit Hilfer einer selbstgeschriebenen Funktion gut gelöst werden (siehe nächstes Kapitel)
# Durch Spalten loopen
for (column in 2:6) { # this loop runs through 2 to 6
print(names(swiss)[column])
print(mean(swiss[, column]))
}
# aber wieder geht es einfacher und schneller:
colMeans(swiss[, 2:6]) # oder
apply(swiss[, 2:6], 2, mean)
# die 2 verweist auf "spaltenweise" (1 wäre zeilenweise).
# D.h. für jede Spalte der Daten wird mean() angewendet
# While Schleife
x <- 0 # Startbedingung sollte gelten
while(x < 13) {
x <- x+1 # inkrementieren, da sonst die Bedingung für immer gilt -> Endlosschleife
print(x)
}
# wird wiederholt solange x<13==TRUE
# sicherstellen, dass irgendwann das Kriterium FALSE wird!
# Beispiel für eine sinnvole while-Schleife: Abfrage einer Web-Ressource, die nicht immer erreichbar ist. while(keinen erfolg) try(ressource abfragen)
# if-Beispiel:
# Daten einlesen aus einer Liste von Files
setwd("C:/path/to/some/excel/files")
myfiles <- list.files()
# manche sind aber nun xls, und andere xlsx:
library(tools)
for(file in myfiles) {
if(file_ext == "xls") {
daten <- read.xls(file)
}
if(file_ext == "xlsx") {
daten <- read.xlsx(file)
}
}
# if prüft immer nur genau eine Bedinung.
# es unterscheidet sich dadurch vom Kommando ifelse, das vektorisiert arbeitet
# ifelse Beispiel:
a<- sample(1:100, 10)
b<-ifelse(a < 50, "Nicht bestanden", "Bestanden")
b
# ifelse prüft einen Vektor von Bedingungen. Naheliegenderweise ist so
# ein Konstrukt also auch gut zur Datenaufbereitung geeignet.
# obiges Beispiel ist identisch mit aber einfacher als:
b[a < 50] <- "Nicht bestanden"
b[a >= 50] <- "Bestanden"
b
rnorm(100)). Berechnen Sie innerhalb der Schleife
Mittelwert und Standardabweichung dieser 100 Werte
(mean() und sd()) und geben Sie diese aus
(print).tapply an. Versuchen Sie,
die vorherige Aufgabe damit zu lösen. Erzeugen Sie hierfür zwei
Vektoren. Der erste soll alle 10*100 = 1000 Zufallszahlen beinhalten,
der zweite die Samplezugehörigkeit (erste hundert, zweite hundert,
usw.), also z.B. ein Vektor der Länge 1000 mit 100 1ern, 100 2ern, …
Diese zwei Vektoren können dann an tapply “gefüttert”
werden.# 1.
for (i in 1:10) {
x <- rnorm(100)
mittel <- mean(x)
std <- sd(x)
print(paste("Mittelwert:", mittel, "Standardabweichung:", std))
}
# Alternative mit tapply:
x <- rnorm(1000)
sample <- rep(1:10, each=100)
tapply(x, sample, mean)
tapply(x, sample, sd)
+, - oder %in%)meinefunktion <- funktion(argumente) { aktionen }:
Definiert eine Funktionprint() der Inhalt der Funktion ausgeworfenprint(),
plot() oder summary())print("Hallo")
print
lm
# Beispiel: Definieren einer Funktion
wurzel <- function(x) {
x^0.5
}
wurzel(4)
# Namen eingeben um Inhalt zu sehen
wurzel
x
und w, die einen gewichteten Mittelwert berechnet
(x gewichtet mit w) und zurückgibt: \(\frac{\sum_{i=1}^{N}w_{i}x_{i}}{\sum_{i=1}^{N}w_{i}}\).
Testen Sie ihre Funktion mit x <- 1:5 und
w <- c(2,4,5,6,7).tapply-Aufgabe. Wie könnte
eine Lösung auf Basis einer selbstgeschriebenen Funktion aussehen
(konzeptionell, lauffähiger Code nicht nötig)?# 1.
plus2hoch2 <- function(x) {
(x+2)^2
}
plus2hoch2(1:10)
# 2.
w <- c(2, 4, 5, 6, 7)
x <- 1:5
gew.mittelwert <- function(x, w) sum(w * x) / sum(w)
gew.mittelwert(x, w)
# 3.
drawSample <- function(i) {
x <- rnorm(100)
data.frame(mean.val = mean(x), std = sd(x))
}
do.call(rbind, lapply(1:10, drawSample))
# noch kompakter mit Package data.table (und schneller bei grossen Datenmengen!):
library(data.table)
rbindlist(lapply(1:10, drawSample))
magrittr
grosser Beliebtheit. Der Grund dafür ist, dass das Package
Shortcut-Funktionen enthält, die das Programmieren und das Lesen von
Code deutlich intuitiver machen.daten <- rnorm(100)
# normale Schreibweise
mean(daten)
# mit Piping
daten |> mean()
# auf diese Art lassen sich aber mehrere Schritte verketten, z.B.:
daten <- c("1", "2", "3") # Strings statt Zahlen
daten |>
as.numeric() |> # umwandeln
plus2hoch2()
# später mehr zum Thema Piping!
mean(x): Meansd(x): Standardabweichungvar(x): Varianzmedian(x): Medianmin(x): Minimummax(x): Maximumcov(): Kovarianzcor(): Korrelationcor(x, y, method = "spearman"): Rangkorrelation?cor: mehr Infos im Helpfilechisq.test(): Chi-Quadrat-Testt.test(): t-Testtable(x): eindimensionale Kontingenztabelletable(x, y): zweidimensionale Kontingenztabelleprop.table(table(x)): relative Häufigkeit# zwei fiktive Vektoren erstellen
auto <- factor(c(1, 0, 0, 0, 0,
1, 0, 1, 0, 0,
1, 1, 0, 0, 1,
1, 1, 0, 0, 0),
labels = c("kein Auto", "Auto"))
geschlecht <- c(rep("Frau", 10),
rep("Mann", 10))
# Tabelle
table(geschlecht)
table(geschlecht, auto)
# Chi2-Test
chisq.test(table(geschlecht, auto))
# Tabelle in Prozent
100*prop.table(table(geschlecht))
100*prop.table(table(geschlecht, auto))
round(100*prop.table(table(geschlecht, auto)), 2) # gerundete Werte
round(100*prop.table(table(geschlecht, auto), margin=1), 2) # margin=1 berechnet Zeilenprozente, margin=2 Spaltenprozente
# oder mit Piping
table(geschlecht, auto) |>
prop.table(margin = 1) |>
round(2) * 100
# Lagemasse
## Mean
mean(x)
## Median
median(x)
sort(x)
## gibt es verkürzt über die generische Funktion summary
summary(x)
# Streuung, z.B.
sd(x)
var(x)
# Korrelation zwischen Vektoren
cor(x, y)
cor(x, y, method = "spearman") # Rangkorrelation
cov(x, y)
# Mittelwertvergleich
t.test(x, y)
foreign.
Laden Sie die Daten über den Untergang der Titanic mit folgendem Befehl:
titanic <- read.dta("http://www.stata-press.com/data/kkd/titanic2.dta")
und machen Sie sich ein bisschen mit den Daten vertraut (z.B.
head() oder summary()).class
und survived.### 1.
library(foreign)
titanic <- read.dta("http://www.stata-press.com/data/kkd/titanic2.dta")
head(titanic)
summary(titanic)
### 2.
# survived aus Konvention auf die Y-Achse, class auf die X-Achse (Y ist die abhängige, X die erklärende Variable)
table(titanic$survived, titanic$class) # Faktor crew ist hier nicht sauber gelabelt
### 3.
mean(titanic$age2[titanic$survived == "yes"])
mean(titanic$age2[titanic$survived == "no"])
# alternativ mit tapply
tapply(titanic$age2, titanic$survived, mean)
# ja sie unterscheiden sich um ca. 5 Jahre. Die Verstorbenen sind älter. Man könnte an der Stelle auch noch einen Signifikanztest machen:
t.test(titanic$age2 ~ titanic$survived)
# dieser zeigt, dass die 5 Jahre nicht zufällig sondern systematisch sind.
tolower() /
toupper()trimws() aus Base oder str_trim()
aus stringrsubstr() aus Base oder
str_extract() aus stringrstrsplit() aus Base oder
str_split() aus stringrpastesub und gsub aus Base
oder str_replace(), str_extract(),
str_sub() aus dem Package stringr# Gross-/Kleinschreibung
"trump" == "trump"
"Trump" == "trump"
tolower("Trump") == tolower("trump")
# Trimmen
s <- " Hello! "
trimws(s)
trimws(s, "right")
trimws(s, "left")
# Extrahieren
s <- "I will build a great, great wall on our southern border, and I will have Mexico pay for that wall. Mark my words."
substr(s, 3, 6)
substr(s, 74, 79)
# Splitten
s <- c("To be blunt, people would vote for me. They just would.")
strsplit(s, ".", fixed = TRUE)
# Joinen
paste("one", "two", "three")
paste("one", "two", "three", sep = "-")
# paste erwartet normalerweise eine Reihe von ein-elementigen Inputs, nicht Vektoren:
s <- c("one", "two", "three")
paste(s)
# wir können paste() aber sagen es soll diesen Vektor "collapsen"
s <- c("one", "two", "three")
paste(s, collapse = " + ")
paste(s, collapse = "")
# Suchen und ersetzen
sub('Trump', 'Donald', 'Trump became president. Trump makes everyone great again.')
gsub('Trump', 'Donald', 'Trump became president. Trump makes everyone great again.')
Verwenden Sie nun folgenden String:
s <- c("Kontostand: 100 EUR", "Kontostand: 150 EUR", "Kontostand: 185 EUR")
# 1.
paste("objekt", 1:100, sep = "_")
# 2.
s <- c("Kontostand: 100 EUR", "Kontostand: 150 EUR", "Kontostand: 185 EUR")
sub("EUR", "CHF", s)
# 3.
strsplit(s, ": ", fixed = TRUE)
# um die gesplitteten Vektoren einzeln wieder einzusammeln kann man bspw. sapply() verwenden:
liste <- strsplit(s, ": ", fixed = TRUE)
sapply(liste, "[", 2)
# Man könnte so auch direkt an den numerischen Wert kommen, z.B.
liste <- strsplit(s, " ", fixed = TRUE)
as.numeric(sapply(liste, "[", 2))
# andere Ansätze könnten natürlich auch sein:
sub("Kontostand: ", "", s) # ersetze den Teil den man nicht will mit "nichts"
substr(s, 13, 100) # nimm den Teil ab Character Nr. 13
# dafür gibt es aber effiztientere Lösungen, z.B. mit regulären Ausdrücken
Einige R-Funktionen erlauben die Verwendung von sogenannten regulären Ausdrucken, beispielsweise sub/gsub
Reguläre Ausdrücke erlauben “wildcards” und andere Elemente um ein Suchmuster zu definieren
Eine Auswahl von Metacharacters:
.: matcht einen beliebigen Character.[a-z]: lowercase a bis z[A-Z]: uppercase A bis Z[a-zA-Z]: lower a bis z und
uppercase A bis Z[0-9]: Ziffern|: Alternation / “oder”*: matcht mindestens 0 mal.+: matcht mindestens 1 mal.?: matcht höchstens 1 mal.{n}: matcht genau n mal.{n,}: matcht mindestens n mal.{n,m}: matcht zwischen n und m mal.^: matcht am Anfang des Strings.$: matcht am Ende des Strings.()Es gibt viele weitere Metacharacter, siehe z.B.: https://stat.ethz.ch/R-manual/R-devel/library/base/html/regex.html
Siehe auch https://emailregex.com/ für ein ausführliches Regex Beispiel
# wildcards
s <- c("Trump", "Trrump", "Trrrump", "Tump")
grepl("T.ump", s)
grepl("Tr+ump", s)
grepl("Tr{1,2}ump", s)
grepl("Tr{2,}ump", s)
grepl("Tr*ump", s)
grepl("T.*ump", s)
grepl("T[a-z]ump", c("Tump", "Trump", "Trrump", "Tdump"))
# ersetzen
gsub("T.*ump", "Donald", c("Tump", "Trump", "Trrump", "Tdump"))
# wir können Teile des Strings als Gruppe erfassen um diese zu verwenden
# z.B.:
gsub("(Donald) (Trump) (.*)", "\\2 \\1 \\3", "Donald Trump is president.")
# gsub erinnert sich hier an die drei Gruppen und gibt sie in der Reihenfolge 2., 1., 3. zurück
# Das Package stringr hat zudem einige convenience Funktionen die auf sub/gsub/grep/grepl aufbauen
library(stringr)
library(magrittr)
# CHF Beträge aus Text extrahieren ({1,5} -> min. 1 Ziffer, max. 5 Ziffern)
str_extract(c("Ein Brot kostet 4 CHF.", "Ein Auto kostet 50000 CHF."), "[0-9]{1,5} CHF")
# Beispiel für die Verwendung eines oder-Operators, bei unterschiedlichen Schreibweisen:
str_extract(c("Ein Brot kostet 4 Franken.", "Ein Auto kostet 50000 CHF."), "[0-9]{1,5} (CHF|Franken)")
# Strings matchen aber nur Teile davon zurückgeben:
str_match(c("Ein Brot kostet 4 CHF.", "Ein Auto kostet 50000 CHF."), "([0-9]{1,5}) CHF")
# die () definieren den Teil der zusätzlich zurückgegeben wird.
# Beispiel wie man an die zweite Spalte kommt:
str_match(c("Ein Brot kostet 4 CHF.", "Ein Auto kostet 50000 CHF."), "([0-9]{1,5}) CHF")[, 2] |> as.numeric()
Aus folgendem String
s <- "George Bush, born July 6, 1946, is a former American president. His full name is George Walker Bush."
filter(): wählt ein Subset von Zeilen (siehe auch
slice())arrange(): sortiertselect(): wählt Spaltenmutate(): erzeugt neue Spaltensummarise(): aggregiert (collapses) Daten zu einzelnen
Datenpunktengroup_by(): Definiert Untergruppen in den Daten, damit
o.g. Funktionen separat pro Gruppe angewandt werden können|> wodurch der Code deutlich besser lesbar und kompakter
wird. (Base-R Pipe |>)library(foreign)
titanic <- read.dta("http://www.stata-press.com/data/kkd/titanic2.dta")
# install.packages("dplyr")
library(dplyr)
filter(titanic, class == "1st class", age2 < 18)
# konventionell wäre das komplizierter:
titanic[titanic$class == "1st class" & titanic$age2 < 18, ]
# zusätzlich Spalten selektieren:
titanic |>
filter(class == "1st class", age2 < 18) |>
select(sex, age2, survived)
# neue Variable "child" bauen
titanic |>
mutate(child = age2 < 18) |>
head()
# Auszählen wer gestorben ist nach Geschlecht und Kind (ja/nein)
titanic |>
mutate(child = ifelse(age2 < 18, "yes", "no")) |>
group_by(sex, child, survived) |>
summarise(n=n()) |>
arrange(sex, child, survived)
# Für Datenaufbereitung ebenfalls oft hilfreich ist case_when
titanic |>
mutate(age_group = case_when(
age2 < 5 ~ "Kleinkind",
age2 >= 5 & age2 < 12 ~ "Kind",
age2 >= 12 & age2 < 18 ~ "Jugendlicher",
age2 >= 18 & age2 < 30 ~ "Junger Erwachsener",
age2 >= 30 & age2 < 50 ~ "Erwachsener mittleren Alters",
age2 >= 50 & age2 < 70 ~ "Älterer Erwachsener",
age2 >= 70 ~ "Senior",
TRUE ~ "Unbekannt" # Für den Fall, dass age2 NA ist oder nicht in die anderen Kategorien fällt
)) |>
group_by(age_group, survived) |> summarise(count = n())
read.dta() aus dem Package
foreign. Es handelt sich um Daten aus einer
Bevölkerungsumfrage. R Version 3.4.x hat offenbar ein bislang nicht
gefixtes Problem im Zusammenspiel mit read.dta(). Bei einem
“factor level duplicated” Error bitte die Option
convert.factors=FALSE verwenden.select direkt neue Spaltennamen vergeben:
select(neuername = altername).filter()).group_by(),
summarise(), mean()).dplyr, ist aber stärker auf Performance und
Funktionalität ausgerichtet während dplyr mehr die
Lesbarkeit des Codes für den Menschen im Fokus hatdata.table bringt einen eigenen Objekttyp
data.table mit, der mit allen data.frame
Operationen abwärtskompatibel ist aber wesentliche Neuerungen
beinhaltet:DT[i,j,by]: “Nimm das DT
an Zeilen i und berechne j, gruppiert nach
by”.N: Anzahl Zeilen im Subset,.BY: Platzhalter für den aktuellen Wert der
Gruppierungsvariable vom Typ list. z.b. Nützlich, wenn man
bei nach numerischen Werten gruppierten Variablenberechnungen auf den
Gruppenwert zurückgreifen will. Oder wenn man bei nach kategorisch
gruppierten Berechnungen auf den Namen zugreifen will (z.b. um zu ploten),.SD: Enthält die Daten des Subsets das gerade
verarbeitet wird (alle Spalten ausser by). Mit
.SDcols spezifizieren sie, welche Spalten zu
.SD gehören sollen. Nützlich bei der Anwendung
einer Funktion auf mehrere, aber nicht alle Spalten eines
DT..I: Index eines Falls innerhalb des Subsets,.GRP: Group-Counterlibrary(foreign)
titanic <- read.dta("http://www.stata-press.com/data/kkd/titanic2.dta")
library(data.table)
titanic <- as.data.table(titanic) # als data.table definieren
titanic # man beachte die andere Darstellung. data.table zeigt
# sicherheitshalber niemals alle Zeilen auf der Konsole
#Filtern ####
titanic[class=="1st class" & age2 < 18]
#Selektieren ####
titanic[,list(sex,class)]#shortcut für list(sex,class): .(sex,class)
titanic[,c("sex","class")]#data.table versteht c("name") als Liste
#Neue Variable berechnen ####
titanic[,child := ifelse(age2 < 18, "yes", "no")]# := um Variable by reference hinzuzufügen
titanic[,":="(child = ifelse(age2 < 18, "yes", "no"),
oldperson = ifelse(age2 > 65, "yes", "no"))]#mehrere Variablen gleichzeitig
#Gruppierte Berechnungen ####
titanic[,Durchschnittsalter.Klasse:=mean(age2),by=.(class)] #Neue Spalte
titanic[,.(Durchschnittsalter.Klasse=mean(age2)),by=.(class)] #Aggregation
#Anwendung (eigener) Funktion auf eine Auswahl von Spalten####
# Z.b. Modusfunktion
Mode <- function(x) {
val<-max(table(x))
names(table(x))[table(x)==val]
}
#.SDcols wird als viertes Argument spezifiziert: definiert die zu bearbeitenden Spalten
vars <- c("survived","sex")
titanic[,lapply(.SD, Mode),,.SDcols=vars]
#Wenn man das Resultat in einer Variable behalten möchte: '(namensvektor):=' davor
varsnew <- paste0(vars,".Modus")
titanic[,(varsnew):=lapply(.SD, Mode),,.SDcols=vars]
#Chaining#####
titanic[, child := ifelse(age2 < 18, "yes", "no")][ # chaining: Äquivalent zum piping
,child.died := ifelse(child == "yes" &
survived == "no",
"yes", "no")]
#Tipps und Kniffs######
# Vorsicht beim Selektieren######
a<-c("sex","class")
titanic[,a] # Charaktervektoren können nicht als Objekte übergeben werden:
# data.table sucht immer nach Namen innerhalb des data.table, wenn es sie nicht findet
titanic[,..a] # Wenn Sie '..' vor einen Charactervector setzen funktioniert es.
titanic[,a,with=F] # Besser with=F (nicht within evaluieren).
# Dies ist nützlich, wenn sie nach Variablen mit bestimmtem Mustern suchen
# (z.b. mit grepl("s",names(titanic))). So versteht data.table auch logische
# Vektoren (oder Spaltennummern) als Selektionskriterien
titanic[,grepl("s",names(titanic)),with=F]
# Vorsicht beim Kopieren von Objekten#####
#Wenn man einen data.table kopiert, copy() verwenden, sonst werden neue Variablen auf das ursprüngliche Objekt geschrieben
titanic.copy <- copy(titanic)
#Beispiel für .I#####
a <- data.table(a = c(0,2,3,1,6,7,8))
a[, b := a[.I+1]]#Alternative zu lead() in dplyr
a[, c := c(NA, a[.I-1])]#Alternative zu lag() in dplyr
#neu auch
a[, b := shift(a, n = 1, type = "lag")]
a[, c := shift(a, n = 1, type = "lead")]
load(url("http://www.farys.org/daten/ESS.RDATA"))..N und chainen sie die Bedingung [N==max(N)]
um die Anzahl und den Namen des Landes auszugeben))?mean(happy[year==2008],na.rm=T))?gincdiff
1=agree strongly,5=disagree strongly). Tipp: verwenden Sie dabei die
Modusfunktion aus dem Beispiel oben.# Beispiel data.frame:
data <- data.frame(A=1:10, B=2:11, C=3:12, D=c("a","b"))
# Beispiel data.table:
library(data.table)
data <- data.table(A=1:10, B=2:11, C=3:12, D=c("a","b"))
| Base-R | dplyr | data.table | |
|---|---|---|---|
Auswahl von Zeilen nach Kriterium B==2 |
data <- data[data$B==2, ] |
data <- filter(data, B==2) oderdata <- data |> filter(B==2) |
data <- data[B==2] oderdata <- data[B==2, ] |
| Auswahl von Spalten B und C | data <- data[c("B", "C")] oderdata <- data[, 2:3] oderdata <- data[, c("B", "C")] odervars <- c("B", "C")
data <- data[, vars] |
data <- select(data, B, C) oderdata <- data |> select(B,C) |
data <- data[, .(B, C)] oderdata <- data[, c("B", "C")] odervars <- c("B", "C") data <- data[, ..vars] |
| Erstellen neuer Variablen E (A/B) | data$E <- data$A/data$B |
data$E <- mutate(data, A/B) oderdata <- data |> mutate(E = A/B) |
data[,E := A/B] oderdata[, E:=A/B, ] oderdata[, ":="(E=A/B)] |
| Erstellen neuer, gruppierter Variablen E (Mittelwert A gruppiert nach D) | data$E <- ave(data$A,data$D,FUN=mean) |
data <- group_by(data,D) data <- mutate(data,E = mean(A)) oderdata <- data |> group_by(D) |>
mutate(E = mean(A)) |
data[,E := mean(A),D] oderdata[,E := mean(A),by=D] oderdata[, ":="(E=mean(A)),by=D] |
| Aggregieren Mittelwert von A und B | sapply(data[c("A", "B")], mean) |
data |>summarise_at(vars=vars(A, B), .funs = mean) |
data[, .(meanB = mean(B), meanA = mean(A))]
oderdata[, lapply(.SD, mean), , .SDcols = c("A", "B")] |
| Nach B gruppiert Aggregieren durch Mittelwert von A und C | aggregate(data$A,data$B,mean)
aggregate(data$C,data$B, mean) |
data <- group_by(data,B) data <- summarise_at(data,vars = vars(A,C), .funs = mean)
oderdata |>group_by(B) |>summarise_at(vars = vars(A,C), .funs = mean) |
data[, .(meanC = mean(C), meanD=mean(A)), B]
oderdata[, lapply(.SD, mean),B, .SDcols=c("C", "A")] |
melt()
und dcast() aus dplyr resp.
data.table verwendengather und
spread aus tidyr sowie
pivot_longer und pivot_wider ebenfalls aus
tidyrtidyr Packages macht generell Sinn.
Dieses enthält eine Vielzahl von weiteren Paketen, die wir später
teilweise noch brauchen werden (z.B. dplyr)billboard. Dieser
enthält Chartplatzierungen von Songs für 76 Wochen (jeweils in separaten
Spalten).pivot_longer
(Gegenstück ist pivot_wider) aus Package
tidyr.library(tidyr)
billboard
# reshapen von wide nach long:
billboard |>
pivot_longer(
cols = starts_with("wk"),
names_to = "week",
values_to = "rank",
values_drop_na = TRUE
)
# diese Darstellung macht mehr Sinn, wenn wir Auswertungen der Chartplatzierungen machen wollen.
# Ein bisschen Finetuning:
billboard |>
pivot_longer(
cols = starts_with("wk"),
names_to = "week",
names_prefix = "wk",
names_transform = as.integer,
values_to = "rank",
values_drop_na = TRUE,
)
pivot_longer kann aber noch vieles mehr, siehe https://tidyr.tidyverse.org/articles/pivot.html !us_rent_income
# Daten sind nicht tidy weil estimate und moe Informationen zu verschiedenen Attributen (income und rent) mischen!
us_rent_income |>
pivot_wider(
names_from = variable,
values_from = c(estimate, moe)
)
# beide Variablen werden aufgesplittet in Attribute bzgl income und rent.
age) und die
Zufriedenheit (c44) von 5 Personen über die Jahre 2012 bis
2014 enthalten. Sie sehen: es gibt für jedes Beobachtungsjahr und jede
Beobachtungsdimension (Alter und Zufriedenheit) eine Variable. Die
Information “Beobachtungsjahr” steckt implizit in den Variablennamen
obwohl das Jahr eine eigene Variable sein sollte.Das fertige Ergebnis soll so aussehen:
> data.wide
# A tibble: 15 x 4
idpers jahr zufriedenheit alter
<int> <dbl> <int> <int>
1 1 2012 5 45
2 1 2013 5 46
3 1 2014 6 47
4 2 2012 6 57
5 2 2013 9 58
6 2 2014 9 59
Tipps:
pivot_longer).ifelse plus
grepl() verwenden oder auch andere Tools aus der
Regex-Werkzeugkiste.substr() oder anderen
Werkzeugen.pivot_wider).# Wetterdaten
weather <- read.table("https://raw.githubusercontent.com/justmarkham/tidy-data/master/data/weather.txt", header=TRUE)
head(weather) # hier sind Variablen in Zeilen und Spalten
# Daten reshapen von wide nach long
library(tidyr)
weather1 <- pivot_longer(weather, cols = d1:d31, values_drop_na = TRUE)
head(weather1)
# saubere Spalte für "day"
library(stringr) # für str_replace(), str_sub()
weather1$day <- as.integer(str_replace(weather1$name, "d", ""))
# die krude Spalte "variable" brauchen wir nicht. Löschen durch Zuweisen von NULL.
weather1$name <- NULL
# die Spalte element beherbergt zwei unterschiedliche Variablen tmin und tmax.
# Diese sollen in zwei Spalten:
weather1$element <- tolower(weather1$element) # Kleinbuchstaben
weather.tidy <- pivot_wider(weather1, names_from = element)
head(weather.tidy)
# das Datum laesst sich zudem in einer Spalte darstellen als echtes Datum:
weather.tidy$date <- as.Date(paste(weather.tidy$year,
weather.tidy$month,
weather.tidy$day, sep="-"))
weather.tidy[, c("year", "month", "day")] <- NULL
head(weather.tidy)
# ABER: was für ein Mess mit weather1 weather.tidy etc. Warum nicht Piping verwenden?
weather |>
pivot_longer(cols = d1:d31, values_drop_na = TRUE) |>
mutate(day = as.integer(str_replace(name, "d", "")),
element = tolower(element)) |>
select(-name) |>
pivot_wider(names_from = element) |>
mutate(date = as.Date(paste(year, month, day, sep = "-"))) |>
select(-year, -month, -day)
merge() (siehe Quick
R)dplyr sind allerdings
schnellerinner_join(x, y, by = NULL, copy = FALSE, ...) # behält alle Fälle die zwischen den Daten x und y überlappen; häufigster Fallleft_join(x, y, by = NULL, copy = FALSE, ...) # behält alle Fälle von x. Missing falls in y nicht vorhandensemi_join(x, y, by = NULL, copy = FALSE, ...) # behält alle Fälle von x und behält nur die Spalten von xanti_join(x, y, by = NULL, copy = FALSE, ...) # alle Fälle, für die es keinen Match gibt
x = Datensatz 1, y = Datensatz 2by = “Matchingvariable (z.B. ID, Name, Countrycodes,
etc.)”?join: Das Hilfe File ist sehr zu empfehlen!rbind() um data frames mit
gleichen Spalten zeilenweise zusammenzuklebenname <- c("Rudi","Simon","Daniela","Viktor")
geschlecht <- c("Mann", "Mann", "Frau","Mann")
daten1 <- data.frame(name, geschlecht)
name <- c("Johanna","Rudi","Simon","Daniela")
alter <- c(33,32,38,45)
daten2 <- data.frame(name, alter)
daten1
daten2
# klassisch
merge(daten1, daten2, by="name") # entspricht inner_join)
# oder über match()
# daten1$alter <- daten2$alter[match(daten1$name,daten2$name)] # entspricht left_join
# mit dplyr
inner_join(daten1,daten2,by="name")
left_join(daten1,daten2,by="name")
right_join(daten1,daten2,by="name")
full_join(daten1,daten2,by="name")
# mit data.table
# mit data.table
name <- c("Rudi","Simon","Daniela","Viktor")
geschlecht <- c("Mann", "Mann", "Frau","Mann")
daten1 <- data.table(name, geschlecht)
name <- c("Johanna","Rudi","Simon","Daniela")
alter <- c(33,32,38,45)
daten2 <- data.table(name, alter)
setkey(daten1, name)
setkey(daten2, name)
#Inner join
daten1[daten2,nomatch=0]
#Right-join
daten1[daten2]
#Left-join
daten2[daten1]
#Full outer-join
unique_keys <- unique(c(daten1[, name], daten2[, name]))
daten1[daten2[.(unique_keys)]]
#data.table kennt auch eine verbesserte Version von merge
merge(daten1, daten2, all = T)
# Rekodieren mit left_join() plus look-up-Tabellen kann ebenfalls sehr nützlich sein, hier ein Beispiel:
# Mitarbeiterdaten mit Abteilungsnummern
employee_data <- data.frame(
employee_id = c(101, 102, 103, 104),
dept_id = c(1, 2, 1, 3)
)
# Look-Up-Tabelle mit Abteilungsnamen
department_lookup <- data.frame(
dept_id = c(1, 2, 3),
dept_name = c("Sales", "HR", "IT")
)
# Zuordnung der Abteilungsnummern zu Abteilungsnamen
employee_data_recode <- employee_data |>
left_join(department_lookup, by = "dept_id")
employee_data_recode
# rbind / zeilenweise verknüpfen von Daten
name <- c("Rudi","Simon","Daniela","Viktor")
geschlecht <- c("Mann", "Mann", "Frau","Mann")
daten1 <- data.frame(name, geschlecht)
name <- c("Johanna","Ralf")
geschlecht <- c("Frau","Mann")
daten2 <- data.frame(name, geschlecht)
rbind(daten1,daten2)
# rbind() ist sehr langsam. Besser ist rbindlist() aus data.table
rbindlist(list(daten1,daten2))
read.table(): Liest Textformate
(.txt, .csv)
read.table("meinedaten.csv", header = TRUE, sep = ",", dec = ".")
header = TRUE: wenn die erste Zeile Spaltennamen
enthältsep = ",": Textseparatordec = ".": Dezimalzeichen (Default ist
.)?read.table.csv oder .txt) ist der kleinste
Gemeinsame Nenner zwischen Softwarepaketen und empfiehlt sich, wenn mit
vielen verschiedenen Programmen gearbeitet wirdfread() aus data.table (Geschwindigkeit ca.
Faktor 10)foreign: kann enbtsprechende Formate lesen, z.B.
(.dta, .por)haven: neueres Package, das versucht
foreign abzulösenread_excel() aus Package readxl: baut auf
C. Ebenfalls sehr unproblematisch und versteht exceltypische
Sheet-/Zellennotation.read.xlsx() aus Package openxlsx (keine
Abhängigkeiten jenseits von R, liest aber keine alten .xls Files, nur
xlsx)read.xls() aus Package gdata, braucht
allerdings eine Perl Installationread.xlsx() und read.xlsx2() aus Package
xlsx; kann auch bestimmte Zeilen-/Spaltenbereiche auslesen
statt komplette Sheets, benötigt jedoch Java; ggf. Java installieren und
sicherstellen, dass R und Java dieselbe Architektur (32 oder 64bit)
haben. Für Probleme bei Mac-Usern: http://stackoverflow.com/questions/28796108/r-rstudio-yosemite-and-javawrite.table(swiss, "swiss.txt", sep=",") speichert das
Objekt swiss kommasepariert nach
swiss.txt?write.table für weitere Optionen (z.B.
Zeilennamen, Separator, Dezimalzeichen)read_fst() und write_fst() aus Package
fst: ist ein Format, in dem sich grosse Datenmengen
effizient abspeichern und daraus einlesen lassen.metadata_fst() aus demselben Package: gibt
Meta-Informationen (Anzahl Zeilen und Spalten, sowie Namen und Formate
der Variablen) von FST-Dateien wieder, ohne dass diese zuvor in R
geladen werden müssen.download.file(..., mode="wb") (write binary mode). Hinweis
2: Achten Sie auf das File-Encoding der Daten.read_excel
trimmt diese automatisch weg, read.xlsx jedoch nicht).
Säubern Sie die Daten, damit die Kantonsnamen mit denen aus 1.
korrespondiert.arrange() aus dplyr).write.table()).Tipps: Schauen Sie in die Hilfe von read_excel().
Hilfreiche Argumente, die hier spezifiziert werden können sind z.B.
range, sheet und col_names.
RODBC, RJDBC,
RMySQL, RPostgreSQL, DBIlibrary(DBI)
library(RMySQL)
# Verbindung definieren
con <- dbConnect(MySQL(),
username = "dataviz",
password = "CASdataviz2016",
host = "db4free.net",
port = 3306,
dbname = "cas_dataviz"
)
# Get-Anfrage schicken
dbGetQuery(con, "show databases")
# neue Tabelle erstellen
dbSendQuery(con, "CREATE TABLE anmeldungen (id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
vorname VARCHAR(30) NOT NULL,
nachname VARCHAR(30) NOT NULL,
email VARCHAR(50),
reg_date TIMESTAMP)"
)
# schauen welche Tabellen es gibt
dbGetQuery(con, "show tables")
# Tables_in_cas_dataviz
# 1 anmeldungen
# Alle Zeilen der Tabelle löschen
# dbSendQuery(con, "DELETE from anmeldungen")
dbSendQuery(con, "INSERT into anmeldungen (vorname, nachname) values ('Rudi', 'Farys')")
dbSendQuery(con, "INSERT into anmeldungen (vorname, nachname,email) values ('Test', 'Test','test@test.de')")
# einfache Abfrage
daten <- data.frame(dbGetQuery(con, "SELECT * FROM anmeldungen"))
daten
dbDisconnect(con)
rvest ist aktuell die beste Option. Es gibt
andere aber rvest ist am besten (z.B. bzgl. Handling von
verschlüsselten Verbindungen und File-encodings; es beinhaltet auch
einige convenience functions für css/xpath)download.file() ist trotzdem häufig
nützlichlibrary(rvest)
raw <- read_html("https://en.wikipedia.org/wiki/Cantons_of_Switzerland")
table <- html_table(raw, header = TRUE)[[3]]
head(table)
rvest, das eine Reihe von Funktionen
zum komfortablen Extrahieren von Elementen enthält, z.B.
html_table, html_node, html_attr
und andere.read_html() von xml2, auf dem
rvest aufbaut.library(rvest)
# Betrachten Sie folgende Websites
# https://www.transfermarkt.de/fill/profil/spieler/28003 and
# https://www.transfermarkt.de/fill/profil/spieler/401173
# Beachten Sie, wie es möglich ist, durch die Spielerprofile zu navigieren, wenn wir eine Liste von Spieler-IDs verwenden, z.B.
listofplayers <- c(28003, 401173)
for(playerid in listofplayers) {
url <- paste0("https://www.transfermarkt.de/fill/profil/spieler/", playerid)
raw <- read_html(url)
...
}
# oder noch performanter mit einer Funktion.
# nehmen wir an, dass wir Name und Marktwert des Spielers extrahieren wollen.
# Die Untersuchung des Marktwertelements zeigt, dass wir einen Anker (<a>) in einem <div> Container haben, der die Klasse "dataMarktwert" hat, die wahrscheinlich verwendbar ist. Der Text innerhalb von <a> ist das, was wir wollen:
raw <- read_html("https://www.transfermarkt.de/fill/profil/spieler/28003")
rawelement <- html_node(raw, ".data-header__market-value-wrapper") |>
html_text()
rawelement
# was wir dann noch säubern müssen. Z.B.:
library(stringr)
rawelement |>
str_extract("[0-9]+,[0-9]{2}") |>
str_replace(",", ".") |>
as.numeric()
# Name des Spielers:
# Es gibt viele Möglichkeiten, aber z.B. Quellcode anschauen und nach dem Spielernamen suchen. Wählen Sie einen geeigneten Eintrag aus, den Sie mit XPath gut greifen können. Z.B. das Titel-Tag:
raw |>
html_node("title") |>
html_text() |>
str_extract("^[^-]*") |> # nur den Teil vor "-"
trimws()
# ein anderer Weg über ein Attribut aus einem meta-Tag:
raw |>
html_node("meta[name = 'description']") |>
html_attr("content") |>
str_extract("^[^,]*")
# Das ganze können wir jetzt in eine Funktion stecken und damit zahlreiche Profile scrapen
getPlayerStats <- function(playerid) {
print(paste("Scraping player:", playerid))
url <- paste0("https://www.transfermarkt.de/fill/profil/spieler/", playerid)
raw <- read_html(url)
marketvalue <- raw |>
html_node(".data-header__market-value-wrapper") |>
html_text() |>
str_extract("[0-9]+,[0-9]{2}") |>
str_replace(",", ".") |>
as.numeric()
name <- raw |>
html_node("title") |>
html_text() |>
str_extract("^[^-]*") |>
trimws()
# Sys.sleep(10) # falls wir viele Scrapen wollen, ist es gut, eine Pause einzubauen, damit der Server nicht automatisiert blockt
data.frame("id" = playerid, "name" = name, "marketvalue" = marketvalue)
}
# Messi example
getPlayerStats(28003)
# now for a list of IDs
listofplayers <- c(28003, 401173)
do.call(rbind, lapply(listofplayers, getPlayerStats))
# How to get a list of players?
# e.g.:
swiss_team_page <- read_html("https://www.transfermarkt.com/schweiz/startseite/verein/3384")
# get all player IDs
swiss_players <- swiss_team_page |>
html_nodes("td.hauptlink a") |>
html_attr("href") |>
str_extract("[0-9]{4,8}") |>
unique()
# swiss_team <- do.call(rbind, lapply(swiss_players, getPlayerStats)) # -> machen wir besser nicht ohne vorher Pausen einzubauen
# Stand 10/2025
# id name marketvalue
# 1 257814 Gregor Kobel 40.0
# 2 365108 Dan Ndoye 40.0
# 3 334526 Denis Zakaria 30.0
# 4 284730 Manuel Akanji 22.0
# 5 927353 Johan Manzambi 15.0
# 6 111455 Granit Xhaka 12.0
# 7 237662 Breel Embolo 12.0
# 8 345886 Rubén Vargas 11.0
# 9 539798 Luca Jaquez 10.0
# 10 500763 Marvin Keller 9.0
# 11 192635 Nico Elvedi 8.0
# 12 990637 Zachary Athekame 8.0
# 13 635645 Aurèle Amenda 7.5
# 14 212723 Djibril Sow 7.5
# 15 507341 Fabian Rieder 6.0
# 16 148252 Remo Freuler 5.0
# 17 298603 Miro Muheim 4.0
# 18 280387 Vincent Sierro 4.0
# 19 345468 Andi Zeqiri 4.0
# 20 147051 Yvon Mvogo 3.0
# 21 449597 Isaac Schmidt 2.2
# 22 582987 Adrian Bajrami 2.0
# 23 86784 Ricardo Rodríguez 2.0
# 24 243856 Cedric Itten 1.5
# 25 168989 Silvan Widmer 1.2
RSelenium ist eine Lösung für einige dieser Probleme,
da man damit einen Browser-Benutzer simulieren kann. Auf diese Weise
können Sie z.B:
jsonlite.library(jsonlite)
# Mini Beispiel aus dem Package
json <-
'[
{"Name" : "Mario", "Age" : 32, "Occupation" : "Plumber"},
{"Name" : "Peach", "Age" : 21, "Occupation" : "Princess"},
{},
{"Name" : "Bowser", "Occupation" : "Koopa"}
]'
mydf <- fromJSON(json)
mydf
# editieren wir die Daten ein bisschen
mydf$Ranking <- c(3, 1, 2, 4)
# und wandeln es zurück nach JSON
toJSON(mydf, pretty=TRUE)
# Indicator: NY.GDP.PCAP.PP.CD (GDP per capita PPP)
url <- "http://api.worldbank.org/v2/country/all/indicator/NY.GDP.PCAP.PP.CD?date=2021&format=json&per_page=10000"
# was für Teile gibt es hier in der URL?
baseurl <- "http://api.worldbank.org"
endpoint <- "/v2/country/all/indicator/NY.GDP.PCAP.PP.CD"
parameters <- "?date=2021&format=json&per_page=10000"
url <- paste0(baseurl, endpoint, parameters)
library(jsonlite)
res <- fromJSON(url)
str(res)
class(res[[2]])
names(res[[2]])
library(dplyr)
res[[2]] |>
select(country, gdp = value) |>
arrange(-gdp) |>
head(10)
# XML mini Beispiel
library(XML)
download.file("https://www.w3schools.com/xml/simple.xml", "simple.xml")
# Leider erfordern die meisten Seiten, dass Sie sie zuerst herunterladen, da xmlParse() https nicht unterstützt.
roh <- xmlParse("simple.xml")
liste <- xmlToList(roh)
df <- xmlToDataFrame(roh)
Einige Pakete wurden bereits für R geschrieben, die es einfacher machen, mit einzelnen APIs zu arbeiten.
Für die eben genannten World development indicators (Weltbank)
gibt es ein Package WDI, aber auch für Daten der OECD,
IWF-Daten, Börsendaten (quantmod und andere),
twitteR, rfacebook, quandl,
tidycensus, gapminder, Rspotify
uvvm.
# install.packages("WDI")
library(WDI)
#WDIsearch(string="gdp", field="name", cache=NULL)
DF <- WDI(country="all",
indicator="NY.GDP.PCAP.PP.CD", # Pro-Kopf-BIP kaufkraftbereinigt
start=2010, end=2024)
# Welche sind die reichsten Länder im Jahr 2010?
library(dplyr)
filter(DF, year==2010) |>
arrange(-NY.GDP.PCAP.PP.CD) |>
head()
# Und wie sieht es 2024 aus?
filter(DF, year==2024) |>
arrange(-NY.GDP.PCAP.PP.CD) |>
head()
# Was passiert da im Hintergrund?
# ?WDI
# ?wdi.query
country = "all"
indicator = "NY.GDP.PCAP.PP.CD"
extra = FALSE
cache = NULL
latest = NULL
language = "en"
years <- NULL
url <- paste0("https://api.worldbank.org/v2/", language,
"/country/", country, "/indicator/", indicator, "?format=json",
years, "&per_page=32500", "&page=", 1:10, latest)
url
Was sonst noch wichtig wäre
Andere interessante Beispiele
# Namsor Documentation
# https://namsor.app/api-documentation/
library(httr)
api_key <- "0173ee5ae9184eda7203f90ff9d528aa" # bitte nicht missbrauchen
df <- data.frame(
"name" = c("Rudolf Farys", "Chandrika Kumaratunga", "Adam Smith"),
"age" = c(40, 60, 75)
)
getGender <- function(name, api_key) {
# URL zusammenbauen je nach gewuenschtem endpoint
baseurl <- "https://v2.namsor.com/NamSorAPIv2/api2/json/"
endpoint <- "gender/"
name <- URLencode(sub(" ", "/", name))
url <- paste0(baseurl, endpoint, name)
# Headers definieren (z.B. API Key zur Authentifizierung)
headers <- add_headers("X-API-KEY" = api_key)
# API call ausfuehren, Rueckgabe speichern
response <- GET(url, headers)
# Pruefen ob es funktioniert hat (Code 200)
if (status_code(response) == 200) {
# json lesen und in data frame umwandeln
content <- fromJSON(rawToChar(response$content))
# den interessanten Teil der Antwort ausgeben
return(content$likelyGender)
} else {
print(paste("Request failed with status", status_code(response)))
return(NA)
}
}
# Funktion auf die Liste der Namen anwenden
df$gender <- sapply(df$name, getGender, api_key = api_key)
# Dasselbe Spiel fuer die Herkunft (leider nicht ganz so straightforward, da fuer diesen endpoint gleich mehrere Namen verarbeitet werden)
library(httr)
library(jsonlite)
library(dplyr)
library(tidyr)
getOrigin <- function(df, name_col = "name", api_key) {
# Prepare the JSON body
df <- df |> mutate(id = row_number())
json_data <- toJSON(list(personalNames = select(df, id, !!sym(name_col))))
# NamSor API endpoint for batch processing
url <- "https://v2.namsor.com/NamSorAPIv2/api2/json/originFullBatch"
# Set up the HTTP header with your API Key and specify content type as JSON
headers <- add_headers("X-API-KEY" = api_key, "Content-Type" = "application/json")
# Make the POST request with the data
response <- POST(url, headers, body = json_data, encode = "json")
# Check if the request was successful
if (status_code(response) == 200) {
# Parse the JSON response
content <- fromJSON(rawToChar(response$content))
return(content$personalNames)
} else {
print(paste("Request failed with status", status_code(response)))
return(NA)
}
}
origins <- getOrigin(df, "name", api_key = api_key)
df$origin <- origins$countryOrigin
df
kable aus
knitr und kableExtra
um Formattierungen anzubringenkable wurde insbesondere auf die Verwendung in R
Markdown ausgerichtetkable-Format abgespeichert,
nicht mehr editierbar mit Rhuxtable
huxtable: package nur von David Hugh Jones
unterhalten.stargazer, das einige
Sachen gut out-of-the-box kann, aber weniger flexibel ist oder Package
texreg für Regressionstabellengt, da es mehrere
Entwickler gibt und es ebenfalls eine Trennung zwischen Daten und Design
kennt.library(huxtable) # ggf. Installation von officer und flextable erforderlich
library(dplyr)
library(tidyr)
# Beispiel für Formatierung
mtcars |>
group_by(Zylinder = cyl, Gaenge = gear) |>
summarise(PS = round(mean(hp)),
"Sek. 1/4 Meile" = round(mean(qsec))) |>
arrange(Zylinder, Gaenge) |>
as_hux(add_colnames = TRUE) |>
set_bold(1, 1:4, TRUE) |>
set_bottom_border(1, 1:4, TRUE) |>
set_left_border(everywhere,3, TRUE) |>
set_bold(1:9, 1:2 ,TRUE) |>
merge_cells(2:4, 1) |>
merge_cells(5:7, 1) |>
merge_cells(8:9, 1) |>
map_text_color(everywhere, 3:4,
by_quantiles(c(0.2, 0.8), c("blue", "darkgreen", "red"))) |>
quick_docx(file = "Test_format.docx")
# Beispiel für Summary Tabelle
tabelle <- read.csv("http://farys.org/daten/Prestige.csv") |>
select(education, income) |>
#Kann beliebig ergänzt werden
summarise_all(list(Minimum = min,
Median = median,
Mittelwert = mean,
Maximum = max,
Fallzahl = ~sum(!is.na(.))), na.rm = TRUE) |>
pivot_longer(everything()) |>
mutate(Dimension = gsub("_.*", "", name),
Mass = gsub(".*_", "", name),
value = as.character(round(value))) |>
select(-name) |>
pivot_wider(names_from = Mass, values_from = value) |>
as_hux(add_colnames = T) |>
set_bold(1,everywhere,TRUE)
# Schöne Spaltennamen
tabelle[, 1] <- c("", "Bildung (in Jahren)", "Einkommen")
quick_docx(tabelle, file = "Test_summary.docx")
# Regressionstabellen
x1 <- rnorm(100)
x2 <- rnorm(100)
y <- rnorm(100) + 2*x1 + 1*x2
fit1 <- lm(y~x1)
fit2 <- lm(y~x1+x2)
huxreg(fit1, fit2)
#https://cran.r-project.org/web/packages/huxtable/vignettes/huxreg.html für Formatierungsmöglichkeiten
Öffnen Sie die Daten http://www.farys.org/daten/bmi.dta. Diese enthalten (unter anderem) Alter, Geschlecht und den BMI (Body-Mass-Index) aus einer Befragung.
Erzeugen Sie eine Summary-Statistik für die Variablen BMI, Alter und Einkommen. Nutzen Sie am besten das Beispiel als Template und passen Sie dieses an.
Hübschen Sie die Tabelle noch etwas auf, indem Sie die Zeilen besser beschriften.
Exportieren Sie die Tabelle als Word/HTML, Latex oder Ascii
Erzeugen/exportieren Sie eine Tabelle, die alle Variablen der fünf höchsten und der fünf niedrigsten BMI-Werte zeigt.
lm(): Schätzt ein lineares Modelllm(y ~ x): abhängige Variable y wird
linear durch die erklärende Variable x beschriebenlm(y ~ x, data = MyData): Das Datenobjekt kann direkt
spezifiziert werdenlm(y ~ x1 + x2 + x3, data = MyData): y
wird mit mehreren erklärende Variablen modelliert.lm(y ~ x1 + x2 + x1:x2,data = MyData): Interaction
effectslm(y ~ x1 * x2, data = MyData): Der Einfluss von x1
hängt von der Ausprägung von x2 (und umgekehrt); Interaktionseffektresult <- lm(y ~ x): Speichert das geschätzte
Modellresult,
summary(result), coeftest(result) u.a.coef(result): gibt den Vektor von Koeffizienten zurück
(z.B. um damit weiterzurechnen, zu prognostizieren, Hypothen zu testen,
etc.)residuals(result): Gibt den Vektor der Residuen
zurück| i | prestige \(= y_{i}\) | education \(= X_{i2}\) | income \(= X_{i3}\) | |
|---|---|---|---|---|
| 1 | gov.administrators | 68.8 | 13.11 | 12351 |
| 2 | general.managers | 69.1 | 12.26 | 25879 |
| 3 | accountants | 63.4 | 12.77 | 9271 |
| 4 | purchasing.officers | 56.8 | 11.42 | 8865 |
| 5 | chemists | 73.5 | 14.62 | 8403 |
| 6 | physicists | 77.6 | 15.64 | 11030 |
| 7 | biologists | 72.6 | 15.09 | 8258 |
| 8 | architects | 78.1 | 15.44 | 14163 |
| 9 | civil.engineers | 73.1 | 14.52 | 11377 |
| 10 | computer.programers | 53.8 | 13.83 | 8425 |
| 11 | economists | 62.2 | 14.44 | 8049 |
| … | … | … | … | … |
Prestige und befindet sich im Package
car (siehe ?Prestige) (“Companion to Applied
Regression” - John Fox)# Berufliches Prestige und Bildung bzw. Einkommen
# Paket "car" laden bzw. installieren, da dort der
# Beispieldatensatz "Prestige" enthalten ist
install.packages("car")
library(car)
# alternativ, falls car Probleme mit Abhaenigkeiten macht:
# Prestige <- read.csv("http://farys.org/daten/Prestige.csv")
# Für folgendes Beispiel brauchen wir jedoch die Funktion scatter3d() aus car.
# Das Dependency Problem lässt sich ggf. so lösen:
# install.packages("lme4") # dependency für altes pbkrtest
# packageurl <- "https://cran.r-project.org/src/contrib/Archive/pbkrtest/pbkrtest_0.4-4.tar.gz"
# install.packages(packageurl, repos=NULL, type="source") # von hand installieren
# install.packages("car") # jetzt car installieren mit den
# manuell installierten dependencies
# library(car)
# Wie kann man sich eine Regression mit zwei erklärenden Variablen vorstellen?
# Als Ebene durch eine 3d Punktewolke!
scatter3d(Prestige$income,Prestige$prestige,Prestige$education, fit="linear")
# Ein kleines Modell schätzen:
fit <- lm(prestige ~ education + income, data=Prestige)
# summary() ist eine generische Funktion. Für ein lm() Objekt wird
# ein typischer Überblick über Koeffizienten und Modellgüte gegeben
summary(fit)
# Was steckt im fit Objekt?
names(fit)
# Zugriff auf Koeffizienten und andere Bestandteile
fit$fitted.values # Vorhersage (y-Dach)
fit$residuals # Residuen = Fehler, d.h. die Abweichung der beobachteten Werte
# von der Modellvorhersage
coef(fit) # Koeffizienten
fit$coefficients
avPlots() aus Package
carcrPlots() aus
carncv.test(fit) und spread.level.plot(fit)
aus carsandwich berechnen.zoo, xts,
tseries, forecast sowie arima()
aus stats# install.packages("quantmod")
library(quantmod)
getSymbols("^SSMI")
head(SSMI)
# install.packages("forecast")
library(forecast)
fit <- auto.arima(to.monthly(SSMI)[,"SSMI.Adjusted"])
plot(forecast(fit,h=6))
IMDb gibt für nicht-kommerzielle Zwecke Daten über Filme und Schauspieler heraus:
Im Folgenden analysieren Sie diese Daten. Die Daten sind in mehreren Teildatensätzen organisiert, u.a.:
title.basics.tsv.gz: Titel, Genre, Title: Dataset on
Titles, Genres und andere Stammdaten von Filmentitle.crew.tsv.gz: Daten zu Regisseuren und
Drehbuchautorentitle.ratings.tsv.gz: User-Ratingsname.basics.tsv.gz: Namen von Schauspielern und anderen
relevanten PersonenIm Detail sind die Daten in der Dokumentation beschrieben: https://developer.imdb.com/non-commercial-datasets/
fread() aus data.table geht das Einlesen
verlässlicher und schneller). Falls das Einlesen nicht klappen sollte,
können Sie mit folgendem Code die 4 Datensätze laden. Achtung: da es
sich um grössere Datenmengen handelt kann das Einlesen einige Sekunden
dauern und die Daten werden ca. 6-7 GB im Memory benötigen.# Notfall-Code zum Einlesen
load(url("http://www.farys.org/daten/imdb.RData"))
Reduzieren Sie den “Basics” Datensatz auf Filme mit einer Laufzeit von mindestens einer Stunde.
Wie haben sich Filmgenres über die Dekaden entwickelt? Gruppieren Sie hierfür die Daten nach Dekade und Genre und zählen Sie die Häufigkeit von Filmen pro Gruppe. Betrachten Sie die aggregierten Daten (optional auch mit einer Grafik). Welche Trends und Auffälligkeiten lassen sich erkennen?
Welche Genres haben im Durchschnitt die beste Bewertung?
Wer war/ist der bedeutendste Filmregisseur? Verknüpfen Sie hierzu den Datensatz zusätzlich mit dem “Crew” Datensatz. Was könnten geeignete Kennzahlen dafür sein?
Erdenken Sie eine weitere interessante Analyse (etwa im gleichen Umfang wie eine der vorherigen Aufgaben).
Machen Sie sich mit der API von Wikipedia vertraut: https://www.mediawiki.org/wiki/API:Query und schreiben Sie Code, der via API den Wikipedia-Artikel über “Bern” bereitstellt.
Sie wollen wissen, ob Bern oder Zürich populärer ist und überlegen folgendes Vorgehen:
Einreichung bis 4. Januar 2026
Tipps:
Viel Erfolg!
Hat meistens eine dieser zwei Ursachen:
??funktionsname kann man suchen, in welchem Paket die
Funktion stecktinstall.packages("paketname").
Anführungszeichen nicht vergessen!!income ->
“not found”. daten$income ->
funktioniertgetwd(). Beim
Eingeben des Pfads hilft das Drücken von TAB bei der
Autovervollständigung des Pfads, was Fehler vermeidetR erwartet einen anderen Datentyp/Klasse als man ihm gibt.
length() oder nrow().TRUE oder FALSE) ist.NA, NaN) innerhalb der
Daten.R ist nicht bereit für neue Eingaben!