Skip to content

Set pd.options.display.max_columns=0 by default #17023

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Mar 28, 2018
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions doc/source/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ with no argument ``describe_option`` will print out the descriptions for all ava
Getting and Setting Options
---------------------------

As described above, :func:`~pandas.get_option` and :func:`~pandas.set_option`
are available from the pandas namespace. To change an option, call
As described above, :func:`~pandas.get_option` and :func:`~pandas.set_option`
are available from the pandas namespace. To change an option, call
``set_option('option regex', new_value)``.

.. ipython:: python
Expand Down Expand Up @@ -230,7 +230,7 @@ can specify the option ``df.info(null_counts=True)`` to override on showing a pa
df.info()
pd.reset_option('max_info_rows')

``display.precision`` sets the output display precision in terms of decimal places.
``display.precision`` sets the output display precision in terms of decimal places.
This is only a suggestion.

.. ipython:: python
Expand Down Expand Up @@ -323,21 +323,21 @@ display.latex.multicolumn_format 'l' Alignment of multicolumn la
display.latex.multirow False Combines rows when using a MultiIndex.
Centered instead of top-aligned,
separated by clines.
display.max_columns 20 max_rows and max_columns are used
display.max_columns 0 or 20 max_rows and max_columns are used
in __repr__() methods to decide if
to_string() or info() is used to
render an object to a string. In
case python/IPython is running in
a terminal this can be set to 0 and
case Python/IPython is running in
a terminal this is set to 0 by default and
pandas will correctly auto-detect
the width the terminal and swap to
the width of the terminal and switch to
a smaller format in case all columns
would not fit vertically. The IPython
notebook, IPython qtconsole, or IDLE
do not run in a terminal and hence
it is not possible to do correct
auto-detection. 'None' value means
unlimited.
auto-detection, in which case the default
is set to 20. 'None' value means unlimited.
display.max_colwidth 50 The maximum width in characters of
a column in the repr of a pandas
data structure. When the column overflows,
Expand Down Expand Up @@ -402,9 +402,9 @@ display.html.table_schema False Whether to publish a Table
display.html.border 1 A ``border=value`` attribute is
inserted in the ``<table>`` tag
for the DataFrame HTML repr.
display.html.use_mathjax True When True, Jupyter notebook will process
table contents using MathJax, rendering
mathematical expressions enclosed by the
display.html.use_mathjax True When True, Jupyter notebook will process
table contents using MathJax, rendering
mathematical expressions enclosed by the
dollar symbol.
io.excel.xls.writer xlwt The default Excel writer engine for
'xls' files.
Expand All @@ -422,7 +422,7 @@ io.hdf.dropna_table True drop ALL nan rows when appe
io.parquet.engine None The engine to use as a default for
parquet reading and writing. If None
then try 'pyarrow' and 'fastparquet'
mode.chained_assignment warn Controls ``SettingWithCopyWarning``:
mode.chained_assignment warn Controls ``SettingWithCopyWarning``:
'raise', 'warn', or None. Raise an
exception, warn, or no action if
trying to use :ref:`chained assignment <indexing.evaluation_order>`.
Expand Down
Binary file added doc/source/whatsnew/print_df_new.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/source/whatsnew/print_df_old.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions doc/source/whatsnew/v0.23.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,36 @@ Notice in the example above that the converted ``Categorical`` has retained ``or

Note that the unintenional conversion of ``ordered`` discussed above did not arise in previous versions due to separate bugs that prevented ``astype`` from doing any type of category to category conversion (:issue:`10696`, :issue:`18593`). These bugs have been fixed in this release, and motivated changing the default value of ``ordered``.

+.. _whatsnew_0230.api_breaking.pretty_printing:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a '+' here at the beginning of the line that should not be there


Better pretty-printing of DataFrames in a terminal
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Previously, the default value for the maximum number of columns was
``pd.options.display.max_columns=20``. This meant that relatively wide data
frames would not fit within the terminal width, and pandas would introduce line
breaks to display these 20 columns. This resulted in an output that was
relatively difficult to read:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add this PR number here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you push the new change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed I forgot to push, the PR number is now a few lines below.


.. image:: print_df_old.png

If Python runs in a terminal, the maximum number of columns is now determined
automatically so that the printed data frame fits within the current terminal
width (``pd.options.display.max_columns=0``). If Python runs as a Jupyter
kernel (such as the Jupyter QtConsole or a Jupyter notebook, as well as in many
IDEs), this value cannot be inferred automatically and is thus set to `20` as
in previos versions. In a terminal, this results in a much nicer output:

.. image:: print_df_new.png

Note that if you don't like the new default, you can always set this option
yourself. To revert to the old setting, you can run this line:

.. code-block:: python

pd.options.display.max_columns = 20

.. _whatsnew_0230.api:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one sneaked in due to merge conflict? (anyhow it can be removed)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry, what do you mean?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the diff, you added this line. But this should not be added (therefore I assumed you added it by accident while updating against master with rebasing/merging). But so you can just remove this line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean line 690 (.. _whatsnew_0230.api:)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's the line on which I commented. There is no header following for which that link would make sense (there is actually already another link label on line 692)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought there should be a section header before introducing the subsections. At least that's how it is done with .. _whatsnew_0230.api_breaking: in line 350 (but there is a heading after that). In any case, I'm happy to delete it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is correct. But I don't understand the relation with this line? This link is just floating with no section or subsection header following it. You already have a header with link at line 661 - 664 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's for the section below:

.. _whatsnew_0230.api:

.. _whatsnew_0230.api.datetimelike:

Datetimelike API Changes
^^^^^^^^^^^^^^^^^^^^^^^^

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But that header already has the "whatsnew_0230.api.datetimelike" label, it does not need two labels.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, done.


.. _whatsnew_0230.api.datetimelike:

Datetimelike API Changes
Expand Down
7 changes: 6 additions & 1 deletion pandas/core/config_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from pandas.core.config import (is_int, is_bool, is_text, is_instance_factory,
is_one_of_factory, is_callable)
from pandas.io.formats.console import detect_console_encoding
from pandas.io.formats.terminal import is_terminal

# compute

Expand Down Expand Up @@ -314,7 +315,11 @@ def table_schema_cb(key):
cf.register_option('max_categories', 8, pc_max_categories_doc,
validator=is_int)
cf.register_option('max_colwidth', 50, max_colwidth_doc, validator=is_int)
cf.register_option('max_columns', 20, pc_max_cols_doc,
if is_terminal():
max_cols = 0 # automatically determine optimal number of columns
else:
max_cols = 20 # cannot determine optimal number of columns
cf.register_option('max_columns', max_cols, pc_max_cols_doc,
validator=is_instance_factory([type(None), int]))
cf.register_option('large_repr', 'truncate', pc_large_repr_doc,
validator=is_one_of_factory(['truncate', 'info']))
Expand Down
19 changes: 18 additions & 1 deletion pandas/io/formats/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import sys
import shutil

__all__ = ['get_terminal_size']
__all__ = ['get_terminal_size', 'is_terminal']


def get_terminal_size():
Expand Down Expand Up @@ -48,6 +48,23 @@ def get_terminal_size():
return tuple_xy


def is_terminal():
"""
Detect if Python is running in a terminal.

Returns True if Python is running in a terminal or False if not.
"""
try:
ip = get_ipython()
except NameError: # assume standard Python interpreter in a terminal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don’t you need to import this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is available in IPython.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I.e. when you run IPython get_ipython is automatically imported.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it, this is ok then.

return True
else:
if hasattr(ip, 'kernel'): # IPython as a Jupyter kernel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be an else block of the try / except?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course...

return False
else: # IPython in a terminal
return True


def _get_terminal_size_windows():
res = None
try:
Expand Down
15 changes: 8 additions & 7 deletions pandas/tests/frame/test_dtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -875,10 +875,11 @@ def test_astype_str(self):
columns=self.tzframe.columns)
tm.assert_frame_equal(result, expected)

result = str(self.tzframe)
assert ('0 2013-01-01 2013-01-01 00:00:00-05:00 '
'2013-01-01 00:00:00+01:00') in result
assert ('1 2013-01-02 '
'NaT NaT') in result
assert ('2 2013-01-03 2013-01-03 00:00:00-05:00 '
'2013-01-03 00:00:00+01:00') in result
with option_context('display.max_columns', 20):
result = str(self.tzframe)
assert ('0 2013-01-01 2013-01-01 00:00:00-05:00 '
'2013-01-01 00:00:00+01:00') in result
assert ('1 2013-01-02 '
'NaT NaT') in result
assert ('2 2013-01-03 2013-01-03 00:00:00-05:00 '
'2013-01-03 00:00:00+01:00') in result
4 changes: 2 additions & 2 deletions pandas/tests/frame/test_repr_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,8 @@ def test_repr_column_name_unicode_truncation_bug(self):
'the CSV file externally. I want to Call'
' the File through the code..')})

result = repr(df)
assert 'StringCol' in result
with option_context('display.max_columns', 20):
assert 'StringCol' in repr(df)

def test_latex_repr(self):
result = r"""\begin{tabular}{llll}
Expand Down
61 changes: 35 additions & 26 deletions pandas/tests/io/formats/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,7 +961,8 @@ def test_pprint_thing(self):

def test_wide_repr(self):
with option_context('mode.sim_interactive', True,
'display.show_dimensions', True):
'display.show_dimensions', True,
'display.max_columns', 20):
max_cols = get_option('display.max_columns')
df = DataFrame(tm.rands_array(25, size=(10, max_cols - 1)))
set_option('display.expand_frame_repr', False)
Expand All @@ -979,15 +980,17 @@ def test_wide_repr(self):
reset_option('display.expand_frame_repr')

def test_wide_repr_wide_columns(self):
with option_context('mode.sim_interactive', True):
with option_context('mode.sim_interactive', True,
'display.max_columns', 20):
df = DataFrame(np.random.randn(5, 3),
columns=['a' * 90, 'b' * 90, 'c' * 90])
rep_str = repr(df)

assert len(rep_str.splitlines()) == 20

def test_wide_repr_named(self):
with option_context('mode.sim_interactive', True):
with option_context('mode.sim_interactive', True,
'display.max_columns', 20):
max_cols = get_option('display.max_columns')
df = DataFrame(tm.rands_array(25, size=(10, max_cols - 1)))
df.index.name = 'DataFrame Index'
Expand All @@ -1008,7 +1011,8 @@ def test_wide_repr_named(self):
reset_option('display.expand_frame_repr')

def test_wide_repr_multiindex(self):
with option_context('mode.sim_interactive', True):
with option_context('mode.sim_interactive', True,
'display.max_columns', 20):
midx = MultiIndex.from_arrays(tm.rands_array(5, size=(2, 10)))
max_cols = get_option('display.max_columns')
df = DataFrame(tm.rands_array(25, size=(10, max_cols - 1)),
Expand All @@ -1030,7 +1034,8 @@ def test_wide_repr_multiindex(self):
reset_option('display.expand_frame_repr')

def test_wide_repr_multiindex_cols(self):
with option_context('mode.sim_interactive', True):
with option_context('mode.sim_interactive', True,
'display.max_columns', 20):
max_cols = get_option('display.max_columns')
midx = MultiIndex.from_arrays(tm.rands_array(5, size=(2, 10)))
mcols = MultiIndex.from_arrays(
Expand All @@ -1044,15 +1049,16 @@ def test_wide_repr_multiindex_cols(self):
wide_repr = repr(df)
assert rep_str != wide_repr

with option_context('display.width', 150):
with option_context('display.width', 150, 'display.max_columns', 20):
wider_repr = repr(df)
assert len(wider_repr) < len(wide_repr)

reset_option('display.expand_frame_repr')

def test_wide_repr_unicode(self):
with option_context('mode.sim_interactive', True):
max_cols = get_option('display.max_columns')
with option_context('mode.sim_interactive', True,
'display.max_columns', 20):
max_cols = 20
df = DataFrame(tm.rands_array(25, size=(10, max_cols - 1)))
set_option('display.expand_frame_repr', False)
rep_str = repr(df)
Expand Down Expand Up @@ -1442,17 +1448,17 @@ def test_repr_html_mathjax(self):
assert 'tex2jax_ignore' in df._repr_html_()

def test_repr_html_wide(self):
max_cols = get_option('display.max_columns')
max_cols = 20
df = DataFrame(tm.rands_array(25, size=(10, max_cols - 1)))
reg_repr = df._repr_html_()
assert "..." not in reg_repr
with option_context('display.max_rows', 60, 'display.max_columns', 20):
assert "..." not in df._repr_html_()

wide_df = DataFrame(tm.rands_array(25, size=(10, max_cols + 1)))
wide_repr = wide_df._repr_html_()
assert "..." in wide_repr
with option_context('display.max_rows', 60, 'display.max_columns', 20):
assert "..." in wide_df._repr_html_()

def test_repr_html_wide_multiindex_cols(self):
max_cols = get_option('display.max_columns')
max_cols = 20

mcols = MultiIndex.from_product([np.arange(max_cols // 2),
['foo', 'bar']],
Expand All @@ -1467,8 +1473,8 @@ def test_repr_html_wide_multiindex_cols(self):
names=['first', 'second'])
df = DataFrame(tm.rands_array(25, size=(10, len(mcols))),
columns=mcols)
wide_repr = df._repr_html_()
assert '...' in wide_repr
with option_context('display.max_rows', 60, 'display.max_columns', 20):
assert '...' in df._repr_html_()

def test_repr_html_long(self):
with option_context('display.max_rows', 60):
Expand Down Expand Up @@ -1512,14 +1518,15 @@ def test_repr_html_float(self):
assert u('2 columns') in long_repr

def test_repr_html_long_multiindex(self):
max_rows = get_option('display.max_rows')
max_rows = 60
max_L1 = max_rows // 2

tuples = list(itertools.product(np.arange(max_L1), ['foo', 'bar']))
idx = MultiIndex.from_tuples(tuples, names=['first', 'second'])
df = DataFrame(np.random.randn(max_L1 * 2, 2), index=idx,
columns=['A', 'B'])
reg_repr = df._repr_html_()
with option_context('display.max_rows', 60, 'display.max_columns', 20):
reg_repr = df._repr_html_()
assert '...' not in reg_repr

tuples = list(itertools.product(np.arange(max_L1 + 1), ['foo', 'bar']))
Expand All @@ -1530,20 +1537,22 @@ def test_repr_html_long_multiindex(self):
assert '...' in long_repr

def test_repr_html_long_and_wide(self):
max_cols = get_option('display.max_columns')
max_rows = get_option('display.max_rows')
max_cols = 20
max_rows = 60

h, w = max_rows - 1, max_cols - 1
df = DataFrame({k: np.arange(1, 1 + h) for k in np.arange(w)})
assert '...' not in df._repr_html_()
with option_context('display.max_rows', 60, 'display.max_columns', 20):
assert '...' not in df._repr_html_()

h, w = max_rows + 1, max_cols + 1
df = DataFrame({k: np.arange(1, 1 + h) for k in np.arange(w)})
assert '...' in df._repr_html_()
with option_context('display.max_rows', 60, 'display.max_columns', 20):
assert '...' in df._repr_html_()

def test_info_repr(self):
max_rows = get_option('display.max_rows')
max_cols = get_option('display.max_columns')
max_rows = 60
max_cols = 20
# Long
h, w = max_rows + 1, max_cols - 1
df = DataFrame({k: np.arange(1, 1 + h) for k in np.arange(w)})
Expand Down Expand Up @@ -1575,8 +1584,8 @@ def test_info_repr_max_cols(self):
# fmt.set_option('display.max_info_columns', 4) # exceeded

def test_info_repr_html(self):
max_rows = get_option('display.max_rows')
max_cols = get_option('display.max_columns')
max_rows = 60
max_cols = 20
# Long
h, w = max_rows + 1, max_cols - 1
df = DataFrame({k: np.arange(1, 1 + h) for k in np.arange(w)})
Expand Down