xan vs csvkit

2026-10-17

I had a table of orders sitting in a text file. Two hundred thousand rows. Four columns: an id, a region, an amount, and a status. The file was 5,058,594 bytes, a little over five megabytes. The next click most people make is a spreadsheet. I did not open one. I ran xan and csvkit on that same file.

xan vs csvkit comes down to this: for a sum, a mean, and a filter, xan finished in a fraction of a second and stayed near 12 MB of memory. csvstat, the stats command in csvkit, took 4.58 seconds and about 170 MB, and it also computed the median and the most common values. Same file. Same sum. Not the same job.

xan vs csvkit on the same orders.csv: xan answered in 0.03 seconds with about 12 MB, csvkit took 4.58 seconds and about 170 MB but added the median
Same file, same sum, not the same job.

What is a CSV

Simple mind map of xan vs csvkit: what is a CSV, fast sums with xan, median with csvkit, filter with eq, Excel via in2csv, keep both

Before either tool makes sense, the file itself has to. A CSV is a text table. CSV means comma-separated values (plain text where each line is one row and commas split the cells). A row is one record, like one order. A column is one fact about that record, like the amount. There is no grid until some program draws one.

What a CSV is: a header row of column names with one order per line underneath
The header names the columns; every line under it is one record.

The first line of my file was id,region,amount,status. That line is the header (the names of the columns, not an order). Every line under it is one order. If a cell itself contains a comma, the file wraps that cell in quotes so the comma is not read as a new column. That quoting is why a quick split on commas in a homemade script eventually corrupts a real export. Both tools exist so you do not write that split yourself.

A spreadsheet draws those rows as a grid and also keeps formulas, colors, and several sheets inside one file. A CSV keeps none of that. It is the letters and the commas. The grid tries to hold the whole table where you can see it. A small program can read one line, update a total, and forget the line. That difference is the whole reason to leave the spreadsheet closed.

A spreadsheet draws the whole CSV as a grid, while xan or csvkit read one line at a time
A grid holds the whole table; a stream reads a line and moves on.

xan vs csvkit

xan vs csvkit is a choice between two command line tools for that text table. A command line tool, also called a CLI (a program you start by typing its name, not by clicking an icon), reads the file and prints an answer. xan is one program with many subcommands, such as count, filter, and stats. csvkit is a set of separate programs, such as csvstat and csvgrep, built for people who already use Python. Both can count rows, pick columns, and keep some rows. They do not both do that at the same cost, and they do not both accept an Excel file.

Jobxancsvkit
Count, sum, mean, min, maxxan stats -s amount file.csvcsvstat -c amount file.csv
Keep rows where status is exactly paidxan filter 'status eq "paid"' file.csvcsvgrep -c status -m paid file.csv
Turn an Excel file into CSVnot this toolin2csv budget.xlsx
Ask a SQL questionnot the usual pathcsvsql --query "..."
Median in the default stats runoff unless you add -qincluded in csvstat

I expected xan to feel like a faster csvkit with the same buttons. What happened was narrower. The fast path is streaming math. A stream means the program reads a row, updates a running total, and moves on, instead of loading every row into a list first. csvstat does more work on purpose. It finds the median (the middle value once the numbers are ordered), how many distinct amounts exist, and which amounts show up most. That extra work is real. It is also why xan is not a hundred times faster at the same statistics. It was much faster at the shorter list.

xan vs csvkit decision: a sum or exact filter goes to xan, a median, Excel file, or SQL question goes to csvkit
Pick by the question, not by the speed chart.

How to install xan

Install xan from a package if your system already has one. On a Mac that is brew install xan. If you already have Rust’s installer, cargo install xan --locked builds it. csvkit is pip install csvkit. After that, xan --version should print a version and csvstat --help should print help. If the terminal says the command is not found, the install did not land on your PATH (the list of folders the terminal searches when you type a name).

xan --version
csvstat --help

One caveat. The first Linux build I tried refused to start. The error was GLIBC_2.39 not found. GLIBC is the C library (the shared base almost every Linux program is built against). That build wanted a newer base than the machine had. The build marked musl (a different C library shipped inside the file, so it does not borrow the machine’s) started and printed 0.61.0. If you see that GLIBC line, switch to the musl file. Do not keep retrying the same download.

How to install xan on Linux: if the ordinary build fails with GLIBC_2.39 not found, switch to the musl build
A GLIBC error means switch builds, not retry the download.

How do I filter a large CSV without Excel

You filter a large CSV without Excel by naming the rows you want and letting the rest fall on the floor. You do not load the grid. A filter here means a test each row must pass. Fail the test, and that row is not printed.

My status column held three words: paid, open, and refund. I wanted paid. This is the command that worked:

xan filter 'status eq "paid"' orders.csv | xan count

The vertical bar is a pipe (it hands the first program’s printed text to the next program as if that text were a file). xan count then counts the rows that survived. I got 66,712.

The obvious comparison operator did not work the first time. I first typed status == "paid", the way I would in Python. xan stopped. It said it could not safely cast the word “open” from bytes to a number. Bytes here just means the raw text of the cell. The == path was trying to do arithmetic, so a word like “open” had nowhere to go. The word eq compares the cell as text. That is the whole fix. If a filter errors on a word that is clearly text, try eq before you edit the file.

xan filter on text: status == paid errors because it tries numbers, status eq paid returns 66,712 rows
Use eq for text cells.

csvkit’s matching command is a different test:

csvgrep -c status -m paid orders.csv | wc -l

wc -l counts lines, and the header is a line too. I got 66,713 lines, which is the header plus the same 66,712 paid rows. -m is a pattern, not “the cell equals this word.” A cell that said prepaid would also match paid. eq does not do that. On this file the three statuses did not contain each other, so the counts agreed. On a messier export they might not. Read the values before you trust either count. xan freq -s status orders.csv prints how many rows hold each value, which is the check I now run first.

Filter a large CSV without Excel: run xan freq on the status column first, then keep paid rows with eq
Check the real values before trusting a count.

xan vs csvkit on a big file

On that same file, xan vs csvkit looked like this. Times are wall time from start to a printed answer. The memory number is the high water mark while the process ran.

What I ranTimeMemoryAnswer
xan count0.024 s200000
xan freq -s status0.027 spaid 66712, open 66685, refund 66603
xan stats -s amount0.030 s12,484 KB, about 12 MBsum 50002638.08, mean 250.013, min 0, max 500
xan filter 'status eq "paid"' then xan count0.057 s66712
Python’s csv reader, mean of amount0.38 smean 250.0132
Python’s csv reader, paid rows only0.295 s66712 rows, sum 16667378.72
xan filter then xan stats -s amount0.054 ssame paid sum, 16667378.72
csvstat -c amount4.580 s174,552 KB, about 170 MBsame sum and mean, plus median 249.98 and 49,132 unique amounts
csvgrep -c status -m paid then wc -l0.429 s66713 lines

Memory here is RAM (the scratch space a program uses while it runs, not the size of the file on disk). The file was about 5 MB. xan stats stayed near 12 MB. csvstat climbed to about 170 MB. Python’s reader, which is the script people write when they do not want a new tool, took 0.38 seconds for the mean and agreed with xan to the rounding I printed: 250.013. The paid sum agreed to the cent: 16,667,378.72.

xan vs csvkit memory on a 5 MB CSV: xan stats near 12 MB, Python csv reader 0.38 seconds, csvstat about 170 MB and 4.58 seconds
Memory is RAM while the program runs, not file size.

The result of all this is not “uninstall csvkit.” csvstat’s extra seconds bought a median of 249.98 and a count of distinct amounts. If that is the question, the wait is the work. If the question was the sum, xan had it before csvstat had finished reading.

When csvkit is the better tool

csvkit is the better tool when the file is not a CSV yet, or when the question is already a database question. in2csv budget.xlsx > budget.csv turns a spreadsheet into text. xan does not open xlsx (the Excel file format, a zip of sheets rather than lines of commas). If the sheet has formulas, those formulas are not in the CSV either. They have already been flattened to values, or they have been dropped, depending on the converter. For a real spreadsheet an agent can edit, OfficeCLI writes the xlsx. xan is for the text table after you have decided the formulas are not the point.

When csvkit is better: in2csv turns an Excel file into CSV text for xan, and OfficeCLI keeps formulas
Convert first, or keep the spreadsheet when formulas matter.

csvsql lets you type SQL against the file. SQL is the sentence people use on databases (“select the sum where the region is north”). If that sentence is how you already think, stay with csvkit. xan has its own small expression language. I paid a learning cost on the first filter, the == that was not eq. A language you already know beats a faster language you will mistype.

csvstat’s median was 249.98. xan can compute a median too, with xan stats -q, but that flag was not part of the timing above, so there is no speed number for it here. If the median is the number you came for, run csvstat and accept the wait, or add -q and time it on your file. The full command list is in the csvkit manual, and the xan commands are in the xan project on GitHub.

When not to use xan

Do not use xan when the table is small enough that opening it is the fastest way to see a typo. Fifty rows do not need a specialized tool. Do not use it when you need colors, a chart you can click, or a second sheet. That is a spreadsheet. A picture of a database is a different job again. An ER diagram from a Prisma schema starts from a schema file, not from orders.csv. A resume written as a text file is a different job too, which is what rendercv vs latex is about. The habit that transfers is “text in, a result out.” The tool does not transfer.

When not to use xan: route CSV numbers to xan, xlsx or SQL to csvkit, PDFs and documents to other tools, and tiny files to your eyes
The tool depends on the file.

Do not point xan at a PDF or a Word file. Those are not rows and commas. pdfcraft vs qpdf is the page for changing a PDF. wordcraft vs libreoffice is the page for a document. And if the only reason you opened a huge app was one small edit, the same instinct that keeps you out of Excel is the one in CLI-Anything vs computer use.

Also skip xan if the install will not start and you do not want to swap builds. A tool you cannot run is slower than csvkit, no matter what the table says. A slow answer beats a binary that prints a library error and exits.

How do I query a huge CSV in the terminal

You query a huge CSV in the terminal by naming the column and the test, then sending the surviving rows into a count or a stat. Start with the headers so you are not guessing the spelling.

xan headers orders.csv
xan freq -s status orders.csv
xan filter 'status eq "paid"' orders.csv | xan stats -s amount

xan headers prints each column name with a number in front of it. If the column you care about is Status with a capital S, use that capital. These tools do not fix your case. xan freq then shows the real values. If the file contains both Paid and paid, eq "paid" keeps only one of them. I almost missed that class of bug because my file was already one case.

How to query a huge CSV in the terminal: xan headers, then xan freq, then filter with eq, then xan stats
Headers, values, filter, stats.

A huge file is still a text file. You can keep the filtered rows with a redirect, which is > sending the printout into a new file instead of onto the screen:

xan filter 'region eq "north"' orders.csv > north.csv

Open north.csv only if you need to see a few lines. xan head -l 5 north.csv prints the header and five rows. head here is xan’s command, and -l is the limit. I first tried a different flag and xan looked for a file named 3. The limit flag is -l, not the one other tools use. That is the second small trap, after eq.

Is xan worth it

Yes, if you already have CSV files that are clumsy to scroll, and the question is a count, a filter, a frequency, or a sum. The install is one command once the right build starts. The paid-row filter is one line. On this file the answer matched Python to the cent.

No, if your day is Excel in and SQL out. csvkit already speaks that day. Half a second to filter with csvgrep is not a problem on a file this size. Four and a half seconds for a median you actually wanted is not a problem either. xan would be a second habit on top of a tool that already finishes.

Is xan worth it: use xan for a sum, count, or exact filter; stay with csvkit for a median, xlsx, SQL, or when the binary will not start
Keep both; pick per question.

Send a coworker the filter line and the GLIBC note, not a speech about speed. The orders file did not care which tool I picked. It had the same sum either way. I am keeping both. xan is what I type when the file is already CSV and I want the number before I stand up. csvkit is what I type when the file is still a spreadsheet, or when the question is SQL.

Common questions about xan vs csvkit

How do I filter a large CSV without Excel?

Run xan filter 'status eq "paid"' file.csv, and replace the column and the word with yours. Pipe it to xan count if you only need how many rows survived. Use eq for text. == tried to treat my status words as numbers and stopped.

Is xan worth it over csvkit?

For a sum or an exact filter on a large text table, yes. On 200,000 rows, xan stats took 0.03 seconds and about 12 MB. csvstat took 4.58 seconds and about 170 MB because it also computed the median and the common values. If you need that median, or you need to convert an Excel file, keep csvkit.

Can xan open an Excel file?

No. xan reads CSV and formats that are already close to rows of text. Convert first with in2csv file.xlsx > file.csv, or edit the spreadsheet with a spreadsheet tool. Once the formulas matter, you have left CSV.

Why did xan filter fail on a word?

Because == was the wrong test for text. The error said xan could not cast a word to a number. status eq "paid" compared the cell as text and returned 66,712 rows, the same count Python got by reading the file.

JOIN OUR NEWSLETTER
Be the first to know. Get fresh AI/Tech updates instantly, no spam, unsubscribe anytime

1 comment

Leave a comment