From 1b4972ac0942d66197e0d2b30de6eda5575f68d4 Mon Sep 17 00:00:00 2001 From: John McNamara Date: Tue, 3 Sep 2013 23:45:59 +0100 Subject: [PATCH] ENH: Added xlsxwriter as an ExcelWriter option. Refactored pandas.io.excel.ExcelWriter to allow other writer engines and added xlsxwriter as an option. GitHub issue #4542. --- ci/print_versions.py | 6 + ci/requirements-2.6.txt | 1 + ci/requirements-2.7.txt | 1 + ci/requirements-2.7_LOCALE.txt | 1 + ci/requirements-3.2.txt | 1 + ci/requirements-3.3.txt | 1 + doc/source/10min.rst | 4 +- doc/source/io.rst | 6 +- pandas/core/frame.py | 14 +- pandas/core/panel.py | 5 +- pandas/io/excel.py | 177 ++++++++++++++--- pandas/io/tests/data/xw_frame01.xlsx | Bin 0 -> 7064 bytes pandas/io/tests/data/xw_frame02.xlsx | Bin 0 -> 7483 bytes pandas/io/tests/data/xw_frame03.xlsx | Bin 0 -> 7516 bytes pandas/io/tests/data/xw_frame04.xlsx | Bin 0 -> 7053 bytes pandas/io/tests/data/xw_frame05.xlsx | Bin 0 -> 7067 bytes pandas/io/tests/data/xw_frame06.xlsx | Bin 0 -> 7532 bytes pandas/io/tests/data/xw_frame07.xlsx | Bin 0 -> 7066 bytes pandas/io/tests/test_excel.py | 150 +++++++++++++- pandas/io/tests/test_xlsxwriter_frame01.py | 144 ++++++++++++++ pandas/io/tests/test_xlsxwriter_frame02.py | 142 +++++++++++++ pandas/io/tests/test_xlsxwriter_frame03.py | 140 +++++++++++++ pandas/io/tests/test_xlsxwriter_frame04.py | 145 ++++++++++++++ pandas/io/tests/test_xlsxwriter_frame05.py | 148 ++++++++++++++ pandas/io/tests/test_xlsxwriter_frame06.py | 144 ++++++++++++++ pandas/io/tests/test_xlsxwriter_frame07.py | 152 ++++++++++++++ pandas/io/tests/xlsxwriter_test_helper.py | 220 +++++++++++++++++++++ pandas/tests/test_panel.py | 20 ++ 28 files changed, 1582 insertions(+), 40 deletions(-) create mode 100644 pandas/io/tests/data/xw_frame01.xlsx create mode 100644 pandas/io/tests/data/xw_frame02.xlsx create mode 100644 pandas/io/tests/data/xw_frame03.xlsx create mode 100644 pandas/io/tests/data/xw_frame04.xlsx create mode 100644 pandas/io/tests/data/xw_frame05.xlsx create mode 100644 pandas/io/tests/data/xw_frame06.xlsx create mode 100644 pandas/io/tests/data/xw_frame07.xlsx create mode 100644 pandas/io/tests/test_xlsxwriter_frame01.py create mode 100644 pandas/io/tests/test_xlsxwriter_frame02.py create mode 100644 pandas/io/tests/test_xlsxwriter_frame03.py create mode 100644 pandas/io/tests/test_xlsxwriter_frame04.py create mode 100644 pandas/io/tests/test_xlsxwriter_frame05.py create mode 100644 pandas/io/tests/test_xlsxwriter_frame06.py create mode 100644 pandas/io/tests/test_xlsxwriter_frame07.py create mode 100644 pandas/io/tests/xlsxwriter_test_helper.py diff --git a/ci/print_versions.py b/ci/print_versions.py index 0df8bb7e28786..a805a43ac256e 100755 --- a/ci/print_versions.py +++ b/ci/print_versions.py @@ -92,6 +92,12 @@ except: print("openpyxl: Not installed") +try: + import xlsxwriter + print("xlsxwriter: %s" % xlsxwriter.__version__) +except: + print("xlwt: Not installed") + try: import xlrd print("xlrd: %s" % xlrd.__VERSION__) diff --git a/ci/requirements-2.6.txt b/ci/requirements-2.6.txt index 5038b9e2b6552..b92ccd2754661 100644 --- a/ci/requirements-2.6.txt +++ b/ci/requirements-2.6.txt @@ -4,3 +4,4 @@ python-dateutil==1.5 pytz==2013b http://www.crummy.com/software/BeautifulSoup/bs4/download/4.2/beautifulsoup4-4.2.0.tar.gz html5lib==1.0b2 +xlsxwriter==0.4.2 diff --git a/ci/requirements-2.7.txt b/ci/requirements-2.7.txt index 6a94d48ad7a5f..d35931257d4d3 100644 --- a/ci/requirements-2.7.txt +++ b/ci/requirements-2.7.txt @@ -8,6 +8,7 @@ numexpr==2.1 tables==2.3.1 matplotlib==1.1.1 openpyxl==1.6.2 +xlsxwriter==0.4.2 xlrd==0.9.2 patsy==0.1.0 html5lib==1.0b2 diff --git a/ci/requirements-2.7_LOCALE.txt b/ci/requirements-2.7_LOCALE.txt index 70c398816f23c..e09726b6d93d7 100644 --- a/ci/requirements-2.7_LOCALE.txt +++ b/ci/requirements-2.7_LOCALE.txt @@ -2,6 +2,7 @@ python-dateutil pytz==2013b xlwt==0.7.5 openpyxl==1.6.2 +xlsxwriter==0.4.2 xlrd==0.9.2 numpy==1.6.1 cython==0.19.1 diff --git a/ci/requirements-3.2.txt b/ci/requirements-3.2.txt index e907a2fa828f1..88ba3f4edf723 100644 --- a/ci/requirements-3.2.txt +++ b/ci/requirements-3.2.txt @@ -1,6 +1,7 @@ python-dateutil==2.1 pytz==2013b openpyxl==1.6.2 +xlsxwriter==0.4.2 xlrd==0.9.2 numpy==1.6.2 cython==0.19.1 diff --git a/ci/requirements-3.3.txt b/ci/requirements-3.3.txt index eb1e725d98040..5ff99cdb1f627 100644 --- a/ci/requirements-3.3.txt +++ b/ci/requirements-3.3.txt @@ -1,6 +1,7 @@ python-dateutil==2.1 pytz==2013b openpyxl==1.6.2 +xlsxwriter==0.4.2 xlrd==0.9.2 html5lib==1.0b2 numpy==1.7.1 diff --git a/doc/source/10min.rst b/doc/source/10min.rst index 96f9fd912b664..325573a44409f 100644 --- a/doc/source/10min.rst +++ b/doc/source/10min.rst @@ -695,13 +695,13 @@ Writing to an excel file .. ipython:: python - df.to_excel('foo.xlsx', sheet_name='sheet1') + df.to_excel('foo.xlsx', sheet_name='Sheet1') Reading from an excel file .. ipython:: python - pd.read_excel('foo.xlsx', 'sheet1', index_col=None, na_values=['NA']) + pd.read_excel('foo.xlsx', 'Sheet1', index_col=None, na_values=['NA']) .. ipython:: python :suppress: diff --git a/doc/source/io.rst b/doc/source/io.rst index 67cbe35144461..7de37bc4c8a7d 100644 --- a/doc/source/io.rst +++ b/doc/source/io.rst @@ -1667,7 +1667,7 @@ written. For example: .. code-block:: python - df.to_excel('path_to_file.xlsx', sheet_name='sheet1') + df.to_excel('path_to_file.xlsx', sheet_name='Sheet1') Files with a ``.xls`` extension will be written using ``xlwt`` and those with a ``.xlsx`` extension will be written using ``openpyxl``. @@ -1680,8 +1680,8 @@ one can use the ExcelWriter class, as in the following example: .. code-block:: python writer = ExcelWriter('path_to_file.xlsx') - df1.to_excel(writer, sheet_name='sheet1') - df2.to_excel(writer, sheet_name='sheet2') + df1.to_excel(writer, sheet_name='Sheet1') + df2.to_excel(writer, sheet_name='Sheet2') writer.save() .. _io.hdf5: diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 4e9f28122b43d..cf70bce12c403 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -1356,9 +1356,10 @@ def to_csv(self, path_or_buf, sep=",", na_rep='', float_format=None, tupleize_cols=tupleize_cols) formatter.save() - def to_excel(self, excel_writer, sheet_name='sheet1', na_rep='', + def to_excel(self, excel_writer, sheet_name='Sheet1', na_rep='', float_format=None, cols=None, header=True, index=True, - index_label=None, startrow=0, startcol=0): + index_label=None, startrow=0, startcol=0, + engine=None): """ Write DataFrame to a excel sheet @@ -1366,7 +1367,7 @@ def to_excel(self, excel_writer, sheet_name='sheet1', na_rep='', ---------- excel_writer : string or ExcelWriter object File path or existing ExcelWriter - sheet_name : string, default 'sheet1' + sheet_name : string, default 'Sheet1' Name of sheet which will contain DataFrame na_rep : string, default '' Missing data representation @@ -1385,6 +1386,7 @@ def to_excel(self, excel_writer, sheet_name='sheet1', na_rep='', sequence should be given if the DataFrame uses MultiIndex. startow : upper left cell row to dump data frame startcol : upper left cell column to dump data frame + engine : Excel writer class Notes @@ -1393,14 +1395,14 @@ def to_excel(self, excel_writer, sheet_name='sheet1', na_rep='', to the existing workbook. This can be used to save different DataFrames to one workbook >>> writer = ExcelWriter('output.xlsx') - >>> df1.to_excel(writer,'sheet1') - >>> df2.to_excel(writer,'sheet2') + >>> df1.to_excel(writer,'Sheet1') + >>> df2.to_excel(writer,'Sheet2') >>> writer.save() """ from pandas.io.excel import ExcelWriter need_save = False if isinstance(excel_writer, compat.string_types): - excel_writer = ExcelWriter(excel_writer) + excel_writer = ExcelWriter(excel_writer, engine) need_save = True formatter = fmt.ExcelFormatter(self, diff --git a/pandas/core/panel.py b/pandas/core/panel.py index bca6f985ac689..3b1dfdf9c08d8 100644 --- a/pandas/core/panel.py +++ b/pandas/core/panel.py @@ -458,7 +458,7 @@ def to_sparse(self, fill_value=None, kind='block'): default_kind=kind, default_fill_value=fill_value) - def to_excel(self, path, na_rep=''): + def to_excel(self, path, na_rep='', engine=None): """ Write each DataFrame in Panel to a separate excel sheet @@ -468,9 +468,10 @@ def to_excel(self, path, na_rep=''): File path or existing ExcelWriter na_rep : string, default '' Missing data representation + engine : string, Excel writer class """ from pandas.io.excel import ExcelWriter - writer = ExcelWriter(path) + writer = ExcelWriter(path, engine=engine) for item, df in compat.iteritems(self): name = str(item) df.to_excel(writer, name, na_rep=na_rep) diff --git a/pandas/io/excel.py b/pandas/io/excel.py index 5ff42c5cd12a6..ad7c0b081b9b5 100644 --- a/pandas/io/excel.py +++ b/pandas/io/excel.py @@ -12,9 +12,20 @@ from pandas.tseries.period import Period from pandas import json from pandas.compat import map, zip, reduce, range, lrange +from pandas.core import config import pandas.compat as compat from warnings import warn +# Set up the io.excel specific configuration. +writer_engine_doc = """ +: string + The default Excel engine. The options are 'openpyxl' (the default), 'xlwt' + and 'xlsxwriter'. +""" +with config.config_prefix('io.excel'): + config.register_option('writer_engine', None, writer_engine_doc, + validator=str) + def read_excel(path_or_buf, sheetname, **kwds): """Read an Excel table into a pandas DataFrame @@ -256,7 +267,7 @@ def to_xls(style_dict, num_format_str=None): import xlwt def style_to_xlwt(item, firstlevel=True, field_sep=',', line_sep=';'): - """helper wich recursively generate an xlwt easy style string + """helper which recursively generate an xlwt easy style string for example: hstyle = {"font": {"bold": True}, @@ -318,6 +329,37 @@ def to_xlsx(style_dict): return xls_style + @staticmethod + def to_xlsxwriter(workbook, style_dict, num_format_str=None): + """ + Converts a style_dict to an XlxsWriter format object. + Parameters + ---------- + workbook: Reference to the ExcelWriter XlxsWriter workbook. + style_dict: Style dictionary to convert. + num_format: Optional number format for the cell format. + """ + if style_dict is None: + return None + + # Create a XlsxWriter format object. + xl_format = workbook.add_format() + + # Map the cell font to XlsxWriter font properties. + if style_dict.get('font'): + font = style_dict['font'] + if font.get('bold'): + xl_format.set_bold() + + # Map the cell borders to XlsxWriter border properties. + if style_dict.get('borders'): + xl_format.set_border() + + if num_format_str is not None: + xl_format.set_num_format(num_format_str) + + return xl_format + def _conv_value(val): # convert value for excel dump @@ -341,22 +383,24 @@ class ExcelWriter(object): path : string Path to xls file """ - def __init__(self, path): - self.use_xlsx = True - if path.endswith('.xls'): - self.use_xlsx = False - import xlwt - self.book = xlwt.Workbook() - self.fm_datetime = xlwt.easyxf( - num_format_str='YYYY-MM-DD HH:MM:SS') - self.fm_date = xlwt.easyxf(num_format_str='YYYY-MM-DD') - else: - from openpyxl.workbook import Workbook - self.book = Workbook() # optimized_write=True) - # open pyxl 1.6.1 adds a dummy sheet remove it - if self.book.worksheets: - self.book.remove_sheet(self.book.worksheets[0]) - self.path = path + def __init__(self, path, engine=None, **engine_kwargs): + + if engine is None: + default = config.get_option('io.excel.writer_engine') + if default is not None: + engine = default + elif path.endswith('.xls'): + engine = 'xlwt' + else: + engine = 'openpyxl' + + try: + writer_init = getattr(self, "_init_%s" % engine) + except AttributeError: + raise ValueError("No engine: %s" % engine) + + writer_init(path, **engine_kwargs) + self.sheets = {} self.cur_sheet = None @@ -364,7 +408,10 @@ def save(self): """ Save workbook to disk """ - self.book.save(self.path) + if self.engine == 'xlsxwriter': + self.book.close() + else: + self.book.save(self.path) def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0): """ @@ -381,16 +428,20 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0): """ if sheet_name is None: sheet_name = self.cur_sheet + if sheet_name is None: # pragma: no cover raise ValueError('Must pass explicit sheet_name or set ' - 'cur_sheet property') - if self.use_xlsx: - self._writecells_xlsx(cells, sheet_name, startrow, startcol) - else: - self._writecells_xls(cells, sheet_name, startrow, startcol) + 'cur_sheet property') - def _writecells_xlsx(self, cells, sheet_name, startrow, startcol): + try: + _writecells = getattr(self, "_writecells_%s" % self.engine) + except AttributeError: + raise ValueError("No _writecells_%s() method" % self.engine) + _writecells(cells, sheet_name, startrow, startcol) + + def _writecells_openpyxl(self, cells, sheet_name, startrow, startcol): + # Write the frame cells using openpyxl. from openpyxl.cell import get_column_letter if sheet_name in self.sheets: @@ -426,7 +477,8 @@ def _writecells_xlsx(self, cells, sheet_name, startrow, startcol): cletterend, startrow + cell.mergestart + 1)) - def _writecells_xls(self, cells, sheet_name, startrow, startcol): + def _writecells_xlwt(self, cells, sheet_name, startrow, startcol): + # Write the frame cells using xlwt. if sheet_name in self.sheets: wks = self.sheets[sheet_name] else: @@ -464,3 +516,78 @@ def _writecells_xls(self, cells, sheet_name, startrow, startcol): wks.write(startrow + cell.row, startcol + cell.col, val, style) + + def _writecells_xlsxwriter(self, cells, sheet_name, startrow, startcol): + # Write the frame cells using xlsxwriter. + if sheet_name in self.sheets: + wks = self.sheets[sheet_name] + else: + wks = self.book.add_worksheet(sheet_name) + self.sheets[sheet_name] = wks + + style_dict = {} + + for cell in cells: + val = _conv_value(cell.val) + + num_format_str = None + if isinstance(cell.val, datetime.datetime): + num_format_str = "YYYY-MM-DD HH:MM:SS" + if isinstance(cell.val, datetime.date): + num_format_str = "YYYY-MM-DD" + + stylekey = json.dumps(cell.style) + if num_format_str: + stylekey += num_format_str + + if stylekey in style_dict: + style = style_dict[stylekey] + else: + style = CellStyleConverter.to_xlsxwriter(self.book, + cell.style, + num_format_str) + style_dict[stylekey] = style + + if cell.mergestart is not None and cell.mergeend is not None: + wks.merge_range(startrow + cell.row, + startrow + cell.mergestart, + startcol + cell.col, + startcol + cell.mergeend, + val, style) + else: + wks.write(startrow + cell.row, + startcol + cell.col, + val, style) + + def _init_xlwt(self, filename, **engine_kwargs): + # Use the xlwt module as the Excel writer. + import xlwt + + self.engine = 'xlwt' + self.path = filename + self.book = xlwt.Workbook() + self.fm_datetime = xlwt.easyxf(num_format_str='YYYY-MM-DD HH:MM:SS') + self.fm_date = xlwt.easyxf(num_format_str='YYYY-MM-DD') + + def _init_openpyxl(self, filename, **engine_kwargs): + # Use the openpyxl module as the Excel writer. + from openpyxl.workbook import Workbook + + self.engine = 'openpyxl' + self.path = filename + # Create workbook object with default optimized_write=True. + self.book = Workbook() + # Openpyxl 1.6.1 adds a dummy sheet. We remove it. + if self.book.worksheets: + self.book.remove_sheet(self.book.worksheets[0]) + + def _init_xlsxwriter(self, filename, **engine_kwargs): + # Use the xlsxwriter module as the Excel writer. + import xlsxwriter + + options = dict(engine_kwargs) + + options.setdefault('default_date_format', 'YYYY-MM-DD HH:MM:SS') + + self.engine = 'xlsxwriter' + self.book = xlsxwriter.Workbook(filename, options) diff --git a/pandas/io/tests/data/xw_frame01.xlsx b/pandas/io/tests/data/xw_frame01.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..afc3cbc0eee09b8cf101987a5b3463565ee782c0 GIT binary patch literal 7064 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 literal 0 HcmV?d00001 diff --git a/pandas/io/tests/data/xw_frame02.xlsx b/pandas/io/tests/data/xw_frame02.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0c4c70210b66543037d42066b20bc2dd641de1ef GIT binary patch literal 7483 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 U=&0QV0B}&3FDgzKQhtB?50tdfEdT%j literal 0 HcmV?d00001 diff --git a/pandas/io/tests/data/xw_frame03.xlsx b/pandas/io/tests/data/xw_frame03.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..46c1d437ae3d5b800a594f1264c2f6732c8004e4 GIT binary patch literal 7516 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 literal 0 HcmV?d00001 diff --git a/pandas/io/tests/data/xw_frame04.xlsx b/pandas/io/tests/data/xw_frame04.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c817d3f7bd80313906289033da6ee1c506e8ed3a GIT binary patch literal 7053 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 b0{_o$rYwg9_YDAm2ERn%Syte|_h0`5FRLIr literal 0 HcmV?d00001 diff --git a/pandas/io/tests/data/xw_frame05.xlsx b/pandas/io/tests/data/xw_frame05.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..7e18d7282df8e0ec450fd9b95b63f7948aa98255 GIT binary patch literal 7067 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 literal 0 HcmV?d00001 diff --git a/pandas/io/tests/data/xw_frame07.xlsx b/pandas/io/tests/data/xw_frame07.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..1ef363edd887ddd90843c9822eabb7caaa6ec87d GIT binary patch literal 7066 zcmeHMg;!PE_CANBqzKZ|T~g9gk_v|wLAp!o9J-P6&`NigfYP0Eq@+t)Qc}9(h`gis z-JkdJeE-1v-L=QqYwWT2H^&}pesj$^zojA%x$dH<8=Q#@?A70kk$_@qt0JWYrT{9z)>{Gw%LU2qoql~xn{Uv zKxR(4k=EfvhnjQmT_VD8hx_9FuD)LU%}t4HE*O!j&aKI{dAY(Ysm4)+YluLL&>OHCP)PvT_VlFckR2|=?G!p640i2U&%E@;6Y|?~bDBhpD4?x)HP{pmfG*UFD;z8bNzn`H&&mg+r0~P)G62 zO+Std%fhqf0g-{W0Wuhq|;_vQD1L~l_S%)Ho-Q?ZgY9hU%_*HrECf#%WU zFANs_XT@dtr_f7KXC3KnT_1kQR3fN9Rg#KY$^58ga9T39r|vD%>~DO(gg?MF*N7IT zcYaoOnG2T{YS*LE%p>X|6^hz}eXPkDhOUC@d7pccN92;|-0GxMkgw}LBo4n4&J@{v ziqMw&JTaJA#6o|tZ8y)`oaYL8P4}SQS3Lf?EU`*l2zvv~BTD(P|kLllbRdfg{6WtX-EFTd6jvq(QG*BNSeNKqv2M*HlNPhg48R4ow z(@+!LK2H4ZCplSGZN9=lz%Z6%_UpP_lMi=L^cWW77XU{dlf zYthbJdq}V_+_tW3gfhvR9lB@*m}icc9!}-cwGyIb+kU~Npx%v_t}w-yd5LZ;VgLF_ zk6q_+m^uUD)?=B#+HEpOCAtrSC0+^nGPm7zLx;R7U8)@cjZBXg`GpPqYAmp5N(Kh%3MhaBPfCoQwztJ>(a?SI?@dgx+QxZN6_j znaH8-_1pZD8PHymgIse{kHPnH`zfL*3v7l>vXGYyW8B;@sB71Siz`#4d2*pO;hPPoS@foJ*i%oF?5zrSWVil0o zaa1{=Macv>cM$l!MqqkPV`1SmJzasteY_rnDw4CJRSPm_dTCv)g?Mk)QQtgf(Q$7^ zAb;Y3cCaz_{b3ABJvi6{Dt;Ss^UY>u{sRN;vTl58y~E!Aeyz7qrf}P1fRiSUygQt8 zybgY)nWI0;GDNT1gXJ?2GcBf|rnI@M+WcE@Zsar6`J(~=YGglZ^`8;sWNBt=#`*Jp z|A$Yvw3Hz;`~>fDCS9qWAht|3WN6NzljSMJie&9}L6pyv;x*N>#Yg14$)>;f0^1sl z5`c}{TsHTHP#T>w7-16fZ-IZH3Jee@!_S%i47u&$7Gz=AN$tSGd_knr$Bta z+St}eZ~ohe^M?PvQ~w?ToY_Kqb3*5ti^BzK>nHA#iA`tE>eO_g;XImRF`G>C@%Kl? zF~e-WTVJ@h?W$Fa8_!TKj~SA5(v;o|gU!mNu|JB9C+v}pnR|Qo!^t8$1mgFO2V81r zoc2&n_xl;VuE1}#T=!84xJi{0hNiPFeZZnY3W782FChT0oa7J0%)u~`2Hr4?*JDMR zVSC;j7l0vg7-2+IGLxylckmL&t4IFE{QwA_ZeqDqi!SL%Q{>T2#iug}E_mb}ERWLk z7gt};)sDWN$9xGId`0!$>D&77ej61#yTu~MtAM2s3G+TDYZFvC57T`P5B7;y+_c)S zF4~yhfBWYC0GHd6W;l#9zZkxQ581*w!jObR3DI3 zGpjR#GUYMH6J{ay9Cv(2ed(RO=K}4jPhb>7f^5bQBh}DV!%ym219t5Nq>hBofL-@y zy%X@dX5SF|3-dyywQmHcCt+;MGk;RLjIjLX+L5SQ(8-TFNBE_t7F$P63%r%i41Wu~ zf`4cXu-F&#(=?cKn;V;Tb)N9t+}tVB6|=6^BhV~pZg4O2F5igGiBmiX+~l1wAlxYS`Nym5A3R{6Rs0CtVG2q5zA&J4;1vHY+QX#>!%=xhR2}$VQS!3 z!pOMW`e>Z!UH+pa_&6wJjFM_*(OEQ=0^5PxyceVIoX4c91S2(W>LM>BCQ_1ln7(t z>#Dh4v7)<7X642lAZiz|SMH$u8_nX@;=Jw_U~Q#f;m48|f4$Xaf&C{$a4gP^Cef%@ zY#fYCVZ*93)_minln3@BCydZ{FT0q5rSh|9MntW%k)?sy2(_3g)AJ80#R~TYFsz_z zM%yj9Wol@6G-J2pv#_yw;$VOhM;Lx7QR)z&*9sa1L|H&n~ZmY-OM{RPG$>Nghe(6_5In56Z;NO_OaPm7T-Q zM?wZOl*FvdDlelWFT8;f0fiK}>`)4~Z7UMV5$OWG5fMXC1<#e!8qF_`snM>{GJw8GITSUrVV~0a=B;G1)}Pcn(A^Qn z>tbcpHSS`QR|v0UQX_ydPDykMc6yDm`nWCSAQsjs>XtI@GbnPwEg1Z=j>!{6e3xs< z`$vl3BNNhpHU)}}y-ME50^|}|^pX53`W!9I%$yuKe@;L1R(N!;UBC|!BJKhw{xkx^ zy(-j~%|F{kweYL;ZYw~eHNb}H`Be+ZPWElZ`ha79s<5Md)ptuPmm4O8UlWy?fEJlN zeQ_Kn-%lEv>%a5~6*_9=z!G2huWT+~unJ{O&0x0Pr?n(I?5THQ2<&II%uO@IO18t~ zjc|IJ{A6<6w-1q@Ph^D4CPNpunqJ0zWn`#YaO+9mZEVXm8dR$dOCO!W@dNrE!dYjm z`yiPqtWqO> zk6E#>%8ZYIz?!RmrSB{lQQ21WtlK?kz{s(L)x%tT3Kbu-NZe^H8xhmirr7dZm(@>@ z#6Cp2Obh9BHg>EA&`;04fLxSUS5S(W&$*_%TgaA+d}O>p*|aJK$1>iK$*d!) zo{n2Ls|+nwT^3RE=hK!5d9^T9(a^+Pm!+%YRx#~+%vZ-88JqJt)^fKwi$u5uty>q5 zoHba-mf>*mx$7j?cesl$k7P;Njlmm-w_IB%6{LA-Z)QZUj`E9&LvLZrs?06&%I&|N z2#DJmgy|2c>9ARGz+m!IHQ_?P4sYVU37>O!d2R#P{n% z6#E>LEqInNR0z+uc%IJxmUT>mVeP?4*4;r?zPNv7 zoe>oJ59xl;?T?Wf4Y!-&x&_+>o!o})M5G#$z{E9Fb16I~%K&nXm5ih1Dq48b4QcuK-5F=cA+ZgjE-mXcIbm6Ny)rDA==|dxHzMWb83@OL9El^78hLElAeRgKst$n ztQI*4%-BKq^yq!Qa}|h6Y*#tNuew3LTi80-pzytL3SB2GG$`k0>9X!qg<23InIg@p za9KoM*jmJ9Qd6Llj@g~1;4xR*P)(=Vr=mB`IPu0DLzk=N#nSt|)VBoV))ieCaZRvo z)wnywXv{8oR*7wZ=yA)iLP6N!ocos8#>!+X-1&e`3-;e)C#c|!r-_zbN8vpMM j|Gt0y6&_6V7x=$-GZp!pNZ$Ye7|2TunP(r}{qgiKY@t47 literal 0 HcmV?d00001 diff --git a/pandas/io/tests/test_excel.py b/pandas/io/tests/test_excel.py index 3f41be6ae64c6..76f3a5fd3c8f8 100644 --- a/pandas/io/tests/test_excel.py +++ b/pandas/io/tests/test_excel.py @@ -35,6 +35,8 @@ from numpy.testing.decorators import slow from pandas.parser import OverflowError +from pandas.core import config + def _skip_if_no_xlrd(): try: @@ -60,6 +62,13 @@ def _skip_if_no_openpyxl(): raise nose.SkipTest('openpyxl not installed, skipping') +def _skip_if_no_xlsxwriter(): + try: + import xlsxwriter + except ImportError: + raise nose.SkipTest('xlsxwriter not installed, skipping') + + def _skip_if_no_excelsuite(): _skip_if_no_xlrd() _skip_if_no_xlwt() @@ -239,6 +248,7 @@ def check_excel_sheet_by_name_raise(self, ext): def test_excel_sheet_by_name_raise(self): _skip_if_no_xlrd() _skip_if_no_xlwt() + _skip_if_no_openpyxl for ext in ('xls', 'xlsx'): self.check_excel_sheet_by_name_raise(ext) @@ -320,6 +330,13 @@ def test_excel_roundtrip_xlsx(self): _skip_if_no_excelsuite() self._check_extension('xlsx') + def test_excel_roundtrip_xlsxwriter(self): + _skip_if_no_xlsxwriter() + _skip_if_no_xlrd() + config.set_option('io.excel.writer_engine', 'xlsxwriter') + self._check_extension('xlsx') + config.set_option('io.excel.writer_engine', None) + def _check_extension(self, ext): path = '__tmp_to_excel_from_excel__.' + ext @@ -366,6 +383,14 @@ def test_excel_roundtrip_xlsx_mixed(self): self._check_extension_mixed('xlsx') + def test_excel_roundtrip_xlsxwriter_mixed(self): + _skip_if_no_xlsxwriter() + _skip_if_no_xlrd() + + config.set_option('io.excel.writer_engine', 'xlsxwriter') + self._check_extension_mixed('xlsx') + config.set_option('io.excel.writer_engine', None) + def _check_extension_mixed(self, ext): path = '__tmp_to_excel_from_excel_mixed__.' + ext @@ -386,6 +411,13 @@ def test_excel_roundtrip_xlsx_tsframe(self): _skip_if_no_xlrd() self._check_extension_tsframe('xlsx') + def test_excel_roundtrip_xlsxwriter_tsframe(self): + _skip_if_no_xlsxwriter() + _skip_if_no_xlrd() + config.set_option('io.excel.writer_engine', 'xlsxwriter') + self._check_extension_tsframe('xlsx') + config.set_option('io.excel.writer_engine', None) + def _check_extension_tsframe(self, ext): path = '__tmp_to_excel_from_excel_tsframe__.' + ext @@ -405,6 +437,13 @@ def test_excel_roundtrip_xlsx_int64(self): _skip_if_no_excelsuite() self._check_extension_int64('xlsx') + def test_excel_roundtrip_xlsxwriter_int64(self): + _skip_if_no_xlsxwriter() + _skip_if_no_xlrd() + config.set_option('io.excel.writer_engine', 'xlsxwriter') + self._check_extension_int64('xlsx') + config.set_option('io.excel.writer_engine', None) + def _check_extension_int64(self, ext): path = '__tmp_to_excel_from_excel_int64__.' + ext @@ -431,6 +470,13 @@ def test_excel_roundtrip_xlsx_bool(self): _skip_if_no_excelsuite() self._check_extension_bool('xlsx') + def test_excel_roundtrip_xlsxwriter_bool(self): + _skip_if_no_xlsxwriter() + _skip_if_no_xlrd() + config.set_option('io.excel.writer_engine', 'xlsxwriter') + self._check_extension_bool('xlsx') + config.set_option('io.excel.writer_engine', None) + def _check_extension_bool(self, ext): path = '__tmp_to_excel_from_excel_bool__.' + ext @@ -457,6 +503,13 @@ def test_excel_roundtrip_xlsx_sheets(self): _skip_if_no_excelsuite() self._check_extension_sheets('xlsx') + def test_excel_roundtrip_xlsxwriter_sheets(self): + _skip_if_no_xlsxwriter() + _skip_if_no_xlrd() + config.set_option('io.excel.writer_engine', 'xlsxwriter') + self._check_extension_sheets('xlsx') + config.set_option('io.excel.writer_engine', None) + def _check_extension_sheets(self, ext): path = '__tmp_to_excel_from_excel_sheets__.' + ext @@ -490,6 +543,13 @@ def test_excel_roundtrip_xlsx_colaliases(self): _skip_if_no_excelsuite() self._check_extension_colaliases('xlsx') + def test_excel_roundtrip_xlsxwriter_colaliases(self): + _skip_if_no_xlsxwriter() + _skip_if_no_xlrd() + config.set_option('io.excel.writer_engine', 'xlsxwriter') + self._check_extension_colaliases('xlsx') + config.set_option('io.excel.writer_engine', None) + def _check_extension_colaliases(self, ext): path = '__tmp_to_excel_from_excel_aliases__.' + ext @@ -518,6 +578,13 @@ def test_excel_roundtrip_xlsx_indexlabels(self): _skip_if_no_excelsuite() self._check_extension_indexlabels('xlsx') + def test_excel_roundtrip_xlsxwriter_indexlabels(self): + _skip_if_no_xlsxwriter() + _skip_if_no_xlrd() + config.set_option('io.excel.writer_engine', 'xlsxwriter') + self._check_extension_indexlabels('xlsx') + config.set_option('io.excel.writer_engine', None) + def _check_extension_indexlabels(self, ext): path = '__tmp_to_excel_from_excel_indexlabels__.' + ext @@ -554,7 +621,7 @@ def _check_extension_indexlabels(self, ext): self.assertEqual(frame.index.names, recons.index.names) # test index_labels in same row as column names - path = '%s.xls' % tm.rands(10) + path = '%s.%s' % (tm.rands(10), ext) with ensure_clean(path) as path: @@ -567,7 +634,9 @@ def _check_extension_indexlabels(self, ext): reader = ExcelFile(path) recons = reader.parse('test1', index_col=[0, 1]) - tm.assert_frame_equal(df, recons) + # Test with less_precise or else xlsxwriter fails with no + # visible precision difference. + tm.assert_frame_equal(df, recons, check_less_precise=True) def test_excel_roundtrip_indexname(self): _skip_if_no_xlrd() @@ -617,6 +686,21 @@ def test_to_excel_periodindex(self): rs = reader.parse('sht1', index_col=0, parse_dates=True) tm.assert_frame_equal(xp, rs.to_period('M')) + def test_to_excel_periodindex_xlsxwriter(self): + _skip_if_no_xlsxwriter() + _skip_if_no_xlrd() + + path = '__tmp_to_excel_periodindex__.xlsx' + frame = self.tsframe + xp = frame.resample('M', kind='period') + + with ensure_clean(path) as path: + xp.to_excel(path, 'sht1', engine='xlsxwriter') + + reader = ExcelFile(path) + rs = reader.parse('sht1', index_col=0, parse_dates=True) + tm.assert_frame_equal(xp, rs.to_period('M')) + def test_to_excel_multiindex(self): _skip_if_no_xlrd() _skip_if_no_xlwt() @@ -628,6 +712,13 @@ def test_to_excel_multiindex_xlsx(self): _skip_if_no_openpyxl() self._check_excel_multiindex('xlsx') + def test_to_excel_multiindex_xlsxwriter(self): + _skip_if_no_xlsxwriter() + _skip_if_no_xlrd() + config.set_option('io.excel.writer_engine', 'xlsxwriter') + self._check_excel_multiindex('xlsx') + config.set_option('io.excel.writer_engine', None) + def _check_excel_multiindex(self, ext): path = '__tmp_to_excel_multiindex__' + ext + '__.' + ext @@ -660,6 +751,13 @@ def test_to_excel_multiindex_xlsx_dates(self): _skip_if_no_xlrd() self._check_excel_multiindex_dates('xlsx') + def test_to_excel_multiindex_xlsxwriter_dates(self): + _skip_if_no_xlsxwriter() + _skip_if_no_xlrd() + config.set_option('io.excel.writer_engine', 'xlsxwriter') + self._check_excel_multiindex_dates('xlsx') + config.set_option('io.excel.writer_engine', None) + def _check_excel_multiindex_dates(self, ext): path = '__tmp_to_excel_multiindex_dates__' + ext + '__.' + ext @@ -703,6 +801,26 @@ def test_to_excel_float_format(self): index=['A', 'B'], columns=['X', 'Y', 'Z']) tm.assert_frame_equal(rs, xp) + def test_to_excel_float_format_xlsxwriter(self): + _skip_if_no_xlsxwriter() + _skip_if_no_xlrd() + + filename = '__tmp_to_excel_float_format__.xlsx' + df = DataFrame([[0.123456, 0.234567, 0.567567], + [12.32112, 123123.2, 321321.2]], + index=['A', 'B'], columns=['X', 'Y', 'Z']) + + with ensure_clean(filename) as filename: + df.to_excel(filename, 'test1', float_format='%.2f', + engine='xlsxwriter') + + reader = ExcelFile(filename) + rs = reader.parse('test1', index_col=None) + xp = DataFrame([[0.12, 0.23, 0.57], + [12.32, 123123.20, 321321.20]], + index=['A', 'B'], columns=['X', 'Y', 'Z']) + tm.assert_frame_equal(rs, xp) + def test_to_excel_unicode_filename(self): _skip_if_no_excelsuite() @@ -730,6 +848,34 @@ def test_to_excel_unicode_filename(self): index=['A', 'B'], columns=['X', 'Y', 'Z']) tm.assert_frame_equal(rs, xp) + def test_to_excel_unicode_filename_xlsxwriter(self): + _skip_if_no_xlsxwriter() + _skip_if_no_xlrd() + + filename = u('\u0192u.xlsx') + + try: + f = open(filename, 'wb') + except UnicodeEncodeError: + raise nose.SkipTest('no unicode file names on this system') + else: + f.close() + + df = DataFrame([[0.123456, 0.234567, 0.567567], + [12.32112, 123123.2, 321321.2]], + index=['A', 'B'], columns=['X', 'Y', 'Z']) + + with ensure_clean(filename) as filename: + df.to_excel(filename, 'test1', float_format='%.2f', + engine='xlsxwriter') + + reader = ExcelFile(filename) + rs = reader.parse('test1', index_col=None) + xp = DataFrame([[0.12, 0.23, 0.57], + [12.32, 123123.20, 321321.20]], + index=['A', 'B'], columns=['X', 'Y', 'Z']) + tm.assert_frame_equal(rs, xp) + def test_to_excel_styleconverter(self): from pandas.io.excel import CellStyleConverter diff --git a/pandas/io/tests/test_xlsxwriter_frame01.py b/pandas/io/tests/test_xlsxwriter_frame01.py new file mode 100644 index 0000000000000..48a124d286998 --- /dev/null +++ b/pandas/io/tests/test_xlsxwriter_frame01.py @@ -0,0 +1,144 @@ +############################################################################### +# +# Tests for Pandas ExcelWriter xlsxwriter option. +# + +import unittest +import os +from pandas.core.api import DataFrame +import pandas.util.testing as testutil +from pandas.io.excel import ExcelWriter +from .xlsxwriter_test_helper import _compare_xlsx_files + + +class TestCompareXLSXFiles(unittest.TestCase): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + self.maxDiff = None + + filename = 'xw_frame01.xlsx' + test_dir = testutil.get_data_path() + self.got_filename = test_dir + '_test_' + filename + self.exp_filename = test_dir + filename + + self.ignore_files = [] + self.ignore_elements = {} + + def test_to_excel(self): + """Test the creation of a simple workbook using to_excel().""" + filename = self.got_filename + + #################################################### + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + df.to_excel(filename, + sheet_name='Sheet1', + header=False, + index=False, + engine='xlsxwriter') + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_excelwriter_to_excel(self): + """Test the creation of a simple workbook using ExcelWriter().""" + filename = self.got_filename + + #################################################### + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + writer = ExcelWriter(filename, engine='xlsxwriter') + + df.to_excel(writer, + sheet_name='Sheet1', + header=False, + index=False) + + writer.save() + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_to_excel_with_config(self): + """Test workbook creation using to_excel() and pandas.config.""" + filename = self.got_filename + + #################################################### + + from pandas.core import config + config.set_option('io.excel.writer_engine', 'xlsxwriter') + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + df.to_excel(filename, + sheet_name='Sheet1', + header=False, + index=False) + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_excelwriter_to_excel_with_config(self): + """Test workbook creation using ExcelWriter() and pandas.config.""" + filename = self.got_filename + + #################################################### + + from pandas.core import config + config.set_option('io.excel.writer_engine', 'xlsxwriter') + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + writer = ExcelWriter(filename) + + df.to_excel(writer, + sheet_name='Sheet1', + header=False, + index=False) + + writer.save() + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def tearDown(self): + # Cleanup. + if os.path.exists(self.got_filename): + os.remove(self.got_filename) + +if __name__ == '__main__': + unittest.main() diff --git a/pandas/io/tests/test_xlsxwriter_frame02.py b/pandas/io/tests/test_xlsxwriter_frame02.py new file mode 100644 index 0000000000000..130921a03b2ef --- /dev/null +++ b/pandas/io/tests/test_xlsxwriter_frame02.py @@ -0,0 +1,142 @@ +############################################################################### +# +# Tests for Pandas ExcelWriter xlsxwriter option. +# + +import unittest +import os +from pandas.core.api import DataFrame +import pandas.util.testing as testutil +from pandas.io.excel import ExcelWriter +from .xlsxwriter_test_helper import _compare_xlsx_files + + +class TestCompareXLSXFiles(unittest.TestCase): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + self.maxDiff = None + + filename = 'xw_frame02.xlsx' + test_dir = testutil.get_data_path() + self.got_filename = test_dir + '_test_' + filename + self.exp_filename = test_dir + filename + + self.ignore_files = [] + self.ignore_elements = {} + + def test_to_excel(self): + """Test the creation of a simple workbook using to_excel().""" + filename = self.got_filename + + #################################################### + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + df.to_excel(filename, + sheet_name='Sheet1', + header=True, + index=False, + engine='xlsxwriter') + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_excelwriter_to_excel(self): + """Test the creation of a simple workbook using ExcelWriter().""" + filename = self.got_filename + + #################################################### + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + writer = ExcelWriter(filename, engine='xlsxwriter') + + df.to_excel(writer, + sheet_name='Sheet1', + index=False) + + writer.save() + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_to_excel_with_config(self): + """Test workbook creation using to_excel() and pandas.config.""" + filename = self.got_filename + + #################################################### + + from pandas.core import config + config.set_option('io.excel.writer_engine', 'xlsxwriter') + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + df.to_excel(filename, + sheet_name='Sheet1', + header=True, + index=False) + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_excelwriter_to_excel_with_config(self): + """Test workbook creation using ExcelWriter() and pandas.config.""" + filename = self.got_filename + + #################################################### + + from pandas.core import config + config.set_option('io.excel.writer_engine', 'xlsxwriter') + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + writer = ExcelWriter(filename) + + df.to_excel(writer, + sheet_name='Sheet1', + index=False) + + writer.save() + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def tearDown(self): + # Cleanup. + if os.path.exists(self.got_filename): + os.remove(self.got_filename) + +if __name__ == '__main__': + unittest.main() diff --git a/pandas/io/tests/test_xlsxwriter_frame03.py b/pandas/io/tests/test_xlsxwriter_frame03.py new file mode 100644 index 0000000000000..dda70136633b8 --- /dev/null +++ b/pandas/io/tests/test_xlsxwriter_frame03.py @@ -0,0 +1,140 @@ +############################################################################### +# +# Tests for Pandas ExcelWriter xlsxwriter option. +# + +import unittest +import os +from pandas.core.api import DataFrame +import pandas.util.testing as testutil +from pandas.io.excel import ExcelWriter +from .xlsxwriter_test_helper import _compare_xlsx_files + + +class TestCompareXLSXFiles(unittest.TestCase): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + self.maxDiff = None + + filename = 'xw_frame03.xlsx' + test_dir = testutil.get_data_path() + self.got_filename = test_dir + '_test_' + filename + self.exp_filename = test_dir + filename + + self.ignore_files = [] + self.ignore_elements = {} + + def test_to_excel(self): + """Test the creation of a simple workbook using to_excel().""" + filename = self.got_filename + + #################################################### + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + df.to_excel(filename, + sheet_name='Sheet1', + header=True, + index=True, + engine='xlsxwriter') + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_excelwriter_to_excel(self): + """Test the creation of a simple workbook using ExcelWriter().""" + filename = self.got_filename + + #################################################### + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + writer = ExcelWriter(filename, + engine='xlsxwriter') + + df.to_excel(writer, sheet_name='Sheet1') + + writer.save() + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_to_excel_with_config(self): + """Test workbook creation using to_excel() and pandas.config.""" + filename = self.got_filename + + #################################################### + + from pandas.core import config + config.set_option('io.excel.writer_engine', 'xlsxwriter') + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + df.to_excel(filename, + sheet_name='Sheet1', + header=True, + index=True) + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_excelwriter_to_excel_with_config(self): + """Test workbook creation using ExcelWriter() and pandas.config.""" + filename = self.got_filename + + #################################################### + + from pandas.core import config + config.set_option('io.excel.writer_engine', 'xlsxwriter') + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + writer = ExcelWriter(filename) + + df.to_excel(writer, + sheet_name='Sheet1') + + writer.save() + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def tearDown(self): + # Cleanup. + if os.path.exists(self.got_filename): + os.remove(self.got_filename) + +if __name__ == '__main__': + unittest.main() diff --git a/pandas/io/tests/test_xlsxwriter_frame04.py b/pandas/io/tests/test_xlsxwriter_frame04.py new file mode 100644 index 0000000000000..cf6d567dcb758 --- /dev/null +++ b/pandas/io/tests/test_xlsxwriter_frame04.py @@ -0,0 +1,145 @@ +############################################################################### +# +# Tests for Pandas ExcelWriter xlsxwriter option. +# + +import unittest +import os +from pandas.core.api import DataFrame +import pandas.util.testing as testutil +from pandas.io.excel import ExcelWriter +from numpy import nan +from .xlsxwriter_test_helper import _compare_xlsx_files + + +class TestCompareXLSXFiles(unittest.TestCase): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + self.maxDiff = None + + filename = 'xw_frame04.xlsx' + test_dir = testutil.get_data_path() + self.got_filename = test_dir + '_test_' + filename + self.exp_filename = test_dir + filename + + self.ignore_files = [] + self.ignore_elements = {} + + def test_to_excel(self): + """Test the creation of a simple workbook using to_excel().""" + filename = self.got_filename + + #################################################### + + df = DataFrame({'A': [nan, 11, 12, 13], + 'B': [2, 4, nan, 8]}) + + df.to_excel(filename, + sheet_name='Sheet1', + header=False, + index=False, + engine='xlsxwriter') + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_excelwriter_to_excel(self): + """Test the creation of a simple workbook using ExcelWriter().""" + filename = self.got_filename + + #################################################### + + df = DataFrame({'A': [nan, 11, 12, 13], + 'B': [2, 4, nan, 8]}) + + writer = ExcelWriter(filename, engine='xlsxwriter') + + df.to_excel(writer, + sheet_name='Sheet1', + header=False, + index=False) + + writer.save() + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_to_excel_with_config(self): + """Test workbook creation using to_excel() and pandas.config.""" + filename = self.got_filename + + #################################################### + + from pandas.core import config + config.set_option('io.excel.writer_engine', 'xlsxwriter') + + df = DataFrame({'A': [nan, 11, 12, 13], + 'B': [2, 4, nan, 8]}) + + df.to_excel(filename, + sheet_name='Sheet1', + header=False, + index=False) + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_excelwriter_to_excel_with_config(self): + """Test workbook creation using ExcelWriter() and pandas.config.""" + filename = self.got_filename + + #################################################### + + from pandas.core import config + config.set_option('io.excel.writer_engine', 'xlsxwriter') + + df = DataFrame({'A': [nan, 11, 12, 13], + 'B': [2, 4, nan, 8]}) + + writer = ExcelWriter(filename) + + df.to_excel(writer, + sheet_name='Sheet1', + header=False, + index=False) + + writer.save() + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def tearDown(self): + # Cleanup. + if os.path.exists(self.got_filename): + os.remove(self.got_filename) + +if __name__ == '__main__': + unittest.main() diff --git a/pandas/io/tests/test_xlsxwriter_frame05.py b/pandas/io/tests/test_xlsxwriter_frame05.py new file mode 100644 index 0000000000000..342df9fc5f9e5 --- /dev/null +++ b/pandas/io/tests/test_xlsxwriter_frame05.py @@ -0,0 +1,148 @@ +############################################################################### +# +# Tests for Pandas ExcelWriter xlsxwriter option. +# + +import unittest +import os +from pandas.core.api import DataFrame +import pandas.util.testing as testutil +from pandas.io.excel import ExcelWriter +from .xlsxwriter_test_helper import _compare_xlsx_files + + +class TestCompareXLSXFiles(unittest.TestCase): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + self.maxDiff = None + + filename = 'xw_frame05.xlsx' + test_dir = testutil.get_data_path() + self.got_filename = test_dir + '_test_' + filename + self.exp_filename = test_dir + filename + + self.ignore_files = [] + self.ignore_elements = {} + + def test_to_excel(self): + """Test the creation of a simple workbook using to_excel().""" + filename = self.got_filename + + #################################################### + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + df.to_excel(filename, + sheet_name='Sheet1', + cols=['B', 'A'], + header=False, + index=False, + engine='xlsxwriter') + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_excelwriter_to_excel(self): + """Test the creation of a simple workbook using ExcelWriter().""" + filename = self.got_filename + + #################################################### + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + writer = ExcelWriter(filename, engine='xlsxwriter') + + df.to_excel(writer, + sheet_name='Sheet1', + cols=['B', 'A'], + header=False, + index=False) + + writer.save() + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_to_excel_with_config(self): + """Test workbook creation using to_excel() and pandas.config.""" + filename = self.got_filename + + #################################################### + + from pandas.core import config + config.set_option('io.excel.writer_engine', 'xlsxwriter') + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + df.to_excel(filename, + sheet_name='Sheet1', + cols=['B', 'A'], + header=False, + index=False) + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_excelwriter_to_excel_with_config(self): + """Test workbook creation using ExcelWriter() and pandas.config.""" + filename = self.got_filename + + #################################################### + + from pandas.core import config + config.set_option('io.excel.writer_engine', 'xlsxwriter') + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + writer = ExcelWriter(filename) + + df.to_excel(writer, + sheet_name='Sheet1', + cols=['B', 'A'], + header=False, + index=False) + + writer.save() + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def tearDown(self): + # Cleanup. + if os.path.exists(self.got_filename): + os.remove(self.got_filename) + +if __name__ == '__main__': + unittest.main() diff --git a/pandas/io/tests/test_xlsxwriter_frame06.py b/pandas/io/tests/test_xlsxwriter_frame06.py new file mode 100644 index 0000000000000..3a47954a7f478 --- /dev/null +++ b/pandas/io/tests/test_xlsxwriter_frame06.py @@ -0,0 +1,144 @@ +############################################################################### +# +# Tests for Pandas ExcelWriter xlsxwriter option. +# + +import unittest +import os +from pandas.core.api import DataFrame +import pandas.util.testing as testutil +from pandas.io.excel import ExcelWriter +from .xlsxwriter_test_helper import _compare_xlsx_files + + +class TestCompareXLSXFiles(unittest.TestCase): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + self.maxDiff = None + + filename = 'xw_frame06.xlsx' + test_dir = testutil.get_data_path() + self.got_filename = test_dir + '_test_' + filename + self.exp_filename = test_dir + filename + + self.ignore_files = [] + self.ignore_elements = {} + + def test_to_excel(self): + """Test the creation of a simple workbook using to_excel().""" + filename = self.got_filename + + #################################################### + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + df.to_excel(filename, + sheet_name='Sheet1', + header=True, + index=True, + index_label='Foo', + engine='xlsxwriter') + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_excelwriter_to_excel(self): + """Test the creation of a simple workbook using ExcelWriter().""" + filename = self.got_filename + + #################################################### + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + writer = ExcelWriter(filename, engine='xlsxwriter') + + df.to_excel(writer, + sheet_name='Sheet1', + index_label='Foo',) + + writer.save() + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_to_excel_with_config(self): + """Test workbook creation using to_excel() and pandas.config.""" + filename = self.got_filename + + #################################################### + + from pandas.core import config + config.set_option('io.excel.writer_engine', 'xlsxwriter') + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + df.to_excel(filename, + sheet_name='Sheet1', + header=True, + index=True, + index_label='Foo') + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_excelwriter_to_excel_with_config(self): + """Test workbook creation using ExcelWriter() and pandas.config.""" + filename = self.got_filename + + #################################################### + + from pandas.core import config + config.set_option('io.excel.writer_engine', 'xlsxwriter') + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + writer = ExcelWriter(filename) + + df.to_excel(writer, + sheet_name='Sheet1', + index_label='Foo') + + writer.save() + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def tearDown(self): + # Cleanup. + if os.path.exists(self.got_filename): + os.remove(self.got_filename) + +if __name__ == '__main__': + unittest.main() diff --git a/pandas/io/tests/test_xlsxwriter_frame07.py b/pandas/io/tests/test_xlsxwriter_frame07.py new file mode 100644 index 0000000000000..989bfbcb5b879 --- /dev/null +++ b/pandas/io/tests/test_xlsxwriter_frame07.py @@ -0,0 +1,152 @@ +############################################################################### +# +# Tests for Pandas ExcelWriter xlsxwriter option. +# + +import unittest +import os +from pandas.core.api import DataFrame +import pandas.util.testing as testutil +from pandas.io.excel import ExcelWriter +from .xlsxwriter_test_helper import _compare_xlsx_files + + +class TestCompareXLSXFiles(unittest.TestCase): + """ + Test file created by XlsxWriter against a file created by Excel. + + """ + + def setUp(self): + self.maxDiff = None + + filename = 'xw_frame07.xlsx' + test_dir = testutil.get_data_path() + self.got_filename = test_dir + '_test_' + filename + self.exp_filename = test_dir + filename + + self.ignore_files = [] + self.ignore_elements = {} + + def test_to_excel(self): + """Test the creation of a simple workbook using to_excel().""" + filename = self.got_filename + + #################################################### + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + df.to_excel(filename, + sheet_name='Sheet1', + startcol=2, + startrow=1, + header=False, + index=False, + engine='xlsxwriter') + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_excelwriter_to_excel(self): + """Test the creation of a simple workbook using ExcelWriter().""" + filename = self.got_filename + + #################################################### + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + writer = ExcelWriter(filename, engine='xlsxwriter') + + df.to_excel(writer, + sheet_name='Sheet1', + startcol=2, + startrow=1, + header=False, + index=False) + + writer.save() + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_to_excel_with_config(self): + """Test workbook creation using to_excel() and pandas.config.""" + filename = self.got_filename + + #################################################### + + from pandas.core import config + config.set_option('io.excel.writer_engine', 'xlsxwriter') + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + df.to_excel(filename, + sheet_name='Sheet1', + startcol=2, + startrow=1, + header=False, + index=False) + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def test_excelwriter_to_excel_with_config(self): + """Test workbook creation using ExcelWriter() and pandas.config.""" + filename = self.got_filename + + #################################################### + + from pandas.core import config + config.set_option('io.excel.writer_engine', 'xlsxwriter') + + df = DataFrame({'A': [10, 11, 12, 13], + 'B': [2, 4, 6, 8]}) + + writer = ExcelWriter(filename) + + df.to_excel(writer, + sheet_name='Sheet1', + startcol=2, + startrow=1, + header=False, + index=False) + + writer.save() + + #################################################### + + got, exp = _compare_xlsx_files(self.got_filename, + self.exp_filename, + self.ignore_files, + self.ignore_elements) + + self.assertEqual(got, exp) + + def tearDown(self): + # Cleanup. + if os.path.exists(self.got_filename): + os.remove(self.got_filename) + +if __name__ == '__main__': + unittest.main() diff --git a/pandas/io/tests/xlsxwriter_test_helper.py b/pandas/io/tests/xlsxwriter_test_helper.py new file mode 100644 index 0000000000000..1780db2e48d71 --- /dev/null +++ b/pandas/io/tests/xlsxwriter_test_helper.py @@ -0,0 +1,220 @@ +############################################################################### +# +# Helper functions for testing XlsxWriter generated files against Excel +# files. Copy of helperfunctions.py in XlsxWriter. +# + +import re +import sys +import os.path +from zipfile import ZipFile +from zipfile import BadZipfile +from zipfile import LargeZipFile + + +def _xml_to_list(xml_str): + # Convert test generated XML strings into lists for comparison testing. + + # Split the XML string at tag boundaries. + parser = re.compile(r'>\s*<') + elements = parser.split(xml_str.strip()) + + # Add back the removed brackets. + for index, element in enumerate(elements): + if not element[0] == '<': + elements[index] = '<' + elements[index] + if not element[-1] == '>': + elements[index] = elements[index] + '>' + + return elements + + +def _vml_to_list(vml_str): + # Convert an Excel generated VML string into a list for comparison testing. + # + # The VML data in the testcases is taken from Excel 2007 files. The data + # has to be massaged significantly to make it suitable for comparison. + # + # The VML produced by XlsxWriter can be parsed as ordinary XML. + vml_str = vml_str.replace("\r", "") + + vml = vml_str.split("\n") + vml_str = '' + + for line in vml: + # Skip blank lines. + if not line: + continue + + # Strip leading and trailing whitespace. + line = line.strip() + + # Convert VMLs attribute quotes. + line = line.replace("'", '"') + + # Add space between attributes. + if re.search('"$', line): + line += " " + + # Add newline after element end. + if re.search('>$', line): + line += "\n" + + # Split multiple elements. + line = line.replace('><', ">\n<") + + # Put all of Anchor on one line. + if line == "\n": + line = line.strip() + + vml_str += line + + # Remove the final newline. + vml_str = vml_str.rstrip() + + return vml_str.split("\n") + + +def _sort_rel_file_data(xml_elements): + # Re-order the relationship elements in an array of XLSX XML rel + # (relationship) data. This is necessary for comparison since + # Excel can produce the elements in a semi-random order. + + # We don't want to sort the first or last elements. + first = xml_elements.pop(0) + last = xml_elements.pop() + + # Sort the relationship elements. + xml_elements.sort() + + # Add back the first and last elements. + xml_elements.insert(0, first) + xml_elements.append(last) + + return xml_elements + + +def _compare_xlsx_files(got_file, exp_file, ignore_files, ignore_elements): + # Compare two XLSX files by extracting the XML files from each + # zip archive and comparing them. + # + # This is used to compare an "expected" file produced by Excel + # with a "got" file produced by XlsxWriter. + # + # In order to compare the XLSX files we convert the data in each + # XML file into an list of XML elements. + try: + # Open the XlsxWriter as a zip file for testing. + got_zip = ZipFile(got_file, 'r') + except IOError: + e = sys.exc_info()[1] + error = "XlsxWriter file error: " + str(e) + return error, '' + except (BadZipfile, LargeZipFile): + e = sys.exc_info()[1] + error = "XlsxWriter zipfile error, '" + exp_file + "': " + str(e) + return error, '' + + try: + # Open the Excel as a zip file for testing. + exp_zip = ZipFile(exp_file, 'r') + except IOError: + # For Python 2.5+ compatibility. + e = sys.exc_info()[1] + error = "Excel file error: " + str(e) + return error, '' + except (BadZipfile, LargeZipFile): + e = sys.exc_info()[1] + error = "Excel zipfile error, '" + exp_file + "': " + str(e) + return error, '' + + # Get the filenames from the zip files. + got_files = sorted(got_zip.namelist()) + exp_files = sorted(exp_zip.namelist()) + + # Ignore some test specific filenames. + got_files = [name for name in got_files if name not in ignore_files] + exp_files = [name for name in exp_files if name not in ignore_files] + + # Check that each XLSX container has the same files. + if got_files != exp_files: + return got_files, exp_files + + # Compare each file in the XLSX containers. + for filename in exp_files: + + # Skip comparison of binary files based on extension. + extension = os.path.splitext(filename)[1] + if extension in ('.png', '.jpeg', '.bmp'): + continue + + got_xml_str = got_zip.read(filename) + exp_xml_str = exp_zip.read(filename) + + if sys.hexversion >= 0x030000: + got_xml_str = got_xml_str.decode('utf-8') + exp_xml_str = exp_xml_str.decode('utf-8') + + # Remove dates and user specific data from the core.xml data. + if filename == 'docProps/core.xml': + exp_xml_str = re.sub(r' ?John', '', exp_xml_str) + exp_xml_str = re.sub(r'\d\d\d\d-\d\d-\d\dT\d\d\:\d\d:\d\dZ', + '', exp_xml_str) + got_xml_str = re.sub(r'\d\d\d\d-\d\d-\d\dT\d\d\:\d\d:\d\dZ', + '', got_xml_str) + + # Remove workbookView dimensions which are almost always different + # and calcPr which can have different Excel version ids. + if filename == 'xl/workbook.xml': + exp_xml_str = re.sub(r']*>', + '', exp_xml_str) + got_xml_str = re.sub(r']*>', + '', got_xml_str) + exp_xml_str = re.sub(r']*>', + '', exp_xml_str) + got_xml_str = re.sub(r']*>', + '', got_xml_str) + + # Remove printer specific settings from Worksheet pageSetup elements. + if re.match(r'xl/worksheets/sheet\d.xml', filename): + exp_xml_str = re.sub(r'horizontalDpi="200" ', '', exp_xml_str) + exp_xml_str = re.sub(r'verticalDpi="200" ', '', exp_xml_str) + exp_xml_str = re.sub(r'(]*>', + '', exp_xml_str) + got_xml_str = re.sub(r']*>', + '', got_xml_str) + + # Convert the XML string to lists for comparison. + if re.search('.vml$', filename): + got_xml = _xml_to_list(got_xml_str) + exp_xml = _vml_to_list(exp_xml_str) + else: + got_xml = _xml_to_list(got_xml_str) + exp_xml = _xml_to_list(exp_xml_str) + + # Ignore test specific XML elements for defined filenames. + if filename in ignore_elements: + patterns = ignore_elements[filename] + + for pat in patterns: + exp_xml = [tag for tag in exp_xml if not re.match(pat, tag)] + got_xml = [tag for tag in got_xml if not re.match(pat, tag)] + + # Reorder the XML elements in the XLSX relationship files. + if filename == '[Content_Types].xml' or re.search('.rels$', filename): + got_xml = _sort_rel_file_data(got_xml) + exp_xml = _sort_rel_file_data(exp_xml) + + # Compared the XML elements in each file. + if got_xml != exp_xml: + got_xml.insert(0, filename) + exp_xml.insert(0, filename) + return got_xml, exp_xml + + # If we got here the files are the same. + return 'Ok', 'Ok' diff --git a/pandas/tests/test_panel.py b/pandas/tests/test_panel.py index 8ad88374f40f6..0c7e8b15bc7a8 100644 --- a/pandas/tests/test_panel.py +++ b/pandas/tests/test_panel.py @@ -1423,6 +1423,26 @@ def test_to_excel(self): recdf = reader.parse(str(item), index_col=0) assert_frame_equal(df, recdf) + def test_to_excel_xlsxwriter(self): + try: + import xlrd + import xlsxwriter + from pandas.io.excel import ExcelFile + except ImportError: + raise nose.SkipTest + + path = '__tmp__.xlsx' + with ensure_clean(path) as path: + self.panel.to_excel(path, engine='xlsxwriter') + try: + reader = ExcelFile(path) + except ImportError: + raise nose.SkipTest + + for item, df in compat.iteritems(self.panel): + recdf = reader.parse(str(item), index_col=0) + assert_frame_equal(df, recdf) + def test_dropna(self): p = Panel(np.random.randn(4, 5, 6), major_axis=list('abcde')) p.ix[:, ['b', 'd'], 0] = np.nan