Skip to content

BUG: to_html misses truncation indicators (...) when index=False #22786

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 12 commits into from
Nov 15, 2018
1 change: 1 addition & 0 deletions doc/source/whatsnew/v0.24.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,7 @@ Notice how we now instead output ``np.nan`` itself instead of a stringified form
- :func:`read_sas()` will correctly parse sas7bdat files with many columns (:issue:`22628`)
- :func:`read_sas()` will correctly parse sas7bdat files with data page types having also bit 7 set (so page type is 128 + 256 = 384) (:issue:`16615`)
- Bug in :meth:`detect_client_encoding` where potential ``IOError`` goes unhandled when importing in a mod_wsgi process due to restricted access to stdout. (:issue:`21552`)
- Bug in :func:`to_html()` with ``index=False`` misses truncation indicators (...) on truncated DataFrame (:issue:`15019`, :issue:`22783`)
- Bug in :func:`DataFrame.to_string()` that broke column alignment when ``index=False`` and width of first column's values is greater than the width of first column's header (:issue:`16839`, :issue:`13032`)
- Bug in :func:`DataFrame.to_string()` that caused representations of :class:`DataFrame` to not take up the whole window (:issue:`22984`)
- Bug in :func:`DataFrame.to_csv` where a single level MultiIndex incorrectly wrote a tuple. Now just the value of the index is written (:issue:`19589`).
Expand Down
35 changes: 19 additions & 16 deletions pandas/io/formats/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ def _column_header():
align = self.fmt.justify

if truncate_h:
if not self.fmt.index:
row_levels = 0
ins_col = row_levels + self.fmt.tr_col_num
col_row.insert(ins_col, '...')

Expand Down Expand Up @@ -336,15 +338,10 @@ def _write_body(self, indent):
fmt_values[i] = self.fmt._format_col(i)

# write values
if self.fmt.index:
if isinstance(self.frame.index, ABCMultiIndex):
self._write_hierarchical_rows(fmt_values, indent)
else:
self._write_regular_rows(fmt_values, indent)
if self.fmt.index and isinstance(self.frame.index, ABCMultiIndex):
self._write_hierarchical_rows(fmt_values, indent)
else:
for i in range(min(len(self.frame), self.max_rows)):
row = [fmt_values[j][i] for j in range(len(self.columns))]
self.write_tr(row, indent, self.indent_delta, tags=None)
self._write_regular_rows(fmt_values, indent)

indent -= self.indent_delta
self.write('</tbody>', indent)
Expand All @@ -358,29 +355,35 @@ def _write_regular_rows(self, fmt_values, indent):

ncols = len(self.fmt.tr_frame.columns)
nrows = len(self.fmt.tr_frame)
fmt = self.fmt._get_formatter('__index__')
if fmt is not None:
index_values = self.fmt.tr_frame.index.map(fmt)

if self.fmt.index:
fmt = self.fmt._get_formatter('__index__')
if fmt is not None:
index_values = self.fmt.tr_frame.index.map(fmt)
else:
index_values = self.fmt.tr_frame.index.format()
row_levels = 1
else:
index_values = self.fmt.tr_frame.index.format()
row_levels = 0

row = []
for i in range(nrows):

if truncate_v and i == (self.fmt.tr_row_num):
str_sep_row = ['...'] * len(row)
self.write_tr(str_sep_row, indent, self.indent_delta,
tags=None, nindex_levels=1)
tags=None, nindex_levels=row_levels)

row = []
row.append(index_values[i])
if self.fmt.index:
row.append(index_values[i])
row.extend(fmt_values[j][i] for j in range(ncols))

if truncate_h:
dot_col_ix = self.fmt.tr_col_num + 1
dot_col_ix = self.fmt.tr_col_num + row_levels
row.insert(dot_col_ix, '...')
self.write_tr(row, indent, self.indent_delta, tags=None,
nindex_levels=1)
nindex_levels=row_levels)

def _write_hierarchical_rows(self, fmt_values, indent):
template = 'rowspan="{span}" valign="top"'
Expand Down
30 changes: 30 additions & 0 deletions pandas/tests/io/formats/data/gh15019_expected_output.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th>0</th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<td>1.764052</td>
<td>0.400157</td>
</tr>
<tr>
<td>0.978738</td>
<td>2.240893</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
</tr>
<tr>
<td>0.950088</td>
<td>-0.151357</td>
</tr>
<tr>
<td>-0.103219</td>
<td>0.410599</td>
</tr>
</tbody>
</table>
27 changes: 27 additions & 0 deletions pandas/tests/io/formats/data/gh22783_expected_output.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th>0</th>
<th>1</th>
<th>...</th>
<th>3</th>
<th>4</th>
</tr>
</thead>
<tbody>
<tr>
<td>1.764052</td>
<td>0.400157</td>
<td>...</td>
<td>2.240893</td>
<td>1.867558</td>
</tr>
<tr>
<td>-0.977278</td>
<td>0.950088</td>
<td>...</td>
<td>-0.103219</td>
<td>0.410599</td>
</tr>
</tbody>
</table>
45 changes: 45 additions & 0 deletions pandas/tests/io/formats/test_to_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,28 @@
pass


def expected_html(datapath, name):
"""
Read HTML file from formats data directory.

Copy link
Contributor

Choose a reason for hiding this comment

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

might be nice to change some of the existing tests to use this function (future PR though)

Copy link
Member Author

Choose a reason for hiding this comment

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

PR #23747 to change existing tests to use this function.

Parameters
----------
datapath : pytest fixture
The datapath fixture injected into a test by pytest.
name : str
The name of the HTML file without the suffix.

Returns
-------
str : contents of HTML file.
"""
filename = '.'.join([name, 'html'])
filepath = datapath('io', 'formats', 'data', filename)
with open(filepath) as f:
html = f.read()
return html.rstrip()


class TestToHTML(object):

def test_to_html_with_col_space(self):
Expand Down Expand Up @@ -1881,6 +1903,29 @@ def test_to_html_multiindex_max_cols(self):
</table>""")
assert result == expected

@pytest.mark.parametrize('index', [False, 0])
def test_to_html_truncation_index_false_max_rows(self, datapath, index):
# GH 15019
data = [[1.764052, 0.400157],
[0.978738, 2.240893],
[1.867558, -0.977278],
[0.950088, -0.151357],
[-0.103219, 0.410599]]
df = pd.DataFrame(data)
result = df.to_html(max_rows=4, index=index)
expected = expected_html(datapath, 'gh15019_expected_output')
assert result == expected

@pytest.mark.parametrize('index', [False, 0])
def test_to_html_truncation_index_false_max_cols(self, datapath, index):
# GH 22783
data = [[1.764052, 0.400157, 0.978738, 2.240893, 1.867558],
[-0.977278, 0.950088, -0.151357, -0.103219, 0.410599]]
df = pd.DataFrame(data)
result = df.to_html(max_cols=4, index=index)
expected = expected_html(datapath, 'gh22783_expected_output')
assert result == expected

def test_to_html_notebook_has_style(self):
df = pd.DataFrame({"A": [1, 2, 3]})
result = df.to_html(notebook=True)
Expand Down