CSV-Beispiel#
[1]:
import pandas as pd
Nach dem Import von pandas lesen wir zunächst mit read_csv
eine CSV-Datei ein:
[2]:
pd.read_csv('https://raw.githubusercontent.com/veit/python-basics-tutorial-de/main/docs/save-data/books.csv')
[2]:
Python basics | en | Veit Schiele | BSD-3-Clause | 2021-10-28 | |
---|---|---|---|---|---|
0 | Jupyter Tutorial | en | Veit Schiele | BSD-3-Clause | 2019-06-27 |
1 | Jupyter Tutorial | de | Veit Schiele | BSD-3-Clause | 2020-10-26 |
2 | PyViz Tutorial | en | Veit Schiele | BSD-3-Clause | 2020-04-13 |
Wie ihr seht, hat diese Datei keine Kopfzeile. Um dem DataFrame eine Kopfzeile zu geben, habt ihr mehrere Möglichkeiten. Ihr könnt pandas erlauben, Standard-Spaltennamen zuzuweisen, oder ihr könnt die Namen auch selbst festlegen:
[3]:
pd.read_csv('https://raw.githubusercontent.com/veit/python-basics-tutorial-de/main/docs/save-data/books.csv',
header=None)
[3]:
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | Python basics | en | Veit Schiele | BSD-3-Clause | 2021-10-28 |
1 | Jupyter Tutorial | en | Veit Schiele | BSD-3-Clause | 2019-06-27 |
2 | Jupyter Tutorial | de | Veit Schiele | BSD-3-Clause | 2020-10-26 |
3 | PyViz Tutorial | en | Veit Schiele | BSD-3-Clause | 2020-04-13 |
[4]:
pd.read_csv('https://raw.githubusercontent.com/veit/python-basics-tutorial-de/main/docs/save-data/books.csv',
names=['Titel', 'Sprache', 'Autor*innen', 'Lizenz', 'Veröffentlichungsdatum'])
[4]:
Titel | Sprache | Autor*innen | Lizenz | Veröffentlichungsdatum | |
---|---|---|---|---|---|
0 | Python basics | en | Veit Schiele | BSD-3-Clause | 2021-10-28 |
1 | Jupyter Tutorial | en | Veit Schiele | BSD-3-Clause | 2019-06-27 |
2 | Jupyter Tutorial | de | Veit Schiele | BSD-3-Clause | 2020-10-26 |
3 | PyViz Tutorial | en | Veit Schiele | BSD-3-Clause | 2020-04-13 |
Angenommen, ihr möchtet, dass die Spalte Autor*innen
der Index des zurückgegebenen DataFrame ist. Ihr könnt entweder angeben, dass ihr die Spalte bei Index 3 oder mit dem Namen Autor*innen
haben möchtet, indem ihr das Argument index_col
verwendet:
[5]:
pd.read_csv('https://raw.githubusercontent.com/veit/python-basics-tutorial-de/main/docs/save-data/books.csv',
index_col=['Autor*innen'],
names=['Titel', 'Sprache', 'Autor*innen', 'Lizenz', 'Veröffentlichungsdatum'])
[5]:
Titel | Sprache | Lizenz | Veröffentlichungsdatum | |
---|---|---|---|---|
Autor*innen | ||||
Veit Schiele | Python basics | en | BSD-3-Clause | 2021-10-28 |
Veit Schiele | Jupyter Tutorial | en | BSD-3-Clause | 2019-06-27 |
Veit Schiele | Jupyter Tutorial | de | BSD-3-Clause | 2020-10-26 |
Veit Schiele | PyViz Tutorial | en | BSD-3-Clause | 2020-04-13 |
Für den Fall, dass ihr einen hierarchischen Index aus mehreren Spalten bilden wollt, übergebt eine Liste von Spaltennummern oder -namen:
[6]:
pd.read_csv('https://raw.githubusercontent.com/veit/python-basics-tutorial-de/main/docs/save-data/books.csv',
index_col=[2, 0],
names=['Titel', 'Sprache', 'Autor*innen', 'Lizenz', 'Veröffentlichungsdatum'])
[6]:
Sprache | Lizenz | Veröffentlichungsdatum | ||
---|---|---|---|---|
Autor*innen | Titel | |||
Veit Schiele | Python basics | en | BSD-3-Clause | 2021-10-28 |
Jupyter Tutorial | en | BSD-3-Clause | 2019-06-27 | |
Jupyter Tutorial | de | BSD-3-Clause | 2020-10-26 | |
PyViz Tutorial | en | BSD-3-Clause | 2020-04-13 |
In manchen Fällen hat eine Tabelle kein festes Trennzeichen, sondern verwendet mehrere Leerzeichen oder ein anderes Muster zur Trennung von Feldern. Angenommen, eine Datei sieht folgendermaßen aus:
[7]:
list(open('books.txt'))
[7]:
[' Titel Sprache Autor*innen Lizenz Veröffentlichungsdatum\n',
'1 Python basics en Veit Schiele BSD-3-Clause 2021-10-28\n',
'2 Jupyter Tutorial en Veit Schiele BSD-3-Clause 2019-06-27\n',
'3 Jupyter Tutorial de Veit Schiele BSD-3-Clause 2020-10-26\n',
'4 PyViz Tutorial en Veit Schiele BSD-3-Clause 2020-04-13']
In solchen Fällen könnt ihr einen regulären Ausdruck als Trennzeichen für read_csv
übergeben. Dies kann durch den regulären Ausdruck \s\s+
ausgedrückt werden, also haben wir dann:
[8]:
pd.read_csv('books.txt', sep='\s\s+', engine='python')
[8]:
Titel | Sprache | Autor*innen | Lizenz | Veröffentlichungsdatum | |
---|---|---|---|---|---|
1 | Python basics | en | Veit Schiele | BSD-3-Clause | 2021-10-28 |
2 | Jupyter Tutorial | en | Veit Schiele | BSD-3-Clause | 2019-06-27 |
3 | Jupyter Tutorial | de | Veit Schiele | BSD-3-Clause | 2020-10-26 |
4 | PyViz Tutorial | en | Veit Schiele | BSD-3-Clause | 2020-04-13 |
Da es einen Spaltennamen weniger als die Anzahl der Datenzeilen gab, folgert read_csv
, dass in diesem Fall die erste Spalte der Index des DataFrame sein sollte.
Die Parser-Funktionen haben viele zusätzliche Argumente, die euch helfen, die große Vielfalt der auftretenden Ausnahmedateiformate zu handhaben. So könnt ihr beispielsweise mit skiprows
einzelne Zeilen einer Datei überspringen:
[9]:
pd.read_csv('https://raw.githubusercontent.com/veit/python-basics-tutorial-de/main/docs/save-data/books.csv',
skiprows=[2],
names=['Titel', 'Sprache', 'Autor*innen', 'Lizenz', 'Veröffentlichungsdatum'])
[9]:
Titel | Sprache | Autor*innen | Lizenz | Veröffentlichungsdatum | |
---|---|---|---|---|---|
0 | Python basics | en | Veit Schiele | BSD-3-Clause | 2021-10-28 |
1 | Jupyter Tutorial | en | Veit Schiele | BSD-3-Clause | 2019-06-27 |
2 | PyViz Tutorial | en | Veit Schiele | BSD-3-Clause | 2020-04-13 |
Der Umgang mit fehlenden Werten ist ein wichtiger und häufig komplizierter Teil beim Parsen von Daten. Fehlende Daten sind normalerweise entweder nicht vorhanden (leerer String) oder durch einen Platzhalter gekennzeichnet. Standardmäßig verwendet Pandas eine Reihe von häufig vorkommenden Platzhalter, wie NA
und NULL
:
[10]:
df = pd.read_csv('https://raw.githubusercontent.com/veit/python-basics-tutorial-de/main/docs/save-data/books.csv',
names=['Titel', 'Sprache', 'Autor*innen', 'Lizenz', 'Veröffentlichungsdatum', 'doi'])
df
[10]:
Titel | Sprache | Autor*innen | Lizenz | Veröffentlichungsdatum | doi | |
---|---|---|---|---|---|---|
0 | Python basics | en | Veit Schiele | BSD-3-Clause | 2021-10-28 | NaN |
1 | Jupyter Tutorial | en | Veit Schiele | BSD-3-Clause | 2019-06-27 | NaN |
2 | Jupyter Tutorial | de | Veit Schiele | BSD-3-Clause | 2020-10-26 | NaN |
3 | PyViz Tutorial | en | Veit Schiele | BSD-3-Clause | 2020-04-13 | NaN |
[11]:
df.isna()
[11]:
Titel | Sprache | Autor*innen | Lizenz | Veröffentlichungsdatum | doi | |
---|---|---|---|---|---|---|
0 | False | False | False | False | False | True |
1 | False | False | False | False | False | True |
2 | False | False | False | False | False | True |
3 | False | False | False | False | False | True |
Die Option na_values
kann entweder eine Liste oder eine Reihe von Strings annehmen, um fehlende Werte zu berücksichtigen:
[12]:
df = pd.read_csv('https://raw.githubusercontent.com/veit/python-basics-tutorial-de/main/docs/save-data/books.csv',
na_values=['BSD-3-Clause'],
names=['Titel', 'Sprache', 'Autor*innen', 'Lizenz', 'Veröffentlichungsdatum', 'doi'])
df
[12]:
Titel | Sprache | Autor*innen | Lizenz | Veröffentlichungsdatum | doi | |
---|---|---|---|---|---|---|
0 | Python basics | en | Veit Schiele | NaN | 2021-10-28 | NaN |
1 | Jupyter Tutorial | en | Veit Schiele | NaN | 2019-06-27 | NaN |
2 | Jupyter Tutorial | de | Veit Schiele | NaN | 2020-10-26 | NaN |
3 | PyViz Tutorial | en | Veit Schiele | NaN | 2020-04-13 | NaN |
Die häufigsten Argumente der Funktion read_csv
:
Argument |
Beschreibung |
---|---|
|
Zeichenfolge, die den Speicherort im Dateisystem, eine URL oder ein dateiähnliches Objekt angibt |
|
Zeichenfolge oder regulärer Ausdruck zum Trennen der Felder in jeder Zeile |
|
Zeilennummer, die als Spaltennamen zu verwenden ist; Standardwert ist |
|
index_col Zeilennummern oder -namen, die als Zeilenindex im Ergebnis verwendet werden sollen; kann ein einzelner Name/eine einzelne Zahl oder eine Liste von ihnen für einen hierarchischen Index sein |
|
Liste der Spaltennamen |
|
Anzahl der zu ignorierenden Zeilen am Anfang der Datei oder Liste der Zeilennummern beginnend bei |
|
Folge von Werten, die durch NA ersetzt werden sollen |
|
Zeichen, um Kommentare vom Zeilenende abzutrennen |
|
Versuch, Daten mit datetime zu parsen; standardmäßig |
|
wenn Spalten zum Parsen des Datums kombiniert werden, werden die kombinierten Spalten beibehalten; Standardeinstellung: |
|
Dict, das die Spaltennummer der Namen enthält, die auf Funktionen abgebildet werden, z.B. würde |
|
beim Parsen potenziell mehrdeutiger Datumsangaben als internationales Format behandeln, z.B. |
|
zu verwendende Funktion zum Parsen von Datumsangaben |
|
Anzahl der zu lesenden Zeilen vom Anfang der Datei |
|
Rückgabe eines |
|
Für die Iteration die Größe der Datenblöcke. |
|
Anzahl der Zeilen, die am Ende der Datei ignoriert werden sollen |
|
gibt verschiedene Informationen zur Parser-Ausgabe aus, z.B. die Anzahl der fehlenden Werte in nicht-numerischen Spalten. |
|
Textkodierung für Unicode, z.B. |
|
wenn die geparsten Daten nur eine Spalte enthalten, wird eine Serie zurückgegeben |
|
Trennzeichen für Tausender, z.B. |
Stückweises Einlesen von Textdateien#
Wenn ihr sehr große Dateien verarbeiten wollt, könnt ihr auch nur einen kleinen Teil einer Datei einlesen oder durch kleinere Teile einer Datei iterieren.
Bevor wir uns eine große Datei ansehen, verringern wir mit options.display.max_rows
die Anzahl der angezeigten Zeilen:
[13]:
pd.options.display.max_rows = 10
[14]:
pd.read_csv('example.csv')
[14]:
Date | Mon. | Tues. | Wed. | Thurs. | Fri. | Sat. | Sun. | |
---|---|---|---|---|---|---|---|---|
0 | 1996-01-01 | 0.129453 | -0.023836 | 1.121460 | 1.698286 | -0.598506 | 1.042221 | -0.726412 |
1 | 1996-01-02 | -0.094021 | -0.727942 | 0.698641 | -1.198040 | 1.927505 | 1.147445 | -1.134103 |
2 | 1996-01-03 | -0.560857 | 0.145222 | -0.990202 | 1.200214 | 0.717339 | 1.117095 | -1.793565 |
3 | 1996-01-04 | -0.169755 | -0.677391 | -1.533519 | -0.343477 | -0.109705 | 1.038236 | -0.799088 |
4 | 1996-01-05 | 1.344705 | -1.817261 | 0.460991 | -0.839633 | 0.265814 | 0.477659 | 0.636383 |
... | ... | ... | ... | ... | ... | ... | ... | ... |
9127 | 2020-12-27 | -0.881800 | -0.074270 | -0.351769 | 1.381641 | -0.049548 | 1.664180 | -1.032204 |
9128 | 2020-12-28 | -0.143386 | 0.198217 | -1.243861 | 1.196576 | 1.338166 | -0.212333 | -0.023131 |
9129 | 2020-12-29 | 0.398787 | -0.848786 | 1.791707 | -1.167592 | -0.033881 | -0.285559 | -0.323477 |
9130 | 2020-12-30 | 0.587846 | 0.411580 | 1.150380 | 0.444638 | -1.093577 | 0.605456 | 1.463345 |
9131 | 2020-12-31 | 0.736350 | 0.436292 | -0.260171 | -0.066066 | -0.328324 | -0.586792 | -1.204582 |
9132 rows × 8 columns
Wenn ihr nur eine kleine Anzahl von Zeilen lesen wollt (ohne die gesamte Datei zu lesen), könnt ihr dies mit nrows
angeben:
[15]:
pd.read_csv('example.csv', nrows=7)
[15]:
Date | Mon. | Tues. | Wed. | Thurs. | Fri. | Sat. | Sun. | |
---|---|---|---|---|---|---|---|---|
0 | 1996-01-01 | 0.129453 | -0.023836 | 1.121460 | 1.698286 | -0.598506 | 1.042221 | -0.726412 |
1 | 1996-01-02 | -0.094021 | -0.727942 | 0.698641 | -1.198040 | 1.927505 | 1.147445 | -1.134103 |
2 | 1996-01-03 | -0.560857 | 0.145222 | -0.990202 | 1.200214 | 0.717339 | 1.117095 | -1.793565 |
3 | 1996-01-04 | -0.169755 | -0.677391 | -1.533519 | -0.343477 | -0.109705 | 1.038236 | -0.799088 |
4 | 1996-01-05 | 1.344705 | -1.817261 | 0.460991 | -0.839633 | 0.265814 | 0.477659 | 0.636383 |
5 | 1996-01-06 | -0.354445 | -0.065182 | -1.244963 | -0.559732 | 0.042362 | -0.303712 | 0.067632 |
6 | 1996-01-07 | 1.460922 | 0.164412 | 0.883960 | -0.833642 | 0.001582 | 1.138469 | 0.561618 |
Um eine Datei stückweise zu lesen, könnt ihr mit chunksize
die Anzahl der Zeilen angeben:
[16]:
pd.read_csv('example.csv', chunksize=1000)
[16]:
<pandas.io.parsers.readers.TextFileReader at 0x7f11f207bca0>
Das von read_csv
zurückgegebene TextFileReader
-Objekt ermöglicht die Iteration über Teile der Datei entsprechend der chunksize
. Zum Beispiel können wir über die example.csv
-Datei iterieren und die Anzahl der Werte in der Spalte Date
wie folgt aggregieren:
[17]:
chunks = pd.read_csv('example.csv', chunksize=1000)
serie = pd.Series([], dtype='float64')
for chunk in chunks:
values = serie.add(chunk['Date'].value_counts(), fill_value=0)
sorted_values = values.sort_values(ascending=False)
[18]:
sorted_values[:10]
[18]:
2020-08-22 1.0
2020-09-07 1.0
2020-08-24 1.0
2020-08-25 1.0
2020-08-26 1.0
2020-08-27 1.0
2020-08-28 1.0
2020-08-29 1.0
2020-08-30 1.0
2020-08-31 1.0
dtype: float64
TextFileReader
verfügt außerdem über eine get_chunk
-Methode, mit der ihr Stücke beliebiger Größe lesen könnt.
DataFrame und Series als csv-Datei schreiben#
Daten können auch in ein mit Trennzeichen versehenes Format exportiert werden. Mit der Methode pandas.DataFrame.to_csv
können wir die Daten in eine kommagetrennte Datei schreiben:
[19]:
df.to_csv('out.csv')
Natürlich können auch andere Begrenzungszeichen verwendet werden, z.B. zum Schreiben nach sys.stdout
, so dass das Textergebnis auf der Konsole und nicht in einer Datei ausgegeben wird:
[20]:
import sys
[21]:
df.to_csv(sys.stdout, sep='|')
|Titel|Sprache|Autor*innen|Lizenz|Veröffentlichungsdatum|doi
0|Python basics|en|Veit Schiele||2021-10-28|
1|Jupyter Tutorial|en|Veit Schiele||2019-06-27|
2|Jupyter Tutorial|de|Veit Schiele||2020-10-26|
3|PyViz Tutorial|en|Veit Schiele||2020-04-13|
Fehlende Werte erscheinen in der Ausgabe als leere Zeichenketten. Möglicherweise möchtet ihr sie durch einen anderen Platzhalter kennzeichnen:
[22]:
df.to_csv(sys.stdout, na_rep='NaN')
,Titel,Sprache,Autor*innen,Lizenz,Veröffentlichungsdatum,doi
0,Python basics,en,Veit Schiele,NaN,2021-10-28,NaN
1,Jupyter Tutorial,en,Veit Schiele,NaN,2019-06-27,NaN
2,Jupyter Tutorial,de,Veit Schiele,NaN,2020-10-26,NaN
3,PyViz Tutorial,en,Veit Schiele,NaN,2020-04-13,NaN
Wenn keine anderen Optionen angegeben sind, werden sowohl die Zeilen- als auch die Spaltenbeschriftungen geschrieben. Beides kann deaktiviert werden:
[23]:
df.to_csv(sys.stdout, index=False, header=False)
Python basics,en,Veit Schiele,,2021-10-28,
Jupyter Tutorial,en,Veit Schiele,,2019-06-27,
Jupyter Tutorial,de,Veit Schiele,,2020-10-26,
PyViz Tutorial,en,Veit Schiele,,2020-04-13,
Ihr könnt auch nur eine Teilmenge der Spalten schreiben, und zwar in einer von euch gewählten Reihenfolge:
[24]:
df.to_csv(sys.stdout,
index=False,
columns=['Titel', 'Sprache', 'Autor*innen', 'Veröffentlichungsdatum'])
Titel,Sprache,Autor*innen,Veröffentlichungsdatum
Python basics,en,Veit Schiele,2021-10-28
Jupyter Tutorial,en,Veit Schiele,2019-06-27
Jupyter Tutorial,de,Veit Schiele,2020-10-26
PyViz Tutorial,en,Veit Schiele,2020-04-13
Arbeiten mit dem csv-Modul von Python#
Die meisten Formen von Tabellendaten können mit Funktionen wie pandas.read_csv
geladen werden. In einigen Fällen kann jedoch auch eine manuelle Bearbeitung erforderlich sein. Es ist nicht ungewöhnlich, eine Datei mit einer oder mehreren fehlerhaften Zeilen zu erhalten, die read_csv
zum Scheitern bringen. Für jede Datei mit einem einstelligen Begrenzungszeichen könnt ihr das in Python eingebaute csv-Modul verwenden. Um es zu verwenden,
übergebt eine offene Datei oder ein dateiähnliches Objekt an csv.reader
:
[25]:
import csv
f = open('out.csv')
reader = csv.reader(f)
for line in reader:
print(line)
['', 'Titel', 'Sprache', 'Autor*innen', 'Lizenz', 'Veröffentlichungsdatum', 'doi']
['0', 'Python basics', 'en', 'Veit Schiele', '', '2021-10-28', '']
['1', 'Jupyter Tutorial', 'en', 'Veit Schiele', '', '2019-06-27', '']
['2', 'Jupyter Tutorial', 'de', 'Veit Schiele', '', '2020-10-26', '']
['3', 'PyViz Tutorial', 'en', 'Veit Schiele', '', '2020-04-13', '']
Dialekte#
csv-Dateien gibt es in vielen verschiedenen Varianten. Das Python csv-Modul kommt bereits mit drei verschiedenen Dialekten:
Parameter |
excel |
excel-tab |
unix |
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Ihr könnt damit auch euer eigenes Format definieren mit einem anderen Trennzeichen, einer anderen Zeichenkettenkonvention oder einem anderen Zeilenendezeichen. Hierfür empfiehlt sich die Registrierung eines eigenen Dialekts. Mögliche Optionen und Funktionen von csv.register_dialect
sind:
Argument |
Beschreibung |
---|---|
|
Ein-Zeichen-Zeichenfolge zur Trennung von Feldern; Standardwert ist |
|
Zeilenabschlusszeichen zum Schreiben; Standardwert ist |
|
Anführungszeichen für Felder mit Sonderzeichen (wie ein Trennzeichen); Standardwert ist |
|
Zitier-Konvention. Zu den Optionen gehören |
|
Leerzeichen nach jedem Begrenzungszeichen ignorieren; Standardwert ist |
|
bei |
|
Zeichenkette, um das Trennzeichen zu umgehen, wenn |
[26]:
csv.register_dialect('my_csv_dialect',
lineterminator = '\n',
delimiter = ',',
quotechar = '\'',
quoting = csv.QUOTE_MINIMAL
)
Nun kann die CSV-Datei geöffnet werden mit:
[27]:
with open('out.csv') as f:
reader = csv.reader(f, 'my_csv_dialect')
for line in reader:
print(line)
['', 'Titel', 'Sprache', 'Autor*innen', 'Lizenz', 'Veröffentlichungsdatum', 'doi']
['0', 'Python basics', 'en', 'Veit Schiele', '', '2021-10-28', '']
['1', 'Jupyter Tutorial', 'en', 'Veit Schiele', '', '2019-06-27', '']
['2', 'Jupyter Tutorial', 'de', 'Veit Schiele', '', '2020-10-26', '']
['3', 'PyViz Tutorial', 'en', 'Veit Schiele', '', '2020-04-13', '']
Dann können wir ein Dict mit Datenspalten erstellen, indem wir Dict Comprehensions verwenden und mit zip über die Werte aus Values
iterieren. Beachtet dabei, dass dies bei großen Dateien viel Speicherplatz benötigt, da hierfürdie Zeilen in Spalten umgewandelt werden:
[28]:
with open('out.csv') as f:
reader = csv.reader(f, 'my_csv_dialect')
lines = list(reader)
header, values = lines[0], lines[1:]
data_dict = {h: v for h, v in zip(header, zip(*values))}
data_dict
[28]:
{'': ('0', '1', '2', '3'),
'Titel': ('Python basics',
'Jupyter Tutorial',
'Jupyter Tutorial',
'PyViz Tutorial'),
'Sprache': ('en', 'en', 'de', 'en'),
'Autor*innen': ('Veit Schiele',
'Veit Schiele',
'Veit Schiele',
'Veit Schiele'),
'Lizenz': ('', '', '', ''),
'Veröffentlichungsdatum': ('2021-10-28',
'2019-06-27',
'2020-10-26',
'2020-04-13'),
'doi': ('', '', '', '')}
Um Dateien mit Trennzeichen manuell zu schreiben, könnt ihr csv.writer
verwenden. Er akzeptiert ein offenes, beschreibbares Dateiobjekt und die gleichen Dialekt- und Formatoptionen wie csv.reader
:
[29]:
with open('new.csv', 'w') as f:
writer = csv.writer(f, 'my_csv_dialect')
writer.writerow(('', 'Titel', 'Sprache', 'Autor*innen'))
writer.writerow(('1', 'Python basics', 'en', 'Veit Schiele'))
writer.writerow(('2', 'Jupyter Tutorial', 'en', 'Veit Schiele'))
[30]:
list(open('new.csv'))
[30]:
[',Titel,Sprache,Autor*innen\n',
'1,Python basics,en,Veit Schiele\n',
'2,Jupyter Tutorial,en,Veit Schiele\n']