Skip to content

ValueError: could not convert string to float

Did a column of numbers refuse to become numbers?

pd.Series(['1,200', '850']).astype(float)
ValueError: could not convert string to float: '1,200'

The message names the value that stopped it, which is more help than it first appears: '1,200' has a comma in it, and Python's float() does not read thousands separators.

Strip the formatting and the conversion goes through:

pd.Series(['1,200', '850']).str.replace(',', '').astype(float)
0    1200.0
1     850.0
dtype: float64

Finding every bad value, not just the first

astype stops at the first failure, so on a large column you fix one value, re-run, and meet the next. to_numeric with errors='coerce' converts everything it can and turns the rest into NaN, which lets you see the whole problem at once:

revenue = pd.to_numeric(raw['revenue'], errors='coerce')
raw.loc[revenue.isna(), 'revenue'].unique()

That returns the distinct values that failed. In practice it is a short list, and it is usually more interesting than you expect: 'n/a', '-', 'N/A', '', '1.234,56' in European format, a stray '12 ' with a trailing space, or a footnote marker like '450*'.

Look at that list before you decide what to do with it. Coercing to NaN and moving on is fine for genuine missing values and quietly wrong for a footnote marker on your largest row.

Cleaning the common cases

Currency symbols, separators and percent signs go together:

raw['revenue'].str.replace(r'[$,€£%]', '', regex=True).str.strip().astype(float)

Percentages need one more step, because removing the sign leaves you with 45 where you meant 0.45. Divide after converting.

European decimal commas are the trap worth naming, because the value looks clean. '1.234,56' means one thousand two hundred thirty-four point five six. Removing the comma gives you 1.23456, which converts without error and is wrong by a factor of a thousand. Swap the separators rather than deleting one:

raw['revenue'].str.replace('.', '', regex=False).str.replace(',', '.', regex=False).astype(float)

Better to fix it at load time

If the file always looks like this, read_csv can handle most of it as it reads, which means the column is numeric from the start and nothing downstream needs repairing:

pd.read_csv('sales.csv', thousands=',', na_values=['n/a', '-', 'N/A'])

decimal=',' handles the European case, and converters={'revenue': my_cleaner} handles anything genuinely bespoke.

The mistakes I see most often

Using errors='coerce' without looking at what it silenced. It is a diagnostic first and a fix second.

Calling .str.replace without regex=. In current Pandas the default is literal replacement, so a pattern like [$,] will look for that exact text and match nothing. Pass regex=True when you mean a pattern.

Converting to int instead of float first. If any value is missing, astype(int) raises about non-finite values even after the text is clean.

Practice

See to_numeric in Pandas for the conversion itself, str.replace in Pandas for cleaning, and read_csv in Pandas for handling it at load time.