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.$ pd.read_csv()- 44
- Signature parameters
- 0.10
- C parser since
- datetime64[us]
- Parsed dates in 3.0
counted via inspect.signature(pd.read_csv) on pandas 3.0.3
Dec 2012 release; Python parser demoted to opt-in fallback
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: objectOne 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: objectdtype 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.45chunksize=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
- 1Fix types at read time with dtype and parse_dates — after-the-fact astype is rework.
- 2usecols first: the cheapest data is the data you never parse.
- 3Files bigger than memory: chunksize streams them as an iterator of DataFrames.
- 4keep_default_na=False when 'NA' means Namibia, not missing.
- 5Converters are the per-cell escape hatch when no parameter can clean a column.
