ValueError: could not convert string to float in Python (Fixes)

ValueError: could not convert string to float means float() received text that is not a plain decimal literal: an empty string, a thousands separator, a currency symbol, a placeholder such as 'N/A', or an invisible character. The fix is to clean or filter the string before converting, or to let pandas coerce bad values to NaN.
What float() actually accepts
float() parses a very narrow grammar: an optional sign, digits with one optional decimal point, an optional exponent, or the special words nan and inf. Leading and trailing whitespace (including \n) is ignored, and underscores between digits are allowed since Python 3.6. Everything else is rejected. The exact rules are in the float() documentation.
print(float(" 42.0\n"), float("1e3"), float("1_000"), float("nan"))
42.0 1000.0 1000.0 nan
The error message always quotes the offending value with repr(), which is your best clue. These are the inputs that trigger it most often:
for s in ["", "1,234.56", "$19.99", "N/A", "12.5%", "3,14", "1 000", "1.2.3"]:
try:
float(s)
except ValueError as exc:
print(exc)
could not convert string to float: ''
could not convert string to float: '1,234.56'
could not convert string to float: '$19.99'
could not convert string to float: 'N/A'
could not convert string to float: '12.5%'
could not convert string to float: '3,14'
could not convert string to float: '1 000'
could not convert string to float: '1.2.3'
| Value in the message | Cause | Typical fix |
|---|---|---|
'' | Empty cell, blank line, missing form field | Skip it or map it to a default/None |
'1,234.56' | Thousands separator | .replace(",", "") or thousands="," in pandas |
'3,14', '1.234,56' | European decimal comma | Swap separators, locale.atof, or decimal="," |
'$19.99', '12.5%' | Units or currency symbols | Strip the symbol (and divide by 100 for percentages) |
'N/A', '-', 'null' | Placeholder for missing data | Treat as missing (None/NaN) |
'1\xa0000', '12.5' | Non-breaking space, byte order mark, zero-width space | Remove the character; fix the file encoding |
Fix 1: clean the string before converting
Leading and trailing whitespace is harmless, but separators and symbols are not. Remove them explicitly, and keep the transformation obvious so nobody wonders later what was stripped.
raw = " 1,234.56\n"
print(float(raw.strip().replace(",", "")))
price = "$1,299.00"
print(float(price.replace("$", "").replace(",", "")))
eu = "1.234,56" # German/French style
print(float(eu.replace(".", "").replace(",", ".")))
print(float("12.5%".rstrip("%")) / 100)
1234.56
1299.0
1234.56
0.125
When the junk varies (several currencies, stray letters), a regular expression that keeps only the characters a number can contain is more robust:
import re
def clean_number(text):
# keep digits, sign, decimal point and exponent markers
return re.sub(r"[^0-9eE+\-.]", "", text)
print(float(clean_number("USD -2,500.75 ")))
-2500.75
Be careful: this assumes the dot is the decimal separator. Applied to "1.234,56" it would yield 1.23456 without any error, which is worse than a crash. Decide the convention per data source, not per value.
Locale-aware parsing with locale.atof
If the separators follow a known locale, the standard library can parse them for you. locale.atof uses the current LC_NUMERIC setting:
import locale
locale.setlocale(locale.LC_NUMERIC, "de_DE.UTF-8")
print(locale.atof("1.234,56"))
locale.setlocale(locale.LC_NUMERIC, "en_US.UTF-8")
print(locale.atof("1,234.56"))
1234.56
1234.56
Two caveats. The locale must be installed on the machine; otherwise setlocale raises locale.Error: unsupported locale setting (common in slim Docker images). And setlocale changes process-wide state, so avoid it in threaded web servers; prefer explicit replacements there.
Fix 2: a tolerant conversion helper
For user input or messy files, wrap the conversion in try/except and decide what a bad value should become. Catch ValueError specifically; a bare except would also hide real bugs. Note that float(None) raises a different exception, TypeError: float() argument must be a string or a real number, not 'NoneType', so handle None before calling float().
def to_float(value, default=None):
"""Convert value to float, returning default when it is not numeric."""
if value is None:
return default
if isinstance(value, (int, float)):
return float(value)
text = str(value).strip().replace(",", "")
if text.upper() in {"", "N/A", "NA", "NULL", "NONE", "-"}:
return default
try:
return float(text)
except ValueError:
return default
for v in ["3.5", " 1,024 ", "", "N/A", "abc", None, 7]:
print(repr(v), "->", to_float(v))
'3.5' -> 3.5
' 1,024 ' -> 1024.0
'' -> None
'N/A' -> None
'abc' -> None
None -> None
7 -> 7.0
Silently returning a default is convenient but can hide data problems. When parsing a file, it is often better to report where the bad value came from:
for line_no, line in enumerate(["10.5", "11.0", "12,0", "13.25"], start=1):
try:
value = float(line)
except ValueError as exc:
print(f"line {line_no}: {exc}")
line 3: could not convert string to float: '12,0'
Fix 3: pandas to_numeric with errors="coerce"
In pandas the same error shows up when you call .astype(float) on a text column:
import io
import pandas as pd
df = pd.read_csv(io.StringIO('price\n"1,299.50"\n12.00\n'))
df["price"].astype(float)
ValueError: could not convert string to float: '1,299.50'
pd.to_numeric converts a whole column at once. By default it raises (ValueError: Unable to parse string "N/A" at position 1); with errors="coerce" every unparseable value becomes NaN:
s = pd.Series(["10.5", "1,200", "", "N/A", "$7", " 3 "])
print(pd.to_numeric(s, errors="coerce"))
0 10.5
1 NaN
2 NaN
3 NaN
4 NaN
5 3.0
dtype: float64
Notice that "1,200" and "$7" were also coerced to NaN, which is probably not what you want. Clean first, then coerce, and count the NaN values afterwards to see what was lost:
s = pd.Series(["$1,200.50", "€ 80", "N/A", "(15.00)"])
cleaned = (
s.str.replace(r"[^\d.\-()]", "", regex=True) # drop currency, commas, spaces
.str.replace(r"^\((.*)\)$", r"-\1", regex=True) # accounting negatives
)
print(pd.to_numeric(cleaned, errors="coerce"))
0 1200.5
1 80.0
2 NaN
3 -15.0
dtype: float64
errors="ignore" was deprecated in pandas 2.2 and removed in 3.0 (it now raises ValueError: invalid error value specified); use "coerce" or handle the exception yourself.
Fix 4: parse numbers correctly in read_csv
The cleanest fix is to never create the bad strings. pd.read_csv understands thousands separators, decimal commas and custom missing-value markers:
import io
import pandas as pd
us_csv = io.StringIO("""product,price,qty
Widget,"1,299.50",3
Gadget,unknown,
Doohickey,"12,000.00",1
""")
df = pd.read_csv(us_csv, thousands=",", na_values=["unknown"])
print(df)
eu_csv = io.StringIO("""product;price
Widget;1.299,50
Gadget;-
""")
df_eu = pd.read_csv(eu_csv, sep=";", thousands=".", decimal=",", na_values=["-"])
print(df_eu)
print(df_eu.dtypes)
product price qty
0 Widget 1299.5 3.0
1 Gadget NaN NaN
2 Doohickey 12000.0 1.0
product price
0 Widget 1299.5
1 Gadget NaN
product str
price float64
dtype: object
Strings such as "N/A", "NA", "null" and empty cells are already in the default na_values list; pass extra markers for anything else. (pandas 3.0 reports text columns as str; earlier versions show object.)
Troubleshooting
- The value looks numeric but still fails. Print
repr(value). Hidden characters such as'\xa0'(non-breaking space from HTML or Excel),''(a byte order mark at the start of a UTF-8 file),''(zero-width space) or the Unicode minus'−'are not stripped byfloat(). For a BOM, open the file withencoding="utf-8-sig". - The error appears inside a loop over a file. Usually the header row or a trailing empty line. Skip the header with
next(reader)and skip blank lines. - Two dots or two commas.
'1.2.3'often means a version string or a date ('24.10.2026') ended up in a numeric column; that is a data problem, not a parsing one. - You need exact decimal arithmetic (money). Parse into
decimal.Decimalinstead offloat; it accepts the same cleaned strings and raisesdecimal.InvalidOperationon bad input.
Summary
Read the quoted value in the error message, then fix the input rather than the symptom: strip separators and symbols with replace() or a regex, use locale.atof when the format follows a known locale, wrap conversions in a try/except ValueError helper for untrusted input, and in pandas prefer read_csv(thousands=..., decimal=..., na_values=...) followed by pd.to_numeric(errors="coerce") for whatever is left.
DataSuppress scientific notation
Stop pandas and NumPy printing floats like 1.2e+09: set_printoptions(suppress=True), display.float_format, option_context, Styler and to_csv.
PythonSplit a string into a list
Split strings into lists in Python: str.split with and without sep, maxsplit, rsplit, splitlines, re.split for multiple delimiters, list(), csv and shlex.
DataUnicodeDecodeError in read_csv
Why pd.read_csv raises UnicodeDecodeError: 'utf-8' codec can't decode byte, and how to fix it with the right encoding, BOM handling and UTF-8 conversion.