Schlagwort-Archive: Data Driven Journalism

Auswerten, wann die Facebook-Freunde online sind. Und Graphenprobleme.

Dieser Post erklaert, wie man per Facebook-Messenger den Tag-Nacht-Zyklus (oder gut, vielleicht besser den Nicht-auf-Facebook-aktiv/auf-Facebook-aktiv-Zyklus, aber das kann bisweilen ja identisch sein) abgrast und auswertet:

Das Witzigste sind aber Rants wie dieser hier, die sehr bekannt vorkommen:

Let me paint you a word-picture. It’s 11pm, I’m listening to the soundtrack to The Social Network (ironically? meta-ironically? I don’t even know), I have six terminals tiled across two screens as well as fifty thousand browser tabs open and I’m up to my third graphing library.

Making graphs is really hard.

Oh ja! 😉

(via)

Verkehrszaehlungsdaten mit R und ggplot2 visualisieren

Wie gestern erzaehlt, hatte ich mich beim Hackday Moers ja mit den Verkehrsmessdaten der Stadt Moers beschaeftigt. Die liegen als CSV-Export vor und umspannen jeweils einen Zeitraum von 48 Stunden – gemessen mit einer dieser Boxen, die am Strassenrand aufgestellt werden, und Geschwindigkeit, Fahrtrichtung und Laenge des Fahrzeugs erfassen. So sieht sowas aus:

14.05.13 07:39:33;00;030;07;0
14.05.13 07:39:35;00;030;04;0
14.05.13 07:39:37;00;027;04;0
14.05.13 07:40:52;00;024;07;0
14.05.13 07:40:53;00;024;07;1
14.05.13 07:41:39;00;035;01;0 

In der ersten Spalte kommt ein Zeitstempel, gefolgt von einem Platzhalter (00), der Geschwindigkeit in km/h, der Fahrzeuglaenge in Metern und der Fahrtrichtung. Auf den Beschreibungsseiten gibt es noch Hinweise zur Klassifizierung der Fahrzeuge nach der Laenge, und Angaben zum Dateinamen (Name der Messstelle und vorgeschriebene Hoechstgeschwindigkeit).

Ich habe das zum Anlass genommen, die nicht sonderlich uebersichtlichen Rohdaten grafisch in R mit ggplot2 aufzubereiten. Cave: Ich bin kein Statistiker, und auch kein R-Crack – das Ergebnis kam durch strategisches Googeln und Ausprobieren von Codeschnipsel zusammen mit dem Lesen der R-internen Dokumentation zustande, und war vor allem auch eine prima Gelegenheit, meine eingerosteten R-Faehigkeiten wieder aufzufrischen. Pull-Requests fuer Verbesserungen sind gerne gesehen 😉

Daten einlesen

Um die Daten zu analysieren, muessen sie natuerlich erst einmal eingelesen werden. Das geht recht fix – Separator fuer die CSV-Spalten ist das Semikolon, und da die Datei ohne Spaltenueberschriften kommt, wird header=FALSE gesetzt:

endstr <- read.csv("endstrasse_17_t50_03112014.txt",header=FALSE,sep=";")
names(endstr) <- c("date","place","tempo","length","direction")

Danach werden mit names() die passenden Spaltenueberschriften gesetzt.

Das reicht schon, um eine erste Auswertung zu machen:

summary(endstr)
                date          place       tempo        length      
 03.11.14 10:56:07:   2   Min.   :0   Min.   :12   Min.   : 1.000  
 03.11.14 12:42:34:   2   1st Qu.:0   1st Qu.:37   1st Qu.: 2.000  
 03.11.14 16:44:23:   2   Median :0   Median :42   Median : 3.000  
 03.11.14 16:44:30:   2   Mean   :0   Mean   :42   Mean   : 3.512  
 04.11.14 10:03:37:   2   3rd Qu.:0   3rd Qu.:47   3rd Qu.: 4.000  
 04.11.14 11:17:40:   2   Max.   :0   Max.   :89   Max.   :43.000  
 (Other)          :2723                                            

Das ist schon einmal ein aufschlussreicher erster Blick: Der Median der Geschwindigkeit ist 42 km/h – und nur ein Viertel der Messungen lag bei 47 km/h oder mehr.

Das laesst sich auch in einem Histogramm fuer die Geschwindigkeit auswerten:

jpeg('endstr_histo.jpg')
hist(endstr$tempo)
dev.off()

endstr_histo

Schoen ist natuerlich anders 😀

Ausgabe als Scatterplot

Passender waere, die Messung als Scatterplot ueber die Zeit hinweg auszugeben. Mein erster Ansatz analog zum unlaengst hier verlinkten Tutorial war ein wenig naiv:

library(ggplot2)
ggplot(endstr, aes(x=date, y=tempo)) +
geom_point()

endstr

Na, wem faellt auf, was da nicht stimmt? Klar, die Zeitpunkte werden nicht als Zeit interpretiert, sondern gleichverteilt auf der X-Achse aufgetragen. Ich habe am Anfang ein wenig herumgebastelt, das bei der Auswertung in POSIX-Zeit umzuwandeln, letztlich war aber die sinnvollere Variante, einfach die Tabelle um einen „richtigen“ Zeitstempel zu erweitern:

endstr$datetime <- as.POSIXct(endstr$date,format="%d.%m.%y %H:%M:%S")

ggplot(endstr, aes(x=datetime, y=tempo)) +
geom_point()

endstr_datetime

Schon besser 🙂 Analog habe ich auch fuer die Fahrzeuggroesse und die Tempoabstufungen Klassen angelegt – bei der Fahrzeuggroesse analog zur Klassifizierung in der Dateibeschreibung der Stadt, bei der Geschwindigkeit bis zum Tempolimit, bis 6 km/h ueber dem Tempolimit, und mehr als 6 km/h zu schnell:

endstr$tempoclass <- cut(endstr$tempo, breaks = c(0,50,56,Inf), labels=c("<50 km/h", "50–56 km/h", ">56 km/h"))
endstr$vehicleclass <-cut(endstr$length, breaks = c(0,8,12, Inf), labels=c("PKW", "LKW", "Lastzug"))

Einfaerben!

Mit diesen Daten lassen sich nun die einzelnen Punkte auch einfaerben. Ich habe die Punkte semitransparent gemacht, damit die Faerbung an den Haeufungen intensiver wird, und die Groesse anhand der Fahrzeugklasse gewaehlt. Fuer die Farben der Geschwindigkeitsklassen habe ich ein Pseudo-Rot-Gelb-Gruen-Schema gewaehlt, das auf eine fuer Farbenblinde taugliche Farbpalette zurueckgreift.

ggplot(endstr, aes(x=datetime, y=tempo, colour = tempoclass)) +
geom_point(alpha=0.2, aes(size=vehicleclass)) +
scale_color_manual(values= c("#009E73", "#E69F00", "#D55E00"), name="Geschwindigkeit")

endstr_colour

Feinanpassung

Ein wenig fehlt noch: Die Achsen und das Diagramm muessen noch richtig beschriftet werden: Die Zeitachse sollte ein im deutschen Sprachraum „passend“ lesbares Zeitformat bekommen, und in der Legende sollten die Punkte und ich wollte auch gerne eine Trendlinie sowie eine Hilfslinie bei der „richtigen“ Geschwindigkeit haben. Die Datumsformat-Umformatierung ist in library(scales) zu finden. Die Farben in der Legende sollten ausserdem nicht transparent angezeigt werden, das geht mit override_aes.

Fuer die Trendlinie habe ich den Tipp aus dem vorher verlinkten Tutorial verwendet, die ein Generalized Additive Model verwendet – ich habe keinen blassen Schimmer, ob das passt, aber es sieht zumindest fuer Semilaien wie mich passend aus 😉

Der komplette Code fuer den Graphen samt Ausgabe als PNG sieht nun so aus:

library(ggplot2);library(scales);library(Cairo);

ggplot(endstr, aes(x=datetime, y=tempo, colour = tempoclass)) +
# Punkte sind semitransparent, Größe abhängig von der Fahrzeugklasse
  geom_point(alpha=0.2, aes(size=vehicleclass)) +
  scale_color_manual(values= c("#009E73", "#E69F00", "#D55E00"), name="Geschwindigkeit") +
  # Hilfslinie bei 50 km/h
  geom_hline(yintercept=50, size=0.4, color="#efefef") +
  # Hilfslinie auf dem Nullpunkt der Y-Achse
  geom_hline(yintercept=0, size=0.1, color="black") +
  # Trendlinie mit Generalized Additive Model, siehe http://minimaxir.com/2015/02/ggplot-tutorial/
  geom_smooth(alpha=0.25, color="black", fill="black") +
  # Achsenbeschriftungen
  labs(title="Geschwindigkeitsmessung in der Endstraße", x="Zeit", y="Geschwindigkeit", size="Fahrzeugart") +
  guides(colour = guide_legend(override.aes = list(size=5 ,alpha=1))) + 
  scale_x_datetime(labels = date_format("%d.%m %H:%M"))

ggsave("endstr_final.png", width=8, height=4, dpi=300, type="cairo-png")

Mit library(Cairo) funktioniert die Transparenz auch bei der Bildausgabe wenigstens leidlich. Ich musste dazu libcairo-dev installieren (mehr Informationen dazu hier und hier)

Und voila – so sieht's aus:

endstr_final

Juka spielt gerade noch nebenher mit Leaflet herum, um die Graphen auch zu kartieren – die gesamte Ausgabe ist derweil auf Github anzusehen.

Linkschau

Um @sebaso zu zitieren: „Endlich mal ein vernuenftiger Use Case fuer NC-Lizenzen!“ → Lizenzhinweise fuer Ueberwachungskamerabilder, selbstgebastelt 🙂

Was die Polizei alleine nicht schafft – bedrueckende Erlebniserzaehlung einer (gesetzeswidrigen) Polizeikontrolle im Zug allein der Hautfarbe wegen.

Der Ältere fragt nach meinen Ausweispapieren. Die Polizisten hatten also vor mir jede Menge Möglichkeiten nach Ausweispapieren zu fragen, aber nein, nur meine Papiere zu sehen, schien anscheinend interessant. Sehe ich so interessant aus? Wie sehe ich denn aus? Meine Haut hat eine Färbung, die als „schwarz“ bezeichnet wird, obwohl eindeutig nicht schwarz. Ich trage ein grün-schwarzes Holzfällerhemd, eine Base-ball-Mütze und habe ein Schal um den Hals. Vor mir auf dem Tisch liegen zwei dicke Bücher zu Mikroökonometrie und Regressionsmodellen für kategorische abhängige Variablen.

Murder Machines: Why Cars Will Kill 30,000 Americans This Year. Pointiert geschriebenes Stueck rund um die Geschichte der Verantwortung bei Verkehrsunfaellen mit Kraftfahrzeugen.

Roads were seen as a public space, which all citizens had an equal right to, even children at play. “Common law tended to pin responsibility on the person operating the heavier or more dangerous vehicle,” says Norton, “so there was a bias in favor of the pedestrian.” […]

By the end of the 1920s, more than 200,000 Americans had been killed by automobiles. Most of these fatalities were pedestrians in cities, and the majority of these were children. “If a kid is hit in a street in 2014, I think our first reaction would be to ask, ‘What parent is so neglectful that they let their child play in the street?,’” says Norton.

“In 1914, it was pretty much the opposite. It was more like, ‘What evil bastard would drive their speeding car where a kid might be playing?’ That tells us how much our outlook on the public street has changed—blaming the driver was really automatic then. It didn’t help if they said something like, ‘The kid darted out into the street!,’ because the answer would’ve been, ‘That’s what kids do. By choosing to operate this dangerous machine, it’s your job to watch out for others.’ It would be like if you drove a motorcycle in a hallway today and hit somebody—you couldn’t say, ‘Oh, well, they just jumped out in front of me,’ because the response would be that you shouldn’t operate a motorcycle in a hallway.”

(via)

The Data Visualization Catalogue – Welche Datenvisualisierung passt zu welcher Art von Daten? (via)

The Sunlight Foundation’s Data Visualization Style Guidelines – Schoene Einfuehrung zu gut lesbaren Graphen. (via)

Zuletzt noch ein wenig Piratenrelatierter Content:

Wieviel Demokratie vertraegt die FDGO? – Lesestoff nicht nur fuer Piraten, die die freiheitlich-demokratische Grundordnung zu etwas Heiligem verklaeren, ganz ideologiefrei, natuerlich.

Kritisiert wird an dieser Definition zunächst ihre Entstehung. Das BVerfG hatte sie zu großen Teilen aus dem damaligen § 88 Abs. 2 StGB (heute § 92 Abs. 2 StGB) übernommen, aus dem Abschnitt der staatsgefährdenden Straftaten. Das Gericht hat damit eine bereits existierende Definition des Strafrechts zu Verfassungsrecht erhöht. Problematisch ist das besonders, weil mit dem fdGO-Begriff Einschränkung von Grundrechten einhergehen. Die Definition selbst lässt viele Fragen offen, die auch von den Gerichten und in der Rechtswissenschaft nicht einheitlich beantwortet werden. […]

Nach dieser Logik lässt sich jede an Freiheit, Gleichheit und Hierarchieabbau orientierte Gesellschaftskritik, die nicht oder nicht ausschließlich auf die parlamentarische Demokratie, den bürgerlichen National-Staat oder ein kapitalistisches Ökonomiemodell setzt, als extremistisch diskreditieren, ungeachtet dessen, ob sich die Kritiker_innen klar gegen orthodoxen Marxismus-Leninismus, Stalinismus und autoritäre Staatssysteme wie z.B. die DDR positionieren. Der Schutz von Freiheit und Demokratie kehrt sich so in sein Gegenteil.

(via)

Ausserdem zwei Kommentare zu #bombergate und der, hm, „linkskritischen“ Fraktion innerhalb der Piraten, die mal eben die Partei lahmzulegen versuchen, um ihren Willen zu bekommen: Wer eskaliert hier eigentlich? von Lars Reineke und eine rhetorische Frage von Michael Seemann.

Bonuscontent: The Male Gaze in Porn (With Commentary By Doge) – alleine schon der Kommentare wie „such not cunnilingus“ wegen ansehen. Und der Musik 😉

Soviel zur Filterblase

Wer vergangenen Sonntag entsetzt darueber war, wie viele Waehler*innen offenbar ihre Zukunft in der Union sehen, wie vielen wichtiger ist, einen Veggie-Day in der Kantine abzuwenden, als NSA, Totalueberwachung und Vorratsdatenspeicherung – befindet sich vermutlich in der beruechtigten Filterblase des Internet.

Ausserhalb derer machen die klassischen Medien Meinung, nicht Twitter. Und eine Geschichte der letzten Tage machte mir gleich nochmal deutlicher, wie so etwas aussieht.

Die Open Knowledge Foundation betreibt eine Mailingliste zu Data Driven Journalism – fuer Journalist*innen, die ihre Stories mit Daten unterfuettern, diese greifbar aufbereiten wollen, beispielsweise. Dort schreiben professionelle „Datenveredler*innen“ ebenso wie eine mir nicht bekannte Zahl interessierter Journalist*innen aus aller Welt, und die Liste war immer wieder mal Quelle interessanter Arbeiten, die ich auch hier verlinkt habe.

Montag mittag schrieb dort nun eine Abonnentin, dass sie gerne aus der Liste ausgetragen werden moechte – und loeste damit eine Mailflut aus:

Date: Mon, 23 Sep 2013 09:14:45 +0100
Thread-Topic: Can I be removed from mailing list?
Hi there,

I am leaving my current role so need to be removed from 
your mailing list - the link to click through to this 
option doesn't seem to be working. Can I be removed asap 
please?

Man muss nun eingestehen, dass das Mailman-Interface fuer die Abmeldung nicht die beste Usability hat: Man muss hierfuer die eingetragene Mailadresse ins obere Feld eintragen, das Passwort kann leer bleiben, und dann unten auf „unsubscribe“ klicken. Laesst man das Adressfeld leer, beschwert sich Mailman mit einem „Error: No Address given“. Das schien aber eine riesige Huerde fuer viele Abonnent*innen zu sein, denn es folgten (schnell gezaehlt) sieben Antworten in diesem Stil:

Date: Mon, 23 Sep 2013 14:10:13 +0000
Subject: Re: [ddj] Can I be removed from mailing list?

Me too, UNSUBSCRIBE
[Fullquote entfernt]

…bis dann Gregor Aisch irgendwann der Kragen platzte:

Date: Mon, 23 Sep 2013 20:45:56 +0200
Subject: Re: [ddj] Can I be removed from mailing list?
To save us all from more "me too" message I repeat 
the instructions once for the list:
To unsubscribe send an email with the subject 
"unsubscribe" to
data-driven-journalism-request@lists.okfn.org

Alternatively you can just send any email to
data-driven-journalism-remove@lists.okfn.org

OR you can use the unsubscribe-form which is, by the way, 
linked at the end of each email you received.
http://lists.okfn.org/mailman/options/data-driven-journalism

Here's more information if you want to dive into this:
https://www.gnu.org/software/mailman/mailman-member/node14.html

Best,
Gregor

Dieser Hinweis kam aber nicht gut an:

Date: Mon, 23 Sep 2013 16:41:24 -0400
Subject: Re: [ddj] Can I be removed from mailing list?
Yall are being very sassy about this, while many of us 
have tried many times to remove ourselves following all 
logical procedures, and it simply hasn't been working.

Peter Troxler rantete parallel in einem separaten „Bitte meldet mich von der Mailingliste ab“-Thread sueffisant ueber die Recherchefaehigkeiten der Betroffenen:

Date: Mon, 23 Sep 2013 20:53:13 +0200
Subject: Re: [ddj] I'd like to be' removed from your list
To all of you who for some funny reason are not able to 
unsubscribe yourselves (apologies to all others):
1. please grow up (yes, this is unnecessary flame, 
apologies)
2. follow the unsubscribe link at the very bottom of 
every mail from the list
3. IMPORTANT: add the email address you want to 
unsubscribe into the first box provided on the site 
(labelled "email address")
4. hit the unsubscribe button a wee bit further down 
under the heading unsubscribe
5. wait for the confirmation email to arrive
6. follow the link given in the confirmation email to 
*really* unsubscribe
7. hit the unsubscribe button on the site again to confirm
Yes, it is seven steps (and I just confirmed that it 
really works, as in step 8 you get a confirmation email).
But is that really *so* difficult for people wanting to be
 "data driven journalists", PLEASE!
/ Peter

So weit, so gut – das Thema schien durch, ich ging ins Bett. Und brauchte heute morgen beim Anblick dessen, was sich danach entwickelte, erst einmal einen Kaffee. Es begann ganz harmlos:

Date: Tue, 24 Sep 2013 22:33:19 +0100 (BST)
Subject: [ddj] confirmation on data-driven journalism
I confirm my subscription to data-driven journalism

Zwischendrin machte sich Michael Kreil ein Spaesschen:

Date: Wed, 25 Sep 2013 00:25:17 +0200
Subject: Re: [ddj] confirmation on data-driven journalism
I confirm my love to the data-driven journalism

…und auch Marco Maas mischte mit:

Date: Wed, 25 Sep 2013 11:48:06 +0200
Subject: Re: [ddj] confirmation on data-driven journalism

i confirm that i read all the mails about confirmations 
of subscriptions.

An der Mehrzahl der Abonnent*innen schien dieser Humor aber vorbeizugehen:

Date: Wed, 25 Sep 2013 10:12:54 -0300
Subject: Re: [ddj] data-driven-journalism Digest, Vol 30, Issue 23
Did the admin ask for people to confirm their allegiance 
to the list in this fashion? I don't quite get it, really.

Anyway, since I'm unvoluntarily contributing to this 
thread, count me in.

Der Bestaetigungsthread umfasst momentan ueber 40 Mails mit der expliziten und vollkommen unnoetigen Bitte, auf der Liste zu bleiben. Nur so als Erinnerung, dass das die fortschrittlicheren unter den Journalist*innen sind –  und selbst da scheint schon E-Mail manchmal eine grosse Huerde zu sein.

Datenkarten, Datenfest, Datenempfehlungen

Mal wieder ein kleiner Rundumschlag angefallener Informationen, die ich loswerden mag:

Datenkarten erstellen

Datenvisualisierung

Daten bereitstellen

Datenkonferenzen

Sonstiges

Kausalzusammenhaenge konstruieren

Die oertliche Zeitung macht zu meiner Freude neuerdings auch wieder ein wenig auf Datenjournalismus: Der Schwoermontag soll laut Willen des Gemeinderats ein frueheres Ende bekommen, weil „die Exzesse zunaehmen“, und ich war positiv ueberrascht, dass man in der Onlineredaktion bei Stadt und Rettungsdienst nach Zahlen fragte, die das belegen sollen. (Warum ich nicht auf den Originalartikel linke, steht hier.)

Visualisiert sind die Zahlen dort mit dem freien Data Wrapper, und da der die Originaldaten immer gleich mit ausliefert, habe ich die Diagramme mal ein wenig ueberarbeitet und hier mit eingestellt. Die Originale kranken naemlich an mancher Stelle:

Im Original des Diagramms steht hier nur „Koerperverletzungen“, und das ist irrefuehrend. Es ist naemlich nicht einfach nur Kleingeistigkeit, hier auf dem Label „erfasste Koerperverletzungen“ oder „Koerperverletzungen, die Polizei und Stadt bekannt gemacht wurden“ zu beharren, sondern ein wesentlicher Unterschied, da das Dunkelfeld ueberhaupt nicht bekannt sein duerfte. Interessant waeren hier noch zusaetzliche Zahlen, beispielsweise wie intensiv bestreift wurde, ob bei den jeweiligen Koerperverletzungen Strafantrag gestellt wurde oder die Polizei das „Einschreiten von Amts wegen“ fuer geboten hielt, und ob es hier die Moeglichkeit einer Korrelation geben koennte. (Spaeter mehr)

Interessant wird es ab hier: Die Entwicklung von Koerperverletzungen und Festnahmen sieht aehnlich aus. Das kann banalerweise damit zusammenhaengen, dass jemand verletzt und die jeweiligen TaeterInnen danach festgenommen wurden — muss es aber nicht, denn der Grund der Festnahme ist nicht aufgeschluesselt. Genauere Daten waeren — wieder einmal — hilfreich.

…und auch die Zahlen des ASB scheinen aehnlich zu verlaufen wie die von Festnahmen und Koerperverletzungen — wobei man sich wieder die Frage stellen darf, ob es auch hier ein Dunkelfeld gibt. Man denke an eine Alkoholintox, die von BesitzerIn in eine Kneipe in der Neustadt geschleppt wird, wo sie/er dann im Strahl reihert und in die Uniklinik gebracht wird — deren Zahlen waeren ebenfalls interessant. Genauso auch hier wieder die Aufschluesselung, was denn genau passierte: Die hohe Verletztenzahl 2012 koennte prinzipiell auch etwas mit schlechter Gefahrenabsicherung und verstauchten Knoecheln zu tun haben, so einfach ist das aus den nackten Zahlen nicht herauszulesen. Genauso gut haette aber auch 2011 eine Massenschlaegerei stattfinden koennen, die einfach von der Polizei nicht wahrgenommen, von den Beteiligten nicht angezeigt und deren Beteiligte nicht medizinisch behandelt wurden.

Man weiss es schlichtweg nicht.

Abschliessend stellen sich zwei Fragen.
Einmal, welches Jahr denn nun der statistische Ausreisser war, 2011 oder 2012, und wie der Trend ueber einen laengeren Zeitraum aussieht. Die ASB-Zahlen geben eine Ahnung, wie es aussehen koennte, verlassen wuerde ich mich aber nicht darauf.
Und zum Anderen, auf was fuer einer verdammt duennen Datenlage die Stadt ihr Vorgehen rund um den Schwoermontag begruendet, und warum.

Open-Data-Links (hauptsaechlich)

OpenData und Co

datascience

Introduction to Data Science mag ich heute besonders hervorheben, weil es ein komplettes Buch rund um Datenauswertung mit R ist, und weil es frei ist (cc-by-nc-sa).
Es beginnt mit einer kleinen Einfuehrung, was ueberhaupt Daten sind, und warum der Autor das kommandozeilenorientierte R statt Excel und Co verwendet (einen Aufsatz ueber die Verbreitung und damit einhergehende Gefaehrlichkeit von Excel gibt es hier, via @343max/@hulalena), fuehrt in kleinen Schritten ueber Data Frames auf statistische Grundlagen wie Quantile, Histogramme und Signifikanz hin, bevor es mit R Studio dann doch klickibunti benutzerfreundlicher wird, um Twitter und Texte zu analysieren, Daten zu speichern und (mein Lieblingsthema! :D) Karten zu bauen.
Alle Beispiele sind aus der Praxis gezogen und ersetzen zwar mit Sicherheit keine tiefere Beschaeftigung mit statistischen Methoden, eignen sich aber wunderbar als Einfuehrung und Appetitmacher auf mehr. Leseempfehlung!

Ausserdem:

API

Unterhaltung

  • Stenocast, Folge 0/1 / stenocast.de — Herr Urbach und Co. lesen alte Plenarprotokolle der Bonner Republik. Arbeitstitel: „Too old, didn’t read“.
  • The Importance of Excel — ob das so unterhaltend ist, sei mal dahingestellt: Warum Excel so weit verbreitet ist, ueberall benutzt wird — und welche Konsequenzen das hat (nochmal aufgegriffen von oben)
  • The Robbers Cave Experiment — Wie Sozialpsychologen einfach mal zwei sommercampende Kindergruppen aufeinander treffen liessen um herauszufinden, was man braucht, damit die sich bekriegen. Aufloesung: Gar nix weiter. (via erlehmann und plom)
  • Traumatische Momente im Kinderfilm — die 50 verstoerendsten Kinderfilmszenen, von den Geisterelefanten bei Dumbo bis — natuerlich — zu Bambis Mutter. (via/@leitmedium)

Schoenere Karten mit R und ggplot2

Vor einiger Zeit hatte ich hier schon einmal beschrieben, wie sich mit R einfache Karten und Visualisierungen herstellen lassen. Das geht natuerlich noch deutlich komplexer — aber eben auch schoener. Edward Tufte sollte eigentlich allen ein Begriff sein, die sich mit Visualisierung und grafischer Aufbereitung von Information beschaeftigen; „The Visual Display of Quantitative Information“ empfehle ich immer wieder gerne (Amazon-Affilliate-Link; alternativ ISBN 0961392142).

Neben Tufte gibt es noch ein weiteres grosses Werk, auf das ich bei der ganzen Kartiererei in R gestossen bin: „The Grammar of Graphics“ (Amazon-Affilliate-Link, alternativ ISBN 0387245448). „Gestossen“ ist hier weit ausgelegt, ich habe das Buch nicht einmal gelesen. Ich kenne aber die auf ihm basierende R-Library ggplot2, die die Grammar of Graphics fuer ihre Plots verwendet — und sie dabei aesthetisch ansehlich werden laesst.

Ich verwende im folgenden Beispiel wieder das ESRI-Shapefile der Stadt mit den integrierten Altersquotienten pro Stadtviertel. Prinzipiell laesst sich hier noch viel mehr machen, naemlich auch mit Daten aus Drittquellen. Dazu komme ich in einem spaeteren Post.

Zuerst holen wir uns einige Bibliotheken, setzen das Arbeitsverzeichnis und lesen das Shapefile ein. Ich binde in diesem Tutorial die Libraries immer erst dann ein, wenn eine ihrer Funktionen zum Einsatz kommt. Ich hoffe, so ist halbwegs nachzuvollziehen, was wozu gehoert. Die rgdal-Library erfordert PROJ.4, das einige Zeit lang in Debian und seinen Derivaten in einer kaputten Version daher kam; unter Ubuntu lohnt hier ein Blick auf UbuntuGIS.

library(sp)
library(rgdal)
setwd("/media/home/opendata/altersquotient")
ulm <- readOGR(".", "G_Altersquotient_311210")

Mit einem Blick in die ersten Zeilen des Datenteils unseres Shapefiles faellt auf, dass die Codierung (wieder) etwas gelitten hat. Das laesst sich durch Konvertierung und Ueberschreiben der urspruenglichen Spalten beheben.

head(ulm@data)
ulm@data$STT_Name <- iconv(ulm@data$STT_Name, "ISO_8859-1", "UTF-8")
ulm@data$STTV_Name <- iconv(ulm@data$STTV_Name, "ISO_8859-1", "UTF-8")

Im Folgenden trennen wir den Datenteil vom eigentlichen Shape und wandeln letzteres in Koordinatenreihen um. Um hinterher die einzelnen Teile wieder sinnvoll zusammenfuehren zu koennen, verpassen wir dem ulm-Objekt eine Spalte id, die einfach der Zeilennummer entspricht. Anschliessend speichern wir den Datenteil von ulm nach ulm.df

ulm@data$id <- rownames(ulm@data)
ulm.df <- as.data.frame(ulm)

Die bekannte Library maptools hilft uns nun mit seiner fortify()-Methode, aus dem Shapefile Koordinatenreihen zu machen. Leider ist das nicht so ganz GNU-konform, so dass wir erst einmal mit gpclibPermit() die Erlaubnis geben muessen, diese Teilfunktion auch zu benutzen. (Danke an diese Tutorials und Beispiele, die mich auf die Sache mit dem fortify() gebracht haben)

library(maptools)
gpclibPermit()
ulm_fort <- fortify(ulm, region="id")

Nun muessen ulm.df und ulm_fort wieder zu einem gemeinsamen Data Frame kombiniert werden. Das uebernimmt die join()-Funktion aus der plyr-Library, die wie ein SQL-Join funktioniert. Bei komplexeren Verknuepfungen sollte man das im Kopf behalten und gegebenenfalls explizit einen left oder right oder inner join machen, um nicht zu verzweifeln 😉

Wichtig ist natuerlich, dass beide Teile eine gemeinsame Spalte haben, anhand derer sie verbunden werden koennen. Mit einem head() sehen wir, dass das wunderbar der Fall ist.

library(plyr)
head(ulm.df)
head(ulm_fort)

ulm_merged <- join(ulm_fort, ulm.df, by=“id“)

Nun wird geplottet — mit dem altbekannten RColorBrewer und der ggplot2-Library. Wir fuegen hinzu:

  • Eine Aesthetikangabe: Welche Werte sollen fuer x, y und die Fuellung der Flaechen verwendet werden? Wir faerben anhand des Altersquotienten ein.
  • Die Angabe, aus den Koordinatenreihen Polygone zu zeichnen
  • Zusaetzlich weisse Umrisse um die Stadtviertel
  • Eine Farbskala fuer die Altersquotientenfuellung. colours = brewer.pal(9,“Greens“) wuerde schon reichen, dann gaebe es aber nur fuenf Abstufungen. Stattdessen definieren wir die breaks in Zehnerabstaenden, indem wir einen geeigneten Vektor uebergeben.

library(ggplot2)
library(RColorBrewer)
ggplot(ulm_merged) +
aes(long, lat, group=group, fill=ALTQU) +
geom_polygon() +
geom_path (color="white") +
scale_fill_gradientn(colours = brewer.pal(9, "Greens"), breaks = c(10, 20, 30, 40, 50, 60, 70, 80, 90)) +
coord_equal()

Et voila — eine wunderbare Karte mit schoenem, unaufdringlichem Gitternetz und abgesetztem Hintergrund 🙂

R ist jedoch nicht „nur“ dazu da, bunte Karten zu plotten, zumal ohne raeumlichen Bezug und (in diesem Fall) im Gauss-Krueger-Koordinatensystem. Die hier verwendeten Libraries koennen jedoch noch viel mehr: Komplexe Kombinationen von Daten, und vor allem auch die Ausgabe in verschiedensten Formaten. Google Fusion Tables? GeoJSON? Kein Problem.

Mehr dazu irgendwann demnaechst 😉

Linkschleuder

…der Versuch einer Aufarbeitung noch offener Tabs. Leider sind teilweise die QuellengeberInnen verloren geraten 🙁

Netz und Gesellschaft

  • World War 3.0 — interessant geschriebener, tiefgehender Artikel aus der Vanity Fair (sic) ueber Das Netz™, wer es kontrollieren sollte und warum nicht.
  • Revealed: How Twitter’s secret offer for Instagram made Facebook pay $1B — Portrait eines tief paranoiden Mark Zuckerberg, der von der Furcht getrieben scheint, Facebook koennte an Relevanz oder Dominanz verlieren.
  • How to Preserve, Prepare und Produce Your Digital Legacy — Die Frage, was eigentlich aus dem eigenen digitalen Nachlass wird. Nicht nur fuer den Todesfall interessant, sondern auch fuer die Frage, wie man in 30 Jahren mit dem akkumulierten Zeug umgeht, das man (hoffentlich) lokal und nicht nur auf Facebook &c hat.
  • Sprache und Ungleichheit — Anatol Stefanowitsch vom Sprachblog beleuchtet den Weg von der Unterscheidung zur Diskriminierung, und welche Rolle Sprache hierbei spielt. Piraten, die behaupten, es gaebe bei ihnen keine Diskriminierung, sollten das mal lesen 😉

OpenData &c

Nerdstuff

Design und Kunst

  • The design of a signage typeface — sehr schoen bebilderter Vergleich verschiedener Schriften auf (Strassen)schildern, mit Vorstellung einer Eigenentwicklung

Unterhaltung

Karten mit R

Ich haette mich in den letzten Tagen eigentlich mit etwas voellig anderem beschaeftigen muessen. Deswegen habe ich mich in R eingearbeitet. Prokrastination, level: 14. Semester. Und weil mir das Spass gemacht hat, moechte ich meine Erfahrungen festhalten und teilen.

Am Ende dieses kleinen Durchlaufs steht eine Choroplethenkarte — also eine in Subregionen unterteilte Karte, deren Flaechenfaerbung gewisse Kennzahlen anzeigen kann. So etwas geht heutzutage auch schoen als Overlay-Karte mit OpenStreetMap, mich interessierte aber der Prozess in R, und wie man ueberhaupt einmal zu den passenden Farben kommt 😉

0: R installieren

R gibt es fuer Win, MacOS und Linux; ich habe mich mit der letzteren Variante beschaeftigt. Eigentlich ginge das wunderbar mit sudo apt-get install R, dann wird aber bei einigen Distributionen erst einmal eine aeltere Version installiert, die viele Dinge nicht kann, die wir spaeter noch brauchen. Hier ist beschrieben, wie man die passenden Paketquellen einbindet, sofern man nicht ohnehin aus dem Source heraus kompilieren moechte.

Wenn man moechte, kann man auch eine GUI verwenden. Ich habe ein wenig mit RKward gespielt, mit dem man schoen die zu erledigenden Schritte in eine Skriptdatei schreiben und Zeile fuer Zeile abarbeiten kann — das hilft, die eigenen Schritte hinterher auch festzuhalten. Prinzipiell geht aber alles auch in der Konsole → R ausfuehren und anfangen. Mit q() geht’s wieder zurueck in die Shell.

(Mittlerweile bin ich auch auf Deducer gestossen, der recht vielversprechend aussieht, dazu vielleicht spaeter noch etwas)

1: Erste Schritte

Ich werde nicht grossartig auf die R-Grundlagen eingehen — die eignet man sich am besten dann bei, wenn man irgendwo auf ein Problem stoesst. Das kurze Grundlagenhandbuch von Thomas Petzold hat mir hierfuer gut getaugt.

Die Uebersicht aller Ulmer Stadtviertel liegt auf ulmapi.de als ZIP-Archiv mit einem ESRI-Shapefile vor. Und von dort zur ersten primitiven Karte sind es eigentlich nur vier Zeilen:

library(maptools)
setwd("/media/home/opendata/stadtviertel")
ulm <- readShapePoly("Stadtviertel_Gesamt_270209.shp")
plot(ulm)

Falls die maptools- (oder eine beliebige andere) Bibliothek nicht vorhanden ist, kann sie aus R heraus mit install.packages("maptools") nachinstalliert werden. Gegebenenfalls ist hierfuer noch der gcc- und gfortran-Compiler zu installieren. Das Argument in setwd() sollte natuerlich das eigene Arbeitsverzeichnis sein, in dem die Shapes aus dem ZIP-Archiv liegen.

Mit diesen wenigen Zeilen haben wir also eine erstens zum schreien haessliche und zweitens vollkommen informationsfreie Karte mit den Umrissen der Ulmer Stadtviertel hinbekommen. Dieser Schritt bringt uns also ueberhaupt nichts nuetzliches, aber zumindest einmal das gute Gefuehl, dass da etwas geklappt hat 🙂

2: Farbe!

Interessanter wird es, wenn tatsaechlich auch Farbe ins Spiel kommt. Die Shapefiles bringen (in der Regel) eine DBase-dbf-Datei mit, in denen mit den Umrissen verknuepfte Daten mitgebracht werden. Die liegen nun im Datenslot der Variable ulm, die wir vorhin mit dem Shape gefuellt haben. Ein einfaches

ulm

wirft uns den kompletten Inhalt dieser Variable um die Ohren — also auch alle Koordinaten. Mit

names(ulm)

sehen wir die Namen der Datenvektoren, und mit

ulm@data

bekommen wir diese als „Tabelle“ angezeigt. Einzelne Spalten (bzw. Vektoren) koennen wir mit einem angehaengten $Spaltenname selektieren; so gibt

ulm@data$ST_NAME (oder einfach ulm$ST_NAME)

alle Eintraege im Datenvektor der Stadtteilnamen aus. Nach diesen Eintraegen koennen wir nun arbeiten — zum Beispiel, indem wir einfach einmal die Karte nach Stadtteilen faerben. Das geht in zwei Zeilen:

col <- rainbow(length(levels(ulm@data$ST_NAME)))
spplot(ulm, "ST_NAME", col.regions=col, main="Stadtviertel Ulms", sub="cc-by-sa, Datensatz der Stadt Ulm", lwd=.8, col="white")

rainbow() erzeugt hier einfach einen Farbverlauf — oder besser gesagt, einen Zeichenvektor mit so vielen (length()) RGB-Farben, wie es eindeutige Werte (levels()) im Vektor ST_NAME gibt. spplot() plottet hier ulm, faerbt die durch ST_NAME bezeichneten Regionen mit der vorhin erstellten Palette ein, setzt Titel und Untertitel und die Grenzen zwischen den einzelnen Flaechen auf weisse Linien.

(Zusammengebastelt anhand dieser Tutorials)

Auch diese Darstellung ist in der Darstellung eher unspannend — dargestellt werden so nur Raumordnungsdaten, keine statistischen Werte. Dazu kommen wir nun.

3: Malen nach Zahlen

Eigentlich sollen Zahlenwerte dargestellt werden, wie im R Choropleth Challenge demonstriert. Hierzu brauchen wir aber erst einmal die darzustellenden Daten samt Schluesseln, um sie den passenden Regionen zuordnen zu koennen.

Die Tutorials und Anleitungen, die ich durchgeackert habe, gehen von zwei Praemissen aus — entweder werden RData-Objekte aus der GADM-Datenbank verwendet, wie in diesem Tutorial ueber die Einwohnerdichte indischer Bundesstaaten. Oder es geht um ESRI-Shapefiles, die in ihrem dbf-Anhaengsel deutlich mehr Nutzdaten mitbringen als unsere Stadtteilekarte. Schoene Beispiele hierfuer finden sich auf dieser Uebersichtsseite sowie in diesem weiterreichenden Beispiel, beides von der University of Oregon, an der gerade (Fruehling 2012) offenbar eine passende Vorlesung laeuft.

Die Zuordnung von Shape und geschluesselten CSV-Dateien greife ich (vielleicht ^^) in einem Folgeartikel noch einmal auf. Stattdessen gibt es nun was ganz exklusives, eine UlmAPI-Sneak-Preview quasi: Einen nagelneuen Datensatz der Stadt, bevor er auf ulmapi.de landet. Wow!

Es handelt sich um *trommelwirbel* ein Shapefile, das neben den Schluesseln und Namen auch den Altersquotienten fuer das jeweilige Viertel beinhaltet. Wow! 😀

Der Altersquotient gibt das Verhaeltnis von Personen ab 65 zu Personen von 14–64 an und kann somit als Indikator fuer die Altersstruktur der Bevoelkerung dienen. Und das laesst sich natuerlich gut farbig darstellen 🙂

Wir fangen wieder mit unseren bisherigen Bibliotheken und dem Einlesen an:

library(sp)
library(maptools)
setwd("/media/home/opendata/altersquotient")
ulm <- readShapePoly("G_Altersquotient_311210.shp")
# Inhalte kontrollieren
names(ulm)
ulm@data

Na hoppla, da ist was schiefgegangen, oder? Die Chancen sind gross, dass hier nun etwas wie „ M\xe4hringen“ steht. Das ist aber nichts, was man nicht schnell beheben koennte — wir konvertieren die betroffenen Vektoren korrekt nach UTF-8 und ersetzen die Originale damit:

ulm@data$STT_Name <- iconv(ulm@data$STT_Name, "ISO_8859-1", "UTF-8")
ulm@data$STTV_Name <- iconv(ulm@data$STTV_Name, "ISO_8859-1", "UTF-8")

Um nun die Daten aus der Spalte ALTQU in Farben umzusetzen, verwenden wir das Paket RColorBrewer, das einige fuer Diagrammvisualisierungen praktische Farbverlaeufe mitbringt, und die Bibliothek classInt, um die Werte unseres Altersquotientenvektors zu klassieren:

library(RColorBrewer)
library(classInt)

plotvar <- ulm@data$ALTQU
nclr <- 7
plotclr <- brewer.pal(nclr,"Greens")
class <- classIntervals(plotvar, nclr, style="equal")
# class <- classIntervals(plotvar, nclr, style="quantile")
colcode <- findColours(class, plotclr)

in diesem Beispiel sind die Klassenbreiten konstant; classIntervals() laesst jedoch natuerlich auch noch andere Optionen zu.

Diese Vorbereitung reicht schon, um die oben abgebildete Karte anzuzeigen:

spplot(ulm, "ALTQU", col.regions=plotclr, at=round(class$brks, digits=2), main = "Altersquotient nach Stadtvierteln in Ulm", sub="Stand 31.12.2010 nach Daten der Stadt Ulm, cc-by-sa", lwd=.8, col="white")

Weiteres Tuning waere beispielsweise durch die Anzeige der Stadtviertelnamen moeglich, was aber angesichts der teilweise dichten Haeufung nur zur groben Orientierung taugt:

spplot(ulm, "ALTQU", col.regions=plotclr, at=round(class$brks, digits=2), main = "Altersquotient nach Stadtvierteln in Ulm", sub="Stand 31.12.2010 nach Daten der Stadt Ulm, cc-by-sa", lwd=.8, col="white", sp.layout = list("sp.text", coordinates(ulm), as.character(ulm$STTV_Name), cex=0.6))

So weit so gut…

Dieser Einstieg kratzt leider wirklich nur an der Oberflaeche — und auch die Darstellung mit spplot laesst ein wenig zu wuenschen uebrig. Ebenfalls nicht behandelt habe ich das „Problem“, Shapefiles mit ganz anderen Daten zu verknuepfen. Die verlinkten Tutorials helfen hier teilweise schon weiter — ich hoffe aber, die naechsten Tage zwischen Barcamporga und Arbeiten noch ein wenig Zeit fuer ein weiteres Posting ueber ggplot2 und weitere Beispiele hinzubekommen 🙂

Viele weitere Datensaetze und vor allem auch Visualisierungsjunkies findest du ausserdem auf dem OpenCityCamp am 12. und 13. Mai 2012 an der Uni Ulm 😉

(edit: peinliche Typos gefixt)