Repository: pandas-dev/pandas

Issue:
read_csv's na_values dict format cannot parse float type
Minor issue regarding `read_csv`'s `na_values` argument in `dict` format.  I note that the list format works fine when the NA value is given as a float-type (which is often the intuitive choice), e.g.:

``` python
co2 = pd.read_csv("ftp://aftp.cmdl.noaa.gov/products/trends/co2/co2_mm_mlo.txt", 
                 comment = "#", delim_whitespace = True,
                names = ["year", "month", "decimal_date", "average", "interpolated", "trend", "days"],
                na_values =[-99.99, -1])
```

However, the dict format is more appropriate for this classic data set, since different columns are defining different NA values.  Unfortunately, this fails with an error about float type:

``` python
co2 = pd.read_csv("ftp://aftp.cmdl.noaa.gov/products/trends/co2/co2_mm_mlo.txt", 
                 comment = "#", delim_whitespace = True,
                names = ["year", "month", "decimal_date", "average", "interpolated", "trend", "days"],
                na_values = {"decimal_date" : -99.99, "days" : -1})
```

and the NA value must be given as a string; which feels all kinds of wrong here:

``` python
co2 = pd.read_csv("ftp://aftp.cmdl.noaa.gov/products/trends/co2/co2_mm_mlo.txt", 
                 comment = "#", delim_whitespace = True,
                names = ["year", "month", "decimal_date", "average", "interpolated", "trend", "days"],
                na_values = {"decimal_date" : "-99.99", "days" : "-1"})
```

Thanks for all the `pandas` awesomeness,



Discussion:
Tried the errored call:

``` python
In [4]: co2 = pd.read_csv("ftp://aftp.cmdl.noaa.gov/products/trends/co2/co2_mm_m
lo.txt",
   ...:                  comment = "#", delim_whitespace = True,
   ...:                 names = ["year", "month", "decimal_date", "average", "in
terpolated", "trend", "days"],
   ...:                 na_values = {"decimal_date" : -99.99, "days" : -1})
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

....
truncated by bran
....

pandas\io\parsers.py in _clean_na_values(na_values,
keep_default_na)
   2210         if keep_default_na:
   2211             for k, v in compat.iteritems(na_values):
-> 2212                 v = set(list(v)) | _NA_VALUES
   2213                 na_values[k] = v
   2214         na_fvalues = dict([

TypeError: 'int' object is not iterable
```

Seems that read_csv assumes the values in the na_values dict (something like{key:values}) must be an iterable.
To solve this, maybe we should either accept single value in the dict as well, or make it clear in the docstring.

Current docstring for na_values

```
na_values : str, list-like or dict, default None
    Additional strings to recognize as NA/NaN. If dict passed, specific
    per-column NA values
```

I'd be happy to submit a PR for this.

yeah this is an inconsistency.

separately I don't recall why we are defined (different!) `_NA_VALUES` in `parser.pyx` and `io.parser.py`. I think this is a mistake. It might have been added accidently, as this makes different na values defaults for python/c parser.

Here's a nice reproducible example:

``` python
>>> from pandas import read_csv
>>> from pandas.compat import StringIO
>>> data = '1,2\n2,1'
>>> read_csv(StringIO(data), names=['a', 'b'], na_values={'a': 2, 'b': 1})
...
TypeError: 'int' object is not iterable
```

Note that this works, however:

``` python
>>> read_csv(StringIO(data), names=['a', 'b'], na_values=1)
     a    b
0  NaN  2.0
1  2.0  NaN
```


Resolving patch:
diff --git a/pandas/io/excel.py b/pandas/io/excel.py
--- a/pandas/io/excel.py
+++ b/pandas/io/excel.py
@@ -94,7 +94,7 @@
       column ranges (e.g. "A:E" or "A,C,E:F")
 squeeze : boolean, default False
     If the parsed data only contains one column then return a Series
-na_values : str or list-like or dict, default None
+na_values : scalar, str, list-like, or dict, default None
     Additional strings to recognize as NA/NaN. If dict passed, specific
     per-column NA values. By default the following values are interpreted
     as NaN: '""" + "', '".join(sorted(_NA_VALUES)) + """'.
diff --git a/pandas/io/parsers.py b/pandas/io/parsers.py
--- a/pandas/io/parsers.py
+++ b/pandas/io/parsers.py
@@ -129,7 +129,7 @@
     DEPRECATED: use the `skipfooter` parameter instead, as they are identical
 nrows : int, default None
     Number of rows of file to read. Useful for reading pieces of large files
-na_values : str or list-like or dict, default None
+na_values : scalar, str, list-like, or dict, default None
     Additional strings to recognize as NA/NaN. If dict passed, specific
     per-column NA values.  By default the following values are interpreted as
     NaN: `'""" + "'`, `'".join(sorted(_NA_VALUES)) + """'`.
@@ -1604,8 +1604,8 @@ def TextParser(*args, **kwds):
     has_index_names: boolean, default False
         True if the cols defined in index_col have an index name and are
         not in the header
-    na_values : iterable, default None
-        Custom NA values
+    na_values : scalar, str, list-like, or dict, default None
+        Additional strings to recognize as NA/NaN.
     keep_default_na : bool, default True
     thousands : str, default None
         Thousands separator
@@ -2687,7 +2687,9 @@ def _clean_na_values(na_values, keep_default_na=True):
     elif isinstance(na_values, dict):
         if keep_default_na:
             for k, v in compat.iteritems(na_values):
-                v = set(list(v)) | _NA_VALUES
+                if not is_list_like(v):
+                    v = [v]
+                v = set(v) | _NA_VALUES
                 na_values[k] = v
         na_fvalues = dict([
             (k, _floatify_na_values(v)) for k, v in na_values.items()  # noqa


Test patch:
diff --git a/pandas/io/tests/parser/na_values.py b/pandas/io/tests/parser/na_values.py
--- a/pandas/io/tests/parser/na_values.py
+++ b/pandas/io/tests/parser/na_values.py
@@ -250,3 +250,19 @@ def test_na_trailing_columns(self):
         result = self.read_csv(StringIO(data))
         self.assertEqual(result['Date'][1], '2012-05-12')
         self.assertTrue(result['UnitPrice'].isnull().all())
+
+    def test_na_values_scalar(self):
+        # see gh-12224
+        names = ['a', 'b']
+        data = '1,2\n2,1'
+
+        expected = DataFrame([[np.nan, 2.0], [2.0, np.nan]],
+                             columns=names)
+        out = self.read_csv(StringIO(data), names=names, na_values=1)
+        tm.assert_frame_equal(out, expected)
+
+        expected = DataFrame([[1.0, 2.0], [np.nan, np.nan]],
+                             columns=names)
+        out = self.read_csv(StringIO(data), names=names,
+                            na_values={'a': 2, 'b': 1})
+        tm.assert_frame_equal(out, expected)