Arbeiten mit Daten Session 3 1 Lesen und Schreiben von Daten Größere Datenmengen werden üblicherweise nicht manuell in die R Konsole eingegeben, sondern aus Files eingelesen. Hierbei gibt es grundsätzlich mehrere verschiedene Möglichkeiten, die in den nächsten Abschnitten besprochen werden. 1.1 Daten in Binärfiles Einlesen der Daten aus einem R-Data-File. R bietet die Möglichkeit Daten aus der Konsole in sogenannte Datenfiles abzuspeichern. Diese Speicherung erfolgt standardmäßig in binärer Form und ist somit weitaus speichereffizienter als das Speichern in ASCII Files (Textfiles). Diese Speichereffizienz wirkt sich hauptsächlich beim Abspeichern von Zahlen aus. Die Speicherung in einem ASCII File verbraucht für jede Zahl 16 Zeichen, also 16 Byte. Die binäre Speicherung hingegen verbraucht lediglich 64 Bit also 8 Byte und ist zudem in der Lage eine höhere Genauigkeit abzubilden. Das Speichern in R Daten Files erfolgt durch den Befehl save(x, filenamen, ascii = FALSE) oder wenn der gesamte Workspace gesichert werden soll: save.image(file = ”.RData”, ascii = FALSE). Die entsprechenden Befehle zum Laden lauten load(. . . ) und load.image(. . . ). load() lädt hierbei alle Elemente der entsprechenden Datei in den Speicher. Um dies zu vermeiden und um Hauptspeicher zu sparen, steht die Funktion attach() zur Verfügung, welche die R Konsole nur über die Existenz der Daten in dem File ”informiert”. Wird eine Variable von einem Programm oder der Konsole benötigt, dann wird diese automatisch in den Speicher geladen. 1.2 Daten in ASCII Files Die Speicherung von Zahlen als Zeichen ist zwar ineffizient, bietet sich aber oft als die einzige Lösung an, wenn man Daten zwischen verschiedenen Plattformen portieren muss. Eine standardardisierte Möglichkeit Daten in ASCII Form zu speichern, stellt das sogenannte Comma Seperated Value (CSV) File Format dar. Hierbei werden in einer Textdatei Werte - durch gewisse Trennzeichen (zB Komma, Return, Semikolon, . . . ) von einander getrennt - gespeichert. Man verwendet zum Lesen der Daten in ein CSV-File, die Funktion read.table(file, header = FALSE, sep = ””, dec = ”.”, row.names, col.names, na.strings = ”NA”, comment.char = ”#”). Diese bietet eine Vielzahl von möglichen Einstellungen, die die Struktur der einzulesenden Datei beschreiben. Es kann zum Beispiel angegeben werden, ob die Datei als erste Zeile eine sogenannte header Zeile aufweist, also eine Zeile in der die Namen der Variablen angegeben sind. Ist dies nicht der Fall, kann man alternativ die Namen über die Variable col.names spezifizieren. Weiters existieren Möglichkeiten zum Anpassen des verwendeten seperators, des Dezimalzeichens (dec) und der Zeichenkette, die einen Fehlenden Wert (na.strings) bzw ein Kommentar (comment.char ) signalisiert. Zum Schreiben von CSV Dateien wird der Befehl write.table(. . . ) mit einer ähnlichen Parameterliste verwendet. Während der Befehl load(. . . ) Daten in dem Format wieder herstellt in dem sie gespeichert wurden, gibt read.table(. . . ) ein sogenanntes data frame zurück (siehe Sektion 2). 1.3 Direkter Datenbankzugriff Es ist auch möglich Daten direkt aus einer Datenbank oder zum Beispiel aus Excel in R einzulesen. Diese Möglichkeiten werden hier aus Zeitgründen nicht behandelt. 1 2 Data Frames Statistische Daten werden in R meist in data frames gespeichert. Data Frames sind zum Beispiel auch der Output der Funktion read.table(. . . ). Ein Data Frame speichert Beobachtungen (cases), welche für statistische Auswertungen verwendet werden können. Grundsätzlich haben Data Frames eine Matrixstruktur, wobei eine Spalte alle Daten zu einem Merkmal und eine Zeile alle Daten zu einem case enthält. Wir lesen nun Daten aus der Datei electionAustria.csv ein > (elections <- read.table(file=’’electionsAustria.csv’’, sep=’’;’’, dec=’’,’’)) Wahlb. OVP SPO FPO Grüne andere 25.11.1945 3449606 49.80 44.60 NA NA 5.60 09.10.1949 4391815 44.00 38.70 11.70 NA 5.60 22.02.1953 4586870 41.30 42.10 10.90 NA 5.70 13.05.1956 4614464 46.00 43.00 6.50 NA 4.50 10.05.1959 4696603 44.20 44.80 7.70 NA 3.30 18.11.1962 4805351 45.40 44.00 7.00 NA 3.60 06.03.1966 4886818 48.40 42.60 5.40 NA 3.60 01.05.1970 5045841 44.70 48.40 5.50 NA 1.40 10.10.1971 4984448 43.10 50.00 5.50 NA 1.40 05.10.1975 5019277 42.90 50.40 5.40 NA 1.30 06.05.1979 5186735 41.90 51.00 6.10 NA 1.00 24.04.1983 5316436 43.20 47.60 5.00 NA 4.20 23.11.1986 5461414 41.30 43.10 9.70 4.80 1.10 07.10.1990 5628912 32.10 42.80 16.60 4.80 3.70 09.10.1994 5774000 27.70 34.90 22.90 7.30 7.20 17.12.1995 5768039 28.30 38.10 22.00 4.80 6.80 03.10.1999 5838373 26.90 33.20 26.90 7.40 5.60 24.11.2002 5912592 42.30 36.50 10.00 9.50 1.70 01.10.2006 6107686 34.33 35.34 11.04 11.05 8.24 > summary(elections) Die Befehl summary() angewandt auf ein Data Frame Objekt, zeigt eine kurze Zusammenfassung der Daten an. Für jede Spalte werden sowohl Extremwerte (max, min), alle Quartile als auch der Erwartungswert und die Anzahl der fehlenden Beobachtungen ausgegeben. Die Funktion summary() ist nicht nur für Data Frames, sondern auch für viele andere Objekttypen (zB Ergebnisse von Schätzungen) definiert und dient dazu sich einen raschen, summarischen Überblick über die in einem Objekt gespeicherten Daten zu verschaffen. Eine weitere Möglichkeit sich einen Überblick über die Struktur eines Objektes zu verschaffen, bietet der Befehl str(dataframe). Er zeigt an wieviele Objekte welchen Types sich in dem Data Frame bzw in den einzelnen Spalten befinden. > str(elections) ’data.frame’: 19 obs. of 6 variables: $ Wahlberechtigte: int 3449606 4391815 4586870 4614464 4696603 4805351 4886818 ... $ OVP : num 49.8 44 41.3 46 44.2 45.4 48.4 44.7 43.1 42.9 ... $ SPO : num 44.6 38.7 42.1 43 44.8 44 42.6 48.4 50 50.4 ... $ FPO : num NA 11.7 10.9 6.5 7.7 7 5.4 5.5 5.5 5.4 ... $ Grüne : num NA NA NA NA NA NA NA NA NA NA ... $ andere : num 5.6 5.6 5.7 4.5 3.3 3.6 3.6 1.4 1.4 1.3 ... 2 Um direkt auf die Daten zuzugreifen, verwendet man die Indizierung mittels des $ Symboles. Hierbei geht man wie folgt vor. > elections$SPO [1] 44.60 38.70 42.10 43.00 44.80 44.00 42.60 48.40 50.00 50.40 51.00 47.60 43.10 ... Man verwendet $ also zum Indizieren eines bestimmten Feldes des Objektes elections. Für diese Indizierung braucht man den Namen der entsprechenden Spalte. Um sich die Namen eines Data Frames anzeigen zu lassen verwendet man den Befehl names(dataframe) oder colnames(dataframe), der die Namen der Spalten anzeigt. Um sich die Namen der Zeilen anzeigen zu lassen, wird er Befehl rownames(dataframe) verwendet. Man kann diese Funktionen auch verwenden, um die Namen in einem Data Frame zu ändern. > rownames(elections) <- c(’’1945’’, ’’1949’’, ’’1953’’, ’’1956’’, ’’1959’’, ...) Alternativ kann kam auf die Daten in einem Data Frame mittels Indizierung durch Zahlen zugreifen: so gibt zum Beispiel der Befehl > spo <- elections[3] > mode(spo) [1] ’’list’’ die dritte Spalte des Data Frames (die Ergebnisse der SPÖ) als Liste (mit Labels) zurück. Der Befehl > spo <- elections[[3]] > mode(spo) [1] ’’numeric’’ hingegen liefert die selben Werte allerdings diesmal als Vektor - ohne die Struktur aus dem Data Frames (ohne die Labels). Nachträgliche Datenmanipulation kann ebenfalls auf beide oben beschriebenen Arten durchgeführt werden, also zum Beispiel > elections$Wahlberechtigte <- round(elections$Wahlberechtigte/1000)*1000 oder > elections[1] <- round(elections[1]/1000)*1000 3 Fehlende Werte (NA) Oft enthalten Daten fehlende Werte, welche in R NA genannt werden. Wendet man eine Funktion auf Daten an, die fehlende Werte enthalten, dann ist das Ergebnis in vielen Fällen wieder NA. > mean(elections$Grüne) [1] NA Da die Grünen nicht seit Anbeginn der zweiten Republik existieren, sind in dem Data Frame elections bis zum Jahr 1986 NA Werte bei den Grünen eingetragen. Daher liefert die Funktion mean() den Wert NA. Andere Funktionen zeigen ein ähnliches Verhalten (var(), diverse Plots, . . . ). Um dennoch den gewünschten Mittelwert auszurechnen, muss man die Daten entweder von NA bereinigen oder der verwendeten Funktion mitteilen diese zu ingnorieren. Ersteres is mit der Funktion na.omit(data) möglich, welche eine um die NAs bereinigte Datenstruktur zurückgibt. Vorsicht bei Dataframes werden hierbei alle Reihen, die ein zumindest ein NA enthalten weggelassen! > elections$Grüne [1] NA NA NA NA NA NA NA NA NA NA NA NA [13] 4.80 4.80 7.30 4.80 7.40 9.50 11.05 > mean(na.omit(elections$Grüne)) [1] 7.092857 Die zweite Methode wird durch die Übergabeparameter na.action (Werte: na.pass oder na.fail ) und na.rm (Werte: T oder F) gesteuert. > mean(elections$Grüne, na.rm = T) [1] 7.092857 Die Funktion is.na(data) überprüft, ob eine Datenstruktur NAs enthält. Der Rückgabewert hat die selbe Struktur wie daten, die Werte sind allerdings vom Typ logical. > is.na(elections$Grüne) [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE [13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE > sum(is.na(elections$Grüne)) [1] 12 > if(sum(is.na(elections$Grüne))>0) { ... } else { ... } In der letzten Zeile wird in dem if statement abgefragt, ob der Vektor elections$Grüne zumindest ein NA enthält. Not a Number (NaN) und Inf Manche Berechnungen führen zu einem nicht definierten Ergebniss (zB ∞−∞). Solche Berechnungen liefern statt einer Zahl den Wert NaN. Manche Berechnungen wiederum resultieren in dem Wert unendlich (Inf). 4 > 0/0 [1] NaN > Inf-Inf [1] NaN > 1/0 [1] Inf > log(0) [1] -Inf Ob das Ergebnis einer Berechnung endlich bzw eine Zahl ist, kann mit is.finite(data) und is.nan(data) getestet werden. 2.1 Zusammenführen von Data Frames Ähnlich zu den Möglichkeiten, die in man SQL durch diverse join Befehle hat, lassen sich in R durch die Funktion merge(. . . ) Data Frames zusammenfügen. Die Funktion fügt zwei Data Frames anhand übereinstimmender Spaltenwerte zusammen. So werden zB die beiden Datenframes vorname Max Emilia Fritz nachname Mustermann Mustermann Maier gehalt 1200 2500 1500 vorname Max Fritz Herbert nachname Mustermann Maier Huber alter 22 65 3 mit dem Befehl > merge(data1, data2) zu vorname nachname Fritz Maier Max Mustermann gehalt alter 1500 65 1200 22 Es werden also nur jene Zeilen in das Resultat übernommen, welche in allen Spalten, die in beiden Data Frames vorhanden sind, übereinstimmen (entspricht SQL inner join). Man verwendet die Option all.x=T, all.y=T oder all=T, um entweder alle Einträge des ersten (SQL: left join), des zweiten (SQL: right join) oder beiden ((SQL: outer join) Data Frames in das Ergebnis zu zwingen (fehlende Werte werden mit NAs aufgefüllt). 3 Faktoren Mittels Faktoren können in R Ordinal- und Nominalskalen realisiert werden. Man definiert Faktoren mit der Funktion factor(x, levels, labels). Ein Faktor enthält einerseits Daten und andererseits eine Variable levels, welche alle möglichen Levels angibt. Levels können mit der labels Option benannt werden. Die Option levels ermöglicht es explizit alle zulässigen Levels anzugeben. 5 > geschlecht <- c(’’m’’, ’’w’’, ’’w’’, ’’w’’, ’’m’’, ’’w’’, ’’m’’, ’’m’’) > (f1 <- factor(geschlecht) [1] m w w w m w m m Levels: m w > f2 <- factor(geschlecht, levels =c(’’m’’, ’’w’’), labels = c(’’männlich’’, ’’weiblich’’)) [1] männlich weiblich weiblich weiblich männlich weiblich männlich männlich Levels: männlich weiblich Die Funktion levels() ermöglicht es sicht die Levels eines Faktors ausgeben zu lassen und schreibend auf dieselben zuzugreifen. Letzteres ist nützlich, wenn man Levels umsortieren, umbenennen oder zusammenfassen will. > (levels(f2) <- levels(f2)[c(2, 1)]) [1] ’’weiblich’’ ’’männlich’’ > (levels(f2) <- c(’’Frau’’, ’’Mann’’)) [1] ’’Frau’’ ’’Mann’’ Die Funktion unique(data) extrahiert, alle vorkommenden Levels aus einem gegebenen Datenvektor. > unique(geschlecht) [1] ’’m’’ ’’w’’ Geordnete Faktoren Bis jetzt haben wir nur ungeordneten Faktoren kennen gelernt (obwohl durch die Angabe von levels eine gewisse Ordnung definiert wurde, die aber - außer in Spezialfällen - keine Auswirkungen hat). Soll R Faktoren als explizit ordenbare Faktoren (also ordinalskaliert) behandeln, dann verwendet man statt des Befehles levels(. . . ) den Befehel ordered(. . . ). Dieser hat im Wesentlichen den selben Syntax mit dem einzigen Unterschied, dass die Reihenfolge der Levels in dem levels Argument als Ordnung zwischen den Levels interpretiert wird. > bildung <- c(’’vs’’, ’’hs’’, ’’hs’’, ’’ahs’’, ’’ahs’’, ’’fh’’, ’’hs’’, ’’uni’’) > (f <- ordered(bildung, levels=c(’’vs’’, ’’hs’’, ’’ahs’’, ’’fh’’, ’’uni’’))) [1] vs hs hs ahs ahs fh hs uni Levels: vs < hs < ahs < fh < uni Nicht geordnete Faktoren können mit der Funktion ordered(factor) geordnet werden, die Ordnung entsteht dadurch aus der Ordnung der Levels in dem ungeordneten Faktor. Metrische Variablen können mit der Funktion cut() in Faktoren umgewandelt werden. > zigarettenKonsum <- c(0, 1, 25, 0, 35, 15, 12, 0, 60, 0, 5, 0, 20, 10, 30) > cut(zigarettenKonsum, breaks=c(-0.1, 0, 10, 30, 100), labels=c(’’NR’’, ’’LR’’, ’’R’’, ’’SR’’ [1] NR LR R NR SR R R NR SR NR LR NR R LR R Levels: NR LR R SR 6 tapply() Die Funktion tapply(x, factors, fun) kann dazu verwendet werden, die Funktion auf die Teilmengen von x anzuwenden, die den selben Level in dem übergebenen Faktor haben (siehe Hausübung Beispiel 5). 4 Beispiele 1. Generiere eine 1000 × 1000 Matrix von Zufallszahlen (benutze zB die Funktion runif(. . . )) und speichere das Resultat in ein ASCII File (Textfile) und als R Datenfile. Vergleiche die Dateigrößen. 2. Lade 3 Finanzdatenreihen (historische Aktienkurse) mit täglichen Daten über 3 Jahre von http: //finance.yahoo.com/ herunter und bringe diese in eine Form, dass Sie sich als Data Frame einlesen lassen. Verwende alle Felder (open, high, low, close, . . . ). 3. Generiere ein neues Data Frame indem Du die alle Einträge außer die Schlußkurse aus Beispiel 2 löschst und berechne mittels einer selbst geschriebenen Funktion die täglichen Returns rt der Aktien t wobei xt der Kurs zum Zeitpunkt t ist). Speichere die Returns in ein Data Frame. (ie rt = xxt−1 4. Generiere einen Data Frame (Dimension 10 × 3) mit beliebigen Spaltenlabels (zB Spalte1, Spalte2, Spalte3). (a) Speichere dieses Data Frame unter Verwendung von write.table(), wobei das Trennzeichen ’:’ und das Kommazeichen ’,’ sein soll. (b) Lösche alle Variablen im Workspace und lies die soeben gespeicherten Daten wieder in R ein. 5. Betrachte die folgenden beiden Vektoren g = (m, m, m, w, w, w, m, m, w, m, m, w, w, w, m, m, m, m, w, m) und r = (4, 2, 2, 4, 1, 1, 2, 2, 3, 1, 2, 3, 0, 4, 1, 2, 1, 1, 0, 1) Der Vektor g beschreibt das Geschlecht und der Vektor r die Rauchgewohnheiten einer Population mit 20 Individuen (0 entspricht hierbei einem Nichtraucher und 4 einem schweren Raucher). (a) Erstelle Faktoren aus den Vektoren r und g und vergib passende Namen für die Levels. (b) Benutze die Funktion table(. . . ), um Häufigkeitstabellen für die beiden Faktoren zu erstellen. (c) Benutze die Funktion tapply(. . . ), um das mittlere Alter der Personen in den einzelnen Raucherlevels zu berechnen. Das Alter der Testpersonen sei hierbei durch den Vektor a = (28, 86, 62, 99, 61, 50, 52, 57, 36, 44, 70, 48, 92, 42, 76, 79, 79, 89, 41, 92) gegeben. Wiederhole die Analyse mit der zweidimensionalen Klassifikation nach Geschlecht und Raucherlevels. 6. Lade das Datenfile Session3BSP6.R von der Homepage herunter. (a) Lade die beiden Data Frames in R und benutze den Befehl merge(. . . ), um inner, left, right und outer joins der beiden Variablen zu erstellen. (b) Verwende die Funktion cut(. . . ), um die Variable Einkommen in dem Data Frame, das durch den inner join entsteht, zu Kategorisieren (4 Kategorien). (c) Erstelle eine Häufigkeitstabelle. 7