Skip to content

Commit

Permalink
ENH: implement excel table (pandas-dev#24862)
Browse files Browse the repository at this point in the history
  • Loading branch information
tdamsma committed Jan 24, 2019
1 parent 2fa0835 commit 32f10e5
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 11 deletions.
4 changes: 2 additions & 2 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2103,7 +2103,7 @@ def to_excel(self, excel_writer, sheet_name="Sheet1", na_rep="",
float_format=None, columns=None, header=True, index=True,
index_label=None, startrow=0, startcol=0, engine=None,
merge_cells=True, encoding=None, inf_rep="inf", verbose=True,
freeze_panes=None):
freeze_panes=None, as_table=False):
df = self if isinstance(self, ABCDataFrame) else self.to_frame()

from pandas.io.formats.excel import ExcelFormatter
Expand All @@ -2115,7 +2115,7 @@ def to_excel(self, excel_writer, sheet_name="Sheet1", na_rep="",
inf_rep=inf_rep)
formatter.write(excel_writer, sheet_name=sheet_name, startrow=startrow,
startcol=startcol, freeze_panes=freeze_panes,
engine=engine)
engine=engine, as_table=as_table)

def to_json(self, path_or_buf=None, orient=None, date_format=None,
double_precision=10, force_ascii=True, date_unit='ms',
Expand Down
109 changes: 107 additions & 2 deletions pandas/io/excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -1579,6 +1579,14 @@ def _convert_to_protection(cls, protection_dict):

return Protection(**protection_dict)

@classmethod
def _to_excel_range(cls, startrow, startcol, endrow, endcol):
"""Convert (zero based) numeric coordinates to excel range."""
from openpyxl.utils.cell import get_column_letter
return (f"{get_column_letter(startcol+1)}{startrow+1}"
":"
f"{get_column_letter(endcol+1)}{endrow+1}")

def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
freeze_panes=None):
# Write the frame cells using openpyxl.
Expand Down Expand Up @@ -1621,10 +1629,10 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
if cell.mergestart is not None and cell.mergeend is not None:

wks.merge_cells(
start_row=startrow + cell.row + 1,
startrow=startrow + cell.row + 1,
start_column=startcol + cell.col + 1,
end_column=startcol + cell.mergeend + 1,
end_row=startrow + cell.mergestart + 1
endrow=startrow + cell.mergestart + 1
)

# When cells are merged only the top-left cell is preserved
Expand All @@ -1645,6 +1653,65 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
for k, v in style_kwargs.items():
setattr(xcell, k, v)

def write_table(self, cells, sheet_name=None, startrow=0, startcol=0,
freeze_panes=None, header=True):
# Write the frame to an excel table using openpyxl.

from openpyxl.worksheet.table import Table, TableStyleInfo
sheet_name = self._get_sheet_name(sheet_name)

_style_cache = {}

if sheet_name in self.sheets:
wks = self.sheets[sheet_name]
else:
wks = self.book.create_sheet()
wks.title = sheet_name
self.sheets[sheet_name] = wks

if _validate_freeze_panes(freeze_panes):
wks.freeze_panes = wks.cell(row=freeze_panes[0] + 1,
column=freeze_panes[1] + 1)

header_rows = 1 if header > 0 else 0

n_cols = 0
n_rows = 0
header_cells = {}
for cell in cells:
val, fmt = self._value_with_fmt(cell.val)
if header and cell.row == 0 and cell.val:
header_cells[cell.col] = cell.val
continue
xcell = wks.cell(
row=startrow + cell.row + 1,
column=startcol + cell.col + 1,
value=val,
)
n_cols = max(n_cols, cell.col)
n_rows = max(n_rows, cell.row)

# add generic name for every unnamed (index) column that is included
[wks.cell(
row=startrow + 1,
column=startcol + col + 1,
value=(str(header_cells[col])
if col in header_cells
else f'Column{col + 1}'),
) for col in range(n_cols + 1)]

ref = self._to_excel_range(startrow, startcol, startrow + n_rows,
startcol + n_cols)
tab = Table(displayName="Table1", ref=ref, headerRowCount=header_rows)

# Add a default style with striped rows
style = TableStyleInfo(
name="TableStyleMedium9", showFirstColumn=False,
showLastColumn=False, showRowStripes=True, showColumnStripes=False)
tab.tableStyleInfo = style

wks.add_table(tab)


register_writer(_OpenpyxlWriter)

Expand Down Expand Up @@ -1992,5 +2059,43 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
startcol + cell.col,
val, style)

def write_table(self, cells, sheet_name=None, startrow=0, startcol=0,
freeze_panes=None, header=True):
# Write the frame to an excel table using xlsxwriter.
sheet_name = self._get_sheet_name(sheet_name)

if sheet_name in self.sheets:
wks = self.sheets[sheet_name]
else:
wks = self.book.add_worksheet(sheet_name)
self.sheets[sheet_name] = wks

if _validate_freeze_panes(freeze_panes):
wks.freeze_panes(*(freeze_panes))

n_cols = 0
n_rows = 0
header_cells = {}
for cell in cells:
val, fmt = self._value_with_fmt(cell.val)
if header and cell.row == 0 and cell.val:
header_cells[cell.col] = cell.val
continue
wks.write(startrow + cell.row,
startcol + cell.col,
val)
n_cols = max(n_cols, cell.col)
n_rows = max(n_rows, cell.row)

# add generic name for every unnamed (index) column that is included
columns = [{'header': str(header_cells[col])
if col in header_cells else f'Column{col + 1}'}
for col in range(n_cols + 1)]

options = {'columns': columns}

wks.add_table(startrow, startcol, startrow + n_rows,
startcol + n_cols, options)


register_writer(_XlsxWriter)
24 changes: 17 additions & 7 deletions pandas/io/formats/excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,14 +622,18 @@ def _generate_body(self, coloffset):
yield ExcelCell(self.rowcounter + i, colidx + coloffset, val,
xlstyle)

def get_formatted_cells(self):
for cell in itertools.chain(self._format_header(),
self._format_body()):
def get_formatted_cells(self, include_header=True):
if include_header:
cells = itertools.chain(self._format_header(),
self._format_body())
else:
cells = self._format_body()
for cell in cells:
cell.val = self._format_value(cell.val)
yield cell

def write(self, writer, sheet_name='Sheet1', startrow=0,
startcol=0, freeze_panes=None, engine=None):
startcol=0, freeze_panes=None, engine=None, as_table=False):
"""
writer : string or ExcelWriter object
File path or existing ExcelWriter
Expand Down Expand Up @@ -657,8 +661,14 @@ def write(self, writer, sheet_name='Sheet1', startrow=0,
need_save = True

formatted_cells = self.get_formatted_cells()
writer.write_cells(formatted_cells, sheet_name,
startrow=startrow, startcol=startcol,
freeze_panes=freeze_panes)
if as_table:
writer.write_table(formatted_cells, sheet_name,
startrow=startrow, startcol=startcol,
freeze_panes=freeze_panes,
header=self.header)
else:
writer.write_cells(formatted_cells, sheet_name,
startrow=startrow, startcol=startcol,
freeze_panes=freeze_panes)
if need_save:
writer.save()

0 comments on commit 32f10e5

Please sign in to comment.