Skip to content

Commit 9b5d848

Browse files
jeffcareyjreback
authored andcommitted
ENH: Added ability to freeze panes from DataFrame.to_excel() (pandas-dev#15160)
closes pandas-dev#15160 Author: Jeff Carey <[email protected]> Closes pandas-dev#15291 from jeffcarey/enh-15160 and squashes the following commits: cef8fce [Jeff Carey] ENH: Added ability to freeze panes from DataFrame.to_excel()
1 parent c7300ea commit 9b5d848

File tree

6 files changed

+74
-12
lines changed

6 files changed

+74
-12
lines changed

doc/source/io.rst

+13
Original file line numberDiff line numberDiff line change
@@ -2777,6 +2777,7 @@ Added support for Openpyxl >= 2.2
27772777
``'xlsxwriter'`` will produce an Excel 2007-format workbook (xlsx). If
27782778
omitted, an Excel 2007-formatted workbook is produced.
27792779

2780+
27802781
.. _io.excel.writers:
27812782

27822783
Excel writer engines
@@ -2823,6 +2824,18 @@ argument to ``to_excel`` and to ``ExcelWriter``. The built-in engines are:
28232824
28242825
df.to_excel('path_to_file.xlsx', sheet_name='Sheet1')
28252826
2827+
.. _io.excel.style:
2828+
2829+
Style and Formatting
2830+
''''''''''''''''''''
2831+
2832+
The look and feel of Excel worksheets created from pandas can be modified using the following parameters on the ``DataFrame``'s ``to_excel`` method.
2833+
2834+
- ``float_format`` : Format string for floating point numbers (default None)
2835+
- ``freeze_panes`` : A tuple of two integers representing the bottommost row and rightmost column to freeze. Each of these parameters is one-based, so (1, 1) will
2836+
freeze the first row and first column (default None)
2837+
2838+
28262839
.. _io.clipboard:
28272840

28282841
Clipboard

doc/source/whatsnew/v0.20.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ Other enhancements
153153
- ``Series/DataFrame.resample.asfreq`` have gained a ``fill_value`` parameter, to fill missing values during resampling (:issue:`3715`).
154154
- ``pandas.tools.hashing`` has gained a ``hash_tuples`` routine, and ``hash_pandas_object`` has gained the ability to hash a ``MultiIndex`` (:issue:`15224`)
155155
- ``Series/DataFrame.squeeze()`` have gained the ``axis`` parameter. (:issue:`15339`)
156+
- ``DataFrame.to_excel()`` has a new ``freeze_panes`` parameter to turn on Freeze Panes when exporting to Excel (:issue:`15160`)
156157

157158
.. _ISO 8601 duration: https://en.wikipedia.org/wiki/ISO_8601#Durations
158159

pandas/core/frame.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -1390,7 +1390,8 @@ def to_csv(self, path_or_buf=None, sep=",", na_rep='', float_format=None,
13901390
def to_excel(self, excel_writer, sheet_name='Sheet1', na_rep='',
13911391
float_format=None, columns=None, header=True, index=True,
13921392
index_label=None, startrow=0, startcol=0, engine=None,
1393-
merge_cells=True, encoding=None, inf_rep='inf', verbose=True):
1393+
merge_cells=True, encoding=None, inf_rep='inf', verbose=True,
1394+
freeze_panes=None):
13941395
from pandas.io.excel import ExcelWriter
13951396
need_save = False
13961397
if encoding is None:
@@ -1406,12 +1407,26 @@ def to_excel(self, excel_writer, sheet_name='Sheet1', na_rep='',
14061407
index_label=index_label,
14071408
merge_cells=merge_cells,
14081409
inf_rep=inf_rep)
1410+
14091411
formatted_cells = formatter.get_formatted_cells()
1412+
freeze_panes = self._validate_freeze_panes(freeze_panes)
14101413
excel_writer.write_cells(formatted_cells, sheet_name,
1411-
startrow=startrow, startcol=startcol)
1414+
startrow=startrow, startcol=startcol,
1415+
freeze_panes=freeze_panes)
14121416
if need_save:
14131417
excel_writer.save()
14141418

1419+
def _validate_freeze_panes(self, freeze_panes):
1420+
if freeze_panes is not None:
1421+
if (
1422+
len(freeze_panes) == 2 and
1423+
all(isinstance(item, int) for item in freeze_panes)
1424+
):
1425+
return freeze_panes
1426+
1427+
raise ValueError("freeze_panes must be of form (row, column)"
1428+
" where row and column are integers")
1429+
14151430
def to_stata(self, fname, convert_dates=None, write_index=True,
14161431
encoding="latin-1", byteorder=None, time_stamp=None,
14171432
data_label=None, variable_labels=None):

pandas/core/generic.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -1033,7 +1033,7 @@ def __setstate__(self, state):
10331033
# I/O Methods
10341034

10351035
_shared_docs['to_excel'] = """
1036-
Write %(klass)s to a excel sheet
1036+
Write %(klass)s to an excel sheet
10371037
%(versionadded_to_excel)s
10381038
Parameters
10391039
----------
@@ -1072,6 +1072,11 @@ def __setstate__(self, state):
10721072
inf_rep : string, default 'inf'
10731073
Representation for infinity (there is no native representation for
10741074
infinity in Excel)
1075+
freeze_panes : tuple of integer (length 2), default None
1076+
Specifies the bottommost row and rightmost column that
1077+
is to be frozen
1078+
1079+
.. versionadded:: 0.20.0
10751080
10761081
Notes
10771082
-----

pandas/io/excel.py

+27-7
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,8 @@ def engine(self):
693693
pass
694694

695695
@abc.abstractmethod
696-
def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0):
696+
def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
697+
freeze_panes=None):
697698
"""
698699
Write given formated cells into Excel an excel sheet
699700
@@ -705,6 +706,8 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0):
705706
Name of Excel sheet, if None, then use self.cur_sheet
706707
startrow: upper left cell row to dump data frame
707708
startcol: upper left cell column to dump data frame
709+
freeze_panes: integer tuple of length 2
710+
contains the bottom-most row and right-most column to freeze
708711
"""
709712
pass
710713

@@ -804,7 +807,8 @@ def save(self):
804807
"""
805808
return self.book.save(self.path)
806809

807-
def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0):
810+
def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
811+
freeze_panes=None):
808812
# Write the frame cells using openpyxl.
809813
from openpyxl.cell import get_column_letter
810814

@@ -904,7 +908,8 @@ class _Openpyxl20Writer(_Openpyxl1Writer):
904908
engine = 'openpyxl20'
905909
openpyxl_majorver = 2
906910

907-
def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0):
911+
def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
912+
freeze_panes=None):
908913
# Write the frame cells using openpyxl.
909914
from openpyxl.cell import get_column_letter
910915

@@ -1311,7 +1316,8 @@ class _Openpyxl22Writer(_Openpyxl20Writer):
13111316
engine = 'openpyxl22'
13121317
openpyxl_majorver = 2
13131318

1314-
def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0):
1319+
def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
1320+
freeze_panes=None):
13151321
# Write the frame cells using openpyxl.
13161322
sheet_name = self._get_sheet_name(sheet_name)
13171323

@@ -1324,6 +1330,10 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0):
13241330
wks.title = sheet_name
13251331
self.sheets[sheet_name] = wks
13261332

1333+
if freeze_panes is not None:
1334+
wks.freeze_panes = wks.cell(row=freeze_panes[0] + 1,
1335+
column=freeze_panes[1] + 1)
1336+
13271337
for cell in cells:
13281338
xcell = wks.cell(
13291339
row=startrow + cell.row + 1,
@@ -1396,7 +1406,8 @@ def save(self):
13961406
"""
13971407
return self.book.save(self.path)
13981408

1399-
def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0):
1409+
def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
1410+
freeze_panes=None):
14001411
# Write the frame cells using xlwt.
14011412

14021413
sheet_name = self._get_sheet_name(sheet_name)
@@ -1407,6 +1418,11 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0):
14071418
wks = self.book.add_sheet(sheet_name)
14081419
self.sheets[sheet_name] = wks
14091420

1421+
if freeze_panes is not None:
1422+
wks.set_panes_frozen(True)
1423+
wks.set_horz_split_pos(freeze_panes[0])
1424+
wks.set_vert_split_pos(freeze_panes[1])
1425+
14101426
style_dict = {}
14111427

14121428
for cell in cells:
@@ -1518,11 +1534,12 @@ def save(self):
15181534
"""
15191535
Save workbook to disk.
15201536
"""
1537+
15211538
return self.book.close()
15221539

1523-
def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0):
1540+
def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
1541+
freeze_panes=None):
15241542
# Write the frame cells using xlsxwriter.
1525-
15261543
sheet_name = self._get_sheet_name(sheet_name)
15271544

15281545
if sheet_name in self.sheets:
@@ -1533,6 +1550,9 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0):
15331550

15341551
style_dict = {}
15351552

1553+
if freeze_panes is not None:
1554+
wks.freeze_panes(*(freeze_panes))
1555+
15361556
for cell in cells:
15371557
val = _conv_value(cell.val)
15381558

pandas/tests/io/test_excel.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -1836,6 +1836,14 @@ def test_true_and_false_value_options(self):
18361836
false_values=['bar'])
18371837
tm.assert_frame_equal(read_frame, expected)
18381838

1839+
def test_freeze_panes(self):
1840+
# GH15160
1841+
expected = DataFrame([[1, 2], [3, 4]], columns=['col1', 'col2'])
1842+
with ensure_clean(self.ext) as path:
1843+
expected.to_excel(path, "Sheet1", freeze_panes=(1, 1))
1844+
result = read_excel(path)
1845+
tm.assert_frame_equal(expected, result)
1846+
18391847

18401848
def raise_wrapper(major_ver):
18411849
def versioned_raise_wrapper(orig_method):
@@ -1873,7 +1881,7 @@ class OpenpyxlTests(ExcelWriterBase, tm.TestCase):
18731881
def test_to_excel_styleconverter(self):
18741882
_skip_if_no_openpyxl()
18751883
if not openpyxl_compat.is_compat(major_ver=1):
1876-
pytest.skip('incompatiable openpyxl version')
1884+
pytest.skip('incompatible openpyxl version')
18771885

18781886
import openpyxl
18791887

@@ -2095,7 +2103,7 @@ def test_to_excel_styleconverter(self):
20952103

20962104
def test_write_cells_merge_styled(self):
20972105
if not openpyxl_compat.is_compat(major_ver=2):
2098-
pytest.skip('incompatiable openpyxl version')
2106+
pytest.skip('incompatible openpyxl version')
20992107

21002108
from pandas.formats.format import ExcelCell
21012109

0 commit comments

Comments
 (0)