Repository: pandas-dev/pandas

Issue:
df.to_csv should mirror df.to_html
If no `path_or_buf` argument is given `DataFrame.to_csv` should return a string.



Discussion:
hah....we've had many discussions about this....its actually a bad API @y-p !

use a `StringIO(data)` if you want to do this

@filmor sorry thought you were talking about `to_csv`.....yes..marking this an an enhancement...

PR's are welcome

you can of course just pass a `StringIO` here as well...eg.

```
buf=StringIO()
df.to_csv(buf, ....)
```

then `buf.getvalue()` is your string

I know, this was more about removing one small api-wtf ;)
I'll look into making a PR this weekend.

@filmor PR for this?


Resolving patch:
diff --git a/pandas/core/format.py b/pandas/core/format.py
--- a/pandas/core/format.py
+++ b/pandas/core/format.py
@@ -943,7 +943,7 @@ def grouper(x):
 
 class CSVFormatter(object):
 
-    def __init__(self, obj, path_or_buf, sep=",", na_rep='', float_format=None,
+    def __init__(self, obj, path_or_buf=None, sep=",", na_rep='', float_format=None,
                  cols=None, header=True, index=True, index_label=None,
                  mode='w', nanRep=None, encoding=None, quoting=None,
                  line_terminator='\n', chunksize=None, engine=None,
@@ -953,6 +953,9 @@ def __init__(self, obj, path_or_buf, sep=",", na_rep='', float_format=None,
         self.engine = engine  # remove for 0.13
         self.obj = obj
 
+        if path_or_buf is None:
+            path_or_buf = StringIO()
+
         self.path_or_buf = path_or_buf
         self.sep = sep
         self.na_rep = na_rep
@@ -1144,7 +1147,7 @@ def strftime_with_nulls(x):
 
     def save(self):
         # create the writer & save
-        if hasattr(self.path_or_buf, 'read'):
+        if hasattr(self.path_or_buf, 'write'):
             f = self.path_or_buf
             close = False
         else:
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -1067,7 +1067,7 @@ def to_panel(self):
 
     to_wide = deprecate('to_wide', to_panel)
 
-    def to_csv(self, path_or_buf, sep=",", na_rep='', float_format=None,
+    def to_csv(self, path_or_buf=None, sep=",", na_rep='', float_format=None,
                cols=None, header=True, index=True, index_label=None,
                mode='w', nanRep=None, encoding=None, quoting=None,
                quotechar='"', line_terminator='\n', chunksize=None,
@@ -1077,8 +1077,9 @@ def to_csv(self, path_or_buf, sep=",", na_rep='', float_format=None,
 
         Parameters
         ----------
-        path_or_buf : string or file handle / StringIO
-            File path
+        path_or_buf : string or file handle, default None
+            File path or object, if None is provided the result is returned as
+            a string.
         sep : character, default ","
             Field delimiter for the output file.
         na_rep : string, default ''
@@ -1144,6 +1145,9 @@ def to_csv(self, path_or_buf, sep=",", na_rep='', float_format=None,
                                      escapechar=escapechar)
         formatter.save()
 
+        if path_or_buf is None:
+            return formatter.path_or_buf.getvalue()
+
     def to_excel(self, excel_writer, sheet_name='Sheet1', na_rep='',
                  float_format=None, cols=None, header=True, index=True,
                  index_label=None, startrow=0, startcol=0, engine=None,


Test patch:
diff --git a/pandas/tests/test_format.py b/pandas/tests/test_format.py
--- a/pandas/tests/test_format.py
+++ b/pandas/tests/test_format.py
@@ -1787,6 +1787,12 @@ def test_to_csv_escapechar(self):
             with open(path, 'r') as f:
                 self.assertEqual(f.read(), expected)
 
+    def test_csv_to_string(self):
+        df = DataFrame({'col' : [1,2]})
+        expected = ',col\n0,1\n1,2\n'
+        self.assertEqual(df.to_csv(), expected)
+        
+
 class TestSeriesFormatting(tm.TestCase):
     _multiprocess_can_split_ = True