kmail.at
contact

elsewhere

EnglishEN
← learning

pandas · difficulty ◆◆

pandas read_csv() — load CSV files with full control over types and parsing

Eighty percent of a clean DataFrame is decided at read_csv() time.

Your CSV never survives contact with read_csv() unchanged — and that's the point.

AI-generated draft · reviewed and editedThis text was generated with AI as a first draft, then reviewed and edited by the author, who holds editorial responsibility for it.

2026-09-12 · 5 min read

$ pd.read_csv()
44
Signature parameters

counted via inspect.signature(pd.read_csv) on pandas 3.0.3

0.10
C parser since

Dec 2012 release; Python parser demoted to opt-in fallback

datetime64[us]
Parsed dates in 3.0

microsecond resolution, confirmed by df.dtypes on pandas 3.0.3

What it does

read_csv() reads delimited text into a DataFrame in one call. It sniffs headers, infers dtypes per column, splits NA detection from parsing, and hands you a table — with 44 parameters to steer every step. The defaults are good; the craft is knowing which four to touch.

Why it matters

The type coercion happens the moment the file is read: '1,199.00' becomes a text column (str in pandas 3.0, object before it), a stray blank in a units column floats everything to float64, dates stay strings unless you ask. Fixing types after the fact is rework; read_csv() lets you fix them at the door — then usecols trims the file down before the parser even builds the columns.

Example

$ You received orders_q3.csv. Look at what inference did to it before you touch anything:
order_id            int64
region                str
product               str
units             float64
unit_price_eur        str
order_date            str
dtype: object

One blank cell turned units into float64, the thousands separator froze unit_price_eur as str, and dates stayed text. Three fixes, all at read time.

$ Right-size the dtypes so the DataFrame matches reality — IDs as int32, counts as float32:
order_id            int32
region                str
product               str
units             float32
unit_price_eur        str
order_date            str
dtype: object

dtype takes a dict {column: type}. int32 is half the memory of int64 for IDs, and pandas 3.0 makes str the default dtype for text columns — its storage backend is Arrow only when pyarrow is installed (mode.string_storage='auto' resolves to 'pyarrow' if it is present and 'python' if it is not; on this box, without pyarrow, `region`.dtype.storage is 'python').

$ European exports use thousands separators, and you only need four columns with real dates. Strip, parse, trim — in one call:
   order_id region  unit_price_eur order_date
0      1001  North         1199.00 2026-07-14
1      1002  South          229.90 2026-07-15
2      1003   East         1199.00 2026-07-18
3      1004   West           89.50 2026-07-21
4      1005  North         229.90 2026-08-02
5      1006   East          59.99 2026-08-09
6      1007  South         1199.00 2026-08-27
7      1008   West          229.90 2026-09-05

datetime64[us]

converters run per cell and beat inference; parse_dates turns the column into datetime64[us]; usecols drops the other two columns before the parser builds them. Date span here: 53 days, 14 Jul to 5 Sep.

$ The file is 40 GB and your RAM is not. Stream it in chunks and accumulate:
13981.45

chunksize=3 yields an iterator of DataFrames; each chunk is garbage-collected after you use it, so memory stays flat no matter how big the file. The sum matches the one-shot read.

Common flags

sep / delimiter
Field separator. The C engine takes single characters; sep=None is handled by the Python engine, which sniffs the delimiter from the first valid row with csv.Sniffer, and a regex or multi-character separator forces that same engine. delimiter is a plain alias for sep.
usecols
Read only named columns — saves parse time and memory before the DataFrame exists.
dtype
Dict of {column: type}; wins over inference, ideal for int32 IDs or string columns that inference would mangle.
parse_dates / date_format
Columns to parse as dates; pair with date_format to nail non-ISO layouts.
converters
Dict of {column: function} applied per cell — the escape hatch when no parameter can clean the value.
na_values / keep_default_na
Extra strings to treat as missing; keep_default_na=False stops 'NA' (Namibia) becoming NaN.
na_filter / chunksize
na_filter=False skips NA scanning on clean files; chunksize=N streams big files as an iterator of DataFrames.
engine='c' | 'python'
The C engine is the default and engine='python' is the fallback — the only one that supports skipfooter, and on pandas 3.0.3 asking the C engine for skipfooter does not raise, it prints 'Falling back to the python engine because the c engine does not support skipfooter'. Measured here on a 200k-row, 5.6 MB CSV: the C engine read all four columns in 0.112 s; usecols=['a','b'] cut that to 0.051 s and the frame from 16.7 MB to 3.2 MB.

History

0.1–0.4.0 (2009–2011) — parseCSV first, read_csv in 2011

read_csv() first appears in the pandas 0.4.0 sdist, uploaded 2011-09-12 — not 2009. What pandas shipped in its first release on 2009-12-25 was pandas/io/parsers.py with parseCSV() and parseText(), and both read the file with Python's own csv module (csv.reader(f, dialect='excel')), not with matplotlib's mlab.csv2rec: no sdist from 0.1 through 0.4.0 mentions matplotlib or csv2rec anywhere. read_table() arrives in 0.3.0 (2011-02-20); read_csv() lands in 0.4.0 and still sits on top of csv.reader.

0.10 (Dec 2012)

0.10 (Dec 2012) rewrote the parsing engine and made engine='c' the default, with engine='python' as the documented alternative — the engine parameter is therefore as old as the C parser, not a 0.21 addition. The 0.10.0 notes make the speed claim themselves: the new engine 'uses a fraction the amount of memory while parsing, while being 40% or more faster in most use cases (in some cases much faster)'.

Under the hood

Under the hood

The C engine is a tokenizer in pandas/_libs/parsers.pyx (Cython) feeding a C-level tokenizer that, by its own header, 'combines some elements from Python's built-in csv module and Warren Weckesser's textreader project' — the ancestry is the stdlib csv module and textreader, not numpy's parser. It scans once, classifying each field as int, float, bool or string; the tokenizer's fast paths for common numeric formats decide your dtypes before any Python code runs. usecols, na_values and dtype are all resolved inside that single pass — which is why reading a 40 GB file with chunksize costs the same total parse time as one big read, but flat memory.

Fun facts

Pros & cons

pros

  • + One call handles headers, separators, compression and type inference — no boilerplate around a bare csv.reader.
  • + 44 parameters let you fix messy real-world files at the door instead of patching afterwards.
  • + chunksize streams files larger than RAM with a plain for loop.

cons

  • − Type inference is silent: a stray blank or separator can flip a column's dtype with no warning.
  • − The Python engine (needed for skipfooter) can be dramatically slower than the C engine on big files.
  • − 44 parameters is a surface area; dialects, quoting and encodings still need reading the docs.

Takeaways

  1. 1Fix types at read time with dtype and parse_dates — after-the-fact astype is rework.
  2. 2usecols first: the cheapest data is the data you never parse.
  3. 3Files bigger than memory: chunksize streams them as an iterator of DataFrames.
  4. 4keep_default_na=False when 'NA' means Namibia, not missing.
  5. 5Converters are the per-cell escape hatch when no parameter can clean a column.

Related commands