Kapitel 4. Arbeiten mit dateibasierten und feedbasierten Daten in Python
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
In Kapitel 3 haben wir uns auf die vielen Merkmale konzentriert, die zur Datenqualität beitragen - von der Vollständigkeit, Konsistenz und Klarheit der Datenintegrität bis hin zur Zuverlässigkeit, Gültigkeit und Repräsentativität der Datenanpassung. Wir haben über die Notwendigkeit gesprochen, Daten zu "bereinigen" und zu standardisieren sowie sie durch die Kombination mit anderen Datensätzen zu ergänzen. Aber wie erreichen wir diese Dinge in der Praxis?
Natürlich ist es unmöglich, die Qualität eines Datensatzes zu beurteilen, ohne seinen Inhalt zu prüfen - aber das ist manchmal leichter gesagt als getan. Jahrzehntelang war die Datenverarbeitung eine hochspezialisierte Angelegenheit, die Unternehmen und Organisationen dazu veranlasste, eine ganze Reihe verschiedener (und manchmal auch geschützter) digitaler Datenformate zu entwickeln, die auf ihre besonderen Bedürfnisse zugeschnitten waren. Oft hatten diese Formate ihre eigenen Dateierweiterungen - einige davon kennst du vielleicht: xls, csv, dbf und spss sind alles Dateiformate, die typischerweise mit "Daten"-Dateien in Verbindung gebracht werden.1 Auch wenn ihre spezifischen Strukturen und Details variieren, sind alle diese Formate dateibasiert, d.h.sie enthalten (mehr oder weniger) historische Daten in statischen Dateien, die aus einer Datenbank heruntergeladen, von einem Kollegen per E-Mail verschickt oder über File-Sharing-Websites abgerufen werden können. Das Wichtigste ist, dass ein dateibasierter Datensatz größtenteils dieselben Informationen enthält, egal ob du ihn heute, in einer Woche, in einem Monat oder in einem Jahr öffnest.
Heute stehen diese dateibasierten Formate im Gegensatz zu den Datenformaten und Schnittstellen, die sich in den letzten 20 Jahren zusammen mit Echtzeit-Webdiensten entwickelt haben. Webbasierte Daten gibt es heute für alles, von Nachrichten über Wetterüberwachung bis hin zu Social-Media-Seiten, und diese Feed-ähnlichen Datenquellen haben ihre eigenen, einzigartigen Formate und Strukturen. Erweiterungen wie xml, json und rss bezeichnen diese Art von Echtzeitdaten, auf die oft über spezielle Anwendungsprogrammierschnittstellen (APIs) zugegriffen werden muss. Im Gegensatz zu dateibasierten Formaten zeigt dir der Zugriff auf denselben webbasierten Datenort oder "Endpunkt" über eine API immer die aktuellsten verfügbaren Daten - und die können sich innerhalb von Tagen, Stunden oder sogar Sekunden ändern.
Das sind natürlich keine perfekten Unterscheidungen. Es gibt viele Organisationen (vor allem Behörden), die Daten in Form von Dateien zum Herunterladen bereitstellen, diese aber mit neuen Dateien mit demselben Namen überschreiben, wenn die Quelldaten aktualisiert werden. Gleichzeitig können Datenformate in Form von Feeds heruntergeladen und für spätere Zwecke gespeichert werden, aber ihre Online-Quelle bietet in der Regel keinen Zugriff auf ältere Versionen. Trotz dieser manchmal unkonventionellen Verwendungszwecke für die einzelnen Datenformate kannst du in den meisten Fällen die Unterschiede zwischen dateibasierten und feedbasierten Datenformaten nutzen, um die am besten geeigneten Quellen für ein bestimmtes Datenverarbeitungsprojekt auszuwählen.
Woher weißt du, ob du dateibasierte oder feedbasierte Daten möchtest? In vielen Fällen wirst du keine Wahl haben. Social-Media-Unternehmen bieten zum Beispiel über ihre APIs einen einfachen Zugang zu ihren Datenfeeds, stellen aber in der Regel keine retrospektiven Daten zur Verfügung. Andere Arten von Daten - vor allem solche, die aus anderen Quellen zusammengestellt oder vor der Veröffentlichung gründlich überprüft wurden - werden eher in dateibasierten Formaten zur Verfügung gestellt. Wenn du die Wahl zwischen dateibasierten und feedbasierten Formaten hast, hängt die Entscheidung davon ab, welche Art von Daten du suchst: Wenn es darauf ankommt, die aktuellsten Daten zur Verfügung zu haben, ist ein feedbasiertes Format wahrscheinlich vorzuziehen. Wenn es dir aber um Trends geht, sind dateibasierte Daten, die eher Informationen enthalten, die im Laufe der Zeit gesammelt wurden, wahrscheinlich die beste Wahl. Aber selbst wenn beide Formate verfügbar sind, gibt es keine Garantie, dass sie die gleichen Felder enthalten, was wiederum deine Entscheidung für beeinflussen kann.
Im Laufe dieses Kapitels werden wir praktische Beispiele für die Aufbereitung von Daten aus den gängigsten datei- und feedbasierten Datenformaten durchgehen, um sie einfacher zu prüfen, zu bereinigen, zu erweitern und zu analysieren. Wir werfen auch einen Blick auf einige der schwieriger zu verarbeitenden Datenformate, mit denen du vielleicht aus der Not heraus arbeiten musst. Dabei werden wir uns auf die zahlreichen Bibliotheken stützen, die die Python-Gemeinschaft für diese Zwecke entwickelt hat, darunter spezielle Bibliotheken und Programme für die Verarbeitung von Tabellenkalkulationen und Bildern. Wenn wir fertig sind, hast du die Fähigkeiten und Beispielskripte, die du brauchst, um eine Vielzahl von Datenverarbeitungsprojekten zu bewältigen, und ebnest dir den Weg für dein nächstes Datenverarbeitungsprojekt!
Strukturierte versus unstrukturierte Daten
Bevor wir uns mit dem Schreiben von Code und dem Wrangling von Daten beschäftigen, möchte ich kurz auf ein weiteres wichtiges Merkmal von Datenquellen eingehen, das sich auf die Richtung (und Geschwindigkeit) deiner Datenwrangling-Projekte auswirken kann: die Arbeit mit strukturierten und unstrukturierten Daten.
Das Ziel der meisten Datenverarbeitungsprojekte ist es, Erkenntnisse zu gewinnen und Daten zu nutzen, um bessere Entscheidungen zu treffen. Aber Entscheidungen sind zeitkritisch, deshalb müssen wir bei unserer Arbeit mit Daten auch Kompromisse eingehen: Anstatt auf den "perfekten" Datensatz zu warten, kombinieren wir vielleicht zwei oder drei nicht ganz so perfekte Datensätze, um eine gültige Annäherung an das Phänomen, das wir untersuchen, zu erhalten, oder wir suchen nach Datensätzen, die gemeinsame Identifikatoren haben (z. B. Postleitzahlen), auch wenn das bedeutet, dass wir die bestimmte dimensionale Struktur (wie die Nachbarschaft), die uns wirklich interessiert, erst später ableiten müssen. Solange wir diese Effizienzgewinne erzielen können, ohne allzu große Abstriche bei der Datenqualität zu machen, kann die Verbesserung der Aktualität unserer Datenarbeit auch ihre Wirkung erhöhen.
Eine der einfachsten Möglichkeiten, unsere Datenverarbeitung effizienter zu gestalten, ist die Suche nach Datenformaten, die für Python und andere Computerprogramme leicht zugänglich und verständlich sind. Obwohl die Fortschritte in den Bereichen Computer Vision, Verarbeitung natürlicher Sprache und maschinelles Lernen es Computern erleichtert haben, Daten unabhängig von ihrer Struktur oder ihrem Format zu analysieren, bleiben strukturierte, maschinenlesbare Daten - wenig überraschend - die einfachste Art von Daten, mit denen wir arbeiten können. Obwohl alles, von Interviews über Bilder bis hin zu Buchtexten, als Datenquelle genutzt werden kann, denken viele von uns bei "Daten" eher an strukturierte, numerische Daten als an alles andere.
Strukturierte Daten sind alle Arten von Daten, die auf irgendeine Weise organisiert und klassifiziert wurden, und zwar in Form von Datensätzen und Feldern. In dateibasierten Formaten sind dies in der Regel Zeilen und Spalten, in feedbasierten Formaten sind es oft (im Wesentlichen) Listen von Objekten oder Wörterbücher.
Unstrukturierte Daten hingegen können aus einer Mischung verschiedener Datentypen bestehen, die Text, Zahlen und sogar Fotos oder Illustrationen enthalten. Der Inhalt eines Magazins oder eines Romans oder die Wellenformen eines Songs zum Beispiel würden normalerweise als unstrukturierte Daten gelten.
Wenn du jetzt denkst: "Moment mal, Romane haben eine Struktur! Was ist mit Kapiteln?", dann herzlichen Glückwunsch: Du denkst bereits wie ein Datenjongleur. Wir können Daten über fast alles erstellen, indem wir Informationen über die Welt sammeln und die Struktur auf sie anwenden.4 Und in Wahrheit werden alle Daten auf diese Weise erstellt: Die Datensätze, auf die wir über Dateien und Feeds zugreifen, sind alle das Produkt der Entscheidungen von Menschen, wie sie Informationen sammeln und organisieren. Mit anderen Worten: Es gibt immer mehr als eine Möglichkeit, Informationen zu organisieren, aber die gewählte Struktur beeinflusst, wie sie analysiert werden können. Deshalb ist es ein wenig lächerlich zu behaupten, dass Daten irgendwie "objektiv" sein können; schließlich sind sie das Produkt (von Natur aus subjektiver) menschlicher Entscheidungen.
Mach zum Beispiel folgendes Mini-Experiment: Überlege dir, wie du deine Sammlung organisierst (das kann eine Sammlung von Musik, Büchern, Spielen oder Teesorten sein). Frag nun einen Freund oder eine Freundin, wie er oder sie seine oder ihre Sammlung organisiert. Macht ihr das auch so? Was ist "besser"? Frag nun eine andere Person und vielleicht sogar eine dritte Person. Obwohl du vielleicht Ähnlichkeiten zwischen den Systemen findest, die du und deine Freunde zum Beispiel für die Organisation eurer Musiksammlungen verwenden, würde es mich sehr überraschen, wenn du feststellen würdest, dass zwei von euch es genau gleich machen. Wahrscheinlich wirst du sogar feststellen, dass jeder von euch es ein bisschen anders macht, aber auch leidenschaftlich davon überzeugt ist, dass seine Methode die "beste" ist. Und das ist sie auch! Für sie.
Wenn dich das an unsere Diskussion im Abschnitt "Wie und für wen?" erinnert , ist das kein Zufall, denn das Ergebnis deiner Fragen und Bemühungen um Daten wird - du hast es erraten - ein weiterer Datensatz sein, der deine Interessen und Prioritäten widerspiegelt. Auch er wird strukturiert und organisiert sein, was die Arbeit mit ihm auf bestimmte Weise einfacher macht als auf andere. Dabei geht es nicht darum, dass eine bestimmte Vorgehensweise richtig oder falsch ist, sondern darum, dass jede Entscheidung mit Kompromissen verbunden ist. Diese Kompromisse zu erkennen und anzuerkennen, ist ein wichtiger Bestandteil eines ehrlichen und verantwortungsvollen Umgangs mit Daten.
Ein wichtiger Kompromiss bei der Verwendung strukturierter Daten ist also, dass man sich bei der Organisation der zugrunde liegenden Informationen auf die Einschätzungen und Prioritäten anderer verlassen muss. Das kann natürlich eine gute - oder sogar großartige - Sache sein, wenn diese Daten in einem offenen, transparenten Prozess strukturiert wurden, an dem qualifizierte Experten beteiligt sind. Durchdacht eingesetzte Datenstrukturen wie diese können uns einen frühen Einblick in ein Thema geben, über das wir sonst vielleicht wenig oder gar nichts wissen. Andererseits besteht auch die Möglichkeit, dass wir die voreingenommenen oder schlecht durchdachten Entscheidungen eines anderen erben.
Unstrukturierte Daten geben uns natürlich die Freiheit, Informationen in Datenstrukturen zu organisieren, die unseren Bedürfnissen am besten entsprechen. Es überrascht nicht, dass wir deshalb die Verantwortung für einen robusten Datenqualitätsprozess übernehmen müssen, der sowohl komplex als auch zeitaufwändig sein kann.
Woher wissen wir im Voraus, ob ein bestimmter Datensatz strukturiert oder unstrukturiert ist? In diesem Fall können uns die Dateierweiterungen definitiv weiterhelfen. Feed-basierte Datenformate haben immer zumindest eine gewisse Struktur, auch wenn sie "freien Text" enthalten, wie z. B. Social Media Posts. Wenn du also die Dateierweiterungen .json, .xml, .rss oder .atom siehst, haben die Daten zumindest eine Art von Datensatz- und Feldstruktur, wie wir in "Feed-basierte Daten - Web-gesteuerte Live-Updates" untersuchen werden . Dateibasierte Daten, die auf .csv, .tsv, .txt, .xls(x) oder .ods enden, haben in der Regel eine tabellenartige Zeilen- und Spaltenstruktur, wie wir im nächsten Abschnitt sehen werden. Wirklich unstrukturierte Daten hingegen werden uns wahrscheinlich als .doc(x) oder .pdf übermittelt.
Jetzt, da wir die verschiedenen Arten von Datenquellen, auf die wir wahrscheinlich stoßen werden, gut im Griff haben - und sogar eine Ahnung davon haben, wie wir sie finden können -, können wir uns auf herumschlagen!
Arbeiten mit strukturierten Daten
Seit den Anfängen der digitalen Datenverarbeitung ist die Tabelle eine der gebräuchlichsten Methoden, um Daten zu strukturieren. Auch heute noch sind viele der gängigsten und einfach zu handhabenden Datenformate nichts anderes als Tabellen oder Sammlungen von Tabellen. In Kapitel 2 haben wir bereits mit einem sehr verbreiteten tabellenartigen Datenformat gearbeitet: dem .csv-Format (comma-separated value).
Dateibasierte, tabellenartige Daten - Abgrenzen ist angesagt
Im Allgemeinen sind alle tabellenartigen Datenformate, auf die du typischerweise triffst, Beispiele für sogenannte begrenzte Dateien: Jeder Datensatz befindet sich in einer eigenen Zeile, und die Grenzen zwischen Feldern oder Spalten mit Datenwerten werden durchein bestimmtes Textzeichen angegeben - oder abgegrenzt. Oft ist in der Dateierweiterung des Datensatzes angegeben, welches Textzeichen als Begrenzungszeichen in einer Datei verwendet wird. Die Dateierweiterung .csv steht zum Beispiel für comma-separated value, weil diese Dateien ein Komma (,
) als Trennzeichen verwenden; die Dateierweiterung .tsv steht für tab-separated value, weil die Datenspalten durch einen Tabulator getrennt sind. Es folgt eine Liste von Dateierweiterungen, die üblicherweise mit getrennten Daten verbunden sind:
- .csv
-
Kommagetrennte Dateien gehören zu den am häufigsten vorkommenden tabellenartigen strukturierten Datendateien, denen du begegnen wirst. Fast jedes Softwaresystem, das tabellarische Daten verarbeitet (z. B. Behörden- oder Unternehmensdatensysteme, Tabellenkalkulationsprogramme und sogar spezielle kommerzielle Datenprogramme), kann Daten als .csv ausgeben, und wie wir in Kapitel 2 gesehen haben, gibt es praktische Bibliotheken, mit denen du diesen Datentyp in Python leicht bearbeiten kannst.
- .tsv
-
Tabulatorgetrennte Dateien gibt es schon lange, aber die beschreibende .tsv-Erweiterung ist erst seit relativ kurzer Zeit üblich. Obwohl die Datenanbieter oft nicht erklären, warum sie sich für ein bestimmtes Trennzeichen entscheiden, werden tabulatorgetrennte Dateien eher für Datensätze verwendet, deren Werte Kommas enthalten müssen, wie z. B. Postadressen.
- .txt
-
Strukturierte Datendateien mit dieser Endung sind oft getarnte .tsv-Dateien; ältere Datensysteme kennzeichneten tabulatorgetrennte Daten oft mit der Endung .txt. Wie du in den folgenden Beispielen sehen wirst, ist es ratsam, jede Datendatei , die du bearbeiten willst, mit einem einfachen Textprogramm (oder einem Code-Editor wie Atom) zu öffnen und zu überprüfen, bevor du Code schreibst, denn nur wenn du dir den Inhalt der Datei ansiehst, kannst du sicher sein, mit welchen Trennzeichen du arbeitest.
- .xls(x)
-
Dies ist die Dateierweiterung von Tabellenkalkulationen, die mit Microsoft Excel erstellt wurden. Da diese Dateien mehrere "Blätter" sowie Formeln, Formatierungen und andere Funktionen enthalten können, die einfache Dateien mit Trennzeichen nicht nachbilden können, benötigen sie mehr Speicherplatz, um die gleiche Datenmenge zu speichern. Sie haben auch andere Einschränkungen (z. B. können sie nur eine bestimmte Anzahl von Zeilen verarbeiten), die sich auf die Integrität deines Datensatzes auswirken können.
- .ods
-
Open-Document-Tabellenkalkulationsdateien sind die Standarderweiterung für Tabellenkalkulationen, die von einer Reihe von Open-Source-Software-Suiten wie LibreOffice und OpenOffice erstellt werden, und haben ähnliche Einschränkungen und Funktionen wie .xls(x) -Dateien.
Bevor wir uns damit beschäftigen, wie man mit jedem dieser Dateitypen in Python arbeitet, lohnt es sich, ein wenig darüber nachzudenken, wann wir mit tabellenartigen Daten arbeiten wollen und wo wir sie finden, wenn wir das tun.
Wann man mit tabellenartigen Daten arbeitet
In den meisten Fällen haben wir keine große Wahl, was das Format unserer Quelldaten angeht. Der Grund, warum wir uns überhaupt mit Daten auseinandersetzen müssen, liegt oft darin, dass die Daten, die wir haben, unseren Anforderungen nicht genügen. Trotzdem ist es wichtig zu wissen, mit welchem Datenformat du am liebsten arbeiten würdest, damit du dich bei deiner ersten Suche nach Daten danach richten kannst.
In "Strukturierte Daten versus unstrukturierte Daten" haben wir über die Vorteile und Grenzen von strukturierten Daten gesprochen. Wir wissen jetzt, dass tabellenartige Daten eine der ältesten und häufigsten Formen maschinenlesbarer Daten sind. Das bedeutet zum Teil, dass im Laufe der Jahre viele Quelldaten in Tabellen gepackt wurden, obwohl sie sich nicht unbedingt für eine tabellenartige Darstellung eignen. Dennoch kann dieses Format besonders nützlich sein, um Fragen zu Trends und Mustern im Zeitverlauf zu beantworten. In unserer Citi Bike-Übung aus Kapitel 2 haben wir zum Beispiel untersucht, wie viele "Kunden" und wie viele "Abonnenten" im Laufe eines Monats Citi Bike-Fahrten gemacht haben. Wenn wir wollten, könnten wir dieselbe Berechnung für jeden verfügbaren Monat mit Citi Bike-Fahrten durchführen, um etwaige Muster in diesem Verhältnis im Laufe der Zeit zu verstehen.
Natürlich sind tabellenartige Daten im Allgemeinen kein gutes Format für Echtzeitdaten oder Daten, bei denen nicht jede Beobachtung die gleichen möglichen Werte enthält. Diese Art von Daten sind oft besser für die Feed-basierten Datenformate geeignet, die wir in "Feed-basierte Daten - Web-gesteuerte Live-Updates" besprechen .
Wo man tabellenartige Daten findet
Da die große Mehrheit der maschinenlesbaren Daten immer noch in tabellenartigen Datenformaten vorliegt, gehören sie zu den am leichtesten zu findenden Datenformaten. Tabellenkalkulationen sind in allen Disziplinen verbreitet, und viele staatliche und kommerzielle Informationssysteme nutzen Software, die Daten auf diese Weise organisiert. Fast immer, wenn du Daten von einem Experten oder einer Organisation anforderst, wirst du wahrscheinlich ein Tabellenformat erhalten. Das gilt auch für fast alle Portale für offene Daten und Websites zum Austausch von Daten, die du online findest. Wie wir in "Intelligente Suche nach bestimmten Datentypen" beschrieben haben , kannst du sogar über Suchmaschinen tabellenartige Daten (und andere bestimmte Dateiformate) finden, wenn du weißt, wie du suchen musst.
Mit Python tabellenartige Daten verarbeiten
Um zu veranschaulichen, wie einfach es ist, mit tabellenartigen Daten in Python zu arbeiten, gehen wir anhand von Beispielen durch, wie man Daten aus allen in diesem Abschnitt erwähnten Dateitypen einliest - und ausnahmsweise noch ein paar andere. In späteren Kapiteln werden wir uns mit der Bereinigung, Umwandlung und Bewertung der Datenqualität befassen, aber zunächst werden wir uns darauf konzentrieren, auf die Daten in den einzelnen Dateitypen zuzugreifen und mit Python mit ihnen zu arbeiten.
Daten aus CSV-Dateien lesen
Falls du in Kapitel 2 nicht mitgekommen bist, hier eine Auffrischung, wie man Daten aus einer .csv-Datei liest, anhand eines Beispiels aus dem Citi Bike-Datensatz(Beispiel 4-1). Wie immer habe ich in den Kommentaren am Anfang meines Skripts eine Beschreibung der Funktionsweise des Programms sowie Links zu den Quelldateien angegeben. Da wir schon einmal mit diesem Datenformat gearbeitet haben, beschränken wir uns jetzt darauf, die ersten Datenzeilen auszudrucken, um zu sehen, wie sie aussehen.
Beispiel 4-1. csv_parsing.py
# a simple example of reading data from a .csv file with Python
# using the "csv" library.
# the source data was sampled from the Citi Bike system data:
# https://drive.google.com/file/d/17b461NhSjf_akFWvjgNXQfqgh9iFxCu_/
# which can be found here:
# https://s3.amazonaws.com/tripdata/index.html
# import the `csv` library
import
csv
# open the `202009CitibikeTripdataExample.csv` file in read ("r") mode
# this file should be in the same folder as our Python script or notebook
source_file
=
open
(
"
202009CitibikeTripdataExample.csv
"
,
"
r
"
)
# pass our `source_file` as an ingredient to the `csv` library's
# DictReader "recipe".
# store the result in a variable called `citibike_reader`
citibike_reader
=
csv
.
DictReader
(
source_file
)
# the DictReader method has added some useful information to our data,
# like a `fieldnames` property that lets us access all the values
# in the first or "header" row
(
citibike_reader
.
fieldnames
)
# let's just print out the first 5 rows
for
i
in
range
(
0
,
5
)
:
(
next
(
citibike_reader
)
)
Diese Bibliothek ist unser Arbeitspferd, wenn es um den Umgang mit tabellenartigen Daten geht.
open()
ist eine integrierte Funktion, die einen Dateinamen und einen "Modus" als Parameter benötigt. In diesem Beispiel sollte sich die Zieldatei (202009CitibikeTripdataExample.csv
) im selben Ordner befinden wie unser Python-Skript oder -Notizbuch. Die Werte für den "Modus" könnenr
für "Lesen" oderw
für "Schreiben" sein.Wenn wir die Werte von
citibike_reader.fieldnames
ausdrucken, können wir sehen, dass die genaue Bezeichnung für die Spalte "Benutzertyp"usertype
lautet.Mit der Funktion
range()
können wir ein Stück Code eine bestimmte Anzahl von Malen ausführen, beginnend mit dem Wert des ersten Arguments und endend kurz vor dem Wert des zweiten Arguments. Der Code, der unter dieser Zeile eingerückt ist, wird zum Beispiel fünfmal ausgeführt und durchläuft dabei diei
Werte0
,1
,2
,3
und4
. Mehr über die Funktionrange()
erfährst du unter "Iteratoren hinzufügen: Die Bereichsfunktion".
Die Ausgabe sollte dann ungefähr so aussehen:
['tripduration', 'starttime', 'StartDate', 'stoptime', 'start station id', 'start station name', 'start station latitude', 'start station longitude', 'end station id', 'end station name', 'end station latitude', 'end station longitude', 'bikeid', 'usertype', 'birth year', 'gender'] {'tripduration': '4225', 'starttime': '2020-09-01 00:00:01.0430', 'StartDate': '2020-09-01', 'stoptime': '2020-09-01 01:10:26.6350', 'start station id': '3508', 'start station name': 'St Nicholas Ave & Manhattan Ave', 'start station latitude': '40.809725', 'start station longitude': '-73.953149', 'end station id': '116', 'end station name': 'W 17 St & 8 Ave', 'end station latitude': '40. 74177603', 'end station longitude': '-74.00149746', 'bikeid': '44317', 'usertype': 'Customer', 'birth year': '1979', 'gender': '1'} ... {'tripduration': '1193', 'starttime': '2020-09-01 00:00:12.2020', 'StartDate': '2020-09-01', 'stoptime': '2020-09-01 00:20:05.5470', 'start station id': '3081', 'start station name': 'Graham Ave & Grand St', 'start station latitude': '40.711863', 'start station longitude': '-73.944024', 'end station id': '3048', 'end station name': 'Putnam Ave & Nostrand Ave', 'end station latitude': '40.68402', 'end station longitude': '-73.94977', 'bikeid': '26396', 'usertype': 'Customer', 'birth year': '1969', 'gender': '0'}
Daten aus TSV- und TXT-Dateien lesen
Trotz des Namens ist die Python csv-Bibliothek im Grunde genommen ein One-Stop-Shop für den Umgang mit tabellenartigen Daten in Python, und zwar dank der Option delimiter
der Funktion DictReader
. Wenn du ihr nichts anderes sagst, geht DictReader
davon aus, dass das Komma (,
) das Trennzeichen ist, nach dem sie suchen soll. Diese Annahme lässt sich jedoch leicht überschreiben: Du kannst beim Aufruf der Funktion einfach ein anderes Zeichen angeben. In Beispiel 4-2 geben wir das Tabulatorzeichen (\t
) an, aber wir können auch jedes andere Trennzeichen verwenden, das wir bevorzugen (oder das in einer bestimmten Quelldatei vorkommt).
Beispiel 4-2. tsv_parsing.py
# a simple example of reading data from a .tsv file with Python, using
# the `csv` library. The source data was downloaded as a .tsv file
# from Jed Shugerman's Google Sheet on prosecutor politicians:
# https://docs.google.com/spreadsheets/d/1E6Z-jZWbrKmit_4lG36oyQ658Ta6Mh25HCOBaz7YVrA
# import the `csv` library
import
csv
# open the `ShugermanProsecutorPoliticians-SupremeCourtJustices.tsv` file
# in read ("r") mode.
# this file should be in the same folder as our Python script or notebook
tsv_source_file
=
open
(
"
ShugermanProsecutorPoliticians-SupremeCourtJustices.tsv
"
,
"
r
"
)
# pass our `tsv_source_file` as an ingredient to the csv library's
# DictReader "recipe."
# store the result in a variable called `politicians_reader`
politicians_reader
=
csv
.
DictReader
(
tsv_source_file
,
delimiter
=
'
\t
'
)
# the DictReader method has added some useful information to our data,
# like a `fieldnames` property that lets us access all the values
# in the first or "header" row
(
politicians_reader
.
fieldnames
)
# we'll use the `next()` function to print just the first row of data
(
next
(
politicians_reader
)
)
Dieser Datensatz wurde im Newsletter "Data Is Plural" von Jeremy Singer-Vine (@jsvine) aufgeführt(https://data-is-plural.com).
Dies sollte zu einer Ausgabe führen, die ungefähr so aussieht:
['', 'Justice', 'Term Start/End', 'Party', 'State', 'Pres Appt', 'Other Offices Held', 'Relevant Prosecutorial Background'] {'': '40', 'Justice': 'William Strong', 'Term Start/End': '1870-1880', 'Party': 'D/R', 'State': 'PA', 'Pres Appt': 'Grant', 'Other Offices Held': 'US House, Supr Court of PA, elect comm for elec of 1876', 'Relevant Prosecutorial Background': 'lawyer'}
Obwohl die Dateierweiterung .tsv heutzutage relativ weit verbreitet ist, können dich viele Dateien, die von älteren Datenbanken erzeugt werden und eigentlich tabulatorgetrennt sind, mit der Dateierweiterung .txt erreichen. Glücklicherweise ändert das, wie in der vorangegangenen Sidebar beschrieben, nichts an der Handhabung der Datei, solange wir das richtige Trennzeichen angeben - wie du in Beispiel 4-3 sehen kannst.
Beispiel 4-3. txt_parsing.py
# a simple example of reading data from a .tsv file with Python, using
# the `csv` library. The source data was downloaded as a .tsv file
# from Jed Shugerman's Google Sheet on prosecutor politicians:
# https://docs.google.com/spreadsheets/d/1E6Z-jZWbrKmit_4lG36oyQ658Ta6Mh25HCOBaz7YVrA
# the original .tsv file was renamed with a file extension of .txt
# import the `csv` library
import
csv
# open the `ShugermanProsecutorPoliticians-SupremeCourtJustices.txt` file
# in read ("r") mode.
# this file should be in the same folder as our Python script or notebook
txt_source_file
=
open
(
"
ShugermanProsecutorPoliticians-SupremeCourtJustices.txt
"
,
"
r
"
)
# pass our txt_source_file as an ingredient to the csv library's DictReader
# "recipe" and store the result in a variable called `politicians_reader`
# add the "delimiter" parameter and specify the tab character, "\t"
politicians_reader
=
csv
.
DictReader
(
txt_source_file
,
delimiter
=
'
\t
'
)
# the DictReader function has added useful information to our data,
# like a label that shows us all the values in the first or "header" row
(
politicians_reader
.
fieldnames
)
# we'll use the `next()` function to print just the first row of data
(
next
(
politicians_reader
)
)
Wie in "Don't Leave Space!" beschrieben , müssen Leerzeichen mit einem Escape-Zeichen versehen werden, wenn wir sie im Code verwenden. Hier verwenden wir das maskierte Zeichen für
tab
, also\t
. Ein weiterer gängiger Code für Leerzeichen ist\n
fürnewline
(oder\r
fürreturn
, je nach Gerät).
Wenn alles gut gelaufen ist, sollte die Ausgabe dieses Skripts genau so aussehen wie die in Beispiel 4-2.
Eine Frage, die du dir jetzt vielleicht stellst, ist: "Woher weiß ich, welches Trennzeichen meine Datei hat?" Es gibt zwar programmtechnische Möglichkeiten, dies herauszufinden, aber die einfache Antwort ist: Schau hin! Wenn du mit einem neuen Datensatz arbeitest (oder darüber nachdenkst, damit zu arbeiten), öffne ihn zunächst in dem einfachsten Textprogramm, das dein Gerät zu bieten hat (jeder Code-Editor ist ebenfalls eine gute Wahl). Vor allem, wenn es sich um eine große Datei handelt, kannst du mit dem einfachsten Programm so viel Arbeitsspeicher und Rechenleistung wie möglich für das Lesen der Daten nutzen und so die Wahrscheinlichkeit verringern, dass das Programm hängen bleibt oder dein Gerät abstürzt (das Schließen anderer Programme und überflüssiger Browser-Tabs ist ebenfalls hilfreich)!
Obwohl ich später in diesem Buch auf einige Möglichkeiten eingehen werde, wie du kleine Teile wirklich großer Dateien überprüfen kannst, ist es jetzt an der Zeit, die Fähigkeiten zu trainieren, die für die Beurteilung der Datenqualität unerlässlich sind - und die es erfordern, deine Daten zu überprüfen und zu beurteilen. Es gibt zwar Möglichkeiten, Aufgaben wie die Ermittlung des richtigen Trennzeichens für deine Daten zu "automatisieren", aber ein Blick in einen Texteditor ist oft nicht nur schneller und intuitiver, sondern hilft dir gleichzeitig, dich mit anderen wichtigen Aspekten der Daten vertraut zu machen .
Real-World Data Wrangling: Die Arbeitslosigkeit verstehen
Der zugrundeliegende Datensatz, den wir verwenden werden, um einige unserer kniffligeren tabellenartigen Datenformate zu untersuchen, sind Arbeitslosendaten über die Vereinigten Staaten. Warum? Auf die eine oder andere Weise sind die meisten von uns von Arbeitslosigkeit betroffen, und in den letzten Jahrzehnten gab es in den USA besonders hohe Arbeitslosenquoten. Die Arbeitslosenzahlen für die USA werden monatlich vom Bureau of Labor Statistics (BLS) veröffentlicht, und obwohl sie oft von Nachrichtenquellen von allgemeinem Interesse berichtet werden, werden sie in der Regel als eine Art abstrakter Indikator dafür betrachtet, wie es "der Wirtschaft" geht. Was die Zahlen wirklich bedeuten, wird selten ausführlich diskutiert.
Als ich 2007 beim Wall Street Journal anfing, war mein erstes großes Projekt die Entwicklung eines interaktiven Dashboards, mit dem man die monatlichen Wirtschaftsindikatoren - darunter auch die Arbeitslosenquote - einsehen konnte. Eines der interessantesten Dinge, die ich dabei gelernt habe, ist, dass es nicht "eine" Arbeitslosenquote gibt, die jeden Monat berechnet wird, sondern mehrere (sechs, um genau zu sein). Diejenige, über die gewöhnlich in den Nachrichten berichtet wird, ist die so genannte "U3"-Arbeitslosenquote, die das BLS wie folgt beschreibt:
Arbeitslose insgesamt, in Prozent der zivilen Erwerbsbevölkerung (offizielle Arbeitslosenquote).
Oberflächlich betrachtet scheint dies eine einfache Definition von Arbeitslosigkeit zu sein: Wie viel Prozent aller Menschen, die arbeiten könnten, sind es nicht?
Doch die wahre Geschichte ist etwas komplexer. Was bedeutet es, "beschäftigt" zu sein oder zur "Erwerbsbevölkerung" gezählt zu werden? Ein Blick auf verschiedene Arbeitslosenzahlen verdeutlicht, was die "U3"-Zahl nicht berücksichtigt. Die "U6"-Arbeitslosenquote ist definiert als:
Die Gesamtzahl der Arbeitslosen plus alle geringfügig Beschäftigten plus die Gesamtzahl der aus wirtschaftlichen Gründen teilzeitbeschäftigten Personen, in Prozent der zivilen Erwerbsbevölkerung plus alle geringfügig Beschäftigten.
Wenn wir den Begleitbrief lesen, nimmt diese längere Definition Gestalt an:5
ANMERKUNG: Geringfügig Beschäftigte sind Personen, die derzeit weder arbeiten noch Arbeit suchen, aber angeben, dass sie eine Arbeit suchen wollen und dafür zur Verfügung stehen und in den letzten 12 Monaten nach einer Arbeit gesucht haben. Entmutigte Erwerbstätige, eine Untergruppe der geringfügig Beschäftigten, haben einen arbeitsmarktbezogenen Grund angegeben, warum sie derzeit nicht auf Arbeitssuche sind. Personen, die aus wirtschaftlichen Gründen teilzeitbeschäftigt sind, sind diejenigen, die eine Vollzeitbeschäftigung wünschen und dafür zur Verfügung stehen, sich aber mit einer Teilzeitbeschäftigung zufrieden geben mussten. Aktualisierte Bevölkerungskontrollen werden jährlich mit der Veröffentlichung der Januar-Daten eingeführt.
Mit anderen Worten: Wenn du einen Job willst (und im letzten Jahr einen gesucht hast), aber in letzter Zeit keinen gefunden hast - oder wenn du einen Teilzeitjob hast, aber einen Vollzeitjob willst - dann zählst du nach der U3-Definition offiziell nicht als "arbeitslos". Das bedeutet, dass die wirtschaftliche Realität von Amerikanern, die mehrere Jobs haben (die häufiger Frauen sind und mehr Kinder haben)6und möglicherweise von "Gig"-Arbeitern (deren Anteil an der amerikanischen Erwerbsbevölkerung kürzlich auf bis zu 30 % geschätzt wurde),7 spiegeln sich nicht unbedingt in der U3-Zahl wider. Es überrascht nicht, dass die U6-Quote in der Regel jeden Monat um mehrere Prozentpunkte höher ist als die U3-Quote.
Um zu sehen, wie sich diese Raten im Laufe der Zeit entwickeln, können wir sie von der Website der St. Louis Federal Reserve herunterladen, die Tausende von Wirtschaftsdaten in verschiedenen Formaten zum Download bereitstellt, darunter tabellenartige .xls(x) -Dateien und, wie wir später in Beispiel 4-12 sehen werden, auch Feed-Formate.
Du kannst die Daten für diese Übungen von der Website der Federal Reserve Economic Database (FRED) herunterladen. Sie zeigt die aktuelle U6-Arbeitslosenquote seit der Einführung dieser Messgröße in den frühen 1990er Jahren.
Um die U3-Rate zu diesem Diagramm hinzuzufügen, wähle oben rechts Diagramm bearbeiten → LINIE HINZUFÜGEN. Gib in das Suchfeld ein UNRATE
ein und wähle dann "Arbeitslosenquote", wenn es unter der Suchleiste erscheint. Klicke abschließend auf Reihe hinzufügen. Schließe dieses Seitenfenster mit dem X oben rechts und wähle dann Herunterladen und stelle sicher, dass du die erste Option, Excel, auswählst.8 Dies wird eine .xls-Datei sein, die wir als letztes bearbeiten, denn obwohl dieses Dateiformat noch weit verbreitet ist, ist es relativ veraltet (es wurde 2007 durch .xlsx als Standardformat für Microsoft Excel-Tabellen ersetzt).
Um die zusätzlichen Dateiformate zu erhalten, die wir brauchen, öffnest du die heruntergeladene Datei mit einem Tabellenkalkulationsprogramm wie Google Sheets und wählst "Speichern unter", dann wählst du .xlsx und wiederholst den Vorgang mit .ods. Du solltest jetzt die folgenden drei Dateien haben, die alle die gleichen Informationen enthalten: fredgraph.xlsx, fredgraph.ods und fredgraph.xls.9
Hinweis
Wenn du die ursprüngliche Datei fredgraph.xls geöffnet hast, ist dir wahrscheinlich aufgefallen, dass sie mehr als nur die Arbeitslosendaten enthält. Sie enthält auch einige Kopfzeileninformationen darüber, woher die Daten stammen, und die Definitionen der U3- und U6-Arbeitslosigkeit, zum Beispiel. Um die in diesen Dateien enthaltenen Arbeitslosenquoten zu analysieren, müssen diese Metadaten von den tabellarischen Daten getrennt werden, aber erinnere dich daran, dass unser Ziel im Moment einfach darin besteht, alle unsere verschiedenen Dateien in ein .csv-Format zu konvertieren. Wir werden uns in Kapitel 7 mit der Datenbereinigung befassen, bei der diese Metadaten entfernt werden.
XLSX, ODS und der ganze Rest
Für die meisten Fälle ist es besser, Daten, die als .xlsx, .ods und in den meisten anderen nicht textbasierten Tabellenformaten gespeichert sind, nicht direkt zu verarbeiten. Wenn du gerade dabei bist, Datensätze zu erforschen, empfehle ich dir, diese Dateien einfach mit deinem bevorzugten Tabellenkalkulationsprogramm zu öffnen und sie im .csv- oder .tsv-Format zu speichern, bevor du sie in Python aufrufst. Das erleichtert nicht nur die Arbeit mit den Dateien, sondern gibt dir auch die Möglichkeit, dir den Inhalt deiner Datei anzusehen und ein Gefühl dafür zu bekommen, was sie enthält.
Wenn du .xls(x) und ähnliche Datenformate als .csv oder ein ähnliches textbasiertes Dateiformat speicherst und überprüfst, wird die Dateigröße reduziert und du bekommst einen besseren Eindruck davon, wie die "echten" Daten aussehen. Aufgrund der Formatierungsoptionen in Tabellenkalkulationsprogrammen unterscheidet sich das, was du auf dem Bildschirm siehst, manchmal erheblich von den Rohwerten, die in der eigentlichen Datei gespeichert sind. Werte, die in einem Tabellenkalkulationsprogramm als Prozentsätze erscheinen (z. B. 10 %), können in Wirklichkeit Dezimalzahlen sein (.1). Das kann zu Problemen führen, wenn du versuchst, Aspekte deiner Python-Verarbeitung oder -Analyse auf die Werte in der Tabellenkalkulation zu stützen, statt auf ein textbasiertes Datenformat wie .csv.
Dennoch wird es sicherlich Situationen geben, in denen du mit Python direkt auf .xls(x) und ähnliche Dateitypen zugreifen musst.10 Wenn du zum Beispiel regelmäßig (z. B. jeden Monat) einen .xls-Datensatz bearbeiten musst, wäre es unnötig zeitaufwändig, die Datei jedes Mal manuell neu zu speichern.
Glücklicherweise hat die aktive Python-Gemeinschaft, über die wir in "Community" gesprochen haben, Bibliotheken entwickelt, die eine beeindruckende Bandbreite an Datenformaten mühelos verarbeiten können. Um ein Gefühl dafür zu bekommen, wie diese Bibliotheken mit komplexeren Quelldaten (und Datenformaten) arbeiten, lesen die folgenden Codebeispiele das angegebene Dateiformat ein und erstellen dann eine neue .csv-Datei, die die gleichen Daten enthält.
Um diese Bibliotheken nutzen zu können, musst du sie jedoch erst auf deinem Gerät installieren, indem du die folgenden Befehle nacheinander in einem Terminalfenster ausführst:11
pip install openpyxl pip install pyexcel-ods pip install xlrd==2.0.1
In den folgenden Codebeispielen verwenden wir die openpyxl-Bibliothek, um auf .xlsx-Dateien zuzugreifen (oder sie zu parsen), die pyexcel-ods-Bibliothek, um mit .ods-Dateien umzugehen, und die xlrd-Bibliothek, um aus .xls-Dateien zu lesen (mehr über das Finden und Auswählen von Python-Bibliotheken erfährst du unter "Wo du nach Bibliotheken suchst").
Um die Eigenheiten dieser verschiedenen Dateiformate besser zu veranschaulichen, machen wir etwas Ähnliches wie in Beispiel 4-3: Wir nehmen Beispieldaten, die als .xls-Datei bereitgestellt werden, und erstellen .xlsx- und .ods-Dateien, die genau dieselben Daten enthalten, indem wir die Quelldatei mit einem Tabellenkalkulationsprogramm in den anderen Formaten speichern. Ich denke, du wirst langsam ein Gefühl dafür bekommen, wie diese Nicht-Text-Formate den Prozess der Datenverarbeitung komplizierter (und meiner Meinung nach unnötig) machen.
Wir beginnen mit der Bearbeitung einer .xlsx-Datei in \ref(Beispiel 4-4), wobei wir eine Version der von FRED heruntergeladenen Arbeitslosendaten verwenden. Dieses Beispiel verdeutlicht einen der ersten großen Unterschiede zwischen textbasierten Tabellendateien und Nicht-Text-Formaten: Da die Nicht-Text-Formate mehrere "Blätter" unterstützen, mussten wir am Anfang unseres Skripts eine for
-Schleife einfügen, in der wir den Code für die Erstellung unserer einzelnen Ausgabedateien (eine für jedes Blatt) einfügten.
Beispiel 4-4. xlsx_parsing.py
# an example of reading data from an .xlsx file with Python, using the "openpyxl"
# library. First, you'll need to pip install the openpyxl library:
# https://pypi.org/project/openpyxl/
# the source data can be composed and downloaded from:
# https://fred.stlouisfed.org/series/U6RATE
# specify the "chapter" you want to import from the "openpyxl" library
# in this case, "load_workbook"
from
openpyxl
import
load_workbook
# import the `csv` library, to create our output file
import
csv
# pass our filename as an ingredient to the `openpyxl` library's
# `load_workbook()` "recipe"
# store the result in a variable called `source_workbook`
source_workbook
=
load_workbook
(
filename
=
'
fredgraph.xlsx
'
)
# an .xlsx workbook can have multiple sheets
# print their names here for reference
(
source_workbook
.
sheetnames
)
# loop through the worksheets in `source_workbook`
for
sheet_num
,
sheet_name
in
enumerate
(
source_workbook
.
sheetnames
)
:
# create a variable that points to the current worksheet by
# passing the current value of `sheet_name` to `source_workbook`
current_sheet
=
source_workbook
[
sheet_name
]
# print `sheet_name`, just to see what it is
(
sheet_name
)
# create an output file called "xlsx_"+sheet_name
output_file
=
open
(
"
xlsx_
"
+
sheet_name
+
"
.csv
"
,
"
w
"
)
# use this csv library's "writer" recipe to easily write rows of data
# to `output_file`, instead of reading data *from* it
output_writer
=
csv
.
writer
(
output_file
)
# loop through every row in our sheet
for
row
in
current_sheet
.
iter_rows
(
)
:
# we'll create an empty list where we'll put the actual
# values of the cells in each row
row_cells
=
[
]
# for every cell (or column) in each row....
for
cell
in
row
:
# let's print what's in here, just to see how the code sees it
(
cell
,
cell
.
value
)
# add the values to the end of our list with the `append()` method
row_cells
.
append
(
cell
.
value
)
# write our newly (re)constructed data row to the output file
output_writer
.
writerow
(
row_cells
)
# officially close the `.csv` file we just wrote all that data to
output_file
.
close
(
)
Wie die Funktion
DictReader()
der csv-Bibliothek fügt die Funktionload_workbook()
vonopenpyxl
unseren Quelldaten Eigenschaften hinzu, in diesem Fall eine, die uns die Namen aller Datenblätter in unserer Arbeitsmappe anzeigt.Auch wenn unsere Beispielarbeitsmappe nur ein Arbeitsblatt enthält, könnten wir in Zukunft mehr haben. Wir verwenden die Funktion
enumerate()
, damit wir sowohl auf einen Iterator als auch auf den Blattnamen zugreifen können. So können wir eine .csv-Datei pro Arbeitsblatt erstellen.Jedes Blatt in unserem
source_workbook
benötigt eine eigene, eindeutig benannte .csv-Ausgabedatei. Um diese zu erstellen, "öffnen" wir eine neue Datei mit dem Namen"xlsx_"+sheet_name+".csv"
und machen sie schreibbar, indem wirw
als "Modus"-Argument übergeben (bisher haben wir den Modusr
verwendet, um Daten aus .csvs zu lesen ).Die Funktion
iter_rows()
ist spezifisch für die openpyxl-Bibliothek. Hier wandelt sie die Zeilen vonsource_workbook
in eine Liste um, die in einer Schleife durchlaufen werden kann.Die openpyxl-Bibliothek behandelt jede Datenzelle als einen Python
tuple
-Datentyp. Wenn wir versuchen, die Zeilen voncurrent_sheet
direkt auszudrucken, erhalten wir nicht die Datenwerte, die sie enthalten, sondern nur die Zellpositionen. Um dieses Problem zu lösen, machen wir eine weitere Schleife innerhalb dieser Schleife, um jede Zelle in jeder Zeile einzeln durchzugehen und die tatsächlichen Datenwerte zurow_cells
hinzuzufügen.Beachte, dass dieser Code linksbündig mit dem
for cell in row
Code im Beispiel steht. Das bedeutet, dass er sich außerhalb der Schleife befindet und daher erst ausgeführt wird , nachdem alle Zellen in einer bestimmten Zeile an unsere Liste angehängt worden sind.
Dieses Skript verdeutlicht auch, dass die Ersteller von Bibliotheken unterschiedliche Entscheidungen über die (Neu-)Strukturierung der einzelnen Quelldateitypen treffen können, so wie zwei Köche dasselbe Gericht unterschiedlich zubereiten können - mit entsprechenden Auswirkungen auf unseren Code. Die Ersteller der openpyxl-Bibliothek haben sich zum Beispiel dafür entschieden, die Ortsbezeichnung jeder Datenzelle (z. B. A6
) und den darin enthaltenen Wert in einer Python-Datei tuple
zu speichern. Diese Entscheidung ist der Grund, warum wir eine zweite for
Schleife brauchen, um jede Datenzeile durchzugehen - denn wir müssen tatsächlich Zelle für Zelle auf die Daten zugreifen, um die Python-Liste zu erstellen, die zu einer einzelnen Zeile in unserer .csv-Ausgabedatei wird. Wenn du ein Tabellenkalkulationsprogramm verwendest, um die vom Skript in Beispiel 4-4 erstellte xlsx_FRED Graph.csv zu öffnen, wirst du feststellen, dass die Original- .xls-Datei die Werte in der Spalte observation_date
im Format JJJJ-MM-TT anzeigt, unsere Ausgabedatei jedoch im Format JJJJ-MM-TT HH:MM:SS. Das liegt daran, dass die Ersteller von openpyxl beschlossen haben, alle "datumsähnlichen" Datenstrings automatisch in den Python-Datentyp datetime
zu konvertieren. Natürlich ist keine dieser Entscheidungen richtig oder falsch; wir müssen sie nur beim Schreiben unseres Codes berücksichtigen, damit wir die Quelldaten nicht verzerren oder falsch interpretieren.
Nachdem wir nun die .xlsx-Version unserer Datendatei bearbeitet haben, wollen wir sehen, was passiert, wenn wir als .ods parsen, wie in Beispiel 4-5 gezeigt.
Beispiel 4-5. ods_parsing.py
# an example of reading data from an .ods file with Python, using the
# "pyexcel_ods" library. First, you'll need to pip install the library:
# https://pypi.org/project/pyexcel-ods/
# specify the "chapter" of the "pyexcel_ods" library you want to import,
# in this case, `get_data`
from
pyexcel_ods
import
get_data
# import the `csv` library, to create our output file
import
csv
# pass our filename as an ingredient to the `pyexcel_ods` library's
# `get_data()` "recipe"
# store the result in a variable called `source_workbook`
source_workbook
=
get_data
(
"
fredgraph.ods
"
)
# an `.ods` workbook can have multiple sheets
for
sheet_name
,
sheet_data
in
source_workbook
.
items
(
)
:
# print `sheet_name`, just to see what it is
(
sheet_name
)
# create "ods_"+sheet_name+".csv" as an output file for the current sheet
output_file
=
open
(
"
ods_
"
+
sheet_name
+
"
.csv
"
,
"
w
"
)
# use this csv library's "writer" recipe to easily write rows of data
# to `output_file`, instead of reading data *from* it
output_writer
=
csv
.
writer
(
output_file
)
# now, we need to loop through every row in our sheet
for
row
in
sheet_data
:
# use the `writerow` recipe to write each `row`
# directly to our output file
output_writer
.
writerow
(
row
)
# officially close the `.csv` file we just wrote all that data to
output_file
.
close
(
)
Die pyexcel_ods-Bibliothek konvertiert unsere Quelldaten in den Python-Datentyp
OrderedDict
. Mit der zugehörigen Methodeitems()
können wir dann auf den Namen und die Daten jedes Arbeitsblatts als Schlüssel/Wert-Paar zugreifen, durch das wir eine Schleife ziehen können. In diesem Fall istsheet_name
der "Schlüssel" und die Daten des gesamten Arbeitsblatts sind der "Wert".Hier ist
sheet_data
bereits eine Liste, also können wir diese Liste mit einer einfachenfor
Schleife durchlaufen.Diese Bibliothek wandelt jede Zeile in einem Arbeitsblatt in eine Liste um, weshalb wir diese direkt an die Methode
writerow()
übergeben können.
Im Fall der pyexcel_ods-Bibliothek ähnelt der Inhalt unserer csv-Ausgabedatei viel mehr dem, was wir sehen, wenn wir die ursprüngliche fredgraph.xls mit einem Tabellenkalkulationsprogramm wie Google Sheets öffnen - das Feld observation_date
ist zum Beispiel in einem einfachen JJJJ-MM-TT-Format. Außerdem haben die Ersteller der Bibliothek beschlossen, die Werte in jeder Zeile als Liste zu behandeln, sodass wir jeden Datensatz direkt in unsere Ausgabedatei schreiben können, ohne zusätzliche Schleifen oder Listen zu erstellen.
Zum Schluss wollen wir sehen, was passiert, wenn wir die xlrd-Bibliothek verwenden, um die ursprüngliche .xls-Datei in Beispiel 4-6 direkt zu analysieren.
Beispiel 4-6. xls_parsing.py
# a simple example of reading data from a .xls file with Python
# using the "xrld" library. First, pip install the xlrd library:
# https://pypi.org/project/xlrd/2.0.1/
# import the "xlrd" library
import
xlrd
# import the `csv` library, to create our output file
import
csv
# pass our filename as an ingredient to the `xlrd` library's
# `open_workbook()` "recipe"
# store the result in a variable called `source_workbook`
source_workbook
=
xlrd
.
open_workbook
(
"
fredgraph.xls
"
)
# an `.xls` workbook can have multiple sheets
for
sheet_name
in
source_workbook
.
sheet_names
(
)
:
# create a variable that points to the current worksheet by
# passing the current value of `sheet_name` to the `sheet_by_name` recipe
current_sheet
=
source_workbook
.
sheet_by_name
(
sheet_name
)
# print `sheet_name`, just to see what it is
(
sheet_name
)
# create "xls_"+sheet_name+".csv" as an output file for the current sheet
output_file
=
open
(
"
xls_
"
+
sheet_name
+
"
.csv
"
,
"
w
"
)
# use the `csv` library's "writer" recipe to easily write rows of data
# to `output_file`, instead of reading data *from* it
output_writer
=
csv
.
writer
(
output_file
)
# now, we need to loop through every row in our sheet
for
row_num
,
row
in
enumerate
(
current_sheet
.
get_rows
(
)
)
:
# each row is already a list, but we need to use the `row_value()`
# method to access them
# then we can use the `writerow` recipe to write them
# directly to our output file
output_writer
.
writerow
(
current_sheet
.
row_values
(
row_num
)
)
# officially close the `.csv` file we just wrote all that data to
output_file
.
close
(
)
Beachte, dass diese Struktur derjenigen ähnelt, die wir bei der Arbeit mit der csv-Bibliothek verwenden.
Die Funktion
get_rows()
ist spezifisch für die xlrd-Bibliothek; sie wandelt die Zeilen unseres aktuellen Arbeitsblatts in eine Liste um, die in einer Schleife durchlaufen werden kann.12Die Datumsangaben in unserer Ausgabedatei sind nicht ganz fehlerfrei.13 Wie du die Daten in Ordnung bringst, erfährst du in "Excel-Daten entschlüsseln".
Eine Sache, die wir in dieser Ausgabedatei sehen werden, ist, dass die Werte, die im Feld observation_date
aufgezeichnet werden, ziemlich seltsam sind. Dies spiegelt die Tatsache wider, dass, wie die Ersteller der xlrd-Bibliothek es ausdrücken:14
Daten in Excel-Tabellen: In Wirklichkeit gibt es so etwas nicht. Was du hast, sind Fließkommazahlen und fromme Hoffnung.
Um aus einer .xls-Datei ein brauchbares, für Menschen lesbares Datum zu erhalten, ist daher eine erhebliche Aufräumarbeit nötig, die wir in "Excel-Datumsangaben entschlüsseln" behandeln werden .
Wie diese Übungen hoffentlich gezeigt haben, ist es mit einigen cleveren Bibliotheken und ein paar Anpassungen an unserer grundlegenden Codekonfiguration möglich, mit Python schnell und einfach Daten aus einer Vielzahl von tabellenartigen Datenformaten zu verarbeiten. Gleichzeitig hoffe ich, dass diese Beispiele auch verdeutlicht haben, warum die Arbeit mit textbasierten und/oder Open-Source-Formaten fast immer vorzuziehen ist,15 weil sie oft weniger "bereinigt" und umgewandelt werden müssen, um sie in einen klaren, nutzbaren Zustand zu bringen.
Schließlich: Feste Breite
Obwohl ich es am Anfang dieses Abschnitts nicht erwähnt habe, ist eine der ältesten Versionen von tabellenartigen Daten die so genannte "feste Breite". Wie der Name schon sagt, enthält jede Datenspalte in einer Tabelle mit fester Breite eine bestimmte, vordefinierte Anzahl von Zeichen - und zwar immer diese Anzahl von Zeichen. Das bedeutet, dass die aussagekräftigen Daten in Dateien mit fester Breite oft mit zusätzlichen Zeichen, wie Leerzeichen oder Nullen, aufgefüllt werden.
Auch wenn dies in modernen Datensystemen sehr unüblich ist, wirst du wahrscheinlich immer noch auf Formate mit fester Breite stoßen, wenn du mit staatlichen Datenquellen arbeitest, deren Infrastruktur Jahrzehnte alt sein kann.16 Die US-amerikanische National Oceanic and Atmospheric Administration (NOAA), deren Ursprünge bis ins frühe 19. Jahrhundert zurückreichen, bietet zum Beispiel über ihr Global Historical Climatology Network eine Vielzahl detaillierter, aktueller Wetterdaten kostenlos online an, von denen viele in einem Format mit fester Breite veröffentlicht werden. In der Dateighcnd-stations.txt sind zum Beispiel Informationen über die eindeutige Kennung der Stationen, ihre Standorte und das Netzwerk, zu dem sie gehören, gespeichert. Um die tatsächlichen Wetterdaten zu interpretieren (von denen viele auch als Dateien mit fester Breite veröffentlicht werden), musst du die Stationsdaten mit den Wetterdaten abgleichen.
Mehr noch als bei anderen Tabellendateien kann die Arbeit mit Daten mit fester Breite besonders knifflig sein, wenn du keinen Zugriff auf die Metadaten hast, die beschreiben, wie die Datei und ihre Felder organisiert sind. Bei Dateien mit Begrenzungszeichen ist es oft möglich, die Datei in einem Texteditor zu betrachten und das verwendete Begrenzungszeichen mit einem gewissen Maß an Sicherheit zu erkennen. Im schlimmsten Fall kannst du einfach versuchen, die Datei mit verschiedenen Begrenzungszeichen zu analysieren und herausfinden, welches die besten Ergebnisse liefert. Bei Dateien mit fester Breite - vor allem bei großen Dateien - kann es leicht passieren, dass du versehentlich mehrere Datenfelder in einen Topf wirfst, wenn in der von dir geprüften Stichprobe keine Daten für ein bestimmtes Feld vorhanden sind.
Glücklicherweise sind die Metadaten der ghcnd-stations.txt-Datei, die wir als Datenquelle verwenden , in der readme.txt-Datei im selben Ordner auf der NOAA-Website enthalten.
Wenn wir uns die readme.txt-Datei ansehen, finden wir die Überschrift IV. FORMAT OF "ghcnd-stations.txt"
, die die folgende Tabelle enthält:
------------------------------ Variable Columns Type ------------------------------ ID 1-11 Character LATITUDE 13-20 Real LONGITUDE 22-30 Real ELEVATION 32-37 Real STATE 39-40 Character NAME 42-71 Character GSN FLAG 73-75 Character HCN/CRN FLAG 77-79 Character WMO ID 81-85 Character ------------------------------
Danach folgt eine detaillierte Beschreibung, was jedes Feld enthält oder bedeutet, einschließlich Informationen wie Einheiten. Dank dieses robusten Datenwörterbuchs wissen wir jetzt nicht nur, wie die Datei ghcnd-stations.txt aufgebaut ist, sondern auch, wie wir die darin enthaltenen Informationen interpretieren können. Wie wir in Kapitel 6 sehen werden, ist das Finden (oder Erstellen) eines Datenwörterbuchs ein wesentlicher Bestandteil der Bewertung oder Verbesserung der Qualität unserer Daten. Im Moment können wir uns jedoch darauf beschränken, diese Datei mit fester Breite in eine .csv-Datei umzuwandeln, wie in Beispiel 4-7 beschrieben.
Beispiel 4-7. fixed_width_parsing.py
# an example of reading data from a fixed-width file with Python.
# the source file for this example comes from NOAA and can be accessed here:
# https://www1.ncdc.noaa.gov/pub/data/ghcn/daily/ghcnd-stations.txt
# the metadata for the file can be found here:
# https://www1.ncdc.noaa.gov/pub/data/ghcn/daily/readme.txt
# import the `csv` library, to create our output file
import
csv
filename
=
"
ghcnd-stations
"
# reading from a basic text file doesn't require any special libraries
# so we'll just open the file in read format ("r") as usual
source_file
=
open
(
filename
+
"
.txt
"
,
"
r
"
)
# the built-in "readlines()" method does just what you'd think:
# it reads in a text file and converts it to a list of lines
stations_list
=
source_file
.
readlines
(
)
# create an output file for our transformed data
output_file
=
open
(
filename
+
"
.csv
"
,
"
w
"
)
# use the `csv` library's "writer" recipe to easily write rows of data
# to `output_file`, instead of reading data *from* it
output_writer
=
csv
.
writer
(
output_file
)
# create the header list
headers
=
[
"
ID
"
,
"
LATITUDE
"
,
"
LONGITUDE
"
,
"
ELEVATION
"
,
"
STATE
"
,
"
NAME
"
,
"
GSN_FLAG
"
,
"
HCNCRN_FLAG
"
,
"
WMO_ID
"
]
# write our headers to the output file
output_writer
.
writerow
(
headers
)
# loop through each line of our file (multiple "sheets" are not possible)
for
line
in
stations_list
:
# create an empty list, to which we'll append each set of characters that
# makes up a given "column" of data
new_row
=
[
]
# ID: positions 1-11
new_row
.
append
(
line
[
0
:
11
]
)
# LATITUDE: positions 13-20
new_row
.
append
(
line
[
12
:
20
]
)
# LONGITUDE: positions 22-30
new_row
.
append
(
line
[
21
:
30
]
)
# ELEVATION: positions 32-37
new_row
.
append
(
line
[
31
:
37
]
)
# STATE: positions 39-40
new_row
.
append
(
line
[
38
:
40
]
)
# NAME: positions 42-71
new_row
.
append
(
line
[
41
:
71
]
)
# GSN_FLAG: positions 73-75
new_row
.
append
(
line
[
72
:
75
]
)
# HCNCRN_FLAG: positions 77-79
new_row
.
append
(
line
[
76
:
79
]
)
# WMO_ID: positions 81-85
new_row
.
append
(
line
[
80
:
85
]
)
# now all that's left is to use the
# `writerow` function to write new_row to our output file
output_writer
.
writerow
(
new_row
)
# officially close the `.csv` file we just wrote all that data to
output_file
.
close
(
)
Da wir in der Datei nichts haben, worauf wir uns bei den Spaltenüberschriften stützen können, müssen wir sie auf der Grundlage der Informationen in der Dateireadme.txt"hardcoden". Beachte, dass ich Sonderzeichen weggelassen und Unterstriche anstelle von Leerzeichen verwendet habe, um später bei der Bereinigung und Analyse der Daten weniger Probleme zu haben.
Python betrachtet Textzeilen eigentlich nur als Listen von Zeichen. Wir können also einfach sagen, dass wir die Zeichen zwischen zwei nummerierten Indexpositionen erhalten sollen. Wie bei der Funktion
range()
wird das Zeichen an der ersten Position mitgezählt, die zweite Zahl jedoch nicht. Erinnere dich auch daran, dass Python mit der Zählung von Listen bei Null beginnt (oft als Null-Indexierung bezeichnet). Das bedeutet, dass bei jedem Eintrag die erste Zahl um eins niedriger ist als die in den Metadaten angegebene, aber die rechte Zahl gleich bleibt.
Wenn du das Skript in Beispiel 4-7 ausführst und deine .csv-Ausgabedatei in einem Tabellenkalkulationsprogramm öffnest, wirst du feststellen, dass die Werte in einigen der Spalten nicht einheitlich formatiert sind. Zum Beispiel sind in der Spalte ELEVATION
die Zahlen mit Dezimalstellen linksbündig, aber die ohne Dezimalstellen rechtsbündig. Was ist hier los?
Auch hier ist es aufschlussreich, die Datei in einem Texteditor zu öffnen. Obwohl die von uns erstellte Datei technisch gesehen kommagetrennt ist, enthalten die Werte, die wir in jede unserer neu "getrennten" Spalten eingeben, immer noch die zusätzlichen Leerzeichen, die in der ursprünglichen Datei vorhanden waren. Daher sieht unsere neue Datei immer noch ziemlich "fest" aus.
Mit anderen Worten: Die Konvertierung unserer Datei in eine .csv-Datei erzeugt nicht "automatisch" sinnvolle Datentypen in unserer Ausgabedatei, wie wir bei den Excel-"Daten" gesehen haben. Die Bestimmung des Datentyps, den jedes Feld haben sollte, und die Bereinigung der Felder, damit sie sich angemessen verhalten, ist Teil des Datenbereinigungsprozesses, den wir in Kapitel 7 behandeln .
Feed-basierte Daten-Web-gesteuerte Live-Updates
Die Struktur von tabellenartigen Datenformaten eignet sich gut für eine Welt, in der die meisten "Daten" bereits gefiltert, überarbeitet und zu einer relativ gut organisierten Sammlung von Zahlen, Daten und kurzen Zeichenfolgen verarbeitet wurden. Mit dem Aufkommen des Internets entstand jedoch die Notwendigkeit, große Mengen an "freiem" Text zu übermitteln, wie er z. B. in Nachrichten und Social Media Feeds vorkommt. Da diese Art von Dateninhalt in der Regel Zeichen wie Kommas, Punkte und Anführungszeichen enthält, die sich auf die semantische Bedeutung auswirken, ist es im besten Fall problematisch, sie in ein herkömmliches Format mit Trennzeichen einzupassen. Außerdem widerspricht die horizontale Ausrichtung von abgegrenzten Formaten (bei denen viel von links nach rechts gescrollt wird) den Konventionen des vertikalen Scrollens im Internet. Feed-basierte Datenformate wurden entwickelt, um diese beiden Einschränkungen zu umgehen.
Im Großen und Ganzen gibt es zwei Arten von Feed-basierten Datenformaten: XML und JSON. Beides sind textbasierte Formate, die es dem Datenanbieter ermöglichen, seine eigene Datenstruktur zu definieren. Das macht sie extrem flexibel und damit nützlich für die große Vielfalt an Inhalten, die auf Websites und Plattformen mit Internetanschluss zu finden sind. Egal, ob du sie online findest oder eine Kopie lokal speicherst, du erkennst diese Formate zum Teil an ihren koordinierten Dateierweiterungen .xml und .json:
- .xml
-
Extensible Markup Language umfasst eine breite Palette von Dateiformaten, darunter .rss, .atom und sogar .html. Als die allgemeinste Auszeichnungssprache ist XML extrem flexibel und war vielleicht das ursprüngliche Datenformat für webbasierte Datenfeeds.
- .json
-
JavaScript Object Notation-Dateien sind etwas neuer als XML-Dateien, dienen aber einem ähnlichen Zweck. Im Allgemeinen sind JSON-Dateien weniger beschreibend (und daher kürzer und prägnanter) als XML-Dateien. Das bedeutet, dass sie eine fast identische Datenmenge wie eine XML-Datei kodieren können und dabei weniger Platz benötigen, was besonders für die Geschwindigkeit im mobilen Web wichtig ist. Ebenso wichtig ist die Tatsache, dass JSON-Dateien im Wesentlichen große
object
Datentypen in der Programmiersprache JavaScript sind - der Sprache, die vielen, wenn nicht sogar den meisten Websites und mobilen Apps zugrunde liegt. Das bedeutet, dass das Parsen von JSON-formatierten Daten für jede Website oder jedes Programm, das JavaScript verwendet, sehr einfach ist, besonders im Vergleich zu XML. Glücklicherweise sind die JavaScriptobject
Datentypen den Pythondict
Datentypen sehr ähnlich, was die Arbeit mit JSON in Python ebenfalls sehr einfach macht.
Bevor wir uns damit befassen, wie man mit diesen Dateitypen in Python arbeitet, wollen wir uns ansehen, wann wir Daten vom Typ Feed brauchen und wo wir sie finden, wenn wir sie brauchen.
Wann man mit futtermitteltypischen Daten arbeitet
Im Sinne von sind Feed-Daten für das 21. Jahrhundert das, was Tabellendaten für das 20. Jahrhundert waren: Die schiere Menge an Feed-Daten, die täglich im Internet generiert, gespeichert und ausgetauscht werden, ist wahrscheinlich millionenfach größer als die aller Tabellendaten der Welt zusammengenommen.
Aus Sicht der Datenverarbeitung brauchst du in der Regel Feed-Daten, wenn das Phänomen, das du untersuchen willst, zeitkritisch ist und häufig und/oder unvorhersehbar aktualisiert wird. Diese Art von Daten wird in der Regel als Reaktion auf einen menschlichen oder natürlichen Prozess generiert, wie z. B. (wieder einmal) ein Posting in den sozialen Medien, die Veröffentlichung einer Nachrichtenmeldung oder die Aufzeichnung eines Erdbebens.
Sowohl dateibasierte, tabellenartige Daten als auch webbasierte, feedartige Daten können historische Informationen enthalten, aber wie wir zu Beginn dieses Kapitels besprochen haben, spiegeln erstere in der Regel den Stand der Daten zu einem bestimmten Zeitpunkt wider. Letztere sind dagegen in der Regel in "umgekehrter chronologischer" (jüngster zuerst) Reihenfolge organisiert, wobei der erste Eintrag derjenige Datensatz ist, der zu dem Zeitpunkt, an dem du auf die Daten zugreifst, zuletzt erstellt wurde, und nicht ein bestimmtes Veröffentlichungsdatum.
Wo findet man futtermittelähnliche Daten?
Feed-ähnliche Daten sind fast ausschließlich im Internet zu finden, oft unter speziellen URLs, die als API-Endpunkte(Application Programming Interface) bekannt sind. Wir werden in Kapitel 5 näher auf die Arbeit mit APIs eingehen, aber im Moment musst du nur wissen, dass API-Endpunkte eigentlich nur Webseiten sind, die nur Daten enthalten: Du kannst viele von ihnen mit einem normalen Webbrowser ansehen, aber du wirst nur die Daten selbst sehen. Einige API-Endpunkte geben sogar unterschiedliche Daten zurück, je nachdem, welche Informationen du an sie sendest. Das macht die Arbeit mit feedartigen Daten so flexibel: Wenn du nur ein paar Wörter oder Werte in deinem Code änderst, kannst du auf einen völlig anderen Datensatz zugreifen!
Um APIs zu finden, die Daten in Form von Feeds anbieten, sind keine besonderen Suchstrategien erforderlich, denn die Websites und Dienste, die APIs anbieten, wollen normalerweise , dass du sie findest. Und warum? Ganz einfach: Wenn jemand einen Code schreibt, der eine API nutzt, hat das Unternehmen, das sie zur Verfügung stellt, (normalerweise) einen Nutzen davon - selbst wenn dieser Nutzen nur darin besteht, dass die Öffentlichkeit mehr erfährt. In den Anfängen von Twitter haben zum Beispiel viele Webentwickler Programme geschrieben, die die Twitter-API nutzen. Dadurch wurde die Plattform nicht nur nützlicher , sondern das Unternehmen sparte sich auch die Kosten und den Aufwand, herauszufinden, was die Nutzerinnen und Nutzer wollten, und es dann zu entwickeln. Dadurch, dass die API so viele Daten auf der Plattform (zunächst) kostenlos zur Verfügung stellte, entstanden mehrere Unternehmen, die Twitter schließlich aufkaufte - obwohl viele andere auch ihr Geschäft aufgeben mussten, als entweder die API oder die Nutzungsbedingungen geändert wurden.17 Dies verdeutlicht eines der besonderen Probleme, die bei der Arbeit mit jeder Art von Daten auftreten können, insbesondere aber bei Daten, die von gewinnorientierten Unternehmen als Feeds zur Verfügung gestellt werden: Sowohl die Daten als auch dein Recht auf Zugang zu ihnen können sich jederzeit und ohne Vorwarnung ändern. Obwohl Feed-Datenquellen sehr wertvoll sind, sind sie in mehr als einer Hinsicht vergänglich.
Verarbeitung von Feed-Type-Daten mit Python
Wie bei tabellenartigen Daten wird der Umgang mit feedartigen Daten in Python durch eine Kombination aus hilfreichen Bibliotheken und der Tatsache ermöglicht, dass Formate wie JSON bereits bestehenden Datentypen in der Programmiersprache Python ähneln. Außerdem werden wir in den folgenden Abschnitten sehen, dass XML und JSON für unsere Zwecke oft funktional austauschbar sind (obwohl viele APIs Daten nur in dem einen oder anderen Format anbieten).
XML: Ein Markup für alle Fälle
Auszeichnungssprachen gehören zu den ältesten standardisierten Dokumentenformaten in der Informatik und wurden mit dem Ziel entwickelt, textbasierte Dokumente zu erstellen, die sowohl von Menschen als auch von Maschinen leicht gelesen werden können. XML wurde in den 1990er Jahren zu einem immer wichtigeren Teil der Internet-Infrastruktur, da die Vielfalt der Geräte, die auf webbasierte Informationen zugreifen und diese anzeigen, die Trennung von Inhalt (z. B. Text und Bilder) und Formatierung (z. B. Seitenlayout) immer wichtiger machte. Im Gegensatz zu einem HTML-Dokument, in dem Inhalt und Formatierung vollständig vermischt sind, sagt ein XML-Dokument so gut wie nichts darüber aus, wie die Informationen angezeigt werden sollen. Stattdessen fungieren die Tags und Attribute als Metadaten, die angeben, welche Art von Informationen zusammen mit den Daten selbst enthält.
Um ein Gefühl dafür zu bekommen, wie XML aussieht, wirf einen Blick auf Beispiel 4-8.
Beispiel 4-8. Ein Beispiel für ein XML-Dokument
<?xml version="1.0" encoding="UTF-8"?>
<mainDoc>
<!--This is a comment-->
<elements>
<element1>
This is some text in the document.</element1>
<element2>
This is some other data in the document.</element2>
<element3
someAttribute=
"aValue"
/>
</elements>
<someElement
anAttribute=
"anotherValue"
>
More content</someElement>
</mainDoc>
Hier gibt es mehrere Dinge zu beachten. Die allererste Zeile wird genannt und ist die Deklaration des Dokumenttyps (oder doc-type
); sie teilt uns mit, dass der Rest des Dokuments als XML interpretiert werden soll (im Gegensatz zu anderen Web- oder Auszeichnungssprachen, von denen wir einige später in diesem Kapitel behandeln werden).
Beginnend mit der Zeile:
<mainDoc>
sind wir bei der Substanz des Dokuments selbst. XML ist unter anderem deshalb so flexibel, weil es nur zwei echte grammatikalische Strukturen enthält, die beide in Beispiel 4-8 enthalten sind:
- tags
-
Tags können entweder gepaart (wie
element1
,element2
,someElement
, oder sogarmainDoc
) oder selbst geschlossen (wieelement3
) sein. Der Name eines Tags wird immer von Carets umschlossen (<>
). Bei einem geschlossenen Tag folgt auf das öffnende Caret sofort ein Schrägstrich (/
). Ein übereinstimmendes Tag-Paar oder ein in sich geschlossenes Tag werden auch als XML-Elemente beschrieben. - Attribute
-
Attribute können nur innerhalb von Tags existieren (wie
anAttribute
). Attribute sind eine Art Schlüssel/Wert-Paar, bei dem der Attributname (oder Schlüssel) unmittelbarvon einem Gleichheitszeichen (=
) gefolgt wird, gefolgt von dem Wert, der von doppeltenAnführungszeichen (""
) umgeben ist.
Ein XML-Element ist alles, was zwischen einem öffnenden Tag und dem dazugehörigen schließenden Tag enthalten ist (z. B. <elements>
und </elements>
). Ein XML-Element kann viele Tags enthalten, von denen jedes auch andere Tags enthalten kann. Jedes Tag kann auch eine beliebige Anzahl von Attributen haben (auch keine). Ein selbst geschlossenes Tag wird ebenfalls als Element betrachtet.
Die einzige andere sinnvolle Regel für die Strukturierung von XML-Dokumenten ist, dass, wenn Tags innerhalb anderer Tags erscheinen, das zuletzt geöffnete Tag zuerst geschlossen werden muss. Mit anderen Worten: Dies ist zwar eine legitime XML-Struktur:
<outerElement>
<!-- Notice that that the `innerElement1` is closed
before the `innerElement2` tag is opened -->
<innerElement1>
Some content</innerElement1>
<innerElement2>
More content</innerElement2>
</outerElement>
ist dies nicht:
<outerElement>
<!-- NOPE! The `innerElement2` tag was opened
before the `innerElement1` tag was closed -->
<innerElement1>
Some content<innerElement2>
More content</innerElement1>
</innerElement2>
</outerElement>
Dieses Prinzip " zuletzt geöffnet, zuerst geschlossen" wird auch als Verschachtelung bezeichnet, ähnlich wie die "verschachtelten" for...in
Schleifen aus Abbildung 2-3.18 Die Verschachtelung ist in XML-Dokumenten besonders wichtig, weil sie einen der wichtigsten Mechanismen bestimmt, mit dem wir XML-Dokumente (und andere Auszeichnungssprachen) mit Code lesen oder parsen. In einem XML-Dokument ist das erste Element nach der doc-type
Deklaration als Root-Element bekannt. Wenn das XML-Dokument formatiert wurde, wird das Wurzelelement immer linksbündig ausgerichtet, und jedes Element, das direkt in diesem Element verschachtelt ist, wird eine Ebene nach rechts eingerückt und als Kindelement bezeichnet. In Beispiel 4-8 wäre also <mainDoc>
das Wurzelelement und <elements>
wäre sein Kind. Genauso ist <mainDoc>
das Elternelement von <elements>
(Beispiel 4-9).
Beispiel 4-9. Ein annotiertes XML-Dokument
<?xml version="1.0" encoding="UTF-8"?>
<mainDoc>
<!--`mainDoc` is the *root* element, and `elements` is its *child*-->
<elements>
<!-- `elements` is the *parent* of `element1`, `element2`, and
`element3`, which are *siblings* of one another -->
<element1>
This is text data in the document.</element1>
<element2>
This is some other data in the document.</element2>
<element3
someAttribute=
"aValue"
/>
</elements>
<!-- `someElement` is also a *child* of `mainDoc`,
and a *sibling* of `elements` -->
<someElement
anAttribute=
"anotherValue"
>
More content</someElement>
</mainDoc>
Angesichts dieses Trends zum genealogischen Jargon fragst du dich vielleicht: Wenn <elements>
die Mutter von <element3>
und <mainDoc>
die Mutter von <elements>
ist, ist dann <mainDoc>
der Großvater von <element3>
? Die Antwort lautet: Ja, aber nein. Obwohl <mainDoc>
das "Elternteil" des "Elternteils" von <element3>
ist, wird der Begriff "Großelternteil" bei der Beschreibung einer XML-Struktur nie verwendet - das könnte schnell kompliziert werden! Stattdessen beschreiben wir die Beziehung einfach als genau das: <mainDoc>
ist das Elternteil des Elternteils von <element3>
.
Glücklicherweise sind XML-Attribute nicht so komplex: Sie sind einfach Schlüssel/Wert-Paare und können nur innerhalb von XML-Tags existieren:
<element3
someAttribute=
"aValue"
/>
Beachte, dass es auf beiden Seiten des Gleichheitszeichens kein Leerzeichen gibt, genauso wie es kein Leerzeichen zwischen den Carets und Schrägstrichen eines Element-Tags gibt.
Wie beim Schreiben auf Englisch (oder in Python) ist die Frage, wann man Tags und wann man Attribute für eine bestimmte Information verwendet, weitgehend eine Frage der Vorliebe und des Stils. Die Beispiele 4-10 und 4-11 enthalten zum Beispiel dieselben Informationen über dieses Buch, sind aber jeweils etwas anders aufgebaut.
Beispiel 4-10. Beispiel für XML-Buchdaten - weitere Attribute
<aBook>
<bookURL
url=
"https://www.oreilly.com/library/view/practical-python-data/
9781492091493"
/>
<bookAbstract>
There are awesome discoveries to be made and valuable stories to be told in datasets--and this book will help you uncover them.</bookAbstract>
<pubDate
date=
"2022-02-01"
/>
</aBook>
Beispiel 4-11. Beispiel XML-Buch data-more Elemente
<aBook>
<bookURL>
https://www.oreilly.com/library/view/practical-python-data/9781492091493</bookURL>
<bookAbstract>
There are awesome discoveries to be made and valuable stories to be told in datasets--and this book will help you uncover them.</bookAbstract>
<pubDate>
2022-02-01</pubDate>
</aBook>
Dieser Grad an Flexibilität bedeutet, dass XML sehr anpassungsfähig an eine Vielzahl von Datenquellen und Formatierungsvorlieben ist. Gleichzeitig kann es leicht zu einer Situation kommen, in der für jede neue Datenquelle ein eigener Code geschrieben werden muss. Das wäre natürlich ein ziemlich ineffizientes System, vor allem, wenn viele Menschen und Organisationen ziemlich ähnliche Arten von Daten veröffentlichen.
Es ist daher nicht verwunderlich, dass es eine große Anzahl von XML-Spezifikationen gibt, die zusätzliche Regeln für die Formatierung von XML-Dokumenten für bestimmte Datentypen festlegen. Ich hebe hier ein paar bemerkenswerte Beispiele hervor, da du bei deiner Arbeit mit Daten vielleicht auf diese Formate stößt. Trotz der unterschiedlichen Formatnamen und Dateierweiterungen können wir sie alle mit derselben Methode parsen, die wir uns in Beispiel 4-12 ansehen werden:
- RSS
-
Really Simple Syndication ist eine XML-Spezifikation, die in den späten 1990er Jahren für Nachrichteninformationen eingeführt wurde. Das .atom XML-Format wird ebenfalls häufig für diese Zwecke verwendet.
- KML
-
Keyhole Markup Language ist ein international anerkannter Standard für die Kodierung von zwei- und dreidimensionalen geografischen Daten und ist mit Tools wie Google Earth kompatibel.
- SVG
-
Skalierbare Vektorgrafiken ist ein häufig verwendetes Format für Grafiken im Internet, da es Zeichnungen ohne Qualitätsverlust skalieren kann. Viele gängige Grafikprogramme können .svg-Dateien ausgeben, die dann in Webseiten und andere Dokumente eingefügt werden können, die auf einer Vielzahl von Bildschirmgrößen und Geräten gut aussehen.
- EPUB
-
Das elektronische Publikationsformat (.epub) ist der weithin akzeptierte offene Standard für die Veröffentlichung digitaler Bücher.
Wie du aus der vorangegangenen Liste ersehen kannst, weisen einige gängige XML-Formate eindeutig auf ihre Beziehung zu XML hin, viele andere dagegen nicht.19
Nachdem wir nun einen Überblick über die Funktionsweise von XML-Dateien haben, wollen wir uns ansehen, was nötig ist, um sie mit Python zu parsen. Obwohl Python über einige integrierte Tools zum Parsen von XML verfügt, werden wir eine Bibliothek namens lxml ( ) verwenden, die sich besonders für das schnelle Parsen großer XML-Dateien eignet. Auch wenn unsere folgenden Beispieldateien recht klein sind, sollten Sie wissen, dass wir im Grunde denselben Code verwenden könnten, selbst wenn unsere Dateien wesentlich größer wären.
Zunächst verwenden wir eine XML-Version der "U6"-Arbeitslosendaten, die ich bereits von der FRED-Website über deren API heruntergeladen habe.20 Nachdem du eine Kopie dieser Datei von Google Drive heruntergeladen hast, kannst du das Skript in Beispiel 4-12 verwenden, um die XML-Quelle in eine .csv-Datei zu konvertieren. Beginne mit der pip install
:
pip install lxml
Beispiel 4-12. xml_parsing.py
# an example of reading data from an .xml file with Python, using the "lxml"
# library.
# first, you'll need to pip install the lxml library:
# https://pypi.org/project/lxml/
# a helpful tutorial can be found here: https://lxml.de/tutorial.html
# the data used here is an instance of
# https://api.stlouisfed.org/fred/series/observations?series_id=U6RATE& \
# api_key=YOUR_API_KEY_HERE
# specify the "chapter" of the `lxml` library you want to import,
# in this case, `etree`, which stands for "ElementTree"
from
lxml
import
etree
# import the `csv` library, to create our output file
import
csv
# choose a filename
filename
=
"
U6_FRED_data
"
# open our data file in read format, using "rb" as the "mode"
xml_source_file
=
open
(
filename
+
"
.xml
"
,
"
rb
"
)
# pass our xml_source_file as an ingredient to the `lxml` library's
# `etree.parse()` method and store the result in a variable called `xml_doc`
xml_doc
=
etree
.
parse
(
xml_source_file
)
# start by getting the current xml document's "root" element
document_root
=
xml_doc
.
getroot
(
)
# let's print it out to see what it looks like
(
etree
.
tostring
(
document_root
)
)
# confirm that `document_root` is a well-formed XML element
if
etree
.
iselement
(
document_root
)
:
# create our output file, naming it "xml_"+filename+".csv
output_file
=
open
(
"
xml_
"
+
filename
+
"
.csv
"
,
"
w
"
)
# use the `csv` library's "writer" recipe to easily write rows of data
# to `output_file`, instead of reading data *from* it
output_writer
=
csv
.
writer
(
output_file
)
# grab the first element of our xml document (using `document_root[0]`)
# and write its attribute keys as column headers to our output file
output_writer
.
writerow
(
document_root
[
0
]
.
attrib
.
keys
(
)
)
# now, we need to loop through every element in our XML file
for
child
in
document_root
:
# now we'll use the `.values()` method to get each element's values
# as a list and then use that directly with the `writerow` recipe
output_writer
.
writerow
(
child
.
attrib
.
values
(
)
)
# officially close the `.csv` file we just wrote all that data to
output_file
.
close
(
)
In diesem Fall gibt es in der Datendatei nichts, was wir als Dateinamen verwenden können (z. B. einen Blattnamen), also erstellen wir einfach einen eigenen und verwenden ihn, um unsere Quelldaten zu laden und unsere Ausgabedatei zu benennen.
Ich habe gelogen! Die Werte, die wir für den "Modus" der Funktion
open()
verwendet haben, gehen davon aus, dass wir die Quelldatei als Text interpretieren wollen. Da die lxml-Bibliothek aber Byte-Daten und keinen Text erwartet, verwenden wirrb
("read bytes") als "Modus".Es gibt eine Menge fehlerhaftes XML da draußen! Um sicherzugehen, dass das, was wie gutes XML aussieht , auch wirklich eines ist, rufen wir das "Root"-Element des aktuellen XML-Dokuments ab und stellen sicher, dass es funktioniert.
Da unser XML derzeit als Byte-Daten gespeichert ist, müssen wir die Methode
etree.tostring()
verwenden, um es als solche zu betrachten.Dank der lxml hat jedes XML-Element (oder "Knoten") in unserem Dokument eine Eigenschaft namens
attrib
, deren Datentyp ein Python-Wörterbuch ist (dict
). Die Methode.keys()
gibt alle Attributschlüssel unseres XML-Elements als Liste zurück. Da alle Elemente in unserer Quelldatei identisch sind, können wir die Schlüssel des ersten Elements verwenden, um eine "Kopfzeile" für unsere Ausgabedatei zu erstellen.Die lxml-Bibliothek wandelt XML-Elemente in Listen um, sodass wir eine einfache
for...in
-Schleife verwenden können, um die Elemente in unserem Dokument durchzugehen.
Die XML-Version unserer Arbeitslosendaten ist sehr einfach strukturiert: Sie besteht nur aus einer Liste von Elementen, und alle Werte, auf die wir zugreifen wollen, sind als Attribute gespeichert. Daher konnten wir die Attributwerte aus jedem Element als Liste herausziehen und sie mit nur einer Codezeile direkt in unsere CSV-Datei schreiben.
Natürlich gibt es viele Gelegenheiten, in denen wir Daten aus komplexeren XML-Formaten abrufen wollen, insbesondere aus RSS oder Atom. Um zu sehen, wie man mit etwas komplexeren Formaten umgeht, werden wir in Beispiel 4-13 den RSS-Feed der BBC mit Wissenschafts- und Umweltberichten analysieren, den du von meinem Google Drive herunterladen kannst.
Beispiel 4-13. rss_parsing.py
# an example of reading data from an .xml file with Python, using the "lxml"
# library.
# first, you'll need to pip install the lxml library:
# https://pypi.org/project/lxml/
# the data used here is an instance of
# http://feeds.bbci.co.uk/news/science_and_environment/rss.xml
# specify the "chapter" of the `lxml` library you want to import,
# in this case, `etree`, which stands for "ElementTree"
from
lxml
import
etree
# import the `csv` library, to create our output file
import
csv
# choose a filename, for simplicity
filename
=
"
BBC News - Science & Environment XML Feed
"
# open our data file in read format, using "rb" as the "mode"
xml_source_file
=
open
(
filename
+
"
.xml
"
,
"
rb
"
)
# pass our xml_source_file as an ingredient to the `lxml` library's
# `etree.parse()` method and store the result in a variable called `xml_doc`
xml_doc
=
etree
.
parse
(
xml_source_file
)
# start by getting the current xml document's "root" element
document_root
=
xml_doc
.
getroot
(
)
# if the document_root is a well-formed XML element
if
etree
.
iselement
(
document_root
)
:
# create our output file, naming it "rss_"+filename+".csv"
output_file
=
open
(
"
rss_
"
+
filename
+
"
.csv
"
,
"
w
"
)
# use the `csv` library's "writer" recipe to easily write rows of data
# to `output_file`, instead of reading data *from* it
output_writer
=
csv
.
writer
(
output_file
)
# document_root[0] is the "channel" element
main_channel
=
document_root
[
0
]
# the `find()` method returns *only* the first instance of the element name
article_example
=
main_channel
.
find
(
'
item
'
)
# create an empty list in which to store our future column headers
tag_list
=
[
]
for
child
in
article_example
.
iterdescendants
(
)
:
# add each tag to our would-be header list
tag_list
.
append
(
child
.
tag
)
# if the current tag has any attributes
if
child
.
attrib
:
# loop through the attribute keys in the tag
for
attribute_name
in
child
.
attrib
.
keys
(
)
:
# append the attribute name to our `tag_list` column headers
tag_list
.
append
(
attribute_name
)
# write the contents of `tag_list` to our output file as column headers
output_writer
.
writerow
(
tag_list
)
# now we want to grab *every* <item> element in our file
# so we use the `findall()` method instead of `find()`
for
item
in
main_channel
.
findall
(
'
item
'
)
:
# empty list for holding our new row's content
new_row
=
[
]
# now we'll use our list of tags to get the contents of each element
for
tag
in
tag_list
:
# if there is anything in the element with a given tag name
if
item
.
findtext
(
tag
)
:
# append it to our new row
new_row
.
append
(
item
.
findtext
(
tag
)
)
# otherwise, make sure it's the "isPermaLink" attribute
elif
tag
==
"
isPermaLink
"
:
# grab its value from the <guid> element
# and append it to our row
new_row
.
append
(
item
.
find
(
'
guid
'
)
.
get
(
"
isPermaLink
"
)
)
# write the new row to our output file!
output_writer
.
writerow
(
new_row
)
# officially close the `.csv` file we just wrote all that data to
output_file
.
close
(
)
Wie immer müssen wir abwägen, was wir programmatisch bearbeiten und was wir visuell überprüfen. Wenn wir uns unsere Daten ansehen, wird deutlich, dass die Informationen zu jedem Artikel in einem separaten
item
Element gespeichert sind. Da das Kopieren der einzelnen Tag- und Attributnamen jedoch zeitaufwändig und fehleranfällig wäre, gehen wir einitem
Element durch und erstellen eine Liste aller darin enthaltenen Tags (und Attribute), die wir dann als Spaltenüberschriften für unsere .csv-Ausgabedatei verwenden.Die Methode
iterdescendants()
ist eine Besonderheit der lxml-Bibliothek. Sie gibt nur die Nachkommen eines XML-Elements zurück, während die gebräuchlichere Methodeiter()
sowohl das Element selbst als auch seine Kinder oder "Nachkommen" zurückgeben würde.Mit
child.tag
wird der Text des Tagnamens des Kindelements abgerufen. Zum Beispiel wird für das Element<pubDate>`
pubDate
zurückgegeben.Nur ein Tag in unserem
<item>
Element hat ein Attribut, aber wir wollen es trotzdem in unsere Ausgabe aufnehmen.Die Methode
keys()
liefert uns eine Liste aller Schlüssel in der Liste der Attribute, die zum Tag gehören. Achte darauf, dass du den Namen als String bekommst (statt einer Liste mit nur einem Eintrag).Die ganze
article_example
for
Schleife war nur dazu da, umtag_list
zu bauen - aber das war es wert!
Wie du in Beispiel 4-13 sehen kannst, ist das Parsen von selbst etwas komplexerem XML in Python mit Hilfe der lxml-Bibliothek immer noch ziemlich einfach.
XML ist zwar immer noch ein beliebtes Datenformat für Newsfeeds und eine Handvoll anderer Dateitypen, aber es gibt eine Reihe von Merkmalen, die es für die Verarbeitung der großen Datenmengen im modernen Web nicht gerade ideal machen.
Zunächst einmal ist da das einfache Problem der Größe. XML-Dateien können zwar wunderbar beschreibend sein und machen separate Datenwörterbücher überflüssig, aber die Tatsache, dass die meisten Elemente sowohl einen öffnenden als auch einen schließenden Tag enthalten (z. B. <item>
und </item>
), macht XML auch etwas langatmig: Es gibt eine Menge Text in einem XML-Dokument, der kein Inhalt ist. Das ist keine große Sache, wenn dein Dokument nur ein paar Dutzend oder sogar ein paar Tausend Elemente hat, aber wenn du versuchst, Millionen oder Milliarden von Beiträgen im Social Web zu verarbeiten, kann all der überflüssige Text die Dinge wirklich verlangsamen.
Zweitens ist es zwar nicht besonders schwierig, XML in andere Datenformate umzuwandeln, aber der Prozess ist auch nicht gerade nahtlos. Die lxml-Bibliothek (und andere) macht das Parsen von XML mit Python ziemlich einfach, aber die gleiche Aufgabe mit weborientierten Sprachen wie JavaScript ist umständlich und mühsam. Angesichts der weiten Verbreitung von JavaScript im Web ist es nicht verwunderlich, dass irgendwann ein Feed-Datenformat entwickelt wird, das nahtlos mit JavaScript funktioniert. Wie wir im nächsten Abschnitt sehen werden, werden viele der Einschränkungen von XML als Datenformat durch die object
-ähnliche Natur des .json-Formats überwunden, das derzeit das beliebteste Format für feedartige Daten im Internet ist .
JSON: Webdaten, die nächste Generation
JSON ähnelt XML insofern, als es verwandte Informationen in Datensätzen und Feldern verschachtelt. JSON ist auch relativ gut lesbar, obwohl die Tatsache, dass es keine Kommentare unterstützt, bedeutet, dass JSON-Feeds möglicherweise robustere Datenwörterbücher als XML-Dokumente erfordern.
Um loszulegen, schauen wir uns das kleine JSON-Dokument in Beispiel 4-14 an.
Beispiel 4-14. Beispiel für ein JSON-Dokument
{
"author"
:
"Susan E. McGregor"
,
"book"
:
{
"bookURL"
:
"https://www.oreilly.com/library/view/practical-python-data/
9781492091493/"
,
"bookAbstract"
:
"There are awesome discoveries to be made and valuable
stories to be told in datasets--and this book will help you uncover
them."
,
"pubDate"
:
"2022-02-01"
},
"papers"
:
[{
"paperURL"
:
"https://www.usenix.org/conference/usenixsecurity15/
technical-sessions/presentation/mcgregor"
,
"paperTitle"
:
"Investigating the computer security practices and needs
of journalists"
,
"pubDate"
:
"2015-08-12"
},
{
"paperURL"
:
"https://www.aclweb.org/anthology/W18-5104.pdf"
,
"paperTitle"
:
"Predictive embeddings for hate speech detection on
twitter"
,
"pubDate"
:
"2018-10-31"
}
]
}
Wie bei XML sind die grammatikalischen "Regeln" von JSON recht einfach: Es gibt nur drei verschiedene Datenstrukturen in JSON-Dokumenten, die alle in Beispiel 4-14 vorkommen:
- Schlüssel/Wertpaare
-
Technisch gesehen ist alles in einem JSON-Dokument ein Schlüssel/Wert-Paar, wobei der Schlüssel in Anführungszeichen links von einem Doppelpunkt (
:
) steht und der Wert das ist, was rechts von dem Doppelpunkt steht. Beachte, dass Schlüssel immer Strings sein müssen, während Werte Strings (wie inauthor
), Objekte (wie inbook
) oder Listen (wie inpapers
) sein können. - Objekte
-
Diese werden mit Paaren geschweifter Klammern geöffnet und geschlossen (
{}
). In Beispiel 4-14 gibt es insgesamt vier Objekte: das Dokument selbst (gekennzeichnet durch die linksbündigen geschweiften Klammern), das Objektbook
und die beiden unbenannten Objekte in der Listepapers
. - Listen
-
Diese sind von eckigen Klammern umschlossen (
[]
) und können nur durch Komma getrennte Objekte enthalten.
Obwohl XML und JSON zur Kodierung der gleichen Daten verwendet werden können, gibt es einige bemerkenswerte Unterschiede in den Möglichkeiten. JSON-Dateien enthalten zum Beispiel keine doc-type
Spezifikation und können auch keine Kommentare enthalten. Während XML-Listen in gewisser Weise implizit sind (jedes wiederholte Element funktioniert wie eine Liste), müssen Listen in JSON durch eckige Klammern ([]
) angegeben werden.
Obwohl JSON mit Blick auf JavaScript entwickelt wurde, ist dir vielleicht aufgefallen, dass seine Strukturen den Python-Typen dict
und list
sehr ähnlich sind. Das trägt dazu bei, dass das Parsen von JSON sowohl mit Python als auch mit JavaScript (und einer Reihe anderer Sprachen) sehr einfach ist.
Um zu sehen, wie einfach das ist, analysieren wir in Beispiel 4-15 die gleichen Daten wie in Beispiel 4-12, allerdings im .json-Format, das ebenfalls von der FRED-API bereitgestellt wird. Du kannst die Datei über diesen Google Drive-Link herunterladen: https://drive.google.com/file/d/1Mpb2f5qYgHnKcU1sTxTmhOPHfzIdeBsq/view?usp=sharing.
Beispiel 4-15. json_parsing.py
# a simple example of reading data from a .json file with Python,
# using the built-in "json" library. The data used here is an instance of
# https://api.stlouisfed.org/fred/series/observations?series_id=U6RATE& \
# file_type=json&api_key=YOUR_API_KEY_HERE
# import the `json` library, since that's our source file format
import
json
# import the `csv` library, to create our output file
import
csv
# choose a filename
filename
=
"
U6_FRED_data
"
# open the file in read format ("r") as usual
json_source_file
=
open
(
filename
+
"
.json
"
,
"
r
"
)
# pass the `json_source_file` as an ingredient to the json library's `load()`
# method and store the result in a variable called `json_data`
json_data
=
json
.
load
(
json_source_file
)
# create our output file, naming it "json_"+filename
output_file
=
open
(
"
json_
"
+
filename
+
"
.csv
"
,
"
w
"
)
# use the `csv` library's "writer" recipe to easily write rows of data
# to `output_file`, instead of reading data *from* it
output_writer
=
csv
.
writer
(
output_file
)
# grab the first element (at position "0"), and use its keys as the column headers
output_writer
.
writerow
(
list
(
json_data
[
"
observations
"
]
[
0
]
.
keys
(
)
)
)
for
obj
in
json_data
[
"
observations
"
]
:
# we'll create an empty list where we'll put the actual values of each object
obj_values
=
[
]
# for every `key` (which will become a column), in each object
for
key
,
value
in
obj
.
items
(
)
:
# let's print what's in here, just to see how the code sees it
(
key
,
value
)
# add the values to our list
obj_values
.
append
(
value
)
# now we've got the whole row, write the data to our output file
output_writer
.
writerow
(
obj_values
)
# officially close the `.csv` file we just wrote all that data to
output_file
.
close
(
)
Da die json-Bibliothek jedes Objekt als Dictionary-View-Objekt interpretiert, müssen wir Python sagen, dass es mit der Funktion
list()
in eine normale Liste umgewandelt werden soll.In den meisten Fällen ist es am einfachsten, den Namen (oder "Schlüssel") des Haupt-JSON-Objekts in unserem Dokument zu finden, indem wir es uns einfach ansehen. Da JSON-Daten jedoch oft in einer einzigen Zeile dargestellt werden, können wir einen besseren Eindruck von ihrer Struktur bekommen, wenn wir sie in JSONLint einfügen. So können wir sehen, dass unsere Zieldaten eine Liste sind, deren Schlüssel
observations
ist.Aufgrund der Funktionsweise der json-Bibliothek erhalten wir, wenn wir versuchen, die Zeilen direkt zu schreiben, die Werte, die mit
dict
gekennzeichnet sind, und nicht die Datenwerteselbst. Wir müssen also eine weitere Schleife erstellen, die jeden Wert in jedemjson
Objekt einzeln durchgeht und diesen Wert an unsereobj_values
Liste anhängt.
Obwohl JSON nicht ganz so gut lesbar ist wie XML, hat es andere Vorteile, die wir bereits angesprochen haben, wie z. B. eine geringere Dateigröße und eine breitere Code-Kompatibilität. JSON ist zwar nicht so anschaulich wie XML, aber JSON-Datenquellen (oft APIs) sind in der Regel gut dokumentiert, so dass man nicht so einfach auf einSchlüssel/Wert-Paar schließen muss. Wie bei jeder Arbeit mit Daten besteht der erste Schritt beim Umgang mit JSON-formatierten Daten jedoch darin, ihren Kontext so gut wie möglich zu verstehen.
Mit unstrukturierten Daten arbeiten
Wie wir unter in "Strukturierte Daten versus unstrukturierte Daten" erörtert haben , kommt es bei der Datenerstellung darauf an, den Informationen eine Struktur zu geben, sonst können wir sie nicht methodisch analysieren oder eine Bedeutung aus ihnen ableiten. Obwohl letztere oft große Abschnitte mit von Menschen geschriebenem "freiem" Text enthalten, sind sowohl tabellen- als auch futtermitteltypische Daten relativ strukturiert und vor allem maschinenlesbar.
Wenn wir uns mit unstrukturierten Daten befassen, ist unsere Arbeit dagegen immer mit Annäherungen verbunden: Wir können nicht sicher sein, dass unsere programmatischen Bemühungen eine genaue Interpretation der zugrunde liegenden Informationen ergeben. Das liegt daran, dass die meisten unstrukturierten Daten Inhalte darstellen, die von Menschen wahrgenommen und interpretiert werden sollen. Und wie wir in Kapitel 2 besprochen haben, können Computer zwar große Datenmengen viel schneller und mit weniger Fehlern verarbeiten als Menschen, aber dennoch können sie von unstrukturierten Daten getäuscht werden, die einen Menschen niemals täuschen würden, wie z. B. ein leicht verändertes Stoppschild mit einem Tempolimitschild zu verwechseln. Das bedeutet natürlich, dass wir beim Umgang mit Daten, die nicht maschinenlesbar sind, immer eine zusätzliche Prüfung und Verifizierung vornehmen müssen - aber Python kann uns dabei helfen, solche Daten in ein brauchbares Format zu bringen.
Bildbasierter Text: Zugriff auf Daten in PDFs
Das Portable Document Format (PDF) wurde Anfang der 1990er Jahre entwickelt, um die visuelle Integrität elektronischer Dokumente zu bewahren - unabhängig davon, ob sie mit einem Textverarbeitungsprogramm erstellt oder aus gedruckten Materialien übernommen wurden.22 Die Beibehaltung des visuellen Erscheinungsbildes der Dokumente bedeutete auch, dass es im Gegensatz zu maschinenlesbaren Formaten (wie Textverarbeitungsdokumenten) schwierig war, ihren Inhalt zu verändern oder zu extrahieren - eine wichtige Eigenschaft für die Erstellung von digitalen Versionen von Verträgen bis hin zu formellen Briefen.
Mit anderen Worten: Der Umgang mit den Daten in PDFs war anfangs von vornherein etwas schwierig. Da der Zugriff auf die Daten in gedruckten Dokumenten jedoch ein gemeinsames Problem ist, wurde bereits im späten 19. Jahrhundert mit der Arbeit an der optischen Zeichenerkennung (OCR) begonnen.23 Jahrhundert. Auch digitale OCR-Tools sind seit Jahrzehnten in Softwarepaketen und online weit verbreitet, so dass sie zwar noch lange nicht perfekt sind, aber die in dieser Art von Dateien enthaltenen Daten auch nicht völlig unerreichbar sind.
Wann man mit Text in PDFs arbeitet
Im Allgemeinen ist die Arbeit mit PDFs ein letzter Ausweg (wie wir in Kapitel 5 sehen werden, sollte dies auch für Web Scraping gelten). Wenn du es vermeiden kannst, dich auf PDF-Informationen zu verlassen, solltest du das tun. Wie bereits erwähnt, führt das Extrahieren von Informationen aus PDFs in der Regel zu einer Annäherung an den Inhalt des Dokuments, daher ist die Überprüfung der Richtigkeit ein unverzichtbarer Bestandteil jedes PDF-basierten Datenverarbeitungsprozesses. Dennoch gibt es eine enorme Menge an Informationen, die nur als Bilder oder PDFs von gescannten Dokumenten verfügbar sind, und Python ist ein effizienter Weg, um eine einigermaßen genaue erste Version eines solchen Dokumententextes zu extrahieren.
Wo du PDFs findest
Wenn du sicher bist, dass die gewünschten Daten nur im PDF-Format zu finden sind, kannst (und solltest) du die Tipps unter "Intelligente Suche nach bestimmten Datentypen" nutzen, um diesen Dateityp über eine Online-Suche zu finden. Es ist sehr wahrscheinlich, dass du Informationen von einer Person oder Organisation anforderst, die dir diese als PDF-Dateien zur Verfügung stellt, und du dann vor dem Problem stehst, wie du die benötigten Informationen extrahieren kannst. Deshalb musst du in den meisten Fällen nicht nach PDFs suchen - in den meisten Fällen werden sie dich leider finden.
PDFs mit Python bearbeiten
Da PDFs sowohl aus maschinenlesbarem Text (z. B. Textverarbeitungsdokumenten) als auch aus gescannten Bildern erstellt werden können, ist es manchmal möglich, den "echten" Text des Dokuments mit relativ wenigen Fehlern programmatisch zu extrahieren. Diese Methode scheint zwar einfach zu sein, kann aber dennoch unzuverlässig sein, weil pdf-Dateien mit einer Vielzahl von Kodierungen erstellt werden können, die schwer zu erkennen sind. Auch wenn dies eine sehr genaue Methode ist, um Text aus einer PDF-Datei zu extrahieren, ist die Wahrscheinlichkeit, dass sie bei jeder Datei funktioniert, gering.
Deshalb werde ich mich hier darauf konzentrieren, OCR zu verwenden, um den Text in PDF-Dateien zu erkennen und zu extrahieren. Dazu sind zwei Schritte erforderlich:
-
Wandle die Dokumentseiten in einzelne Bilder um.
-
Führe OCR auf den Seitenbildern aus, extrahiere den Text und schreibe ihn in einzelne Textdateien.
Es überrascht nicht, dass wir noch einige weitere Python-Bibliotheken installieren müssen, um das alles zu ermöglichen. Zuerst installieren wir einige Bibliotheken, um unsere PDF-Seiten in Bilder zu konvertieren. Die erste ist eine allgemeine Bibliothek namens poppler
, die benötigt wird, damit unsere Python-spezifische Bibliothek pdf2image
funktioniert. Wir werden pdf2image
verwenden, um (du hast es erraten!) unsere PDF-Datei in eine Reihe von Bildern zu konvertieren:
sudo apt install poppler-utils
Dann:
pip install pdf2image
Als Nächstes müssen wir die Tools für den OCR-Prozess installieren. Das erste ist eine allgemeine Bibliothek namenstesseract-ocr, die maschinelles Lernen nutzt, um den Text in Bildern zu erkennen. Das zweite ist eine Python-Bibliothek namens pytesseract, die auf tesseract-ocr aufbaut:
sudo apt-get install tesseract-ocr
Dann:
pip install pytesseract
Schließlich brauchen wir eine Hilfsbibliothek für Python, die die Computer Vision ausführen kann, um die Lücke zwischen unseren Seitenbildern und unserer OCR-Bibliothek zu schließen:
pip install opencv-python
Puh! Wenn dir das wie eine Menge zusätzlicher Bibliotheken vorkommt, solltest du bedenken, dass wir hier technisch gesehen maschinelles Lernen verwenden, eine der angesagten Data-Science-Technologien, die so viel von der "künstlichen Intelligenz" da draußen bestimmen. Zum Glück für uns ist vor allem Tesseract relativ robust und umfassend: Obwohl es ursprünglich von Hewlett-Packard in den frühen 1980er Jahren als proprietäres System entwickelt wurde, wurde es 2005 als Open Source veröffentlicht und unterstützt derzeit mehr als 100 Sprachen - du kannst die Lösung in Beispiel 4-16 also auch mit nicht-englischem Text ausprobieren!
Beispiel 4-16. pdf_parsing.py
# a basic example of reading data from a .pdf file with Python,
# using `pdf2image` to convert it to images, and then using the
# openCV and `tesseract` libraries to extract the text
# the source data was downloaded from:
# https://files.stlouisfed.org/files/htdocs/publications/page1-econ/2020/12/01/ \
# unemployment-insurance-a-tried-and-true-safety-net_SE.pdf
# the built-in `operating system` or `os` Python library will let us create
# a new folder in which to store our converted images and output text
import
os
# we'll import the `convert_from_path` "chapter" of the `pdf2image` library
from
pdf2image
import
convert_from_path
# the built-in `glob`library offers a handy way to loop through all the files
# in a folder that have a certain file extension, for example
import
glob
# `cv2` is the actual library name for `openCV`
import
cv2
# and of course, we need our Python library for interfacing
# with the tesseract OCR process
import
pytesseract
# we'll use the pdf name to name both our generated images and text files
pdf_name
=
"
SafetyNet
"
# our source pdf is in the same folder as our Python script
pdf_source_file
=
pdf_name
+
"
"
# as long as a folder with the same name as the pdf does not already exist
if
os
.
path
.
isdir
(
pdf_name
)
==
False
:
# create a new folder with that name
target_folder
=
os
.
mkdir
(
pdf_name
)
# store all the pages of the PDF in a variable
pages
=
convert_from_path
(
pdf_source_file
,
300
)
# loop through all the converted pages, enumerating them so that the page
# number can be used to label the resulting images
for
page_num
,
page
in
enumerate
(
pages
)
:
# create unique filenames for each page image, combining the
# folder name and the page number
filename
=
os
.
path
.
join
(
pdf_name
,
"
p
"
+
str
(
page_num
)
+
"
.png
"
)
# save the image of the page in system
page
.
save
(
filename
,
'
PNG
'
)
# next, go through all the files in the folder that end in `.png`
for
img_file
in
glob
.
glob
(
os
.
path
.
join
(
pdf_name
,
'
*.png
'
)
)
:
# replace the slash in the image's filename with a dot
temp_name
=
img_file
.
replace
(
"
/
"
,
"
.
"
)
# pull the unique page name (e.g. `p2`) from the `temp_name`
text_filename
=
temp_name
.
split
(
"
.
"
)
[
1
]
# now! create a new, writable file, also in our target folder, that
# has the same name as the image, but is a `.txt` file
output_file
=
open
(
os
.
path
.
join
(
pdf_name
,
text_filename
+
"
.txt
"
)
,
"
w
"
)
# use the `cv2` library to interpret our image
img
=
cv2
.
imread
(
img_file
)
# create a new variable to hold the results of using pytesseract's
# `image_to_string()` function, which will do just that
converted_text
=
pytesseract
.
image_to_string
(
img
)
# write our extracted text to our output file
output_file
.
write
(
converted_text
)
# close the output file
output_file
.
close
(
)
Hier übergeben wir der Funktion
convert_from_path()
den Pfad zu unserer Quelldatei (in diesem Fall nur den Dateinamen, da sie sich im selben Ordner wie unser Skript befindet) und die gewünschte Auflösung der ausgegebenen Bilder (Dots per Inch, DPI). Eine niedrigere DPI-Auflösung ist zwar viel schneller, aber die schlechtere Qualität der Bilder kann zu deutlich schlechteren OCR-Ergebnissen führen. 300 DPI ist eine Standardauflösung für Druckqualität.Hier verwenden wir die Funktion
.join
der os-Bibliothek, um die neuen Dateien in unserem Zielordner zu speichern. Außerdem müssen wir die Funktionstr()
verwenden, um die Seitenzahl in einen String umzuwandeln, der im Dateinamen verwendet wird.Beachte, dass
*.png
mit "jede Datei, die auf .png endet" übersetzt werden kann. Die Funktionglob()
erstellt eine Liste aller Dateinamen in dem Ordner, in dem unsere Bilder gespeichert sind (der in diesem Fall den Wertpdf_name
hat).Die Manipulation von Zeichenketten ist knifflig! Um eindeutige (aber übereinstimmende!) Dateinamen für unsere OCR-Textdateien zu erzeugen, müssen wir einen eindeutigen Seitennamen aus
img_file
ziehen, dessen Wert mitSafetyNet/
beginnt und mit.png
endet. Wir ersetzen also den Schrägstrich durch einen Punkt, um etwas wieSafetyNet.p1.png
zu erhalten, und wenn wir dannsplit()
an den Punkt hängen, erhalten wir eine Liste wie:["SafetyNet", "p1", "png"]
. Schließlich können wir auf den "Seitennamen" an Position 1 zugreifen. Wir müssen all das tun, weil wir nicht sicher sein können, dassglob()
zum Beispiel zuerstp1.png
aus dem Bilderordner abruft, oder dass die Bilder überhaupt in der richtigen Reihenfolge abgerufen werden.
Im Großen und Ganzen erfüllt dieses Skript unsere Zwecke: Mit ein paar Dutzend Codezeilen wandelt es eine mehrseitige PDF-Datei zunächst in Bilder um und schreibt dann den Inhalt (größtenteils) in eine Reihe von neuen Textdateien.
Dieser All-in-One-Ansatz hat jedoch auch seine Grenzen. Die Umwandlung von PDFs in Bilder - oder von Bildern in Text - ist eine Aufgabe, die wir vielleicht oft, aber nicht immer zur gleichen Zeit erledigen müssen. Mit anderen Worten: Auf lange Sicht wäre es wahrscheinlich viel sinnvoller, zwei separate Skripte zur Lösung dieses Problems zu haben und sie nacheinander auszuführen. Mit ein paar kleinen Änderungen könnten wir das vorhergehende Skript sogar so aufteilen, dass wir jedes PDF in Bilder oder jedes Bild in Text umwandeln können, ohne überhaupt neuen Code schreiben zu müssen. Klingt ziemlich raffiniert, oder?
Dieser Prozess des Überdenkens und Reorganisierens von Arbeitscode ist unter als Code-Refactoring bekannt. Im Schriftsprachenglisch würden wir dies als Überarbeitung oder Editieren bezeichnen, und das Ziel ist in beiden Fällen dasselbe: deine Arbeit einfacher, klarer und effektiver zu machen. Genau wie die Dokumentation ist das Refactoring eine weitere wichtige Methode, um deine Arbeit mit Daten zu skalieren, denn es macht die Wiederverwendung deines Codes viel einfacher. In Kapitel 8 werden wir uns verschiedene Strategien für das Code-Refactoring und die Wiederverwendung von Skripten ansehen .
Zugriff auf PDF-Tabellen mit Tabula
du dir die Textdateien angesehen hast, die im vorangegangenen Abschnitt erstellt wurden, ist dir wahrscheinlich aufgefallen, dass diese Dateien viele "Extras" enthalten: Seitenzahlen und Überschriften, Zeilenumbrüche und anderen "Müll". Es fehlen auch einige wichtige Elemente wie Bilder und Tabellen.
Unsere Datenarbeit wird sich zwar nicht auf die Analyse von Bildern erstrecken (das ist ein viel spezielleres Gebiet), aber es ist nicht ungewöhnlich, Tabellen in PDFs zu finden, die Daten enthalten, mit denen wir arbeiten wollen. In meiner Branche, dem Journalismus, ist dieses Problem sogar so häufig, dass eine Gruppe von investigativen Journalisten ein Tool namens Tabula entwickelt hat, das sich speziell mit diesem Problem befasst.
Tabula ist keine Python-Bibliothek - es ist eine eigenständige Software. Um es auszuprobieren, lade das Installationsprogramm für dein System herunter. Wenn du ein Chromebook oder einen Linux-Rechner verwendest, musst du die .zip-Datei herunterladen und den Anweisungen in README.txt folgen. Unabhängig von deinem System musst du wahrscheinlich zuerst die Java-Programmierbibliothek installieren, indem du den folgenden Befehl in einem Terminalfenster ausführst:
sudo apt install default-jre
Wie einige andere Open-Source-Tools, die wir in späteren Kapiteln besprechen werden (z. B. OpenRefine, mit dem ich einige Beispieldaten in Kapitel 2 aufbereitet habe und auf das ich in Kapitel 11 kurz eingehe), arbeitet Tabula hinter den Kulissen (obwohl ein Teil davon im Terminalfenster sichtbar ist) und du interagierst damit in einem Webbrowser. Auf diese Weise erhältst du Zugang zu einer traditionelleren grafischen Oberfläche, während du die meisten Ressourcen deines Computers für die schwere Datenarbeit freihältst.
Fazit
Die Programmierbeispiele in diesem Kapitel haben dir hoffentlich einen Eindruck davon vermittelt, wie vielfältig die Probleme bei der Datenverarbeitung sind, die du mit relativ wenig Python-Code lösen kannst - dank einer Kombination aus sorgfältig ausgewählten Bibliotheken und den wenigen grundlegenden Python-Skriptkonzepten, die in Kapitel 2 vorgestellt wurden.
Du hast vielleicht auch bemerkt, dass die Ausgabe aller Übungen in diesem Kapitel mit Ausnahme unseres PDF-Textes im Wesentlichen eine .csv-Datei war. Das kommt nicht von ungefähr. .csv-Dateien sind nicht nur effizient und vielseitig, sondern es hat sich auch herausgestellt, dass wir für fast alle grundlegenden statistischen Analysen oder Visualisierungen tabellenartige Daten benötigen. Das soll nicht heißen, dass es nicht möglich ist, Daten zu analysieren, die nicht in Tabellenform vorliegen; genau darum geht es in der modernen Informatikforschung (z. B. beim maschinellen Lernen). Da diese Systeme jedoch oft komplex und undurchsichtig sind, eignen sie sich nicht für die Art von Datenanalyse, auf die wir uns hier konzentrieren. Deshalb werden wir unsere Energie auf die Arten von Analysen verwenden, die uns helfen können, die Welt zu verstehen, zu erklären und neue Erkenntnisse zu vermitteln.
Während wir uns in diesem Kapitel auf dateibasierte Daten und vorgespeicherte Versionen von feedbasierten Daten konzentriert haben, werden wir in Kapitel 5 erkunden, wie wir Python mit APIs und Web Scraping einsetzen können, um Daten aus Online-Systemen und, wenn nötig, direkt aus Webseiten selbst herauszuholen!
1 Vergleiche sie mit einigen anderen, die du vielleicht kennst, wie mp4 oder png, die normalerweise mit Musik bzw. Bildern in Verbindung gebracht werden.
2 Aber du wirst schon bald wissen, wie du es hinbekommst!
3 Eigentlich brauchst du gar nicht so viel Glück - wie das geht, schauen wir uns in "PDFs mit Python bearbeiten" an.
4 In der Informatik werden die Begriffe Daten und Informationen genau umgekehrt verwendet: Daten sind die rohen Fakten, die über die Welt gesammelt werden, und Informationen sind das sinnvolle Endprodukt ihrer Organisation und Strukturierung. In den letzten Jahren, als das Thema "Big Data" viele Bereiche beherrschte, hat sich die von mir hier verwendete Interpretation jedoch durchgesetzt.
5 Vom US Bureau of Labor Statistics.
6 "Multiple Jobholders" von Stéphane Auray, David L. Fuller und Guillaume Vandenbroucke, veröffentlicht am 21. Dezember 2018, https://research.stlouisfed.org/publications/economic-synopses/2018/12/21/multiple-jobholders.
7 Siehe "New Recommendations on Improving Data on Contingent and Alternative Work Arrangements ", https://blogs.bls.gov/blog/tag/contingent-workers;"The Value of Flexible Work: Evidence from Uber Drivers" von M. Keith Chen et al., Nber Working Paper Series No. 23296, https://nber.org/system/files/working_papers/w23296/w23296.pdf.
8 Eine Anleitung dazu findest du auch auf der FRED-Website.
9 Du kannst Kopien dieser Dateien auch direkt von meinem Google Drive herunterladen.
10 Zum jetzigen Zeitpunkt kann LibreOffice die gleiche Anzahl von Zeilen wie Microsoft Excel (220) verarbeiten, aber viel weniger Spalten. Google Sheets kann zwar mehr Spalten als Excel verarbeiten, aber nur etwa 40.000 Zeilen.
11 Zum Zeitpunkt dieses Artikels sind alle diese Bibliotheken bereits verfügbar und können in Google Colab verwendet werden.
12 Mehr über get_rows()
erfährst du in der xlrd-Dokumentation.
13 In der xlrd-Dokumentation findest du mehr zu diesem Thema.
14 Von Stephen John Machin und Chris Withers, "Dates in Excel Spreadsheets".
15 Wenn du die Ausgabedateien der drei vorangegangenen Codebeispiele in einem Texteditor öffnest, wirst du feststellen, dass das Open-Source- .ods-Format das einfachste und sauberste ist.
16 Wie z.B. in Pennsylvania oder Colorado.
17 Siehe den Beitrag von Vassili van der Mersch, "Twitter's 10 Year Struggle with Developer Relations" von Nordic APIs.
18 Im Gegensatz zu Python-Code müssen XML-Dokumente nicht eingerückt werden, um zu funktionieren, obwohl sie dadurch natürlich besser lesbar sind!
19 Spaßfakt: Das zweite x im .xlsx-Format steht eigentlich für XML!
20 Auch hier werden wir die Verwendung von APIs wie dieser in Kapitel 5 Schritt für Schritt durchgehen, aber anhand dieses Dokuments können wir sehen, wie verschiedene Datenformate unsere Interaktionen mit den Daten beeinflussen.
21 Wenn ein Stylesheet angewendet wurde, wie im Fall des BBC-Feeds, den wir in Beispiel 4-13 verwendet haben, kannst du mit Kontext+Klick auf die Seite "Quelle anzeigen" wählen, um das "rohe" XML zu sehen.
22 Weitere Informationen findest du auf der Seite Über PDF von Adobe.
23 George Nagy, "Disruptive Developments in Document Recognition", Pattern Recognition Letters 79 (2016): 106–112, https://doi.org/10.1016/j.patrec.2015.11.024. Verfügbar unter https://ecse.rpi.edu/~nagy/PDF_chrono/2016_PRL_Disruptive_asPublished.pdf.
Get Praktisches Python Data Wrangling und Datenqualität now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.