From 36f872c1b4a8d96e20864eaa3491cb1e4109c2de Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Sat, 6 Apr 2019 10:59:53 -0400 Subject: [PATCH 01/11] BUG: Improve col_space in to_html to allow css length strings (#25941) --- doc/source/whatsnew/v0.25.0.rst | 1 + pandas/core/frame.py | 9 +++++++-- pandas/io/formats/format.py | 4 ++-- pandas/io/formats/html.py | 9 ++++++--- pandas/tests/io/formats/test_to_html.py | 10 ++++++++++ 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index fe047b4a141ef..e24a6688768f7 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -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`) Plotting ^^^^^^^^ diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 13c8e97bff23f..505b57b1e6bf4 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -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, @@ -2149,7 +2151,10 @@ 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') @Substitution(shared_params=fmt.common_docstring, returns=fmt.return_docstring) def to_html(self, buf=None, columns=None, col_space=None, header=True, diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py index b397a370cecbf..4d7ba1540b9bf 100644 --- a/pandas/io/formats/format.py +++ b/pandas/io/formats/format.py @@ -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 diff --git a/pandas/io/formats/html.py b/pandas/io/formats/html.py index 031891cb2f7cb..7f7a24c8ab813 100644 --- a/pandas/io/formats/html.py +++ b/pandas/io/formats/html.py @@ -85,8 +85,11 @@ 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): + if header and self.fmt.col_space is not None: + if isinstance(self.fmt.col_space, int): + self.fmt.col_space = ('{colspace}px' + .format(colspace=self.fmt.col_space)) tags = (tags or "") tags += ('style="min-width: {colspace};"' .format(colspace=self.fmt.col_space)) @@ -137,7 +140,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, header=header, indent=indent, tags=val_tag) else: self.write_td(s, indent, tags=val_tag) diff --git a/pandas/tests/io/formats/test_to_html.py b/pandas/tests/io/formats/test_to_html.py index 0acad80bbeef2..1b5a3271e7cc9 100644 --- a/pandas/tests/io/formats/test_to_html.py +++ b/pandas/tests/io/formats/test_to_html.py @@ -643,3 +643,13 @@ def test_to_html_round_column_headers(): notebook = df.to_html(notebook=True) assert "0.55555" in html assert "0.556" in notebook + + +def test_to_html_with_col_space_percent(): + # GH 25941 + df = DataFrame(np.random.random(size=(1, 3))) + result = df.to_html(col_space='100%') + result = result.split('tbody')[0] + hdrs = [x for x in result.split("\n") if re.search(r"\s]", x)] + for h in hdrs: + assert "min-width: 100%" in h From b4d27e3a3725b2b4439821e452b39cb3fd54ce78 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Sat, 6 Apr 2019 12:00:17 -0400 Subject: [PATCH 02/11] parameterize test --- pandas/tests/io/formats/test_to_html.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pandas/tests/io/formats/test_to_html.py b/pandas/tests/io/formats/test_to_html.py index 1b5a3271e7cc9..eff5745be5ce4 100644 --- a/pandas/tests/io/formats/test_to_html.py +++ b/pandas/tests/io/formats/test_to_html.py @@ -644,12 +644,15 @@ def test_to_html_round_column_headers(): assert "0.55555" in html assert "0.556" in notebook - -def test_to_html_with_col_space_percent(): +@pytest.mark.parametrize("unit", ['100px', '10%', '5em', 150]) +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='100%') + result = df.to_html(col_space=unit) result = result.split('tbody')[0] hdrs = [x for x in result.split("\n") if re.search(r"\s]", x)] + if isinstance(unit, int): + unit = str(unit) + 'px' for h in hdrs: - assert "min-width: 100%" in h + expected = ''.format(unit=unit) + assert expected in h From b316e9c4103320dcae6c631d4e16c7828e9069f8 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Sat, 6 Apr 2019 12:01:47 -0400 Subject: [PATCH 03/11] fix spacing --- pandas/tests/io/formats/test_to_html.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/tests/io/formats/test_to_html.py b/pandas/tests/io/formats/test_to_html.py index eff5745be5ce4..888a4af43d547 100644 --- a/pandas/tests/io/formats/test_to_html.py +++ b/pandas/tests/io/formats/test_to_html.py @@ -644,6 +644,7 @@ def test_to_html_round_column_headers(): assert "0.55555" in html assert "0.556" in notebook + @pytest.mark.parametrize("unit", ['100px', '10%', '5em', 150]) def test_to_html_with_col_space_units(unit): # GH 25941 From 4e2d12ec1e67e34f82b184e714c16e20b6216ae3 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Sat, 6 Apr 2019 13:30:12 -0400 Subject: [PATCH 04/11] add comments, update docs --- pandas/core/frame.py | 4 +++- pandas/io/formats/html.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 505b57b1e6bf4..928a67dd562c1 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -2154,7 +2154,9 @@ def to_parquet(self, fname, engine='auto', compression='snappy', @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') + '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, diff --git a/pandas/io/formats/html.py b/pandas/io/formats/html.py index 7f7a24c8ab813..d1e5bfec7fecb 100644 --- a/pandas/io/formats/html.py +++ b/pandas/io/formats/html.py @@ -86,6 +86,8 @@ def write(self, s, indent=0): self.elements.append(' ' * indent + rs) def write_th(self, s, header=False, indent=0, tags=None): + # header=False is for use of this function inside + # header is set to True for use inside if header and self.fmt.col_space is not None: if isinstance(self.fmt.col_space, int): self.fmt.col_space = ('{colspace}px' @@ -140,7 +142,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, header=header, indent=indent, tags=val_tag) + self.write_th(s, indent=indent, header=header, tags=val_tag) else: self.write_td(s, indent, tags=val_tag) From 59f416044728fc3ec786acdf9da482eb27a41115 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Sun, 7 Apr 2019 19:45:22 -0400 Subject: [PATCH 05/11] docstring and sphinx formatting --- pandas/core/frame.py | 2 +- pandas/io/formats/html.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 928a67dd562c1..8944ec642a7be 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -2156,7 +2156,7 @@ def to_parquet(self, fname, engine='auto', compression='snappy', 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') + ' 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, diff --git a/pandas/io/formats/html.py b/pandas/io/formats/html.py index d1e5bfec7fecb..d7a9fe61cfdbf 100644 --- a/pandas/io/formats/html.py +++ b/pandas/io/formats/html.py @@ -86,8 +86,16 @@ def write(self, s, indent=0): self.elements.append(' ' * indent + rs) def write_th(self, s, header=False, indent=0, tags=None): - # header=False is for use of this function inside - # header is set to True for use inside + """ + Return a formatted cell. + + Most tags are passed in as a parameter, but if col_space is set + then that value is used for min-width. + + Since min-width is only set inside , this function also takes + the 'header' parameter. If 'header' is True, min-width needs to be + set as it is for use inside + """ if header and self.fmt.col_space is not None: if isinstance(self.fmt.col_space, int): self.fmt.col_space = ('{colspace}px' From 5eddf38a43a83408c59edd457d37f5b9c69ba630 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Sun, 7 Apr 2019 20:14:22 -0400 Subject: [PATCH 06/11] force rebuild --- pandas/io/formats/html.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/io/formats/html.py b/pandas/io/formats/html.py index d7a9fe61cfdbf..1b1406fcf33e4 100644 --- a/pandas/io/formats/html.py +++ b/pandas/io/formats/html.py @@ -96,6 +96,7 @@ def write_th(self, s, header=False, indent=0, tags=None): the 'header' parameter. If 'header' is True, min-width needs to be set as it is for use inside """ + if header and self.fmt.col_space is not None: if isinstance(self.fmt.col_space, int): self.fmt.col_space = ('{colspace}px' From 5dbeb9ac4f3a7ff6bd3fbb490cf16c1f7738826c Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Sun, 7 Apr 2019 20:14:31 -0400 Subject: [PATCH 07/11] force rebuild --- pandas/io/formats/html.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/io/formats/html.py b/pandas/io/formats/html.py index 1b1406fcf33e4..d7a9fe61cfdbf 100644 --- a/pandas/io/formats/html.py +++ b/pandas/io/formats/html.py @@ -96,7 +96,6 @@ def write_th(self, s, header=False, indent=0, tags=None): the 'header' parameter. If 'header' is True, min-width needs to be set as it is for use inside """ - if header and self.fmt.col_space is not None: if isinstance(self.fmt.col_space, int): self.fmt.col_space = ('{colspace}px' From 2b6e9e167b1a4e697d2d5d5666024bc1d78cf68f Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Sun, 7 Apr 2019 20:44:46 -0400 Subject: [PATCH 08/11] force rebuild --- pandas/io/formats/html.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/io/formats/html.py b/pandas/io/formats/html.py index d7a9fe61cfdbf..1b1406fcf33e4 100644 --- a/pandas/io/formats/html.py +++ b/pandas/io/formats/html.py @@ -96,6 +96,7 @@ def write_th(self, s, header=False, indent=0, tags=None): the 'header' parameter. If 'header' is True, min-width needs to be set as it is for use inside """ + if header and self.fmt.col_space is not None: if isinstance(self.fmt.col_space, int): self.fmt.col_space = ('{colspace}px' From 5bd5d89f60fa1556163e91f1cecf535510e1176a Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Sun, 7 Apr 2019 20:44:53 -0400 Subject: [PATCH 09/11] force rebuild --- pandas/io/formats/html.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/io/formats/html.py b/pandas/io/formats/html.py index 1b1406fcf33e4..d7a9fe61cfdbf 100644 --- a/pandas/io/formats/html.py +++ b/pandas/io/formats/html.py @@ -96,7 +96,6 @@ def write_th(self, s, header=False, indent=0, tags=None): the 'header' parameter. If 'header' is True, min-width needs to be set as it is for use inside """ - if header and self.fmt.col_space is not None: if isinstance(self.fmt.col_space, int): self.fmt.col_space = ('{colspace}px' From 2138dc9d6d0d9babf4c3c83941ec81fd9301e3e7 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Tue, 9 Apr 2019 17:02:45 -0400 Subject: [PATCH 10/11] updated comment --- pandas/io/formats/html.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/pandas/io/formats/html.py b/pandas/io/formats/html.py index d7a9fe61cfdbf..a209d8c52b588 100644 --- a/pandas/io/formats/html.py +++ b/pandas/io/formats/html.py @@ -87,14 +87,26 @@ def write(self, s, indent=0): def write_th(self, s, header=False, indent=0, tags=None): """ - Return a formatted cell. - - Most tags are passed in as a parameter, but if col_space is set - then that value is used for min-width. - - Since min-width is only set inside , this function also takes - the 'header' parameter. If 'header' is True, min-width needs to be - set as it is for use inside + Method for writting a formatted 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 is for use inside . 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 cell. """ if header and self.fmt.col_space is not None: if isinstance(self.fmt.col_space, int): From 1bcc5c62fa00fffef27097d04ba68d01a7df1a97 Mon Sep 17 00:00:00 2001 From: ArtificialQualia Date: Sun, 28 Apr 2019 15:40:38 -0400 Subject: [PATCH 11/11] move formatter check --- pandas/io/formats/html.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/io/formats/html.py b/pandas/io/formats/html.py index a209d8c52b588..d965b6f5859a0 100644 --- a/pandas/io/formats/html.py +++ b/pandas/io/formats/html.py @@ -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): @@ -109,9 +112,6 @@ def write_th(self, s, header=False, indent=0, tags=None): A written cell. """ if header and self.fmt.col_space is not None: - if isinstance(self.fmt.col_space, int): - self.fmt.col_space = ('{colspace}px' - .format(colspace=self.fmt.col_space)) tags = (tags or "") tags += ('style="min-width: {colspace};"' .format(colspace=self.fmt.col_space))