Skip to content

Commit c9e8f59

Browse files
cbrnrjorisvandenbossche
authored andcommitted
Set pd.options.display.max_columns=0 by default (#17023)
Change `max_columns` to `0` (automatically adapt the number of displayed columns to the actual terminal width)
1 parent a6ff9e6 commit c9e8f59

File tree

10 files changed

+117
-53
lines changed

10 files changed

+117
-53
lines changed

doc/source/_static/print_df_new.png

75.4 KB
Loading

doc/source/_static/print_df_old.png

87.1 KB
Loading

doc/source/options.rst

+13-13
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ with no argument ``describe_option`` will print out the descriptions for all ava
7878
Getting and Setting Options
7979
---------------------------
8080

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

8585
.. ipython:: python
@@ -230,7 +230,7 @@ can specify the option ``df.info(null_counts=True)`` to override on showing a pa
230230
df.info()
231231
pd.reset_option('max_info_rows')
232232
233-
``display.precision`` sets the output display precision in terms of decimal places.
233+
``display.precision`` sets the output display precision in terms of decimal places.
234234
This is only a suggestion.
235235

236236
.. ipython:: python
@@ -323,21 +323,21 @@ display.latex.multicolumn_format 'l' Alignment of multicolumn la
323323
display.latex.multirow False Combines rows when using a MultiIndex.
324324
Centered instead of top-aligned,
325325
separated by clines.
326-
display.max_columns 20 max_rows and max_columns are used
326+
display.max_columns 0 or 20 max_rows and max_columns are used
327327
in __repr__() methods to decide if
328328
to_string() or info() is used to
329329
render an object to a string. In
330-
case python/IPython is running in
331-
a terminal this can be set to 0 and
330+
case Python/IPython is running in
331+
a terminal this is set to 0 by default and
332332
pandas will correctly auto-detect
333-
the width the terminal and swap to
333+
the width of the terminal and switch to
334334
a smaller format in case all columns
335335
would not fit vertically. The IPython
336336
notebook, IPython qtconsole, or IDLE
337337
do not run in a terminal and hence
338338
it is not possible to do correct
339-
auto-detection. 'None' value means
340-
unlimited.
339+
auto-detection, in which case the default
340+
is set to 20. 'None' value means unlimited.
341341
display.max_colwidth 50 The maximum width in characters of
342342
a column in the repr of a pandas
343343
data structure. When the column overflows,
@@ -402,9 +402,9 @@ display.html.table_schema False Whether to publish a Table
402402
display.html.border 1 A ``border=value`` attribute is
403403
inserted in the ``<table>`` tag
404404
for the DataFrame HTML repr.
405-
display.html.use_mathjax True When True, Jupyter notebook will process
406-
table contents using MathJax, rendering
407-
mathematical expressions enclosed by the
405+
display.html.use_mathjax True When True, Jupyter notebook will process
406+
table contents using MathJax, rendering
407+
mathematical expressions enclosed by the
408408
dollar symbol.
409409
io.excel.xls.writer xlwt The default Excel writer engine for
410410
'xls' files.
@@ -422,7 +422,7 @@ io.hdf.dropna_table True drop ALL nan rows when appe
422422
io.parquet.engine None The engine to use as a default for
423423
parquet reading and writing. If None
424424
then try 'pyarrow' and 'fastparquet'
425-
mode.chained_assignment warn Controls ``SettingWithCopyWarning``:
425+
mode.chained_assignment warn Controls ``SettingWithCopyWarning``:
426426
'raise', 'warn', or None. Raise an
427427
exception, warn, or no action if
428428
trying to use :ref:`chained assignment <indexing.evaluation_order>`.

doc/source/whatsnew/v0.23.0.txt

+29
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,35 @@ Notice in the example above that the converted ``Categorical`` has retained ``or
658658

659659
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``.
660660

661+
.. _whatsnew_0230.api_breaking.pretty_printing:
662+
663+
Better pretty-printing of DataFrames in a terminal
664+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
665+
Previously, the default value for the maximum number of columns was
666+
``pd.options.display.max_columns=20``. This meant that relatively wide data
667+
frames would not fit within the terminal width, and pandas would introduce line
668+
breaks to display these 20 columns. This resulted in an output that was
669+
relatively difficult to read:
670+
671+
.. image:: _static/print_df_old.png
672+
673+
If Python runs in a terminal, the maximum number of columns is now determined
674+
automatically so that the printed data frame fits within the current terminal
675+
width (``pd.options.display.max_columns=0``) (:issue:`17023`). If Python runs
676+
as a Jupyter kernel (such as the Jupyter QtConsole or a Jupyter notebook, as
677+
well as in many IDEs), this value cannot be inferred automatically and is thus
678+
set to `20` as in previous versions. In a terminal, this results in a much
679+
nicer output:
680+
681+
.. image:: _static/print_df_new.png
682+
683+
Note that if you don't like the new default, you can always set this option
684+
yourself. To revert to the old setting, you can run this line:
685+
686+
.. code-block:: python
687+
688+
pd.options.display.max_columns = 20
689+
661690
.. _whatsnew_0230.api.datetimelike:
662691

663692
Datetimelike API Changes

pandas/core/config_init.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from pandas.core.config import (is_int, is_bool, is_text, is_instance_factory,
1414
is_one_of_factory, is_callable)
1515
from pandas.io.formats.console import detect_console_encoding
16+
from pandas.io.formats.terminal import is_terminal
1617

1718
# compute
1819

@@ -314,7 +315,11 @@ def table_schema_cb(key):
314315
cf.register_option('max_categories', 8, pc_max_categories_doc,
315316
validator=is_int)
316317
cf.register_option('max_colwidth', 50, max_colwidth_doc, validator=is_int)
317-
cf.register_option('max_columns', 20, pc_max_cols_doc,
318+
if is_terminal():
319+
max_cols = 0 # automatically determine optimal number of columns
320+
else:
321+
max_cols = 20 # cannot determine optimal number of columns
322+
cf.register_option('max_columns', max_cols, pc_max_cols_doc,
318323
validator=is_instance_factory([type(None), int]))
319324
cf.register_option('large_repr', 'truncate', pc_large_repr_doc,
320325
validator=is_one_of_factory(['truncate', 'info']))

pandas/io/formats/format.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,8 @@ def to_string(self):
625625
max_len += size_tr_col # Need to make space for largest row
626626
# plus truncate dot col
627627
dif = max_len - self.w
628-
adj_dif = dif
628+
# '+ 1' to avoid too wide repr (GH PR #17023)
629+
adj_dif = dif + 1
629630
col_lens = Series([Series(ele).apply(len).max()
630631
for ele in strcols])
631632
n_cols = len(col_lens)

pandas/io/formats/terminal.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import sys
1818
import shutil
1919

20-
__all__ = ['get_terminal_size']
20+
__all__ = ['get_terminal_size', 'is_terminal']
2121

2222

2323
def get_terminal_size():
@@ -48,6 +48,23 @@ def get_terminal_size():
4848
return tuple_xy
4949

5050

51+
def is_terminal():
52+
"""
53+
Detect if Python is running in a terminal.
54+
55+
Returns True if Python is running in a terminal or False if not.
56+
"""
57+
try:
58+
ip = get_ipython()
59+
except NameError: # assume standard Python interpreter in a terminal
60+
return True
61+
else:
62+
if hasattr(ip, 'kernel'): # IPython as a Jupyter kernel
63+
return False
64+
else: # IPython in a terminal
65+
return True
66+
67+
5168
def _get_terminal_size_windows():
5269
res = None
5370
try:

pandas/tests/frame/test_dtypes.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -875,10 +875,11 @@ def test_astype_str(self):
875875
columns=self.tzframe.columns)
876876
tm.assert_frame_equal(result, expected)
877877

878-
result = str(self.tzframe)
879-
assert ('0 2013-01-01 2013-01-01 00:00:00-05:00 '
880-
'2013-01-01 00:00:00+01:00') in result
881-
assert ('1 2013-01-02 '
882-
'NaT NaT') in result
883-
assert ('2 2013-01-03 2013-01-03 00:00:00-05:00 '
884-
'2013-01-03 00:00:00+01:00') in result
878+
with option_context('display.max_columns', 20):
879+
result = str(self.tzframe)
880+
assert ('0 2013-01-01 2013-01-01 00:00:00-05:00 '
881+
'2013-01-01 00:00:00+01:00') in result
882+
assert ('1 2013-01-02 '
883+
'NaT NaT') in result
884+
assert ('2 2013-01-03 2013-01-03 00:00:00-05:00 '
885+
'2013-01-03 00:00:00+01:00') in result

pandas/tests/frame/test_repr_info.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,8 @@ def test_repr_column_name_unicode_truncation_bug(self):
172172
'the CSV file externally. I want to Call'
173173
' the File through the code..')})
174174

175-
result = repr(df)
176-
assert 'StringCol' in result
175+
with option_context('display.max_columns', 20):
176+
assert 'StringCol' in repr(df)
177177

178178
def test_latex_repr(self):
179179
result = r"""\begin{tabular}{llll}

pandas/tests/io/formats/test_format.py

+39-28
Original file line numberDiff line numberDiff line change
@@ -961,7 +961,8 @@ def test_pprint_thing(self):
961961

962962
def test_wide_repr(self):
963963
with option_context('mode.sim_interactive', True,
964-
'display.show_dimensions', True):
964+
'display.show_dimensions', True,
965+
'display.max_columns', 20):
965966
max_cols = get_option('display.max_columns')
966967
df = DataFrame(tm.rands_array(25, size=(10, max_cols - 1)))
967968
set_option('display.expand_frame_repr', False)
@@ -979,15 +980,17 @@ def test_wide_repr(self):
979980
reset_option('display.expand_frame_repr')
980981

981982
def test_wide_repr_wide_columns(self):
982-
with option_context('mode.sim_interactive', True):
983+
with option_context('mode.sim_interactive', True,
984+
'display.max_columns', 20):
983985
df = DataFrame(np.random.randn(5, 3),
984986
columns=['a' * 90, 'b' * 90, 'c' * 90])
985987
rep_str = repr(df)
986988

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

989991
def test_wide_repr_named(self):
990-
with option_context('mode.sim_interactive', True):
992+
with option_context('mode.sim_interactive', True,
993+
'display.max_columns', 20):
991994
max_cols = get_option('display.max_columns')
992995
df = DataFrame(tm.rands_array(25, size=(10, max_cols - 1)))
993996
df.index.name = 'DataFrame Index'
@@ -1008,7 +1011,8 @@ def test_wide_repr_named(self):
10081011
reset_option('display.expand_frame_repr')
10091012

10101013
def test_wide_repr_multiindex(self):
1011-
with option_context('mode.sim_interactive', True):
1014+
with option_context('mode.sim_interactive', True,
1015+
'display.max_columns', 20):
10121016
midx = MultiIndex.from_arrays(tm.rands_array(5, size=(2, 10)))
10131017
max_cols = get_option('display.max_columns')
10141018
df = DataFrame(tm.rands_array(25, size=(10, max_cols - 1)),
@@ -1030,7 +1034,8 @@ def test_wide_repr_multiindex(self):
10301034
reset_option('display.expand_frame_repr')
10311035

10321036
def test_wide_repr_multiindex_cols(self):
1033-
with option_context('mode.sim_interactive', True):
1037+
with option_context('mode.sim_interactive', True,
1038+
'display.max_columns', 20):
10341039
max_cols = get_option('display.max_columns')
10351040
midx = MultiIndex.from_arrays(tm.rands_array(5, size=(2, 10)))
10361041
mcols = MultiIndex.from_arrays(
@@ -1044,15 +1049,16 @@ def test_wide_repr_multiindex_cols(self):
10441049
wide_repr = repr(df)
10451050
assert rep_str != wide_repr
10461051

1047-
with option_context('display.width', 150):
1052+
with option_context('display.width', 150, 'display.max_columns', 20):
10481053
wider_repr = repr(df)
10491054
assert len(wider_repr) < len(wide_repr)
10501055

10511056
reset_option('display.expand_frame_repr')
10521057

10531058
def test_wide_repr_unicode(self):
1054-
with option_context('mode.sim_interactive', True):
1055-
max_cols = get_option('display.max_columns')
1059+
with option_context('mode.sim_interactive', True,
1060+
'display.max_columns', 20):
1061+
max_cols = 20
10561062
df = DataFrame(tm.rands_array(25, size=(10, max_cols - 1)))
10571063
set_option('display.expand_frame_repr', False)
10581064
rep_str = repr(df)
@@ -1442,17 +1448,17 @@ def test_repr_html_mathjax(self):
14421448
assert 'tex2jax_ignore' in df._repr_html_()
14431449

14441450
def test_repr_html_wide(self):
1445-
max_cols = get_option('display.max_columns')
1451+
max_cols = 20
14461452
df = DataFrame(tm.rands_array(25, size=(10, max_cols - 1)))
1447-
reg_repr = df._repr_html_()
1448-
assert "..." not in reg_repr
1453+
with option_context('display.max_rows', 60, 'display.max_columns', 20):
1454+
assert "..." not in df._repr_html_()
14491455

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

14541460
def test_repr_html_wide_multiindex_cols(self):
1455-
max_cols = get_option('display.max_columns')
1461+
max_cols = 20
14561462

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

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

15141520
def test_repr_html_long_multiindex(self):
1515-
max_rows = get_option('display.max_rows')
1521+
max_rows = 60
15161522
max_L1 = max_rows // 2
15171523

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

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

15321539
def test_repr_html_long_and_wide(self):
1533-
max_cols = get_option('display.max_columns')
1534-
max_rows = get_option('display.max_rows')
1540+
max_cols = 20
1541+
max_rows = 60
15351542

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

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

15441553
def test_info_repr(self):
1545-
max_rows = get_option('display.max_rows')
1546-
max_cols = get_option('display.max_columns')
1554+
max_rows = 60
1555+
max_cols = 20
15471556
# Long
15481557
h, w = max_rows + 1, max_cols - 1
15491558
df = DataFrame({k: np.arange(1, 1 + h) for k in np.arange(w)})
@@ -1555,7 +1564,8 @@ def test_info_repr(self):
15551564
h, w = max_rows - 1, max_cols + 1
15561565
df = DataFrame({k: np.arange(1, 1 + h) for k in np.arange(w)})
15571566
assert has_horizontally_truncated_repr(df)
1558-
with option_context('display.large_repr', 'info'):
1567+
with option_context('display.large_repr', 'info',
1568+
'display.max_columns', max_cols):
15591569
assert has_info_repr(df)
15601570

15611571
def test_info_repr_max_cols(self):
@@ -1575,8 +1585,8 @@ def test_info_repr_max_cols(self):
15751585
# fmt.set_option('display.max_info_columns', 4) # exceeded
15761586

15771587
def test_info_repr_html(self):
1578-
max_rows = get_option('display.max_rows')
1579-
max_cols = get_option('display.max_columns')
1588+
max_rows = 60
1589+
max_cols = 20
15801590
# Long
15811591
h, w = max_rows + 1, max_cols - 1
15821592
df = DataFrame({k: np.arange(1, 1 + h) for k in np.arange(w)})
@@ -1588,7 +1598,8 @@ def test_info_repr_html(self):
15881598
h, w = max_rows - 1, max_cols + 1
15891599
df = DataFrame({k: np.arange(1, 1 + h) for k in np.arange(w)})
15901600
assert '<class' not in df._repr_html_()
1591-
with option_context('display.large_repr', 'info'):
1601+
with option_context('display.large_repr', 'info',
1602+
'display.max_columns', max_cols):
15921603
assert '&lt;class' in df._repr_html_()
15931604

15941605
def test_fake_qtconsole_repr_html(self):

0 commit comments

Comments
 (0)