diff --git a/RELEASE.rst b/RELEASE.rst index 5293b858b72a3..71d8054283b57 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -179,6 +179,8 @@ pandas 0.11.1 into today's date - ``DataFrame.from_records`` did not accept empty recarrays (GH3682_) - ``DataFrame.to_csv`` will succeed with the deprecated option ``nanRep``, @tdsmith + - ``DataFrame.to_html`` and ``DataFrame.to_latex`` now accept a path for + their first argument (GH3702_) .. _GH3164: https://github.com/pydata/pandas/issues/3164 .. _GH2786: https://github.com/pydata/pandas/issues/2786 @@ -255,6 +257,7 @@ pandas 0.11.1 .. _GH3676: https://github.com/pydata/pandas/issues/3676 .. _GH3675: https://github.com/pydata/pandas/issues/3675 .. _GH3682: https://github.com/pydata/pandas/issues/3682 +.. _GH3702: https://github.com/pydata/pandas/issues/3702 pandas 0.11.0 ============= diff --git a/doc/source/v0.11.1.txt b/doc/source/v0.11.1.txt index bd4a7c49fbb4d..ae400a199b372 100644 --- a/doc/source/v0.11.1.txt +++ b/doc/source/v0.11.1.txt @@ -233,6 +233,9 @@ Bug Fixes - ``DataFrame.from_records`` did not accept empty recarrays (GH3682_) + - ``DataFrame.to_html`` and ``DataFrame.to_latex`` now accept a path for + their first argument (GH3702_) + See the `full release notes `__ or issue tracker on GitHub for a complete list. @@ -274,3 +277,4 @@ on GitHub for a complete list. .. _GH3675: https://github.com/pydata/pandas/issues/3675 .. _GH3682: https://github.com/pydata/pandas/issues/3682 .. _GH3679: https://github.com/pydata/pandas/issues/3679 +.. _GH3702: https://github.com/pydata/pandas/issues/3702 diff --git a/pandas/core/format.py b/pandas/core/format.py index 7327f3b1b2175..40d80e91f0264 100644 --- a/pandas/core/format.py +++ b/pandas/core/format.py @@ -364,21 +364,31 @@ def get_col_type(dtype): raise AssertionError(('column_format must be str or unicode, not %s' % type(column_format))) - self.buf.write('\\begin{tabular}{%s}\n' % column_format) - self.buf.write('\\toprule\n') - - nlevels = frame.index.nlevels - for i, row in enumerate(izip(*strcols)): - if i == nlevels: - self.buf.write('\\midrule\n') # End of header - crow = [(x.replace('_', '\\_') - .replace('%', '\\%') - .replace('&', '\\&') if x else '{}') for x in row] - self.buf.write(' & '.join(crow)) - self.buf.write(' \\\\\n') - - self.buf.write('\\bottomrule\n') - self.buf.write('\\end{tabular}\n') + def write(buf, frame, column_format, strcols): + buf.write('\\begin{tabular}{%s}\n' % column_format) + buf.write('\\toprule\n') + + nlevels = frame.index.nlevels + for i, row in enumerate(izip(*strcols)): + if i == nlevels: + buf.write('\\midrule\n') # End of header + crow = [(x.replace('_', '\\_') + .replace('%', '\\%') + .replace('&', '\\&') if x else '{}') for x in row] + buf.write(' & '.join(crow)) + buf.write(' \\\\\n') + + buf.write('\\bottomrule\n') + buf.write('\\end{tabular}\n') + + if hasattr(self.buf, 'write'): + write(self.buf, frame, column_format, strcols) + elif isinstance(self.buf, basestring): + with open(self.buf, 'w') as f: + write(f, frame, column_format, strcols) + else: + raise TypeError('buf is not a file name and it has no write ' + 'method') def _format_col(self, i): formatter = self._get_formatter(i) @@ -392,7 +402,14 @@ def to_html(self, classes=None): Render a DataFrame to a html table. """ html_renderer = HTMLFormatter(self, classes=classes) - html_renderer.write_result(self.buf) + if hasattr(self.buf, 'write'): + html_renderer.write_result(self.buf) + elif isinstance(self.buf, basestring): + with open(self.buf, 'w') as f: + html_renderer.write_result(f) + else: + raise TypeError('buf is not a file name and it has no write ' + ' method') def _get_formatted_column_labels(self): from pandas.core.index import _sparsify @@ -574,7 +591,6 @@ def write_result(self, buf): indent = self._write_body(indent) self.write('', indent) - _put_lines(buf, self.elements) def _write_header(self, indent): diff --git a/pandas/core/series.py b/pandas/core/series.py index a04e931cf07e3..ab8a48f4b8eb9 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -1222,7 +1222,11 @@ def to_string(self, buf=None, na_rep='NaN', float_format=None, if buf is None: return the_repr else: - print >> buf, the_repr + try: + buf.write(the_repr) + except AttributeError: + with open(buf, 'w') as f: + f.write(the_repr) def _get_repr(self, name=False, print_header=False, length=True, dtype=True, na_rep='NaN', float_format=None): diff --git a/pandas/tests/test_format.py b/pandas/tests/test_format.py index fb1465f3cdc7b..9e8a69a32d454 100644 --- a/pandas/tests/test_format.py +++ b/pandas/tests/test_format.py @@ -1216,6 +1216,26 @@ def test_to_html(self): frame = DataFrame(index=np.arange(200)) frame.to_html() + def test_to_html_filename(self): + biggie = DataFrame({'A': randn(200), + 'B': tm.makeStringIndex(200)}, + index=range(200)) + + biggie['A'][:20] = nan + biggie['B'][:20] = nan + with tm.ensure_clean('test.html') as path: + biggie.to_html(path) + with open(path, 'r') as f: + s = biggie.to_html() + s2 = f.read() + self.assertEqual(s, s2) + + frame = DataFrame(index=np.arange(200)) + with tm.ensure_clean('test.html') as path: + frame.to_html(path) + with open(path, 'r') as f: + self.assertEqual(frame.to_html(), f.read()) + def test_to_html_with_no_bold(self): x = DataFrame({'x': randn(5)}) ashtml = x.to_html(bold_rows=False) @@ -1474,6 +1494,13 @@ def test_dict_entries(self): self.assertTrue("'a': 1" in val) self.assertTrue("'b': 2" in val) + def test_to_latex_filename(self): + with tm.ensure_clean('test.tex') as path: + self.frame.to_latex(path) + + with open(path, 'r') as f: + self.assertEqual(self.frame.to_latex(), f.read()) + def test_to_latex(self): # it works! self.frame.to_latex()