Skip to content

Commit 1b52b12

Browse files
jnothmanjreback
authored andcommitted
ENH: support Styler in ExcelFormatter
closes #1663 Author: Joel Nothman <[email protected]> Closes #15530 from jnothman/excel_style and squashes the following commits: c7a51ca [Joel Nothman] Test currently fails on openpyxl1 due to version incompatibilities 836f39e [Joel Nothman] Revert changes to xlwt de53808 [Joel Nothman] Remove debug code a5d51f9 [Joel Nothman] Merge branch 'master' into excel_style 934df06 [Joel Nothman] Display df, not styled 6465913 [Joel Nothman] More pytest-like test_styler_to_excel; enhancements to xlwt 6168765 [Joel Nothman] Recommended changes to what's new 9669d7d [Joel Nothman] Require jinja in test with df.style 14035c5 [Joel Nothman] Merge branch 'master' into excel_style 3071bac [Joel Nothman] Complete tests ceb9171 [Joel Nothman] reasons for xfails e2cfa77 [Joel Nothman] Test Styler.to_excel d5db0ac [Joel Nothman] Remove obsolete TODO 0256fc6 [Joel Nothman] Return after unhandled font size warning 60d6a3b [Joel Nothman] add doc/source/styled.xlsx to the gitignore 4e72993 [Joel Nothman] Fix what's new heading d144fdf [Joel Nothman] Font name strings 61fdc69 [Joel Nothman] Complete testing basic CSS -> Excel conversions 6ff8a46 [Joel Nothman] Fix loose character; sorry 6d3ffc6 [Joel Nothman] Lint 79eae41 [Joel Nothman] Documentation tweaks c4f59c6 [Joel Nothman] Doc tweaks 2c3d015 [Joel Nothman] Fix JSON syntax in IPynb b1d774b [Joel Nothman] What's new heading 096f26c [Joel Nothman] Merge remote-tracking branch 'upstream/master' into excel_style 433be03 [Joel Nothman] Documentation 9a62699 [Joel Nothman] Fix tests and add TODOs to tests 7c54a69 [Joel Nothman] Fix test failures; avoid hair border which renders strangely 8e9a567 [Joel Nothman] Fixes from integration testing c1fc232 [Joel Nothman] Remove debugging print statements a43d6b7 [Joel Nothman] Cleaner imports a1127f6 [Joel Nothman] Merge branch 'master' into excel_style 306eebe [Joel Nothman] Module-level docstring 350eab5 [Joel Nothman] remove spurious blank line efce9b6 [Joel Nothman] More CSS to Excel testing; define ExcelFormatter.write f17a0f4 [Joel Nothman] Some border style tests 1a8818f [Joel Nothman] Lint 9a5b791 [Joel Nothman] Fix testing ImportError 1984cab [Joel Nothman] Fix making get_level_lengths non-private eb02cc1 [Joel Nothman] Fix testing ImportError 3b26087 [Joel Nothman] Make get_level_lengths non-private f62f02d [Joel Nothman] File restructure dc953d4 [Joel Nothman] Font size and border width 7db59c0 [Joel Nothman] Test inherited styles in converter d103f61 [Joel Nothman] Refactoring and initial tests for CSS to Excel 176e51c [Joel Nothman] Fix NameError c589c35 [Joel Nothman] Fix some lint errors (yes, the code needs testing) cb5cf02 [Joel Nothman] Fix bug where inherited not being passed; avoid classmethods 0ce72f9 [Joel Nothman] Use inherited font size for em_pt 8780076 [Joel Nothman] Merge branch 'master' into excel_style 96680f9 [Joel Nothman] Largely complete CSSToExcelConverter and Styler.to_excel() f1cde08 [Joel Nothman] FIX column offset incorrect in refactor ada5101 [Joel Nothman] ENH: support Styler in ExcelFormatter
1 parent dd5cef5 commit 1b52b12

File tree

13 files changed

+1670
-380
lines changed

13 files changed

+1670
-380
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,5 @@ doc/source/index.rst
103103
doc/build/html/index.html
104104
# Windows specific leftover:
105105
doc/tmp.sv
106+
doc/source/styled.xlsx
106107
doc/source/templates/

doc/source/_static/style-excel.png

56.8 KB
Loading

doc/source/style.ipynb

+56-21
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
"cell_type": "markdown",
55
"metadata": {},
66
"source": [
7-
"# HTML Styling\n",
7+
"# Styling\n",
88
"\n",
99
"*New in version 0.17.1*\n",
1010
"\n",
11-
"<p style=\"color: red\">*Provisional: This is a new feature and still under development. We'll be adding features and possibly making breaking changes in future releases. We'd love to hear your feedback.*<p style=\"color: red\">\n",
11+
"<span style=\"color: red\">*Provisional: This is a new feature and still under development. We'll be adding features and possibly making breaking changes in future releases. We'd love to hear your feedback.*</span>\n",
1212
"\n",
1313
"This document is written as a Jupyter Notebook, and can be viewed or downloaded [here](http://nbviewer.ipython.org/github/pandas-dev/pandas/blob/master/doc/source/html-styling.ipynb).\n",
1414
"\n",
@@ -49,7 +49,6 @@
4949
"cell_type": "code",
5050
"execution_count": null,
5151
"metadata": {
52-
"collapsed": true,
5352
"nbsphinx": "hidden"
5453
},
5554
"outputs": [],
@@ -62,9 +61,7 @@
6261
{
6362
"cell_type": "code",
6463
"execution_count": null,
65-
"metadata": {
66-
"collapsed": true
67-
},
64+
"metadata": {},
6865
"outputs": [],
6966
"source": [
7067
"import pandas as pd\n",
@@ -130,9 +127,7 @@
130127
{
131128
"cell_type": "code",
132129
"execution_count": null,
133-
"metadata": {
134-
"collapsed": true
135-
},
130+
"metadata": {},
136131
"outputs": [],
137132
"source": [
138133
"def color_negative_red(val):\n",
@@ -186,9 +181,7 @@
186181
{
187182
"cell_type": "code",
188183
"execution_count": null,
189-
"metadata": {
190-
"collapsed": true
191-
},
184+
"metadata": {},
192185
"outputs": [],
193186
"source": [
194187
"def highlight_max(s):\n",
@@ -240,7 +233,7 @@
240233
"source": [
241234
"Above we used `Styler.apply` to pass in each column one at a time.\n",
242235
"\n",
243-
"<p style=\"background-color: #DEDEBE\">*Debugging Tip*: If you're having trouble writing your style function, try just passing it into <code style=\"background-color: #DEDEBE\">DataFrame.apply</code>. Internally, <code style=\"background-color: #DEDEBE\">Styler.apply</code> uses <code style=\"background-color: #DEDEBE\">DataFrame.apply</code> so the result should be the same.</p>\n",
236+
"<span style=\"background-color: #DEDEBE\">*Debugging Tip*: If you're having trouble writing your style function, try just passing it into <code style=\"background-color: #DEDEBE\">DataFrame.apply</code>. Internally, <code style=\"background-color: #DEDEBE\">Styler.apply</code> uses <code style=\"background-color: #DEDEBE\">DataFrame.apply</code> so the result should be the same.</span>\n",
244237
"\n",
245238
"What if you wanted to highlight just the maximum value in the entire table?\n",
246239
"Use `.apply(function, axis=None)` to indicate that your function wants the entire table, not one column or row at a time. Let's try that next.\n",
@@ -251,9 +244,7 @@
251244
{
252245
"cell_type": "code",
253246
"execution_count": null,
254-
"metadata": {
255-
"collapsed": true
256-
},
247+
"metadata": {},
257248
"outputs": [],
258249
"source": [
259250
"def highlight_max(data, color='yellow'):\n",
@@ -819,9 +810,7 @@
819810
{
820811
"cell_type": "code",
821812
"execution_count": null,
822-
"metadata": {
823-
"collapsed": true
824-
},
813+
"metadata": {},
825814
"outputs": [],
826815
"source": [
827816
"def magnify():\n",
@@ -854,6 +843,53 @@
854843
" .set_table_styles(magnify())"
855844
]
856845
},
846+
{
847+
"cell_type": "markdown",
848+
"metadata": {},
849+
"source": [
850+
"## Export to Excel\n",
851+
"\n",
852+
"*New in version 0.20.0*\n",
853+
"\n",
854+
"<span style=\"color: red\">*Experimental: This is a new feature and still under development. We'll be adding features and possibly making breaking changes in future releases. We'd love to hear your feedback.*</span>\n",
855+
"\n",
856+
"Some support is available for exporting styled `DataFrames` to Excel worksheets using the `OpenPyXL` engine. CSS2.2 properties handled include:\n",
857+
"\n",
858+
"- `background-color`\n",
859+
"- `border-style`, `border-width`, `border-color` and their {`top`, `right`, `bottom`, `left` variants}\n",
860+
"- `color`\n",
861+
"- `font-family`\n",
862+
"- `font-style`\n",
863+
"- `font-weight`\n",
864+
"- `text-align`\n",
865+
"- `text-decoration`\n",
866+
"- `vertical-align`\n",
867+
"- `white-space: nowrap`\n",
868+
"\n",
869+
"Only CSS2 named colors and hex colors of the form `#rgb` or `#rrggbb` are currently supported."
870+
]
871+
},
872+
{
873+
"cell_type": "code",
874+
"execution_count": null,
875+
"metadata": {},
876+
"outputs": [],
877+
"source": [
878+
"df.style.\\\n",
879+
" applymap(color_negative_red).\\\n",
880+
" apply(highlight_max).\\\n",
881+
" to_excel('styled.xlsx', engine='openpyxl')"
882+
]
883+
},
884+
{
885+
"cell_type": "markdown",
886+
"metadata": {},
887+
"source": [
888+
"A screenshot of the output:\n",
889+
"\n",
890+
"![Excel spreadsheet with styled DataFrame](_static/style-excel.png)\n"
891+
]
892+
},
857893
{
858894
"cell_type": "markdown",
859895
"metadata": {},
@@ -1039,8 +1075,7 @@
10391075
"mimetype": "text/x-python",
10401076
"name": "python",
10411077
"nbconvert_exporter": "python",
1042-
"pygments_lexer": "ipython3",
1043-
"version": "3.6.1"
1078+
"pygments_lexer": "ipython3"
10441079
}
10451080
},
10461081
"nbformat": 4,

doc/source/whatsnew/v0.20.0.txt

+34
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Highlights include:
1717
- Improved user API when accessing levels in ``.groupby()``, see :ref:`here <whatsnew_0200.enhancements.groupby_access>`
1818
- Improved support for ``UInt64`` dtypes, see :ref:`here <whatsnew_0200.enhancements.uint64_support>`
1919
- A new orient for JSON serialization, ``orient='table'``, that uses the :ref:`Table Schema spec <whatsnew_0200.enhancements.table_schema>`
20+
- Experimental support for exporting ``DataFrame.style`` formats to Excel , see :ref:`here <whatsnew_0200.enhancements.style_excel>`
2021
- Window Binary Corr/Cov operations now return a MultiIndexed ``DataFrame`` rather than a ``Panel``, as ``Panel`` is now deprecated, see :ref:`here <whatsnew_0200.api_breaking.rolling_pairwise>`
2122
- Support for S3 handling now uses ``s3fs``, see :ref:`here <whatsnew_0200.api_breaking.s3>`
2223
- Google BigQuery support now uses the ``pandas-gbq`` library, see :ref:`here <whatsnew_0200.api_breaking.gbq>`
@@ -398,6 +399,39 @@ To convert a ``SparseDataFrame`` back to sparse SciPy matrix in COO format, you
398399

399400
sdf.to_coo()
400401

402+
.. _whatsnew_0200.enhancements.style_excel:
403+
404+
Excel output for styled DataFrames
405+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
406+
407+
Experimental support has been added to export ``DataFrame.style`` formats to Excel using the ``openpyxl`` engine. (:issue:`15530`)
408+
409+
For example, after running the following, ``styled.xlsx`` renders as below:
410+
411+
.. ipython:: python
412+
413+
np.random.seed(24)
414+
df = pd.DataFrame({'A': np.linspace(1, 10, 10)})
415+
df = pd.concat([df, pd.DataFrame(np.random.RandomState(24).randn(10, 4),
416+
columns=list('BCDE'))],
417+
axis=1)
418+
df.iloc[0, 2] = np.nan
419+
df
420+
styled = df.style.\
421+
applymap(lambda val: 'color: %s' % 'red' if val < 0 else 'black').\
422+
apply(lambda s: ['background-color: yellow' if v else ''
423+
for v in s == s.max()])
424+
styled.to_excel('styled.xlsx', engine='openpyxl')
425+
426+
.. image:: _static/style-excel.png
427+
428+
.. ipython:: python
429+
:suppress:
430+
import os
431+
os.remove('styled.xlsx')
432+
433+
See the :ref:`Style documentation <style>` for more detail.
434+
401435
.. _whatsnew_0200.enhancements.intervalindex:
402436

403437
IntervalIndex

pandas/core/frame.py

+11-22
Original file line numberDiff line numberDiff line change
@@ -1419,28 +1419,17 @@ def to_excel(self, excel_writer, sheet_name='Sheet1', na_rep='',
14191419
index_label=None, startrow=0, startcol=0, engine=None,
14201420
merge_cells=True, encoding=None, inf_rep='inf', verbose=True,
14211421
freeze_panes=None):
1422-
from pandas.io.excel import ExcelWriter
1423-
need_save = False
1424-
if encoding is None:
1425-
encoding = 'ascii'
1426-
1427-
if isinstance(excel_writer, compat.string_types):
1428-
excel_writer = ExcelWriter(excel_writer, engine=engine)
1429-
need_save = True
1430-
1431-
formatter = fmt.ExcelFormatter(self, na_rep=na_rep, cols=columns,
1432-
header=header,
1433-
float_format=float_format, index=index,
1434-
index_label=index_label,
1435-
merge_cells=merge_cells,
1436-
inf_rep=inf_rep)
1437-
1438-
formatted_cells = formatter.get_formatted_cells()
1439-
excel_writer.write_cells(formatted_cells, sheet_name,
1440-
startrow=startrow, startcol=startcol,
1441-
freeze_panes=freeze_panes)
1442-
if need_save:
1443-
excel_writer.save()
1422+
1423+
from pandas.io.formats.excel import ExcelFormatter
1424+
formatter = ExcelFormatter(self, na_rep=na_rep, cols=columns,
1425+
header=header,
1426+
float_format=float_format, index=index,
1427+
index_label=index_label,
1428+
merge_cells=merge_cells,
1429+
inf_rep=inf_rep)
1430+
formatter.write(excel_writer, sheet_name=sheet_name, startrow=startrow,
1431+
startcol=startcol, freeze_panes=freeze_panes,
1432+
engine=engine)
14441433

14451434
def to_stata(self, fname, convert_dates=None, write_index=True,
14461435
encoding="latin-1", byteorder=None, time_stamp=None,

pandas/io/formats/common.py

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Common helper methods used in different submodules of pandas.io.formats
4+
"""
5+
6+
7+
def get_level_lengths(levels, sentinel=''):
8+
"""For each index in each level the function returns lengths of indexes.
9+
10+
Parameters
11+
----------
12+
levels : list of lists
13+
List of values on for level.
14+
sentinel : string, optional
15+
Value which states that no new index starts on there.
16+
17+
Returns
18+
----------
19+
Returns list of maps. For each level returns map of indexes (key is index
20+
in row and value is length of index).
21+
"""
22+
if len(levels) == 0:
23+
return []
24+
25+
control = [True for x in levels[0]]
26+
27+
result = []
28+
for level in levels:
29+
last_index = 0
30+
31+
lengths = {}
32+
for i, key in enumerate(level):
33+
if control[i] and key == sentinel:
34+
pass
35+
else:
36+
control[i] = False
37+
lengths[last_index] = i - last_index
38+
last_index = i
39+
40+
lengths[last_index] = len(level) - last_index
41+
42+
result.append(lengths)
43+
44+
return result

0 commit comments

Comments
 (0)