Skip to content

Commit 8853bbc

Browse files
MarcoGorelliAlexKirko
authored andcommitted
[ENH] Add to_markdown method (pandas-dev#30350)
1 parent 52d5520 commit 8853bbc

File tree

13 files changed

+147
-2
lines changed

13 files changed

+147
-2
lines changed

ci/deps/travis-37.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ dependencies:
2020
- pyarrow
2121
- pytz
2222
- s3fs
23+
- tabulate
2324
- pyreadstat
2425
- pip
2526
- pip:

ci/deps/travis-38.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ dependencies:
1717
- nomkl
1818
- pytz
1919
- pip
20+
- tabulate==0.8.3

doc/source/getting_started/install.rst

+4-1
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,8 @@ Optional dependencies
234234
~~~~~~~~~~~~~~~~~~~~~
235235

236236
Pandas has many optional dependencies that are only used for specific methods.
237-
For example, :func:`pandas.read_hdf` requires the ``pytables`` package. If the
237+
For example, :func:`pandas.read_hdf` requires the ``pytables`` package, while
238+
:meth:`DataFrame.to_markdown` requires the ``tabulate`` package. If the
238239
optional dependency is not installed, pandas will raise an ``ImportError`` when
239240
the method requiring that dependency is called.
240241

@@ -264,6 +265,7 @@ pyreadstat SPSS files (.sav) reading
264265
pytables 3.4.2 HDF5 reading / writing
265266
qtpy Clipboard I/O
266267
s3fs 0.3.0 Amazon S3 access
268+
tabulate 0.8.3 Printing in Markdown-friendly format (see `tabulate`_)
267269
xarray 0.8.2 pandas-like API for N-dimensional data
268270
xclip Clipboard I/O on linux
269271
xlrd 1.1.0 Excel reading
@@ -301,3 +303,4 @@ top-level :func:`~pandas.read_html` function:
301303
.. _html5lib: https://github.com/html5lib/html5lib-python
302304
.. _BeautifulSoup4: http://www.crummy.com/software/BeautifulSoup
303305
.. _lxml: http://lxml.de
306+
.. _tabulate: https://github.com/astanin/python-tabulate

doc/source/reference/frame.rst

+1
Original file line numberDiff line numberDiff line change
@@ -361,4 +361,5 @@ Serialization / IO / conversion
361361
DataFrame.to_records
362362
DataFrame.to_string
363363
DataFrame.to_clipboard
364+
DataFrame.to_markdown
364365
DataFrame.style

doc/source/reference/series.rst

+1
Original file line numberDiff line numberDiff line change
@@ -578,3 +578,4 @@ Serialization / IO / conversion
578578
Series.to_string
579579
Series.to_clipboard
580580
Series.to_latex
581+
Series.to_markdown

doc/source/whatsnew/v1.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ Other enhancements
209209
- :func:`to_parquet` now appropriately handles the ``schema`` argument for user defined schemas in the pyarrow engine. (:issue: `30270`)
210210
- DataFrame constructor preserve `ExtensionArray` dtype with `ExtensionArray` (:issue:`11363`)
211211
- :meth:`DataFrame.sort_values` and :meth:`Series.sort_values` have gained ``ignore_index`` keyword to be able to reset index after sorting (:issue:`30114`)
212+
- :meth:`DataFrame.to_markdown` and :meth:`Series.to_markdown` added (:issue:`11052`)
212213

213214
- :meth:`DataFrame.drop_duplicates` has gained ``ignore_index`` keyword to reset index (:issue:`30114`)
214215

environment.yml

+1
Original file line numberDiff line numberDiff line change
@@ -100,5 +100,6 @@ dependencies:
100100
- sqlalchemy # pandas.read_sql, DataFrame.to_sql
101101
- xarray # DataFrame.to_xarray
102102
- pyreadstat # pandas.read_spss
103+
- tabulate>=0.8.3 # DataFrame.to_markdown
103104
- pip:
104105
- git+https://github.com/pandas-dev/pandas-sphinx-theme.git@master

pandas/compat/_optional.py

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"scipy": "0.19.0",
2424
"sqlalchemy": "1.1.4",
2525
"tables": "3.4.2",
26+
"tabulate": "0.8.3",
2627
"xarray": "0.8.2",
2728
"xlrd": "1.1.0",
2829
"xlwt": "1.2.0",

pandas/core/frame.py

+33
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import sys
1616
from textwrap import dedent
1717
from typing import (
18+
IO,
1819
Any,
1920
FrozenSet,
2021
Hashable,
@@ -37,6 +38,7 @@
3738

3839
from pandas._libs import algos as libalgos, lib
3940
from pandas._typing import Axes, Dtype, FilePathOrBuffer
41+
from pandas.compat._optional import import_optional_dependency
4042
from pandas.compat.numpy import function as nv
4143
from pandas.util._decorators import (
4244
Appender,
@@ -118,6 +120,7 @@
118120
from pandas.core.ops.missing import dispatch_fill_zeros
119121
from pandas.core.series import Series
120122

123+
from pandas.io.common import get_filepath_or_buffer
121124
from pandas.io.formats import console, format as fmt
122125
from pandas.io.formats.printing import pprint_thing
123126
import pandas.plotting
@@ -1964,6 +1967,36 @@ def to_feather(self, path):
19641967

19651968
to_feather(self, path)
19661969

1970+
@Appender(
1971+
"""
1972+
Examples
1973+
--------
1974+
>>> df = pd.DataFrame(
1975+
... data={"animal_1": ["elk", "pig"], "animal_2": ["dog", "quetzal"]}
1976+
... )
1977+
>>> print(df.to_markdown())
1978+
| | animal_1 | animal_2 |
1979+
|---:|:-----------|:-----------|
1980+
| 0 | elk | dog |
1981+
| 1 | pig | quetzal |
1982+
"""
1983+
)
1984+
@Substitution(klass="DataFrame")
1985+
@Appender(_shared_docs["to_markdown"])
1986+
def to_markdown(
1987+
self, buf: Optional[IO[str]] = None, mode: Optional[str] = None, **kwargs,
1988+
) -> Optional[str]:
1989+
kwargs.setdefault("headers", "keys")
1990+
kwargs.setdefault("tablefmt", "pipe")
1991+
tabulate = import_optional_dependency("tabulate")
1992+
result = tabulate.tabulate(self, **kwargs)
1993+
if buf is None:
1994+
return result
1995+
buf, _, _, _ = get_filepath_or_buffer(buf, mode=mode)
1996+
assert buf is not None # Help mypy.
1997+
buf.writelines(result)
1998+
return None
1999+
19672000
@deprecate_kwarg(old_arg_name="fname", new_arg_name="path")
19682001
def to_parquet(
19692002
self,

pandas/core/generic.py

+24
Original file line numberDiff line numberDiff line change
@@ -1970,6 +1970,30 @@ def _repr_data_resource_(self):
19701970
# ----------------------------------------------------------------------
19711971
# I/O Methods
19721972

1973+
_shared_docs[
1974+
"to_markdown"
1975+
] = """
1976+
Print %(klass)s in Markdown-friendly format.
1977+
1978+
.. versionadded:: 1.0.0
1979+
1980+
Parameters
1981+
----------
1982+
buf : writable buffer, defaults to sys.stdout
1983+
Where to send the output. By default, the output is printed to
1984+
sys.stdout. Pass a writable buffer if you need to further process
1985+
the output.
1986+
mode : str, optional
1987+
Mode in which file is opened.
1988+
**kwargs
1989+
These parameters will be passed to `tabulate`.
1990+
1991+
Returns
1992+
-------
1993+
str
1994+
%(klass)s in Markdown-friendly format.
1995+
"""
1996+
19731997
_shared_docs[
19741998
"to_excel"
19751999
] = """

pandas/core/series.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from io import StringIO
55
from shutil import get_terminal_size
66
from textwrap import dedent
7-
from typing import Any, Callable, Hashable, List, Optional
7+
from typing import IO, Any, Callable, Hashable, List, Optional
88
import warnings
99

1010
import numpy as np
@@ -59,6 +59,7 @@
5959
is_empty_data,
6060
sanitize_array,
6161
)
62+
from pandas.core.generic import _shared_docs
6263
from pandas.core.indexers import maybe_convert_indices
6364
from pandas.core.indexes.accessors import CombinedDatetimelikeProperties
6465
from pandas.core.indexes.api import (
@@ -1439,6 +1440,27 @@ def to_string(
14391440
with open(buf, "w") as f:
14401441
f.write(result)
14411442

1443+
@Appender(
1444+
"""
1445+
Examples
1446+
--------
1447+
>>> s = pd.Series(["elk", "pig", "dog", "quetzal"], name="animal")
1448+
>>> print(s.to_markdown())
1449+
| | animal |
1450+
|---:|:---------|
1451+
| 0 | elk |
1452+
| 1 | pig |
1453+
| 2 | dog |
1454+
| 3 | quetzal |
1455+
"""
1456+
)
1457+
@Substitution(klass="Series")
1458+
@Appender(_shared_docs["to_markdown"])
1459+
def to_markdown(
1460+
self, buf: Optional[IO[str]] = None, mode: Optional[str] = None, **kwargs,
1461+
) -> Optional[str]:
1462+
return self.to_frame().to_markdown(buf, mode, **kwargs)
1463+
14421464
# ----------------------------------------------------------------------
14431465

14441466
def items(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from io import StringIO
2+
3+
import pytest
4+
5+
import pandas as pd
6+
7+
pytest.importorskip("tabulate")
8+
9+
10+
def test_simple():
11+
buf = StringIO()
12+
df = pd.DataFrame([1, 2, 3])
13+
df.to_markdown(buf=buf)
14+
result = buf.getvalue()
15+
assert (
16+
result == "| | 0 |\n|---:|----:|\n| 0 | 1 |\n| 1 | 2 |\n| 2 | 3 |"
17+
)
18+
19+
20+
def test_other_tablefmt():
21+
buf = StringIO()
22+
df = pd.DataFrame([1, 2, 3])
23+
df.to_markdown(buf=buf, tablefmt="jira")
24+
result = buf.getvalue()
25+
assert result == "|| || 0 ||\n| 0 | 1 |\n| 1 | 2 |\n| 2 | 3 |"
26+
27+
28+
def test_other_headers():
29+
buf = StringIO()
30+
df = pd.DataFrame([1, 2, 3])
31+
df.to_markdown(buf=buf, headers=["foo", "bar"])
32+
result = buf.getvalue()
33+
assert result == (
34+
"| foo | bar |\n|------:|------:|\n| 0 "
35+
"| 1 |\n| 1 | 2 |\n| 2 | 3 |"
36+
)
37+
38+
39+
def test_series():
40+
buf = StringIO()
41+
s = pd.Series([1, 2, 3], name="foo")
42+
s.to_markdown(buf=buf)
43+
result = buf.getvalue()
44+
assert result == (
45+
"| | foo |\n|---:|------:|\n| 0 | 1 "
46+
"|\n| 1 | 2 |\n| 2 | 3 |"
47+
)
48+
49+
50+
def test_no_buf(capsys):
51+
df = pd.DataFrame([1, 2, 3])
52+
result = df.to_markdown()
53+
assert (
54+
result == "| | 0 |\n|---:|----:|\n| 0 | 1 |\n| 1 | 2 |\n| 2 | 3 |"
55+
)

requirements-dev.txt

+1
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,5 @@ s3fs
6767
sqlalchemy
6868
xarray
6969
pyreadstat
70+
tabulate>=0.8.3
7071
git+https://github.com/pandas-dev/pandas-sphinx-theme.git@master

0 commit comments

Comments
 (0)