-
-
Notifications
You must be signed in to change notification settings - Fork 18.5k
[WIP] Excel table output #24899
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Excel table output #24899
Changes from all commits
32f10e5
5f9d664
4b857d4
8ad332f
ca9c36e
5032133
36301fb
f0e9583
c12a9f6
9636999
d0a6391
86d5499
be7033b
0b72d6e
4302231
91deb15
22739b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -409,7 +409,11 @@ def write_cells( | |
row=freeze_panes[0] + 1, column=freeze_panes[1] + 1 | ||
) | ||
|
||
n_cols = 0 | ||
n_rows = 0 | ||
for cell in cells: | ||
n_cols = max(n_cols, cell.col) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can these be inferred outside of the loop from the dimensions of the frame? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is quite some code translating a frame to excel cells, dealing with multiindexes etc. So this is not too straight forward. But that code in intertwined with the formatting code, so I considered the following options:
I picked the latter There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm OK. At least outside the loop though wouldn't the nrows be len(cells) and the ncols just be the length of any item within cells? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unfortunately the cells are a 1D iterator of items (each with a row and col property), not a list of rows. |
||
n_rows = max(n_rows, cell.row) | ||
xcell = wks.cell( | ||
row=startrow + cell.row + 1, column=startcol + cell.col + 1 | ||
) | ||
|
@@ -456,6 +460,38 @@ def write_cells( | |
for k, v in style_kwargs.items(): | ||
setattr(xcell, k, v) | ||
|
||
return wks, n_rows, n_cols, False | ||
|
||
def format_table( | ||
self, wks, table_name, table_range, header=True, index=True, first_row=None | ||
): | ||
# Format the written cells as table | ||
|
||
from openpyxl.worksheet.table import Table, TableStyleInfo | ||
from openpyxl.worksheet.cell_range import CellRange | ||
|
||
ref = str( | ||
CellRange( | ||
min_row=table_range[0] + 1, | ||
min_col=table_range[1] + 1, | ||
max_row=table_range[2] + 1, | ||
max_col=table_range[3] + 1, | ||
) | ||
) | ||
|
||
tab = Table(displayName=table_name, ref=ref, headerRowCount=1 if header else 0) | ||
|
||
# Add a default style with striped rows | ||
style = TableStyleInfo( | ||
name="TableStyleMedium9", | ||
showFirstColumn=index, | ||
showLastColumn=False, | ||
showRowStripes=True, | ||
showColumnStripes=False, | ||
) | ||
tab.tableStyleInfo = style | ||
wks.add_table(tab) | ||
|
||
|
||
class _OpenpyxlReader(_BaseExcelReader): | ||
def __init__(self, filepath_or_buffer: FilePathOrBuffer) -> None: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -373,6 +373,16 @@ def __init__( | |
merge_cells=False, | ||
inf_rep="inf", | ||
style_converter=None, | ||
header_style={ | ||
"font": {"bold": True}, | ||
"borders": { | ||
"top": "thin", | ||
"right": "thin", | ||
"bottom": "thin", | ||
"left": "thin", | ||
}, | ||
"alignment": {"horizontal": "center", "vertical": "top"}, | ||
}, | ||
): | ||
self.rowcounter = 0 | ||
self.na_rep = na_rep | ||
|
@@ -408,19 +418,7 @@ def __init__( | |
self.header = header | ||
self.merge_cells = merge_cells | ||
self.inf_rep = inf_rep | ||
|
||
@property | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did you change this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To provide an interface to override (i.e. disable) the cell styling |
||
def header_style(self): | ||
return { | ||
"font": {"bold": True}, | ||
"borders": { | ||
"top": "thin", | ||
"right": "thin", | ||
"bottom": "thin", | ||
"left": "thin", | ||
}, | ||
"alignment": {"horizontal": "center", "vertical": "top"}, | ||
} | ||
self.header_style = header_style | ||
|
||
def _format_value(self, val): | ||
if is_scalar(val) and missing.isna(val): | ||
|
@@ -695,6 +693,7 @@ def write( | |
startcol=0, | ||
freeze_panes=None, | ||
engine=None, | ||
table=None, | ||
): | ||
""" | ||
writer : string or ExcelWriter object | ||
|
@@ -712,6 +711,9 @@ def write( | |
write engine to use if writer is a path - you can also set this | ||
via the options ``io.excel.xlsx.writer``, ``io.excel.xls.writer``, | ||
and ``io.excel.xlsm.writer``. | ||
table : string, default None | ||
Write the dataframe to a named and formatted excel table object | ||
|
||
""" | ||
from pandas.io.excel import ExcelWriter | ||
from pandas.io.common import _stringify_path | ||
|
@@ -730,13 +732,27 @@ def write( | |
writer = ExcelWriter(_stringify_path(writer), engine=engine) | ||
need_save = True | ||
|
||
if table is not None: | ||
self.header_style = {} | ||
formatted_cells = self.get_formatted_cells() | ||
writer.write_cells( | ||
|
||
worksheet, n_rows, n_cols, first_row = writer.write_cells( | ||
formatted_cells, | ||
sheet_name, | ||
startrow=startrow, | ||
startcol=startcol, | ||
freeze_panes=freeze_panes, | ||
) | ||
|
||
if table is not None: | ||
table_range = (startrow, startcol, startrow + n_rows, startcol + n_cols) | ||
writer.format_table( | ||
worksheet, | ||
table_name=table, | ||
table_range=table_range, | ||
first_row=first_row, | ||
header=self.header, | ||
index=self.index, | ||
) | ||
if need_save: | ||
writer.save() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would accepting a TableStyle be an option instead (speaking only in openpyxl terms, not sure if xlsxwriter offers that)? I feel like that could do the same thing but also give users more power over output formatting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both accept a style, but of course the syntax is a bit different: openpyxl uses
"TableStyleMedium9"
as default, and xlsxwriter uses"Table Style Medium 9"
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm wasn't thinking about that as much as having the user pass in an actual object itself
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean something like?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right that's what I had in mind (not tied to it just asking)