Repository: pandas-dev/pandas

Issue:
New DataFrame display information?
We are using DataFrames to hold information in a lot places now. E.g., ANOVA tables

```
            df  sum_sq     mean_sq          F    PR(>F)
C(Fitness)   2     672  336.000000  16.961538  0.000041
Residual    21     416   19.809524        NaN       NaN

[2 rows x 5 columns] 
```

Where'd that bottom `[]` business come from? Is this new? Isn't this something that's better included in the `info` method?



Discussion:
started in 0.13, you can turn off if you want by: http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#output-formatting-enhancements (I think set `show_dimensions` to False)

I figured you could, but that's a global option that we can't control after we pass back to a user. We're trying more and more to take advantage of DataFrames as containers to display information, to_latex, to_csv, etc. and this is just noise here. It is descriptive information, which is why I think it should be in `info` just not in the `__repr__`.  If this is going to stay, then I'm going to have to go in and change all of our use of DataFrames as container/display objects, because it's pretty ugly for tables of information IMO.

Also, `.shape` is just a tab-complete away...

this is only for interactive display, e.g. `__repr__`  so not sure what the issue is

its actually quite helpful when you have the default to display the actual data, but the frame itself is truncated (e.g. you are not using info, which you always have the option to do of course)

Yes, I'm not saying it affects anything else. I'm saying that we like those features, but we also like a clean `__repr__`. In the case when we're using it as a table-like container, e.g., in ANOVA tables above, the clean `__repr__` trumps everything for me. Trying quickly to scan a bunch of ANOVA tables, I'm interrupted with this noise about the rows and columns and it makes interactive use more difficult to the point where I'd rather fall back on our old, horrible to use summary tables. IIRC, we also have a PR to move our summary tables  partially into DataFrames, which will also be affected.

It used to be that by default when the frame was truncated, that you got the `info()` view, which I preferred, but can live without. Why not show this new information only if the DataFrame is, in fact, truncated?

But, again, as you pointed out, you always have `info`, so you can just call it. And `shapes` is also just one tab-completion away. It just seems that there are so many other ways to get this information than forcing it into the default `__repr__` of everything.

I hate to nit-pick here, but this is a pretty jarring visual change from what I'm used to, and there's not much gained.

I think their was a bit of discussion on the linked issues. http://pandas.pydata.org/pandas-docs/dev/whatsnew.html#dataframe-repr-changes.

I personally like it, but I can see your point. Hard to have everyone like everything all the time!

@jseabold Why not just return a subclass of DataFrame with the repr methods changed?

Maybe the `show_dimensions = get_option("display.show_dimensions")` call should be moved to an instance methods making changing the behavior in a subclass easier?

IMO the case for including that extra information has not been made well enough for me to start subclassing DataFrames to avoid seeing a piece of information I don't want or need. Are there any other data structures out there that do this? Matlab? data.frames? Numpy arrays? I think scipy.sparse matrices do, but are we really saying that a DataFrame that's truncated on output is like a sparse matrix? Originally, DFs showed the `info` view. But sometimes you want to peak at the data. So it grew a `head` and `tail` like data.frames. There's also `shape`. Is anyone really printing out the full `__repr__` of a DataFrame just to find out about the rows and columns? This just seems like an addition for the sake of having an addition to me. No real benefit, potential negative side effects (more noise).

@jseabold My opinion is that when no truncation of the dataframe occurs, printing the dimensions is in fact noise. When truncation does occur, I do believe that it makes sense to print the dimensions of the full frame. 

Yeah, I could live with that. I won't back up to argue whether there should even be this `...` truncation vs just `info`, `head`, and `tail` :)

@jreback What do you think about that?

I think these discussions are personal preferences and have long been discussed
you can simply set options to get the prior behavior if u want

no one is ever going to be happy

so will wait for more consensus to potentially change this again

Ok, then here's another gripe. Discussions on github are not public enough IMO. There is a lot of a github noise from pandas. I try my best to keep up with development, but I don't see this until I install it. It seems to me, you're saying "I participated in the discussion, and it went on for a long time, so that's that."

This was discussed by 3 people on #4886 and #5550. @y-p made some strenuous initial objections (which are in line with my initial reaction) and then withdrew them. Particularly this bit

```
I strongly urge conducting a small usability study (have a few users adopt it for a week 
and report) before making potentially disruptive change like this to UX.
```

There are several other sensible comments to this effect.

It would've been great to see a ping on the mailing list about this. Something. Anything. Maybe I missed it and it's my fault. But, hey, let's think of this release as the usability study.

Coming back to the point, the issue of the footer was discussed only in passing on #5550. And here's what it says (my emphasis).

```
**When truncating**, having a footer with total row count would eliminate the need 
to use df.info in many cases and so reduce the impact of the change on existing users. 
(For example, after filtering a frame you're often interested in the size of the result).
```

@jseabold all for more discussion

esp on UX

pls post an issue to the mailing list if u would then

FWIW, I completely agree with @jseabold that github discussions aren't really sufficient notification  of any large or potentially breaking changes. A case in point - I knew nothing about these UX  changes until I upgraded and am only here now because I saw Skippers post to the mailing list.

I have no strong opinion about these changes though Skippers suggestion of only printing the shape when truncated sounds sensible to me.

One thing I've noticed is that the truncated view displays the first `n` rows and columns whereas I'd vastly prefer displaying the first and last `n/2`. Since I missed the discussion and the changes had already been made I didn't bother piping up.

@dhirschfeld funny thing is I just referened your comment on the ML...hahaha...

their is an issue to do exactly this type of 2-side truncation display, see here: https://github.com/pydata/pandas/issues/5603

@jseabold @dhirschfeld what is the decision on this?

If I look at the dicussion on the mailing list and here, I think there is a majority for the option "only show dimensions when truncated" (4 votes against 1 or two for "always show dimensions" on the mailing list, and also here two extra votes for "only show dimensions when truncated").

And I think I am also +1 on only showing the dimensions when the dataframe is truncated.

BTW, R does something vaguely similar. It also truncates the output of a very large dataframe (only at a much higher limit) and then you get the message `[ reached getOption("max.print") -- omitted ..... rows ]`.

@takluyver You were the author of the display changes, what do you think?

I quite like seeing the number of rows even when it's not truncated. When
you filter a dataframe, it gives you an easy view of how many things you've
got. The number of columns doesn't matter so much to me, but it's there for
consistency.

I'm not going to fight this if people want to change it, but I will note
that people complain more readily than they praise - people who like the
feature may only say so after it's gone.

@takluyver would you mind repeating your comments to the mailing list issue?

Can you point me to it? I'm not on the pandas mailing list.

https://groups.google.com/forum/#!topic/pydata/wyhbADpsUss

@takluyver #5603 ?

@jseabold @jorisvandenbossche 

ok after reading the thread...seems consensus is to allow: `display.show_dimensions`

to be:
- `True` show the dimensions always (currently the default)
- `False` never show the dimensions
- `truncate` show dimensions only if truncated

good?

What will be the new default?
On Apr 29, 2014 8:05 PM, "jreback" notifications@github.com wrote:

> @jseabold https://github.com/jseabold @jorisvandenbosschehttps://github.com/jorisvandenbossche
> 
> ok after reading the thread...seems consensus is to allow:
> display.show_dimensions
> 
> to be:
> - True show the dimensions always (currently the default)
> - False never show the dimensions
> - truncate show dimensions only if truncated
> 
> good?
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/pydata/pandas/issues/6547#issuecomment-41747034
> .

I wouldn't change the default (of course that is the point of an option, users can change).

@jseabold points out that 0.13 made an API change where this did change, but that was a conscious decision.

To have consensus to allow to change the behaviour via an option is not that difficult I think :-) 
But I don't really know if there was a _clear_ consensus on what should be the default, and that is what the real discussion is about (eg in my comment above (https://github.com/pydata/pandas/issues/6547#issuecomment-39967165) I state the opposite ...).

I also don't know if it was that conscious. OK, it was discussed and clearly chosen to show a truncated view, but not really when the dimensions should be showed (see eg comment of @jseabold above https://github.com/pydata/pandas/issues/6547#issuecomment-36904250).

But of course, the options can already be implemented. Then changing the default if a clear consensus arises is trivial.

are their any other options under consideration that I didn't point out?

No, I think those three options (always/never/truncated) are all relevant options. 


Resolving patch:
diff --git a/pandas/core/config_init.py b/pandas/core/config_init.py
--- a/pandas/core/config_init.py
+++ b/pandas/core/config_init.py
@@ -120,8 +120,10 @@
 """
 
 pc_show_dimensions_doc = """
-: boolean
+: boolean or 'truncate'
     Whether to print out dimensions at the end of DataFrame repr.
+    If 'truncate' is specified, only print out the dimensions if the
+    frame is truncated (e.g. not display all rows and/or columns)
 """
 
 pc_line_width_doc = """
@@ -247,7 +249,8 @@ def mpl_style_cb(key):
     cf.register_option('encoding', detect_console_encoding(), pc_encoding_doc,
                        validator=is_text)
     cf.register_option('expand_frame_repr', True, pc_expand_repr_doc)
-    cf.register_option('show_dimensions', True, pc_show_dimensions_doc)
+    cf.register_option('show_dimensions', True, pc_show_dimensions_doc,
+                       validator=is_one_of_factory([True, False, 'truncate']))
     cf.register_option('chop_threshold', None, pc_chop_threshold_doc)
     cf.register_option('max_seq_items', 100, pc_max_seq_items)
     cf.register_option('mpl_style', None, pc_mpl_style_doc,
diff --git a/pandas/core/format.py b/pandas/core/format.py
--- a/pandas/core/format.py
+++ b/pandas/core/format.py
@@ -236,6 +236,12 @@ def _strlen(x):
 
 
 class TableFormatter(object):
+    is_truncated = False
+    show_dimensions = None
+
+    @property
+    def should_show_dimensions(self):
+        return self.show_dimensions is True or (self.show_dimensions == 'truncate' and self.is_truncated)
 
     def _get_formatter(self, i):
         if isinstance(self.formatters, (list, tuple)):
@@ -315,9 +321,9 @@ def _to_str_columns(self):
         _strlen = _strlen_func()
 
         cols_to_show = self.columns[:self.max_cols]
-        truncate_h = self.max_cols and (len(self.columns) > self.max_cols)
-        truncate_v = self.max_rows and (len(self.frame) > self.max_rows)
-        self.truncated_v = truncate_v
+        self.truncated_h = truncate_h = self.max_cols and (len(self.columns) > self.max_cols)
+        self.truncated_v = truncate_v = self.max_rows and (len(self.frame) > self.max_rows)
+        self.is_truncated = self.truncated_h or self.truncated_v
         if truncate_h:
             cols_to_show = self.columns[:self.max_cols]
         else:
@@ -380,7 +386,7 @@ def to_string(self):
 
         self.buf.writelines(text)
 
-        if self.show_dimensions:
+        if self.should_show_dimensions:
             self.buf.write("\n\n[%d rows x %d columns]"
                            % (len(frame), len(frame.columns)))
 
@@ -634,6 +640,8 @@ def __init__(self, formatter, classes=None, max_rows=None, max_cols=None):
 
         self.max_rows = max_rows or len(self.fmt.frame)
         self.max_cols = max_cols or len(self.fmt.columns)
+        self.show_dimensions = self.fmt.show_dimensions
+        self.is_truncated = self.max_rows < len(self.fmt.frame) or self.max_cols < len(self.fmt.columns)
 
     def write(self, s, indent=0):
         rs = com.pprint_thing(s)
@@ -709,7 +717,7 @@ def write_result(self, buf):
         indent = self._write_body(indent)
 
         self.write('</table>', indent)
-        if self.fmt.show_dimensions:
+        if self.should_show_dimensions:
             by = chr(215) if compat.PY3 else unichr(215)  # ×
             self.write(u('<p>%d rows %s %d columns</p>') %
                        (len(frame), by, len(frame.columns)))


Test patch:
diff --git a/pandas/tests/test_format.py b/pandas/tests/test_format.py
--- a/pandas/tests/test_format.py
+++ b/pandas/tests/test_format.py
@@ -1197,6 +1197,26 @@ def test_to_string_line_width(self):
         s = df.to_string(line_width=80)
         self.assertEqual(max(len(l) for l in s.split('\n')), 80)
 
+    def test_show_dimensions(self):
+        df = pd.DataFrame(123, lrange(10, 15), lrange(30))
+
+        with option_context('display.max_rows', 10, 'display.max_columns', 40, 'display.width',
+                            500, 'display.expand_frame_repr', 'info', 'display.show_dimensions', True):
+            self.assertTrue('5 rows' in str(df))
+            self.assertTrue('5 rows' in df._repr_html_())
+        with option_context('display.max_rows', 10, 'display.max_columns', 40, 'display.width',
+                            500, 'display.expand_frame_repr', 'info', 'display.show_dimensions', False):
+            self.assertFalse('5 rows' in str(df))
+            self.assertFalse('5 rows' in df._repr_html_())
+        with option_context('display.max_rows', 2, 'display.max_columns', 2, 'display.width',
+                            500, 'display.expand_frame_repr', 'info', 'display.show_dimensions', 'truncate'):
+            self.assertTrue('5 rows' in str(df))
+            self.assertTrue('5 rows' in df._repr_html_())
+        with option_context('display.max_rows', 10, 'display.max_columns', 40, 'display.width',
+                            500, 'display.expand_frame_repr', 'info', 'display.show_dimensions', 'truncate'):
+            self.assertFalse('5 rows' in str(df))
+            self.assertFalse('5 rows' in df._repr_html_())
+
     def test_to_html(self):
         # big mixed
         biggie = DataFrame({'A': randn(200),