Skip to content
1 change: 1 addition & 0 deletions doc/source/whatsnew/v0.25.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ I/O
- Bug in ``read_csv`` which would not raise ``ValueError`` if a column index in ``usecols`` was out of bounds (:issue:`25623`)
- Improved the explanation for the failure when value labels are repeated in Stata dta files and suggested work-arounds (:issue:`25772`)
- Improved :meth:`pandas.read_stata` and :class:`pandas.io.stata.StataReader` to read incorrectly formatted 118 format files saved by Stata (:issue:`25960`)
- Improved the ``col_space`` parameter in :meth:`DataFrame.to_html` to accept a string so CSS length values can be set correctly (:issue:`25941`)
- Fixed bug in loading objects from S3 that contain ``#`` characters in the URL (:issue:`25945`)

Plotting
Expand Down
11 changes: 9 additions & 2 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,9 @@ def _repr_html_(self):

@Substitution(header='Write out the column names. If a list of strings '
'is given, it is assumed to be aliases for the '
'column names')
'column names',
col_space_type='int',
col_space='The minimum width of each column')
@Substitution(shared_params=fmt.common_docstring,
returns=fmt.return_docstring)
def to_string(self, buf=None, columns=None, col_space=None, header=True,
Expand Down Expand Up @@ -2149,7 +2151,12 @@ def to_parquet(self, fname, engine='auto', compression='snappy',
compression=compression, index=index,
partition_cols=partition_cols, **kwargs)

@Substitution(header='Whether to print column labels, default True')
@Substitution(header='Whether to print column labels, default True',
col_space_type='str or int',
col_space='The minimum width of each column in CSS length '
'units. An int is assumed to be px units.\n\n'
' .. versionadded:: 0.25.0\n'
' Abillity to use str')
@Substitution(shared_params=fmt.common_docstring,
returns=fmt.return_docstring)
def to_html(self, buf=None, columns=None, col_space=None, header=True,
Expand Down
4 changes: 2 additions & 2 deletions pandas/io/formats/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
Buffer to write to.
columns : sequence, optional, default None
The subset of columns to write. Writes all columns by default.
col_space : int, optional
The minimum width of each column.
col_space : %(col_space_type)s, optional
%(col_space)s.
header : bool, optional
%(header)s.
index : bool, optional, default True
Expand Down
31 changes: 28 additions & 3 deletions pandas/io/formats/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ def __init__(self, formatter, classes=None, border=None):
self.border = border
self.table_id = self.fmt.table_id
self.render_links = self.fmt.render_links
if isinstance(self.fmt.col_space, int):
self.fmt.col_space = ('{colspace}px'
.format(colspace=self.fmt.col_space))

@property
def show_row_idx_names(self):
Expand Down Expand Up @@ -85,8 +88,30 @@ def write(self, s, indent=0):
rs = pprint_thing(s)
self.elements.append(' ' * indent + rs)

def write_th(self, s, indent=0, tags=None):
if self.fmt.col_space is not None and self.fmt.col_space > 0:
def write_th(self, s, header=False, indent=0, tags=None):
"""
Method for writting a formatted <th> cell.

If col_space is set on the formatter then that is used for
the value of min-width.

Parameters
----------
s : object
The data to be written inside the cell.
header : boolean, default False
Set to True if the <th> is for use inside <thead>. This will
cause min-width to be set if there is one.
indent : int, default 0
The indentation level of the cell.
tags : string, default None
Tags to include in the cell.

Returns
-------
A written <th> cell.
"""
if header and self.fmt.col_space is not None:
tags = (tags or "")
tags += ('style="min-width: {colspace};"'
.format(colspace=self.fmt.col_space))
Expand Down Expand Up @@ -137,7 +162,7 @@ def write_tr(self, line, indent=0, indent_delta=0, header=False,
for i, s in enumerate(line):
val_tag = tags.get(i, None)
if header or (self.bold_rows and i < nindex_levels):
self.write_th(s, indent, tags=val_tag)
self.write_th(s, indent=indent, header=header, tags=val_tag)
else:
self.write_td(s, indent, tags=val_tag)

Expand Down
14 changes: 14 additions & 0 deletions pandas/tests/io/formats/test_to_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -643,3 +643,17 @@ def test_to_html_round_column_headers():
notebook = df.to_html(notebook=True)
assert "0.55555" in html
assert "0.556" in notebook


@pytest.mark.parametrize("unit", ['100px', '10%', '5em', 150])
Copy link
Member

Choose a reason for hiding this comment

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

Per @simonjayhawkins does it make sense to allow percentages here?

def test_to_html_with_col_space_units(unit):
# GH 25941
df = DataFrame(np.random.random(size=(1, 3)))
result = df.to_html(col_space=unit)
result = result.split('tbody')[0]
hdrs = [x for x in result.split("\n") if re.search(r"<th[>\s]", x)]
if isinstance(unit, int):
unit = str(unit) + 'px'
for h in hdrs:
expected = '<th style="min-width: {unit};">'.format(unit=unit)
assert expected in h