From 26e5e30f97a11561ca72c767a7b54ee8f6805fa0 Mon Sep 17 00:00:00 2001 From: y-p Date: Mon, 20 May 2013 18:48:18 +0300 Subject: [PATCH 1/7] ENH/CLN: add helpers for ipnb detection --- pandas/core/common.py | 17 ++++++++++++++++- pandas/core/format.py | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pandas/core/common.py b/pandas/core/common.py index a52c932b30ba4..cbc85e6b91c33 100644 --- a/pandas/core/common.py +++ b/pandas/core/common.py @@ -1904,8 +1904,23 @@ def in_qtconsole(): return True except: return False + return False + +def in_ipnb(): + """ + check if we're inside an IPython Notebook + """ + try: + ip = get_ipython() + front_end = (ip.config.get('KernelApp',{}).get('parent_appname',"") or + ip.config.get('IPKernelApp',{}).get('parent_appname',"")) + if 'notebook' in front_end.lower(): + return True + except: + return False + return False -def in_ipnb_frontend(): +def in_ipython_frontend(): """ check if we're inside an an IPython zmq frontend """ diff --git a/pandas/core/format.py b/pandas/core/format.py index 3d38caa84492f..608165f4ed340 100644 --- a/pandas/core/format.py +++ b/pandas/core/format.py @@ -1718,7 +1718,7 @@ def get_console_size(): # Simple. yeah. if com.in_interactive_session(): - if com.in_ipnb_frontend(): + if com.in_ipython_frontend(): # sane defaults for interactive non-shell terminal # match default for width,height in config_init from pandas.core.config import get_default_val From 92194336fd63f153beeaaa72ad4e71e1c6d590b0 Mon Sep 17 00:00:00 2001 From: y-p Date: Mon, 20 May 2013 19:25:08 +0300 Subject: [PATCH 2/7] ENH: special case HTML repr behaviour on ipnb GH3573 PTF --- pandas/core/frame.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index ed56a658d817d..43a54ae987241 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -621,19 +621,26 @@ def _repr_fits_vertical_(self): max_rows = max_rows or height +1 return len(self) <= min(max_rows, height) - def _repr_fits_horizontal_(self): + def _repr_fits_horizontal_(self,ignore_width=False): """ Check if full repr fits in horizontal boundaries imposed by the display options width and max_columns. In case off non-interactive session, no boundaries apply. + + ignore_width is here so ipnb+HTML output can behave the way + users expect. display.max_columns remains in effect. + GH3541, GH3573 """ + + # everytime you add an if-clause here, god slaughters a kitten. + # please. think of the kittens. width, height = fmt.get_console_size() max_columns = get_option("display.max_columns") nb_columns = len(self.columns) # exceed max columns if ((max_columns and nb_columns > max_columns) or - (width and nb_columns > (width // 2))): + ((not ignore_width) and width and nb_columns > (width // 2))): return False if width is None: @@ -655,7 +662,12 @@ def _repr_fits_horizontal_(self): d.to_string(buf=buf) value = buf.getvalue() repr_width = max([len(l) for l in value.split('\n')]) - return repr_width <= width + + # special case ipnb+HTML repr + if not ignore_width: + return repr_width <= width + else: + return True def __str__(self): """ @@ -731,12 +743,16 @@ def _repr_html_(self): Return a html representation for a particular DataFrame. Mainly for IPython notebook. """ + # ipnb in html repr mode allows scrolling + # users strongly prefer to h-scroll a wide HTML table in the browser + # then to get a summary view. GH3541, GH3573 + ipnbh = com.in_ipnb() and get_option('display.notebook_repr_html') if get_option("display.notebook_repr_html"): fits_vertical = self._repr_fits_vertical_() fits_horizontal = False if fits_vertical: - fits_horizontal = self._repr_fits_horizontal_() + fits_horizontal = self._repr_fits_horizontal_(ignore_width=ipnbh) if fits_horizontal and fits_vertical: return ('
Date: Tue, 21 May 2013 02:47:22 +0300 Subject: [PATCH 4/7] DOC: update RELEASE.rst --- RELEASE.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/RELEASE.rst b/RELEASE.rst index e02ad66252bdc..c2d4154bf2587 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -40,8 +40,8 @@ pandas 0.11.1 list of the rows from which to read the index. Added the option, ``tupleize_cols`` to provide compatiblity for the pre 0.11.1 behavior of writing and reading multi-index columns via a list of tuples. The default in - 0.11.1 is to write lists of tuples and *not* interpret list of tuples as a - multi-index column. + 0.11.1 is to write lists of tuples and *not* interpret list of tuples as a + multi-index column. Note: The default value will change in 0.12 to make the default *to* write and read multi-index columns in the new format. (GH3571_, GH1651_, GH3141_) - Add iterator to ``Series.str`` (GH3638_) @@ -79,8 +79,8 @@ pandas 0.11.1 ``timedelta64[ns]`` to ``object/int`` (GH3425_) - Do not allow datetimelike/timedeltalike creation except with valid types (e.g. cannot pass ``datetime64[ms]``) (GH3423_) - - Add ``squeeze`` keyword to ``groupby`` to allow reduction from - DataFrame -> Series if groups are unique. Regression from 0.10.1, + - Add ``squeeze`` keyword to ``groupby`` to allow reduction from + DataFrame -> Series if groups are unique. Regression from 0.10.1, partial revert on (GH2893_) with (GH3596_) - Raise on ``iloc`` when boolean indexing with a label based indexer mask e.g. a boolean Series, even with integer labels, will raise. Since ``iloc`` @@ -134,11 +134,12 @@ pandas 0.11.1 is a ``list`` or ``tuple``. - Fixed bug where a time-series was being selected in preference to an actual column name in a frame (GH3594_) - - Fix modulo and integer division on Series,DataFrames to act similary to ``float`` dtypes to return + - Fix modulo and integer division on Series,DataFrames to act similary to ``float`` dtypes to return ``np.nan`` or ``np.inf`` as appropriate (GH3590_) - Fix incorrect dtype on groupby with ``as_index=False`` (GH3610_) - Fix ``read_csv`` to correctly encode identical na_values, e.g. ``na_values=[-999.0,-999]`` was failing (GH3611_) + - Disable HTML output in qtconsole again. (GH3657_) - Fix indexing issue in ndim >= 3 with ``iloc`` (GH3617_) - Correctly parse date columns with embedded (nan/NaT) into datetime64[ns] dtype in ``read_csv`` when ``parse_dates`` is specified (GH3062_) From b036a7e316426fe4a312f8df4fb246512165812b Mon Sep 17 00:00:00 2001 From: y-p Date: Tue, 21 May 2013 01:53:10 +0300 Subject: [PATCH 5/7] BUG: rework display logic again. deprecate display.height --- pandas/core/config_init.py | 16 +++++++--- pandas/core/format.py | 2 +- pandas/core/frame.py | 62 ++++++++++++++++--------------------- pandas/tests/test_format.py | 32 ++++++++++++------- 4 files changed, 58 insertions(+), 54 deletions(-) diff --git a/pandas/core/config_init.py b/pandas/core/config_init.py index 71b4539265069..57bbe747c9c2c 100644 --- a/pandas/core/config_init.py +++ b/pandas/core/config_init.py @@ -120,13 +120,17 @@ pc_line_width_doc = """ : int - When printing wide DataFrames, this is the width of each line. + Deprecated. """ pc_line_width_deprecation_warning = """\ line_width has been deprecated, use display.width instead (currently both are identical) """ +pc_height_deprecation_warning = """\ +height has been deprecated. +""" + pc_width_doc = """ : int Width of the display in characters. In case python/IPython is running in @@ -138,10 +142,7 @@ pc_height_doc = """ : int - Height of the display in lines. In case python/IPython is running in a - terminal this can be set to None and pandas will auto-detect the width. - Note that the IPython notebook, IPython qtconsole, or IDLE do not run - in a terminal, and hence it is not possible to correctly detect the height. + Deprecated. """ pc_chop_threshold_doc = """ @@ -244,10 +245,15 @@ def mpl_style_cb(key): validator=is_instance_factory([type(None), int])) # redirected to width, make defval identical cf.register_option('line_width', get_default_val('display.width'), pc_line_width_doc) + cf.deprecate_option('display.line_width', msg=pc_line_width_deprecation_warning, rkey='display.width') +cf.deprecate_option('display.height', + msg=pc_height_deprecation_warning, + rkey='display.height') + tc_sim_interactive_doc = """ : boolean Whether to simulate interactive mode for purposes of testing diff --git a/pandas/core/format.py b/pandas/core/format.py index 608165f4ed340..7327f3b1b2175 100644 --- a/pandas/core/format.py +++ b/pandas/core/format.py @@ -1702,7 +1702,7 @@ def detect_console_encoding(): def get_console_size(): """Return console size as tuple = (width, height). - May return (None,None) in some cases. + Returns (None,None) in non-interactive session. """ display_width = get_option('display.width') display_height = get_option('display.height') diff --git a/pandas/core/frame.py b/pandas/core/frame.py index d2476735a256d..0580be25a3f04 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -605,21 +605,10 @@ def __nonzero__(self): def _repr_fits_vertical_(self): """ - Check if full repr fits in vertical boundaries imposed by the display - options height and max_rows. In case of non-interactive session, - no boundaries apply. + Check length against max_rows. """ - width, height = fmt.get_console_size() max_rows = get_option("display.max_rows") - - if height is None and max_rows is None: - return True - - else: - # min of two, where one may be None - height = height or max_rows +1 - max_rows = max_rows or height +1 - return len(self) <= min(max_rows, height) + return len(self) <= max_rows def _repr_fits_horizontal_(self,ignore_width=False): """ @@ -632,8 +621,6 @@ def _repr_fits_horizontal_(self,ignore_width=False): GH3541, GH3573 """ - # everytime you add an if-clause here, god slaughters a kitten. - # please. think of the kittens. width, height = fmt.get_console_size() max_columns = get_option("display.max_columns") nb_columns = len(self.columns) @@ -643,31 +630,37 @@ def _repr_fits_horizontal_(self,ignore_width=False): ((not ignore_width) and width and nb_columns > (width // 2))): return False - if width is None: - # no sense finding width of repr if no width set + if (ignore_width # used by repr_html under IPython notebook + or not com.in_interactive_session()): # scripts ignore terminal dims return True + if (get_option('display.width') is not None or + com.in_ipython_frontend()): + # check at least the column row for excessive width + max_rows = 1 + else: + max_rows = get_option("display.max_rows") + + # when auto-detecting, so width=None and not in ipython front end + # check whether repr fits horizontal by actualy checking + # the width of the rendered repr buf = StringIO() # only care about the stuff we'll actually print out # and to_string on entire frame may be expensive d = self - max_rows = get_option("display.max_rows") - if not (height is None and max_rows is None): + + if not (max_rows is None): # unlimited rows # min of two, where one may be None - height = height or max_rows +1 - max_rows = max_rows or height +1 - d=d.iloc[:min(max_rows, height,len(d))] + d=d.iloc[:min(max_rows,len(d))] + else: + return True d.to_string(buf=buf) value = buf.getvalue() repr_width = max([len(l) for l in value.split('\n')]) - # special case ipnb+HTML repr - if not ignore_width: - return repr_width <= width - else: - return True + return repr_width < width def __str__(self): """ @@ -709,14 +702,11 @@ def __unicode__(self): if fits_vertical and fits_horizontal: self.to_string(buf=buf) else: - width, height = fmt.get_console_size() - max_rows = get_option("display.max_rows") or height - # expand_repr basically takes the extrac columns that don't - # fit the width, and creates a new page, which increases - # the effective row count. check number of cols agaibst - # max rows to catch wrapping. that would exceed max_rows. - if (get_option("display.expand_frame_repr") and fits_vertical and - len(self.columns) < max_rows): + width, _ = fmt.get_console_size() + max_rows = get_option("display.max_rows") + if (get_option("display.expand_frame_repr") + and fits_vertical): + # and len(self.columns) < max_rows) self.to_string(buf=buf, line_width=width) else: max_info_rows = get_option('display.max_info_rows') @@ -892,7 +882,7 @@ def __contains__(self, key): # Python 2 division methods if not py3compat.PY3: - __div__ = _arith_method(operator.div, '__div__', '/', + __div__ = _arith_method(operator.div, '__div__', '/', default_axis=None, fill_zeros=np.inf) __rdiv__ = _arith_method(lambda x, y: y / x, '__rdiv__', default_axis=None, fill_zeros=np.inf) diff --git a/pandas/tests/test_format.py b/pandas/tests/test_format.py index 6b281edf17da9..7feb2f17d79a5 100644 --- a/pandas/tests/test_format.py +++ b/pandas/tests/test_format.py @@ -170,8 +170,9 @@ def test_expand_frame_repr(self): df_tall = DataFrame('hello', range(30), range(5)) with option_context('mode.sim_interactive', True): - with option_context('display.width', 50, - 'display.height', 20): + with option_context('display.max_columns', 5, + 'display.width',20, + 'display.max_rows', 20): with option_context('display.expand_frame_repr', True): self.assertFalse(has_info_repr(df_small)) self.assertFalse(has_expanded_repr(df_small)) @@ -226,19 +227,21 @@ def mkframe(n): # since not exceeding width self.assertFalse(has_expanded_repr(df6)) self.assertFalse(has_info_repr(df6)) - + with option_context('display.max_rows', 9, 'display.max_columns', 10): # out vertical bounds can not result in exanded repr self.assertFalse(has_expanded_repr(df10)) self.assertTrue(has_info_repr(df10)) - with option_context('display.max_columns', 0, + # width=None in terminal, auto detection + with option_context('display.max_columns', 100, 'display.max_rows', term_width * 20, - 'display.width', 0): + 'display.width', None): df = mkframe((term_width // 7) - 2) self.assertFalse(has_expanded_repr(df)) df = mkframe((term_width // 7) + 2) + print( df._repr_fits_horizontal_()) self.assertTrue(has_expanded_repr(df)) def test_to_string_repr_unicode(self): @@ -787,7 +790,8 @@ def test_pprint_thing(self): def test_wide_repr(self): with option_context('mode.sim_interactive', True): col = lambda l, k: [tm.rands(k) for _ in xrange(l)] - df = DataFrame([col(20, 25) for _ in range(10)]) + max_cols = get_option('display.max_columns') + df = DataFrame([col(max_cols+1, 25) for _ in range(10)]) set_option('display.expand_frame_repr', False) rep_str = repr(df) set_option('display.expand_frame_repr', True) @@ -810,7 +814,8 @@ def test_wide_repr_wide_columns(self): def test_wide_repr_named(self): with option_context('mode.sim_interactive', True): col = lambda l, k: [tm.rands(k) for _ in xrange(l)] - df = DataFrame([col(20, 25) for _ in range(10)]) + max_cols = get_option('display.max_columns') + df = DataFrame([col(max_cols+1, 25) for _ in range(10)]) df.index.name = 'DataFrame Index' set_option('display.expand_frame_repr', False) @@ -833,7 +838,8 @@ def test_wide_repr_multiindex(self): col = lambda l, k: [tm.rands(k) for _ in xrange(l)] midx = pandas.MultiIndex.from_arrays([np.array(col(10, 5)), np.array(col(10, 5))]) - df = DataFrame([col(20, 25) for _ in range(10)], + max_cols = get_option('display.max_columns') + df = DataFrame([col(max_cols+1, 25) for _ in range(10)], index=midx) df.index.names = ['Level 0', 'Level 1'] set_option('display.expand_frame_repr', False) @@ -853,12 +859,13 @@ def test_wide_repr_multiindex(self): def test_wide_repr_multiindex_cols(self): with option_context('mode.sim_interactive', True): + max_cols = get_option('display.max_columns') col = lambda l, k: [tm.rands(k) for _ in xrange(l)] midx = pandas.MultiIndex.from_arrays([np.array(col(10, 5)), np.array(col(10, 5))]) - mcols = pandas.MultiIndex.from_arrays([np.array(col(20, 3)), - np.array(col(20, 3))]) - df = DataFrame([col(20, 25) for _ in range(10)], + mcols = pandas.MultiIndex.from_arrays([np.array(col(max_cols+1, 3)), + np.array(col(max_cols+1, 3))]) + df = DataFrame([col(max_cols+1, 25) for _ in range(10)], index=midx, columns=mcols) df.index.names = ['Level 0', 'Level 1'] set_option('display.expand_frame_repr', False) @@ -876,7 +883,8 @@ def test_wide_repr_multiindex_cols(self): def test_wide_repr_unicode(self): with option_context('mode.sim_interactive', True): col = lambda l, k: [tm.randu(k) for _ in xrange(l)] - df = DataFrame([col(20, 25) for _ in range(10)]) + max_cols = get_option('display.max_columns') + df = DataFrame([col(max_cols+1, 25) for _ in range(10)]) set_option('display.expand_frame_repr', False) rep_str = repr(df) set_option('display.expand_frame_repr', True) From 33f6c6f87a64d0a6cde89119c1a87f5bb707b577 Mon Sep 17 00:00:00 2001 From: y-p Date: Tue, 21 May 2013 14:08:35 +0300 Subject: [PATCH 6/7] DOC: Update Faq section on repr display options --- doc/source/faq.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/faq.rst b/doc/source/faq.rst index 8009c7014c347..a5b6db2964cd2 100644 --- a/doc/source/faq.rst +++ b/doc/source/faq.rst @@ -35,8 +35,8 @@ horizontal scrolling, auto-detection of width/height. To appropriately address all these environments, the display behavior is controlled by several options, which you're encouraged to tweak to suit your setup. -As of 0.11.0, the relavent options are all under the `display` namespace, -(e.g. display.width, display.height, etc'): +As of 0.11.1, these are the relavent options, all under the `display` namespace, +(e.g. display.width, etc'): - notebook_repr_html: if True, IPython frontends with HTML support will display dataframes as HTML tables when possible. - expand_repr (default True): when the frame width cannot fit within the screen, @@ -45,10 +45,10 @@ As of 0.11.0, the relavent options are all under the `display` namespace, - max_columns: max dataframe columns to display. a wider frame will trigger a summary view, unless `expand_repr` is True and HTML output is disabled. - max_rows: max dataframe rows display. a longer frame will trigger a summary view. -- width: width of display screen in characters. When using a terminal, setting this to None - will trigger auto-detection of terminal width. -- height: height of display screen. When using a terminal, setting this to None - will trigger auto-detection of terminal height. +- width: width of display screen in characters, used to determine the width of lines + when expand_repr is active, Setting this to None will trigger auto-detection of terminal + width, this only works for proper terminals, not IPython frontends such as ipnb. + width is ignored in IPython notebook, since the browser provides horizontal scrolling. IPython users can use the IPython startup file to import pandas and set these options automatically when starting up. From 0c0180fc4d8eecd4f5720b806fb80dcdc60cbd44 Mon Sep 17 00:00:00 2001 From: y-p Date: Tue, 21 May 2013 14:19:54 +0300 Subject: [PATCH 7/7] DOC: update RELEASE.rst PTF --- RELEASE.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE.rst b/RELEASE.rst index c2d4154bf2587..18468ebcd3f4c 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -63,6 +63,7 @@ pandas 0.11.1 - Add modulo operator to Series, DataFrame - Add ``date`` method to DatetimeIndex - Simplified the API and added a describe method to Categorical + - Added Faq section on repr display options, to help users customize their setup. **API Changes** @@ -85,6 +86,8 @@ pandas 0.11.1 - Raise on ``iloc`` when boolean indexing with a label based indexer mask e.g. a boolean Series, even with integer labels, will raise. Since ``iloc`` is purely positional based, the labels on the Series are not alignable (GH3631_) + - Deprecated display.height, display.width is now only a formatting option + does not control triggering of summary, simuliar to < 0.11.0. **Bug Fixes** @@ -140,6 +143,7 @@ pandas 0.11.1 - Fix ``read_csv`` to correctly encode identical na_values, e.g. ``na_values=[-999.0,-999]`` was failing (GH3611_) - Disable HTML output in qtconsole again. (GH3657_) + - Reworked the new repr display logic, which users found confusing. (GH3663_) - Fix indexing issue in ndim >= 3 with ``iloc`` (GH3617_) - Correctly parse date columns with embedded (nan/NaT) into datetime64[ns] dtype in ``read_csv`` when ``parse_dates`` is specified (GH3062_)