Tabellen aus Daten

Eine Zahlenspalte mit r zu setzen ist der häufigste Fehler in LaTeX-Tabellen. Richtet man 182.5, 95.0, 1450.25 und 7 rechtsbündig aus, liegen die Dezimalpunkte exakt eine Ziffernbreite (5pt) auseinander, und die einzelne 7 landet an der Zehntel- statt an der Einerstelle. Das lässt sich nachmessen. Die Abhilfe ist ein einziges Zeichen in der Spaltenspezifikation: die S-Spalte aus siunitx, und die Dezimalmarker stehen auf 0,001pt genau untereinander. Diese Seite behandelt diese Ausrichtung, dann das Einlesen einer .csv zur Übersetzungszeit mit csvsimple, pgfplotstable und datatool sowie den modernen Weg über tabularray. Der Grundsatz durchweg: Die Daten bleiben in der Datendatei, LaTeX liest sie.

Zahlen am Dezimalpunkt ausrichten – die S-Spalte von siunitx

\usepackage{siunitx} laden und in der Spaltenspezifikation das r durch S ersetzen. Die S-Spalte analysiert die Zahl und richtet sie am Dezimalmarker aus, sodass eine Spalte mit unterschiedlich langen Einträgen lesbar wird. Liest man die Glyphenkoordinaten aus dem PDF zurück, ist der Unterschied deutlich. In einer r-Spalte liegen die Dezimalpunkte von 182.5 und 95.0 bei x = 62,0, der von 1450.25 bei x = 57,0 – rund 5pt auseinander, genau eine Ziffernbreite – und die 7 steht an der Zehntel- statt an der Einerstelle. Dieselben Daten als S[table-format=4.2] gesetzt: Alle vier Einerziffern enden bei x = 122,11, und jeder Dezimalmarker beginnt an derselben Stelle. Die Streuung liegt unter 0,001pt.

latex
\usepackage{siunitx}
\usepackage{booktabs}

% table-format = <integer digits>.<decimal digits> of the widest entry
\begin{tabular}{l S[table-format=4.2]}
  \toprule
  Sample & {Mass / \unit{\gram}} \\
  \midrule
  A &  182.5  \\
  B &   95.0  \\
  C & 1450.25 \\
  D &    7    \\
  \bottomrule
\end{tabular}

Zwei Dinge zählen. Erstens: table-format=<Vorkommastellen>.<Nachkommastellen> auf den größten Wert der Spalte setzen. Ein nacktes S funktioniert zwar, reserviert die Breite aber schlechter – im selben Beispiel wuchs die Spalte von 88,44pt auf 98,44pt. Zu klein angegeben, etwa table-format=2.1, erzeugt siunitx 3 überhaupt keinen Fehler, und die Ausrichtung bleibt erhalten; die Zahl läuft lediglich über den reservierten Raum hinaus und kollidiert leicht mit der nächsten Spalte. Zweitens: Alles, was für eine Zahl gehalten werden könnte, mit geschweiften Klammern {…} schützen. Das siunitx-Handbuch formuliert es genau so: Könnte das Material für einen Teil einer Zahl gehalten werden, sollte es durch Klammern geschützt werden. Umgekehrt wird gewöhnlicher Text wie Sample in v3 auch ohne Klammern korrekt zentriert.

So sieht „könnte verwechselt werden“ in Zahlen aus. Schreibt man eine Kopfzeile ohne Klammern als 2024 sales, greift siunitx die führende 2024 als Zahl auf, richtet sie an den Ziffern der übrigen Zeilen aus und setzt das verbleibende sales als nachgestelltes Material. Der Abstand zwischen 2024 und sales öffnet sich auf 7,75bp, gegenüber 3,32bp – einem gewöhnlichen Wortzwischenraum –, wenn das Ganze als {2024 sales} eingeklammert wird. Ein Fehler entsteht nicht: kaputt geht nur das Aussehen. Bei einer Überschrift mit Zahl also die Klammern nicht vergessen. Im Argument von \multicolumn oder \multirow lässt sich gar keine S-Spalte verwenden; dort greift man zur Makroform \tablenum[table-format=4.2]{1450.25}, die das Handbuch als „faktisch eine Makroversion der S-Spalte“ beschreibt.

Das Paket siunitx als Ganzes – die Syntax von \num, \qty und \unit, Unsicherheiten in Klammerschreibweise wie \num{1.234(5)}, Exponenten und die Unterschiede zwischen v2 und v3 – gehört der Seite „Einheiten (siunitx)“. Teilt man die Arbeit so auf, dass \qty die Größen im Fließtext übernimmt und die S-Spalte die Zahlen in Tabellen, bleibt der Zahlensatz im ganzen Dokument einheitlich. Auf der Tabellenseite ist eine typografische Konvention zu merken: Die Einheit gehört in den Spaltenkopf, nicht in jede Zelle. Das booktabs-Handbuch führt dieselbe Regel unter seinen Leitlinien auf.

Warum LaTeX die CSV lesen sollte

Weil man dann nicht bei jeder geänderten Ziffer das Manuskript bearbeitet. Messergebnisse und Auswertungstabellen verlassen Tabellenkalkulation oder Messgerät fast immer als CSV (kommagetrennter Text). Das von Hand in tabular-Zellen zu übertragen ist mühsam, und jede zusätzliche Zeile, jeder korrigierte Wert ist eine weitere Gelegenheit, sich zu vertippen. Dreht man den Gedanken um – die Daten bleiben in der Datendatei, und LaTeX bekommt den Auftrag, sie zu lesen und zu setzen –, genügt es, die Daten zu aktualisieren und neu zu übersetzen, damit die Tabelle folgt. Dieselbe CSV lässt sich beliebig oft im Text, in Folien und im Anhang wiederverwenden, ohne Übertragungsfehler. Das ist die unmittelbare Fortsetzung der LaTeX-Gewohnheit, logische Struktur und Erscheinung zu trennen.

Alle Beispiele dieser Seite verwenden die folgende kleine CSV-Datei. Ihre erste Zeile ist die Kopfzeile mit den Spaltennamen product, price und weight; der Rest sind Daten. Als data.csv neben der .tex-Datei gespeichert, lässt sich der folgende Code unverändert übersetzen.

data.csv
product,price,weight
Apple,380,182.5
Orange,120,95.0
Melon,1280,1450.25

Eine CSV mit csvsimple einlesen

\usepackage{csvsimple} laden und die eine Zeile \csvautotabular{data.csv} schreiben – schon ist die ganze CSV ein tabular. Die erste Zeile wird als Kopf mit Linien gesetzt, was völlig genügt, um sich den Inhalt anzusehen. Was fehlt, ist Kontrolle über die Gestaltung. Wer Ausrichtung, Linien und die auszugebenden Spalten selbst bestimmen will, nimmt \csvreader – das eigentliche Arbeitstier des Pakets. Ein Detail lohnt sich zu wissen: In v2.6.0, ausgeliefert mit TeX Live 2024, lädt ein schlichtes \usepackage{csvsimple} die ältere Implementierung csvsimple-legacy; in csvsimple.sty steht die Zeile \SetKeys{ legacy }. Für die LaTeX3-Implementierung muss man sie anfordern: \usepackage[l3]{csvsimple} oder \usepackage{csvsimple-l3}.

latex
\usepackage{csvsimple}
\usepackage{booktabs}
\usepackage{siunitx}

% NOTE the braces around the column spec: an unbraced S[...] breaks the key list
\csvreader[
  tabular        = {l r S[table-format=4.2]},
  table head     = \toprule Product & {Price} & {Weight} \\ \midrule,
  table foot     = \bottomrule,
  late after line = \\]
{data.csv}
{product=\product, price=\price, weight=\weight}
{\product & \price & \weight}

Die vier Teile von \csvreader[Optionen]{Datei}{Zuweisungen}{Körper} stehen der Reihe nach für die einzulesende Datei, die Bindung von Spaltennamen an Makros und das, was je Zeile ausgegeben wird. Schreibt man price=\price, expandiert \price im Körper zum Wert dieser Zeile. Der Rahmen kommt aus den Optionen: tabular= ist die Spaltenspezifikation, table head= die Kopfzeile, table foot= der Abschluss, und late after line = \\ hängt an jede Zeile einen Zeilenabschluss \\ an – die Redewendung, die einen überzähligen Umbruch nach der letzten Zeile vermeidet. Enthalten die Spaltennamen Leerzeichen oder Sonderzeichen, lässt man die Zuweisungen leer und spricht die Spalten mit \csvcoli, \csvcolii, \csvcoliii – erste, zweite, dritte Spalte – über die Nummer an.

Hier liegt die einzige echte Falle dieser Seite. Die geschweiften Klammern um den Wert von tabular im obigen Beispiel sind keine Zierde. Schreibt man ihn nackt als tabular = l r S[table-format=4.2], kollidiert das Komma, das Schlüssel-Wert-Paare trennt, mit dem Komma innerhalb von S[…]; der Lauf bricht ab mit ! Paragraph ended before \NC@rewrite@S was complete. Danach folgen noch ! Missing $ inserted. und ! Package csvsimple Error: File ',' not existent…, sodass kaum zu erkennen ist, dass die Spaltenspezifikation schuld ist. Mit Klammern – tabular = {l r S[table-format=4.2]} – läuft es durch. Die sichere Gewohnheit reicht über S hinaus: Steht eine Spaltenspezifikation mit eckigen Klammern in einer Schlüssel-Wert-Liste, gehört sie in geschweifte Klammern.

Standardmäßig gilt die erste Zeile als Kopfzeile und wird von den Daten ausgenommen. Für eine CSV ohne Kopfzeile liest die Sternform \csvreader* auch die erste Zeile als Daten. Daneben gibt es filter, um Zeilen nach Bedingung auszuwählen, sowie \csvstyle / \csvnames, um einen Satz Zuweisungen wiederzuverwenden – Möglichkeiten, die über Tabellen hinaus in die zeilenweise Verarbeitung allgemein reichen, etwa in die Erzeugung von Adressetiketten aus einer Kontaktliste.

Das Zahlenformat mit pgfplotstable gestalten

Wer gestalten möchte, wie die Zahlen selbst aussehen, greift am besten zu pgfplotstable. Es gehört zu pgfplots, wird mit \usepackage{pgfplotstable} geladen und hat einen zentralen Befehl: \pgfplotstabletypeset[Optionen]{data.csv}. Er liest die CSV, formatiert sie auf die gewünschte Genauigkeit und Zahlendarstellung und baut intern ein tabular als Ausgabe. Zum Lesen einer CSV muss das Trennzeichen mit col sep=comma angegeben werden – standardmäßig wird durch Leerzeichen getrennt. Gesteuert wird alles über Schlüssel-Wert-Optionen.

OptionWirkung
col sep=commaAls CSV (kommagetrennt) lesen; Vorgabe ist Trennung durch Leerzeichen
header=has colnamesZeile 1 als Spaltennamen behandeln; header=false bedeutet keine Kopfzeile
columnscolumns={a,b,…} wählt die auszugebenden Spalten und ihre Reihenfolge
columns/NAME/.stylecolumns/price/.style={…} wendet Formatierung auf eine benannte Spalte an
column nameErsetzt die gedruckte Überschrift, unabhängig vom CSV-Spaltennamen
fixedFestkomma; fixed zerofill füllt Nachnullen auf, precision=n legt die Nachkommastellen fest
sciIn wissenschaftlicher (Exponenten-)Schreibweise setzen; sci zerofill füllt die Mantisse auf
string typeEine Textspalte; es wird überhaupt keine Zahlenformatierung angewandt
dec sep alignRichtet die Spalte am Dezimalpunkt aus (benötigt array)
latex
\usepackage{pgfplotstable}
\usepackage{booktabs}
\pgfplotsset{compat=1.18}

\pgfplotstabletypeset[
  col sep = comma,
  header  = has colnames,
  columns = {product, price, weight},
  columns/product/.style = {string type, column name = Product},
  columns/price/.style   = {column name = Price, fixed, precision = 0},
  columns/weight/.style  = {column name = {Weight / g}, fixed, fixed zerofill,
                            precision = 1, dec sep align},
  every head row/.style  = {before row = \toprule, after row = \midrule},
  every last row/.style  = {after row = \bottomrule},
]{data.csv}

Die Spalte product ist string type (Text), price eine ganze Zahl (precision=0), weight steht mit einer Nachkommastelle samt Nullauffüllung und wird per dec sep align ausgerichtet. Die Überschriften ersetzt column name, und die Linien stammen aus booktabs-Befehlen, die über every head row und every last row eingeschleust werden. Allein das Ändern von precision ändert die Stellenzahl derselben Daten – das Aussehen der Zahlen zu steuern, ohne die CSV anzufassen, ist genau die Stärke von pgfplotstable. Vorsicht ist trotzdem geboten: Mit diesen Einstellungen erscheint 1450.25 als 1,450.3, also gerundet. Das Streichen von Stellen ist eine Formatanweisung, und gerundet wird stillschweigend; die signifikanten Stellen sollten daher bewusst gewählt werden. (Ohne Tausendertrennzeichen: 1000 sep={} ergänzen.)

pgfplotstable kann aus den eingelesenen Spalten auch berechnete Spalten ableiten: eine Spalte definieren, die bei Verwendung berechnet wird (create on use), oder Werte innerhalb von columns/…/.style über postproc cell content nachbearbeiten. Kurz: Tabellenkalkulationsarbeit vollständig innerhalb von LaTeX. Diese Mächtigkeit macht die Syntax schwer, daher die Faustregel: pgfplotstable für aufwendige Zahlentabellen, csvsimple für die schlichte CSV-zu-Tabelle. Wird dieselbe CSV zusätzlich geplottet, spricht das gemeinsame Werkzeug mit \addplot table aus pgfplots zusätzlich dafür.

datatool – die CSV als Datenbank behandeln

Das dritte Paket, datatool, liest eine CSV als Datenbank und glänzt bei der zeilenweisen Verarbeitung – Serienbrief-artige Arbeit. Nach \usepackage{datatool} holt \DTLloaddb{Name}{data.csv} die Datei in eine benannte Datenbank. Standardmäßig ist die erste Zeile die Kopfzeile, und ihre Spaltennamen werden zu den Schlüsseln der Werte. Bei einer CSV ohne Kopfzeile benennt \DTLloaddb[noheader]{…}{…} die Spalten automatisch Column1, Column2, … Nach dem Laden durchläuft \DTLforeach{Name}{Zuweisungen}{Körper} die Zeilen der Reihe nach. Die Zuweisungen lauten „Makro = Spaltenname“, etwa \DTLforeach{db}{\Product=product,\Price=price}{…}; im Körper expandieren \Product und \Price zu den Werten der jeweiligen Zeile.

latex
\usepackage{datatool}
\usepackage{booktabs}

\DTLloaddb{goods}{data.csv}

% the row break goes at the START of the loop body, not the end
\begin{tabular}{l r}
  \toprule
  Product & Price
  \DTLforeach{goods}{\Product=product, \Price=price}{%
    \\ \Product & \Price}
  \\ \bottomrule
\end{tabular}

Hier wird die CSV als goods geladen, und \DTLforeach innerhalb des tabular gibt für jede Zeile „Produkt & Preis“ aus. Zu beachten: Der Zeilenabschluss \\ steht am Anfang des Körpers, nicht am Ende. Steht er am Ende, eröffnet der letzte Durchlauf eine leere Zeile; das folgende \bottomrule (oder \hline) landet dann in einer Zelle dieser Zeile, und der Lauf bricht mit ! Misplaced \noalign. ab. Ihn voranzustellen und nach der Schleife ein \\ zu ergänzen, ist die sichere Form. Die eigentliche Stärke von datatool liegt weniger im Tabellensatz als in der Datenmanipulation: Es kann Zahlen summieren und mitteln, sortieren und Zeilen bedingt ausschließen, und es dient der Erzeugung von Literaturverzeichnissen und Serienbriefen. Wer umgekehrt nur eine CSV in eine Tabelle verwandeln will, fährt mit csvsimple knapper.

Zahlen mit der Q-Spalte von tabularray ausrichten

Mit \UseTblrLibrary{siunitx} bringt tabularray dieselbe Dezimalausrichtung wie die S-Spalte in die tblr-Umgebung, und zwar als Q[si={table-format=4.2}]. Kommt \UseTblrLibrary{booktabs} hinzu, funktionieren \toprule / \midrule / \bottomrule wie gewohnt, während die Gestaltung vollständig in Schlüssel-Wert-Form angegeben wird, etwa über colspec oder row{1}={font=\bfseries}. Der große Reiz liegt darin, Syntax wie >{…} oder \multirow nicht neu lernen zu müssen. tabularray selbst – das Konzept von tblr, width=, Zellverbindungen, rowsep und colsep – gehört jedoch der Seite „Erweiterte Tabellenumgebungen“, weshalb es hier ausschließlich um Zahlenspalten geht.

latex
\usepackage{tabularray}
\UseTblrLibrary{booktabs}
\UseTblrLibrary{siunitx}

\begin{tblr}{colspec = {l r Q[si={table-format=4.2}]}}
  \toprule
  Product & Price & {{{Weight}}} \\
  \midrule
  Apple  &  380 &  182.5  \\
  Orange &  120 &   95.0  \\
  Melon  & 1280 & 1450.25 \\
  \bottomrule
\end{tblr}

Welches Werkzeug wofür

Nach Zweck zu wählen führt am schnellsten zum Ziel. Umfassen die Daten einige Dutzend Zeilen und sollen lediglich die Zahlen einer von Hand geschriebenen Tabelle fluchten, genügt die S-Spalte. Ändert sich die CSV laufend, wechselt man zu einem der Leseprogramme. Eine grobe Orientierung:

  • Nur die Dezimalpunkte sollen fluchten – die S-Spalte aus siunitx. Sie fügt sich direkt in ein von Hand geschriebenes tabular; innerhalb von \multicolumn nimmt man \tablenum.
  • Schlichte CSV zur Tabellecsvsimple. \csvautotabular für die Sofortlösung, \csvreader, wenn Ausrichtung, Linien und Spaltenauswahl steuerbar sein sollen.
  • Stellenzahlen und Zahlendarstellung gestalten oder berechnete Spaltenpgfplotstable. Am mächtigsten, aber mit schwerer Syntax, und gerundet wird stillschweigend.
  • Die Datenmanipulation ist der Zweck (Summen, Sortieren, bedingte Verarbeitung, Serienbriefe) – datatool.
  • Schlüssel-Wert-Syntax gewünscht oder eine Zahlenspalte in einem bestehenden tblrtabularray mit der siunitx-Bibliothek und Q[si={…}].

Welche Methode auch gewählt wird, am Ende steht dieselbe tabular-Sprache – Spaltenspezifikation, &, \\ und Linien. Funktioniert das Einlesen, bleibt die Tabelle aber schwer lesbar, liegt es meist an den Linien: \hline durch die drei booktabs-Linien ersetzen und die Einheiten in die Spaltenköpfe verschieben, und dieselben Daten lesen sich weitaus leichter. Für eine Tabelle, die zu breit für die Seite ist, siehe „Erweiterte Tabellenumgebungen“; für eine, die über einen Seitenumbruch läuft, „Seitenübergreifende Tabellen“; für Captions und Platzierung „Tabellenplatzierung und Gestaltung“.