When a table’s contents already exist as external data — a .csv file, say — there is no need to retype each cell by hand. LaTeX has several packages that read a CSV and turn it into a table, so the source need not be rewritten when the data changes. This page introduces three, with guidance on when to reach for each: the quick **csvsimple, the formatting-heavy pgfplotstable, and the database-style datatool**. It closes with the siunitx S column for aligning numbers on the decimal point (the S column is covered in full on the “Units (siunitx)” page).
Why build tables from data
Experimental results and summary tables almost always come out of a spreadsheet or an instrument as CSV (comma-separated values). Copying these into tabular cells by hand is tedious and error-prone — every added row, every changed digit means editing the manuscript again.
Turn the idea around: leave the data in a data file and merely tell LaTeX to read and typeset it. Then updating the data and recompiling is enough for the table to follow. With one source CSV you can reuse the same numbers across the body, slides, and an appendix as often as you like, with no transcription mistakes. This separation of data from presentation is of a piece with LaTeX’s habit of separating logical structure from appearance.
The examples on this page use the small CSV file below. Its first line is a header row naming the columns product, price, and weight; the remaining lines are the data.
product,price,weight
Apple,380,182.5
Orange,120,95.0
Melon,1280,1450.2Reading quickly — `csvsimple`
csvsimple is a lightweight package that reads a CSV to build tables and loops. Load it with \usepackage{csvsimple} (the current release selects the LaTeX3 implementation csvsimple-l3 internally). The quickest entry point is **\csvautotabular{data.csv}**: give it only a filename and it pours the whole CSV into a tabular automatically, setting the first line as a heading with rules. It suits a fast look at the contents.
\csvautotabular{data.csv}That single line yields a three-column, three-row table headed product, price, weight. It is convenient, but its formatting is limited. **When you want to decide alignment, rules, and which columns to keep, use \csvreader** — the real workhorse of csvsimple.
\csvreader takes three arguments in the form \csvreader[options]{data.csv}{assignments}{body}. The second argument is the file to read, the third binds column names to macros, and the fourth is what to emit for each row. Writing price=\price, for instance, makes the value of the price column available as \price inside the body. The surrounding frame is given through options such as tabular= and table head= (the heading row).
\csvreader[
tabular = l r r,
table head = \hline Product & Price & Weight \\ \hline,
late after line = \\]
{data.csv}
{product=\product, price=\price, weight=\weight}
{\product & \price & \weight}Here tabular = l r r declares three columns (“left, right, right”), table head places the heading row and a top rule, and the body {\product & \price & \weight} expands each data row into cells. late after line = \\ tells csvsimple to **append a row terminator \\ after each line** — the idiom for putting a break only between rows and leaving no stray break after the last one. If header names contain spaces or symbols, you can instead address columns by number in the third argument: \csvcoli, \csvcolii, \csvcoliii, … stand for the contents of the first, second, third column.
By default the first line is treated as a header and excluded from the data. To read a CSV that has no header row, the starred form \csvreader* reads the first line as data too. csvsimple also offers filter to select rows by condition and \csvstyle / \csvnames to reuse a set of assignments — facilities that extend beyond tables to general per-row processing.
Reading and formatting — `pgfplotstable`
When you want to engineer the very look of the numbers, pgfplotstable is the most powerful choice. Part of pgfplots, it is loaded with \usepackage{pgfplotstable}. Its central command is a single one — **\pgfplotstabletypeset[options]{data.csv}** — which reads the CSV, formats it to the requested precision and number style, and assembles a tabular internally as output. To read a CSV, declare the separator with col sep=comma.
Everything is controlled through key–value options. The most important are listed here.
| Option | What it does | |
|---|---|---|
col sep=comma | col sep=comma | Read as CSV (comma-separated); the default is space-separated |
header | header=has colnames / header=false | Treat line 1 as column names / treat as having no header |
columns | columns={a,b,...} | Select which columns to output, and in what order |
columns/NAME/.style | columns/price/.style={...} | Apply formatting to one named column |
column name | column name=Heading | Replace the printed heading for that column |
fixed | fixed, fixed zerofill, precision=n | Fixed-point, zero-padded decimals, n decimal places |
sci | sci, sci zerofill | Set in scientific (exponent) notation |
string type | string type | A text column (no number formatting applied) |
dec sep align | dec sep align | Align the column on the decimal point (needs array) |
\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},
]{data.csv}Here the product column is set as string type (text), price as an integer (precision=0), and weight to one decimal place with trailing-zero fill, aligned on the decimal point via dec sep align. The headings are replaced with column name. **The same data changes its digit count just by changing precision** — controlling the numbers’ appearance independently of the CSV is exactly what pgfplotstable is good at.
pgfplotstable can also derive computed columns from the ones it reads: you can define a column that is “computed on use” with create on use, or post-process values inside columns/.../.style (via postproc cell content) — spreadsheet-like work done entirely within LaTeX. Rules are tuned with every head row/.style or booktabs integration (a setting that inserts \toprule / \midrule / \bottomrule automatically). All that power makes the syntax heavy, so a good rule of thumb is **pgfplotstable for elaborate numeric tables, csvsimple for plain CSV-to-table**.
As a database — `datatool`
The third package, datatool, reads a CSV as a database and excels at per-row processing (mail-merge–style work). Load it with \usepackage{datatool}, then take a CSV into a named database with \DTLloaddb{name}{data.csv}. By default the first line is the header, and its column names become the keys for each value. For a CSV with no header, \DTLloaddb[noheader]{...}{...} names the columns Column1, Column2, … automatically.
Once loaded, **\DTLforeach{name}{assignments}{body}** walks the rows in turn. Assignments are written “macro = column name,” as in \DTLforeach{db}{\Product=product,\Price=price}{…}, and inside the body \Product and \Price expand to that row’s values. To make a table, run \DTLforeach inside a tabular and end the body with a row terminator \\.
\DTLloaddb{goods}{data.csv}
\begin{tabular}{l r}
\hline
Product & Price \\
\hline
\DTLforeach{goods}{\Product=product, \Price=price}{%
\Product & \Price \\}
\hline
\end{tabular}Here the CSV is loaded as goods, and inside the tabular \DTLforeach emits “product & price” for each row. datatool’s real strength is data manipulation more than table-setting: it can sum and average numbers, sort, and exclude rows by condition through macros, and it is used to generate bibliographies and merged documents. Conversely, if you only need to turn a CSV into a table, csvsimple is more concise.
Aligning numbers on the decimal — the siunitx `S` column
Whether the data comes from a CSV or not, a column of numbers shares one problem: it is hard to read unless the digits line up. The standard way to solve this in a hand-written tabular is the siunitx **S column**. Write S in the column specification in place of r (or others), and the numbers in that column align at the decimal point. It is available once you load \usepackage{siunitx}.
\begin{tabular}{l S[table-format=4.1]}
\toprule
{Product} & {Weight / \unit{\gram}} \\
\midrule
Apple & 182.5 \\
Orange & 95.0 \\
Melon & 1450.2 \\
\bottomrule
\end{tabular}Here the second column is S[table-format=4.1] — “4 integer digits, 1 decimal place” — aligning 182.5, 95.0, and 1450.2 at the decimal point. There are two key points. First, set table-format=integer.decimal to the largest value in the column. Second, **wrap any text such as a column heading in braces {…} to protect it** ({Weight / \unit{\gram}}). Forget the protection and siunitx tries to read the heading as a number, and the alignment breaks.
The fuller use of the S column — specifying table-format, protecting text, the macro form \tablenum for use inside \multicolumn, and handling exponent-bearing values — is covered on the “Units (siunitx)” page. Dividing the work — \qty for quantities in the prose, the S column for numbers in tables — keeps the numeric style consistent across the whole document.
Which to use
The best approach is to pick by purpose. A rough guide follows.
- Plain CSV → table:
csvsimple.\csvautotabularfor an instant table;\csvreaderto control alignment and rules. - Engineering digit counts / number styles, or computed columns:
pgfplotstable. The most powerful, but heavier syntax. - Data manipulation as the main goal — summing, sorting, conditional processing:
datatool. Good for merged documents too. - Just aligning a column of numbers on the decimal: the
siunitxScolumn. Drops straight into a hand-writtentabular.
Whatever the method, the foundation is the same tabular vocabulary — the column spec, &, \\, and rules. Get “tabular basics” down first, then choose a tool to match the volume of data and the formatting you need.