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)
off | def | team | mean |
---|---|---|---|
4146.210 | 3275.007 | Bayern Munich | 3710.608 |
3287.654 | 2557.058 | Dortmund | 2922.356 |
3205.189 | 2892.206 | Leverkusen | 3048.697 |
2567.968 | 1892.386 | Hoffenheim | 2230.177 |
2538.551 | 2369.014 | Stuttgart | 2453.782 |
2408.713 | 2374.429 | Ein Frankfurt | 2391.571 |
2375.921 | 2022.079 | M’gladbach | 2199.000 |
2189.751 | 2272.785 | Wolfsburg | 2231.268 |
2100.420 | 2118.469 | Werder Bremen | 2109.444 |
1996.193 | 2302.602 | Mainz | 2149.397 |
1905.965 | 1918.582 | Augsburg | 1912.273 |
1786.803 | 1840.454 | Hertha | 1813.628 |
1745.362 | 2117.526 | Ingolstadt | 1931.444 |
1712.447 | 2137.553 | FC Koln | 1925.000 |
1638.118 | 1807.842 | Hannover | 1722.980 |
1544.824 | 1758.929 | Schalke 04 | 1651.876 |
1498.660 | 1622.374 | Darmstadt | 1560.517 |
1481.709 | 2092.286 | Hamburg | 1786.997 |
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/0
NA
: 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)i
while(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 anweisung2
if
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 stringr
substr()
aus Base oder
str_extract()
aus stringr
strsplit()
aus Base oder
str_split()
aus stringr
paste
sub
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."
# andere Lösungen ebenso möglich!
# 1.
gsub(".*([0-9]{4}).*", "\\1", s)
# oder mit stringr
library(stringr)
str_extract(s, "[0-9]{4}")
# 2.
gsub(".*George ([a-zA-Z]+) Bush.*", "\\1", s)
# oder mit str_match
str_match(s, "George ([a-zA-Z]+) Bush") # Buchstaben zwischen George und Bush
str_match(s, "George ([A-Z]{1}[a-z]+) Bush") # noch etwas expliziter mit grossen Anfangsbuchstaben
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()
).# Daten laden
allbus <- read.dta("http://www.farys.org/daten/allbus2008.dta")
allbus.agg <- allbus |>
select(geschlecht = v151, # die drei Variablen wählen und direkt umbenennen
alter = v154,
einkommen = v386) |>
filter(einkommen < 99997, alter < 999) |> # fehlende Werte droppen
group_by(geschlecht, alter) |> # gruppieren
summarise(m_einkommen = mean(einkommen)) # aggregieren
# Zusatz: Das ganze könnte man jetzt grafisch anschauen (müsste man ggf. etwas gröber gruppieren)
library(ggplot2)
ggplot(allbus.agg, aes(x=alter,y=m_einkommen,color=geschlecht)) +
geom_line()
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.library(data.table)
library(dplyr)
library(ggplot2)
load(url("http://www.farys.org/daten/ESS.RDATA"))
#gincdif: The government should take measures to reduce differences in income levels. 1=agree strongly,5=disagree strongly
#happy: Taking all things together, how happy would you say you are? #10 extremely happy 1 =Extremely unhappy
#uempla: Unemployed, actively seeking
#um Gruppenzähler kennenzulernen
#__________________________________
#2. Welches Land hat am meisten Befragte?#######
ess[,.N,Land][N==max(N)]
#dplyr
ess |>
group_by(Land) |>
count() |>
arrange(-n)
#Base
a<-table(ess$Land)
names(a)[a==max(a)]
#Um Aggregatsfunktion kennenzulernen
#__________________________________
#3. Welches ist das durchschnittlich glücklichste Land? Welches das unglücklichste?#######
ess[,.(m.happy=mean(happy,na.rm=T)),Land][m.happy==max(m.happy)|m.happy==min(m.happy)]
#Alternativ: ohne order
hap <- ess[,.(m.happy=mean(happy,na.rm=TRUE)),Land]
setorder(hap,m.happy)
hap
#dplyr
ess |>
group_by(Land) |>
summarise(m.happy=mean(happy,na.rm=T)) |>
arrange(-m.happy)
#Base R
ess<-ess[!is.na(ess$happy)&!is.na(ess$Land),]
agghapp <- aggregate(ess$happy,list(ess$Land),FUN=mean)
agghapp[order(agghapp$x),]
#um Variablengenerator kennenzulernen (:=)
#__________________________________
#4. In welchem Land gibt es am meisten komplett glückliche Menschen?#######
ess[,":="(komplett=ifelse(happy==10,1,0))][
,.(`Anteil komplett glücklich`=mean(komplett,na.rm = T)),by=Land][order(`Anteil komplett glücklich`)]
#dplyr
ess |>
mutate(komplett=happy==10 |>
group_by(Land) |>
summarise(`Anteil komplett glücklich`=mean(komplett,na.rm=T)) |>
arrange(-`Anteil komplett glücklich`)
#base R
ess$komplett <- ifelse(ess$happy==10,1,0)
ess <- ess[!is.na(ess$komplett),]
agghapp <- aggregate(ess$komplett,list(ess$Land),FUN=mean)
agghapp[order(agghapp$x),]
#Um die Verwendung von Subsetberechnungen kennenzulernen
#__________________________________
#5. Welches Land hat die grösste Einbusse im Durchschnittsglück gehabt#######
#von 2008 bis 2010?
ess[,.(
Dif=mean(happy[year==2010],na.rm=T)-
mean(happy[year==2008],na.rm=T)),
by=Land][
order(Dif)][!is.na(Dif)]
##alternativ: m.happy-m.happy[-.N] (eine Zeile vorher)
#bei sample mit nur 2008 und 2010 (geordnet)
#dplyr
rank <- ess |>
group_by(Land) |>
summarise(Dif=mean(happy[year==2010],na.rm=T)-
mean(happy[year==2008],na.rm=T)) |>
arrange(Dif) |>
filter(!is.na(Dif))
#Base R
zwei8 <- ess[ess$year==2008&!is.na(ess$happy),]
zwei10 <- ess[ess$year==2010&!is.na(ess$happy),]
agg8<-aggregate(zwei8$happy,list(zwei8$Land),FUN=mean)
names(agg8)<-c("Land","Happy.08")
agg10<-aggregate(zwei10$happy,list(zwei10$Land),FUN=mean)
names(agg10)<-c("Land","Happy.10")
ess.08.10 <- merge(agg8,agg10,by="Land",all.x = F)
ess.08.10$dif <- ess.08.10$Happy.10-ess.08.10$Happy.08
ess.08.10[order(ess.08.10$dif),c("Land","dif")]
library(ggplot2)
ggplot(rank,aes(reorder(Land, Dif),Dif))+#einfache Art Faktoren nach Grösse einer anderen Dimension zu sortieren
geom_bar(stat = "identity")+
theme(axis.text.x = element_text(angle = 90, hjust = 1))+
labs(x="",y="Glücklichkeitsveränderung 2010-2008")
# 6. Berechnen sie gleichzeitig den Modus bezüglich Glücklichkeit und Support für Umverteilung#####
# Um Funktion kennenzulernen, mit der man mehrere Variablen gleichzeitig modifiziert:
Mode <- function(x) {
names(which.max(table(x)))
}
ess[,lapply(.SD, Mode),Land, .SDcols=c("happy","gincdif")]
#dplyr
ess |>
group_by(Land) |>
summarise_at(.vars = vars(happy, gincdif),
.funs = Mode)
#Base R
aggregate(cbind(happy, gincdif)~Land,FUN = Mode,data = ess)
# 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
tidyr
tidyr
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
).data <- read.csv("http://www.farys.org/daten/satisfaction.csv")
library(tidyr)
# Daten von wide nach long
data.long <- pivot_longer(data, cols = p12c44:age14)
# Auf welche Messdimension bezieht sich der Messwert?
data.long$dimension <- ifelse( # weise der Spalte dimension einen von zwei Werten zu, je nachdem ob folgende Bedingung erfüllt ist:
grepl("c44", data.long$name), # Wenn die Spalte mit den ursprünglichen Spaltennamen ein «c44» enthält,
"zufriedenheit", # dann handelt es sich um eine Messung der Lebenszufriedenheit, also weise "zufriedenheit" zu,
"alter") # wenn nicht, dann weise "alter" zu.
# Auf welches Beobachtungsjahr bezieht sich der Messwert?
data.long$jahr <- ifelse(data.long$dimension == "zufriedenheit", # Hat die Spalte den Wert Lebenszufriedenheit
2000 + as.numeric(substr(data.long$name, 2, 3)), # dann ist die Jahresinformation and 2. Bis 3. Stelle
2000 + as.numeric(substr(data.long$name, 4, 5))) # sonst (Alter) an 4. Bis 5. Stelle
# Reshape ins wide Format damit es tidy wird:
data.long$name <- NULL # diese Spalte sollte entfernt werden, da für pivot_wide nicht klar ist, was es damit tun soll!
data.wide <- pivot_wider(data.long, names_from = dimension)
# Alternativ (und viel besser): falls die Namen in einem konsistenteren Format wären könnte man sich etwas Arbeit sparen:
# Bauen wir die Namen ein bisschen um:
names(data) <- sub(
"p([0-9]{2})c44",
"satisfaction\\2\\1",
names(data))
# danach können wir die Struktur der Daten gut durch ein pattern beschreiben.
pivot_longer(data, cols = satisfaction12:age14,
names_pattern = "(.*)([0-9]{2})",
names_to = c(".value", "year"))
# .value erklärt pivot_longer, dass dieser Teil den gemessenen Wert beschreibt und jeweils die neu entstehende Spaltenbeschriftung sein soll.
# 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 Fall
left_join(x, y, by = NULL, copy = FALSE, ...) # behält alle Fälle von x. Missing falls in y nicht vorhanden
semi_join(x, y, by = NULL, copy = FALSE, ...) # behält alle Fälle von x und behält nur die Spalten von x
anti_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
.
# 1.
library(readxl)
# Datei runterladen als binary
download.file("https://dam-api.bfs.admin.ch/hub/api/dam/assets/32229209/master",
"bevoelkerung.xlsx",
mode="wb")
# einlesen als UTF-8 kodiertes File. Sheet 2014, Zeile 8-33,
# ohne Header (den setzen wir besser manuell). über colIndex
# nehmen wir nur die Spalten 1 (A) und 2 (B)
bevoelkerung <- read_excel("bevoelkerung.xlsx", sheet = "2023", range = "A6:B31", col_names = c("Kanton", "Einwohner"))
# 2.
download.file("https://dam-api.bfs.admin.ch/hub/api/dam/assets/32088900/master",
"wald.xlsx",
mode="wb")
wald <- read_excel("wald.xlsx", sheet = 1, range = "A6:C35", col_names = c("Kanton", "Eigentuemer", "Waldflaeche"))[, c(1, 3)]
# säubern (entfällt bei readxl im Vergleich zu read.xlsx)
library(stringr)
wald$Kanton <- str_trim(wald$Kanton, side="both")
# 3. / 4.
# z.B. über dplyr
library(dplyr)
left_join(bevoelkerung, wald, by = "Kanton")
# St.Gallen und die beiden Appenzells werden nicht gematcht
# da die Schreibweise minimal anders ist, fix:
wald$Kanton <- str_replace(wald$Kanton, pattern = "\\. ", replacement = ".")
# der Punkt wird hier escaped, da es sich normal um einen
# Platzhalter für reguläre Ausdrücke handelt
# besser ist normalerweise über das Kantonskürzel oder
# Nummer zu matchen, dann stört die unterschiedliche Schreibweise nicht.
waldranking <- left_join(bevoelkerung,wald,by="Kanton") |>
mutate(bpp = Waldflaeche*400/Einwohner) |>
arrange(-bpp)
waldranking
# 5.
write.csv(waldranking, "waldranking.csv")
RODBC
, RJDBC
,
RMySQL
, RPostgreSQL
, DBI
library(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/2024
# id name marketvalue
# 1 257814 Gregor Kobel 40.0
# 2 251322 Philipp Köhn 5.0
# 3 147051 Yvon Mvogo 3.0
# 4 284730 Manuel Akanji 45.0
# 5 192635 Nico Elvedi 10.0
# 6 382478 Cédric Zesiger 5.0
# 7 298583 Eray Cömert 4.0
# 8 192616 Ulisses Garcia 4.0
# 9 86784 Ricardo Rodríguez 3.5
# 10 168989 Silvan Widmer 2.5
# 11 111455 Granit Xhaka 20.0
# 12 247555 Edimilson Fernandes 3.5
# 13 237658 Michel Aebischer 11.0
# 14 148252 Remo Freuler 6.5
# 15 280387 Vincent Sierro 4.0
# 16 410787 Joël Monteiro 5.0
# 17 394236 Filip Ugrinic 7.0
# 18 507490 Christian Witzig 2.0
# 19 507341 Fabian Rieder 8.0
# 20 365108 Dan Ndoye 22.0
# 21 237662 Breel Embolo 12.0
# 22 548729 Zeki Amdouni 12.0
# 23 345468 Andi Zeqiri 3.5
library(dplyr)
library(rvest)
arivaQuote <- function(isin) {
url <- paste0("https://ariva.de/", isin)
qt <- read_html(url) |>
html_node(".instrument-header-quote") |>
html_text() |>
str_extract("[0-9]+,[0-9]{2}") |>
(\(x) sub(",", ".", x))() |> # anonyme Funktion \(x)
as.numeric()
return(qt)
}
arivaQuote("DE0005190003") # BMW
# auch noch lustig: obwohl die Funktion nur 1 URL aufs mal verarbeiten kann, könnten wir trotzdem eine vektorisierte Version bauen:
arivaQuote <- Vectorize(arivaQuote)
isins <- c("DE0005190003", "US88160R1014") # BMW und Tesla
mydf <- data.frame(isins)
mydf$quote <- arivaQuote(mydf$isins)
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 2023 aus?
filter(DF, year==2023) |>
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
library(jsonlite)
starwars_people <- fromJSON("https://swapi-deno.azurewebsites.net/api/people")
starwars_planets <- fromJSON("https://swapi-deno.azurewebsites.net/api/planets")
starwars_people$homeworld_terrain <- starwars_planets$terrain[match(starwars_people$homeworld, starwars_planets$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.
# 1.
library(foreign)
bmi <- read.dta("http://www.farys.org/daten/bmi.dta")
# 2./3./4.
library(dplyr)
tabelle <- bmi |>
select(bmi, alter, einkommen) |>
#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 Namensgebung der Spalten
tabelle[, 1] <- c("Dimension", "Body-Mass-Index", "Alter", "Einkommen")
tabelle
quick_docx(tabelle, file = "summarytab.docx")
# 5. Es gibt viele Wege, Vorschlag:
topbottom <- rbind(arrange(bmi, -bmi) |> head(n = 5),
arrange(bmi, bmi) |> head(n = 5))
tabelle <- topbottom |>
as_hux(add_colnames = T ) |>
set_bold(1, 1:ncol(topbottom), TRUE) |>
set_bottom_border(1, 1:ncol(topbottom), TRUE)
tabelle[1, ] <- c("Body-Mass-Index","Alter","Geschlecht","Einkommen","Bildungsjahre","ID")
tabelle
# siehe ggf. auch https://hughjonesd.github.io/huxtable/reference/number_format.html für Number formatting
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ücki | 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
car
crPlots()
aus
car
ncv.test(fit)
und spread.level.plot(fit)
aus car
sandwich
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:
Achten Sie auf gut lesbaren Code! Der Einreichungstermin ist der 3. Januar 2025.
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!