diff --git a/pandas/core/format.py b/pandas/core/format.py index 2773cc0c135c1..f46621d4b86bd 100644 --- a/pandas/core/format.py +++ b/pandas/core/format.py @@ -1169,7 +1169,7 @@ def __init__(self, obj, path_or_buf=None, sep=",", na_rep='', float_format=None, mode='w', nanRep=None, encoding=None, quoting=None, line_terminator='\n', chunksize=None, engine=None, tupleize_cols=False, quotechar='"', date_format=None, - doublequote=True, escapechar=None): + doublequote=True, escapechar=None, decimal='.'): self.engine = engine # remove for 0.13 self.obj = obj @@ -1181,6 +1181,7 @@ def __init__(self, obj, path_or_buf=None, sep=",", na_rep='', float_format=None, self.sep = sep self.na_rep = na_rep self.float_format = float_format + self.decimal = decimal self.header = header self.index = index @@ -1509,6 +1510,7 @@ def _save_chunk(self, start_i, end_i): b = self.blocks[i] d = b.to_native_types(slicer=slicer, na_rep=self.na_rep, float_format=self.float_format, + decimal=self.decimal, date_format=self.date_format) for col_loc, col in zip(b.mgr_locs, d): diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 223cb4fe78e94..b7350dfd5d77c 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -1073,7 +1073,7 @@ def to_csv(self, path_or_buf=None, sep=",", na_rep='', float_format=None, mode='w', encoding=None, quoting=None, quotechar='"', line_terminator='\n', chunksize=None, tupleize_cols=False, date_format=None, doublequote=True, - escapechar=None, **kwds): + escapechar=None, decimal='.', **kwds): r"""Write DataFrame to a comma-separated values (csv) file Parameters @@ -1126,6 +1126,8 @@ def to_csv(self, path_or_buf=None, sep=",", na_rep='', float_format=None, date_format : string, default None Format string for datetime objects cols : kwarg only alias of columns [deprecated] + decimal: string, default '.' + Character recognized as decimal separator. E.g. use ',' for European data """ formatter = fmt.CSVFormatter(self, path_or_buf, @@ -1140,7 +1142,8 @@ def to_csv(self, path_or_buf=None, sep=",", na_rep='', float_format=None, tupleize_cols=tupleize_cols, date_format=date_format, doublequote=doublequote, - escapechar=escapechar) + escapechar=escapechar, + decimal=decimal) formatter.save() if path_or_buf is None: diff --git a/pandas/core/internals.py b/pandas/core/internals.py index 354ccd2c94583..65419e2c29d75 100644 --- a/pandas/core/internals.py +++ b/pandas/core/internals.py @@ -1161,7 +1161,7 @@ def _try_cast(self, element): except: # pragma: no cover return element - def to_native_types(self, slicer=None, na_rep='', float_format=None, + def to_native_types(self, slicer=None, na_rep='', float_format=None, decimal='.', **kwargs): """ convert to our native types format, slicing if desired """ @@ -1171,10 +1171,22 @@ def to_native_types(self, slicer=None, na_rep='', float_format=None, values = np.array(values, dtype=object) mask = isnull(values) values[mask] = na_rep - if float_format: + + + if float_format and decimal != '.': + formatter = lambda v : (float_format % v).replace('.',decimal,1) + elif decimal != '.': + formatter = lambda v : ('%g' % v).replace('.',decimal,1) + elif float_format: + formatter = lambda v : float_format % v + else: + formatter = None + + if formatter: imask = (~mask).ravel() values.flat[imask] = np.array( - [float_format % val for val in values.ravel()[imask]]) + [formatter(val) for val in values.ravel()[imask]]) + return values.tolist() def should_store(self, value): diff --git a/pandas/core/series.py b/pandas/core/series.py index 37f66fc56ea56..e19e51fb9c9e5 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -2239,7 +2239,7 @@ def from_csv(cls, path, sep=',', parse_dates=True, header=None, def to_csv(self, path, index=True, sep=",", na_rep='', float_format=None, header=False, index_label=None, mode='w', nanRep=None, encoding=None, - date_format=None): + date_format=None, decimal='.'): """ Write Series to a comma-separated values (csv) file @@ -2267,6 +2267,8 @@ def to_csv(self, path, index=True, sep=",", na_rep='', non-ascii, for python versions prior to 3 date_format: string, default None Format string for datetime objects. + decimal: string, default '.' + Character recognized as decimal separator. E.g. use ',' for European data """ from pandas.core.frame import DataFrame df = DataFrame(self) @@ -2274,7 +2276,7 @@ def to_csv(self, path, index=True, sep=",", na_rep='', result = df.to_csv(path, index=index, sep=sep, na_rep=na_rep, float_format=float_format, header=header, index_label=index_label, mode=mode, nanRep=nanRep, - encoding=encoding, date_format=date_format) + encoding=encoding, date_format=date_format, decimal=decimal) if path is None: return result diff --git a/pandas/tests/test_format.py b/pandas/tests/test_format.py index 9216b7a286c54..e2823571fe258 100644 --- a/pandas/tests/test_format.py +++ b/pandas/tests/test_format.py @@ -2343,7 +2343,22 @@ def test_csv_to_string(self): df = DataFrame({'col' : [1,2]}) expected = ',col\n0,1\n1,2\n' self.assertEqual(df.to_csv(), expected) - + + def test_to_csv_decimal(self): + # GH 8448 + df = DataFrame({'col1' : [1], 'col2' : ['a'], 'col3' : [10.1] }) + + expected_default = ',col1,col2,col3\n0,1,a,10.1\n' + self.assertEqual(df.to_csv(), expected_default) + + expected_european_excel = ';col1;col2;col3\n0;1;a;10,1\n' + self.assertEqual(df.to_csv(decimal=',',sep=';'), expected_european_excel) + + expected_float_format_default = ',col1,col2,col3\n0,1,a,10.10\n' + self.assertEqual(df.to_csv(float_format = '%.2f'), expected_float_format_default) + + expected_float_format = ';col1;col2;col3\n0;1;a;10,10\n' + self.assertEqual(df.to_csv(decimal=',',sep=';', float_format = '%.2f'), expected_float_format) class TestSeriesFormatting(tm.TestCase): _multiprocess_can_split_ = True