pandas Error tokenizing data: Expected N fields (tested)

ParserError: Error tokenizing data. C error: Expected 3 fields in line 3, saw 4 means one row has more separators than the header. error_bad_lines=False no longer exists; use on_bad_lines="skip", or better, fix the separator or quoting. Tested on pandas 2.3.3 and 3.1.0.

1. The error, and why error_bad_lines no longer works

import io
import pandas as pd

data = "a,b,c\n1,2,3\n4,5,6,7\n8,9,10\n"   # line 3 has 4 fields
try:
    pd.read_csv(io.StringIO(data))
except pd.errors.ParserError as e:
    print(type(e).__name__ + ":", e)

try:
    pd.read_csv(io.StringIO(data), error_bad_lines=False)
except TypeError as e:
    print("TypeError:", e)

df = pd.read_csv(io.StringIO(data), on_bad_lines="skip")
print(df.values.tolist())

Output (identical on pandas 2.3.3 and 3.1.0):

ParserError: Error tokenizing data. C error: Expected 3 fields in line 3, saw 4

TypeError: read_csv() got an unexpected keyword argument 'error_bad_lines'. Did you mean 'on_bad_lines'?
[[1, 2, 3], [8, 9, 10]]

"Expected 3 fields in line 3, saw 4" counts lines from 1 including the header, so line 3 is the second data row. Old answers say error_bad_lines=False; that argument was removed in pandas 2.0 and now raises TypeError. The replacement is on_bad_lines="skip" (drop the row) or "warn" (drop it and print which line).

2. Keep or log bad rows instead of dropping them

import io
import pandas as pd

data = "a,b,c\n1,2,3\n4,5,6,7\n"
bad = []
def keep(fields):            # called with the split fields of each bad line
    bad.append(fields)
    return fields[:3]        # return a fixed row, or None to drop it

df = pd.read_csv(io.StringIO(data), on_bad_lines=keep, engine="python")
print(df.values.tolist())
print("bad:", bad)

Output (identical on pandas 2.3.3 and 3.1.0):

[[1, 2, 3], [4, 5, 6]]
bad: [['4', '5', '6', '7']]

on_bad_lines also accepts a function that receives the split fields and returns a repaired list, or None to drop the row. It needs engine="python" (or "pyarrow"); with the default C engine pandas raises ValueError: on_bad_line can only be a callable function if engine='python' or 'pyarrow' (we checked on both versions).

3. Fix the cause: separator, quoting, or header

import io
import pandas as pd

# 1) separator is ; but read with the default ,
semi = "name;note\nann;hi, there\nbob;ok\n"
print(pd.read_csv(io.StringIO(semi), sep=";").values.tolist())

# 2) a comma inside an unquoted field: on the FIRST data row there is no error,
#    pandas silently turns the extra leading field into the index
raw = "name,city\nann,Paris, France\n"
print(pd.read_csv(io.StringIO(raw)))
print(pd.read_csv(io.StringIO('name,city\nann,"Paris, France"\n')).values.tolist())

# 3) the header is shorter than later rows: give the column names yourself
short = "a,b\n1,2\n3,4,5\n"
print(pd.read_csv(io.StringIO(short), names=["a", "b", "c"], skiprows=1).values.tolist())

Output (identical on pandas 2.3.3 and 3.1.0):

[['ann', 'hi, there'], ['bob', 'ok']]
      name     city
ann  Paris   France
[['ann', 'Paris, France']]
[[1.0, 2.0, nan], [3.0, 4.0, 5.0]]

Skipping rows hides data loss, so check the cause first. European exports often use ;: pass sep=";". A comma inside a value needs quotes in the file. Note the second case: when every data row has the extra field there is no error at all; pandas makes the first field the index and shifts the rest, and on_bad_lines="skip" does not help (with two such rows it returned [['Paris', ' France'], ['Rome', ' Italy']]). If the header is simply shorter than the data, pass names= with skiprows=1.

Related: pandas KeyError but the column exists · csv: new-line character seen in unquoted field · csv: line contains NUL.

Frequently asked questions

What replaced error_bad_lines in pandas?

on_bad_lines='skip' (or 'warn', or a function with engine='python'). error_bad_lines was removed in pandas 2.0 and now raises TypeError.

What does 'Expected 3 fields in line 3, saw 4' mean?

Line 3 of the file (counting the header as line 1) has 4 fields while the header has 3, usually an unquoted comma or the wrong separator.

How do I read a semicolon-separated CSV in pandas?

pd.read_csv(path, sep=';').

Written with AI assistance. Measured on 2026-10-23 in the python:3-alpine Docker image (Python 3.14) with pandas 2.3.3 and pandas 3.1.0. Each block was run verbatim on both versions; the outputs were identical and are pasted unedited. PlainHeaders is our extension; Python and pandas are free.