Skip to content

BUG: unsupported type Interval when writing dataframe to excel #19244

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

Merged
merged 1 commit into from
Jan 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/source/whatsnew/v0.23.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,8 @@ I/O
- Bug in :func:`DataFrame.to_latex()` where pairs of braces meant to serve as invisible placeholders were escaped (:issue:`18667`)
- Bug in :func:`read_json` where large numeric values were causing an ``OverflowError`` (:issue:`18842`)
- Bug in :func:`DataFrame.to_parquet` where an exception was raised if the write destination is S3 (:issue:`19134`)
- :class:`Interval` now supported in :func:`DataFrame.to_excel` for all Excel file types (:issue:`19242`)
- :class:`Timedelta` now supported in :func:`DataFrame.to_excel` for xls file type (:issue:`19242`, :issue:`9155`)
-

Plotting
Expand Down
29 changes: 22 additions & 7 deletions pandas/io/excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# ---------------------------------------------------------------------
# ExcelFile class
from datetime import datetime, date, time, MINYEAR
from datetime import datetime, date, time, MINYEAR, timedelta

import os
import abc
Expand All @@ -21,7 +21,6 @@
from pandas.io.common import (_is_url, _urlopen, _validate_header_arg,
get_filepath_or_buffer, _NA_VALUES,
_stringify_path)
from pandas.core.indexes.period import Period
import pandas._libs.json as json
from pandas.compat import (map, zip, reduce, range, lrange, u, add_metaclass,
string_types, OrderedDict)
Expand Down Expand Up @@ -777,17 +776,30 @@ def _pop_header_name(row, index_col):


def _conv_value(val):
# Convert numpy types to Python types for the Excel writers.
""" Convert numpy types to Python types for the Excel writers.

Parameters
----------
val : object
Value to be written into cells

Returns
-------
If val is a numpy int, float, or bool, then the equivalent Python
types are returned. :obj:`datetime`, :obj:`date`, and :obj:`timedelta`
are passed and formatting must be handled in the writer. :obj:`str`
representation is returned for all other types.
"""
if is_integer(val):
val = int(val)
elif is_float(val):
val = float(val)
elif is_bool(val):
val = bool(val)
elif isinstance(val, Period):
val = "{val}".format(val=val)
elif is_list_like(val):
val = str(val)
elif isinstance(val, (datetime, date, timedelta)):
pass
else:
val = compat.to_str(val)

return val

Expand Down Expand Up @@ -1460,6 +1472,9 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
num_format_str = self.datetime_format
elif isinstance(cell.val, date):
num_format_str = self.date_format
elif isinstance(cell.val, timedelta):
delta = cell.val
val = delta.total_seconds() / float(86400)

stylekey = json.dumps(cell.style)
if num_format_str:
Expand Down
52 changes: 51 additions & 1 deletion pandas/tests/io/test_excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
import sys
import warnings
from datetime import datetime, date, time
from datetime import datetime, date, time, timedelta
from distutils.version import LooseVersion
from functools import partial
from warnings import catch_warnings
Expand Down Expand Up @@ -1440,6 +1440,56 @@ def test_excel_date_datetime_format(self):
# to use df_expected to check the result
tm.assert_frame_equal(rs2, df_expected)

def test_to_excel_interval_no_labels(self):
# GH19242 - test writing Interval without labels
_skip_if_no_xlrd()

with ensure_clean(self.ext) as path:
frame = DataFrame(np.random.randint(-10, 10, size=(20, 1)),
dtype=np.int64)
expected = frame.copy()
frame['new'] = pd.cut(frame[0], 10)
expected['new'] = pd.cut(expected[0], 10).astype(str)
frame.to_excel(path, 'test1')
reader = ExcelFile(path)
recons = read_excel(reader, 'test1')
tm.assert_frame_equal(expected, recons)

def test_to_excel_interval_labels(self):
# GH19242 - test writing Interval with labels
_skip_if_no_xlrd()

with ensure_clean(self.ext) as path:
frame = DataFrame(np.random.randint(-10, 10, size=(20, 1)),
dtype=np.int64)
expected = frame.copy()
intervals = pd.cut(frame[0], 10, labels=['A', 'B', 'C', 'D', 'E',
'F', 'G', 'H', 'I', 'J'])
frame['new'] = intervals
expected['new'] = pd.Series(list(intervals))
frame.to_excel(path, 'test1')
reader = ExcelFile(path)
recons = read_excel(reader, 'test1')
tm.assert_frame_equal(expected, recons)

def test_to_excel_timedelta(self):
# GH 19242, GH9155 - test writing timedelta to xls
_skip_if_no_xlrd()

with ensure_clean('.xls') as path:
frame = DataFrame(np.random.randint(-10, 10, size=(20, 1)),
columns=['A'],
dtype=np.int64
)
expected = frame.copy()
frame['new'] = frame['A'].apply(lambda x: timedelta(seconds=x))
expected['new'] = expected['A'].apply(
lambda x: timedelta(seconds=x).total_seconds() / float(86400))
frame.to_excel(path, 'test1')
reader = ExcelFile(path)
recons = read_excel(reader, 'test1')
tm.assert_frame_equal(expected, recons)

def test_to_excel_periodindex(self):
_skip_if_no_xlrd()

Expand Down