Skip to content

Commit b56b380

Browse files
committed
Refactoring and add support for timedelta
- Refactored _conv_value - Added support to write timedelta to xls files - Added tests for Interval and timedelta support - Closes issue 9155
1 parent 8ede5d9 commit b56b380

File tree

3 files changed

+73
-9
lines changed

3 files changed

+73
-9
lines changed

doc/source/whatsnew/v0.23.0.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,8 @@ I/O
489489
- Bug in :func:`DataFrame.to_latex()` where pairs of braces meant to serve as invisible placeholders were escaped (:issue:`18667`)
490490
- Bug in :func:`read_json` where large numeric values were causing an ``OverflowError`` (:issue:`18842`)
491491
- Bug in :func:`DataFrame.to_parquet` where an exception was raised if the write destination is S3 (:issue:`19134`)
492+
- Type ``Interval`` now supported in :func:`DataFrame.to_excel` for all Excel file types (:issue:`19242`)
493+
- Type ``timedelta`` now supported in :func:`DataFrame.to_excel` for xls file type (:issue:`19242`, :issue:`9155`)
492494
-
493495

494496
Plotting
@@ -549,4 +551,3 @@ Other
549551
^^^^^
550552

551553
- Improved error message when attempting to use a Python keyword as an identifier in a ``numexpr`` backed query (:issue:`18221`)
552-
- Bug in :func:`DataFrame.to_excel` where an exception was raised indicating unsupported type when writing a data frame containing a column of Interval type (:issue:`19242`)

pandas/io/excel.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
# ---------------------------------------------------------------------
66
# ExcelFile class
7-
from datetime import datetime, date, time, MINYEAR
7+
from datetime import datetime, date, time, MINYEAR, timedelta
88

99
import os
1010
import abc
@@ -778,18 +778,19 @@ def _pop_header_name(row, index_col):
778778

779779

780780
def _conv_value(val):
781-
# Convert numpy types to Python types for the Excel writers.
781+
""" Convert numpy types to Python types for the Excel writers.
782+
:obj:`datetime` and :obj:`date` formatting must be handled in the
783+
writer. :obj:`str` representation is returned for all other types.
784+
"""
782785
if is_integer(val):
783786
val = int(val)
784787
elif is_float(val):
785788
val = float(val)
786789
elif is_bool(val):
787790
val = bool(val)
788-
elif isinstance(val, Period):
789-
val = "{val}".format(val=val)
790-
elif is_list_like(val):
791-
val = str(val)
792-
elif isinstance(val, Interval):
791+
elif isinstance(val, (datetime, date, timedelta)):
792+
pass
793+
else:
793794
val = str(val)
794795

795796
return val
@@ -1464,6 +1465,12 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0,
14641465
elif isinstance(cell.val, date):
14651466
num_format_str = self.date_format
14661467

1468+
if isinstance(cell.val, timedelta):
1469+
delta = cell.val
1470+
val = (delta.days + (float(delta.seconds)
1471+
+ float(delta.microseconds) / 1E6)
1472+
/ (60 * 60 * 24))
1473+
14671474
stylekey = json.dumps(cell.style)
14681475
if num_format_str:
14691476
stylekey += num_format_str

pandas/tests/io/test_excel.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33
import sys
44
import warnings
5-
from datetime import datetime, date, time
5+
from datetime import datetime, date, time, timedelta
66
from distutils.version import LooseVersion
77
from functools import partial
88
from warnings import catch_warnings
@@ -1440,6 +1440,62 @@ def test_excel_date_datetime_format(self):
14401440
# to use df_expected to check the result
14411441
tm.assert_frame_equal(rs2, df_expected)
14421442

1443+
# GH19242 - test writing Interval without labels
1444+
def test_to_excel_interval_no_label(self):
1445+
_skip_if_no_xlrd()
1446+
1447+
with ensure_clean(self.ext) as path:
1448+
rand = np.random.randint(-10, 10, size=(20, 2))
1449+
frame = DataFrame(rand)
1450+
intervals = pd.cut(frame[0], 10)
1451+
frame['new'] = intervals
1452+
frame_expected = DataFrame(rand)
1453+
frame_expected['new'] = intervals.astype(str)
1454+
frame.to_excel(path, 'test1')
1455+
reader = ExcelFile(path)
1456+
recons = read_excel(reader, 'test1')
1457+
tm.assert_frame_equal(frame_expected, recons)
1458+
1459+
# GH19242 - test writing Interval with labels
1460+
def test_to_excel_interval_w_label(self):
1461+
_skip_if_no_xlrd()
1462+
1463+
with ensure_clean(self.ext) as path:
1464+
rand = np.random.randint(-10, 10, size=(20, 2))
1465+
frame = DataFrame(rand)
1466+
intervals = pd.cut(frame[0], 10, labels=['A', 'B', 'C', 'D', 'E',
1467+
'F', 'G', 'H', 'I', 'J'])
1468+
frame['new'] = intervals
1469+
frame_expected = DataFrame(rand)
1470+
frame_expected['new'] = pd.Series(list(intervals))
1471+
frame.to_excel(path, 'test1')
1472+
reader = ExcelFile(path)
1473+
recons = read_excel(reader, 'test1')
1474+
tm.assert_frame_equal(frame_expected, recons)
1475+
1476+
# GH 19242 & GH9155 - test writing timedelta to xls
1477+
def test_to_excel_timedelta(self):
1478+
_skip_if_no_xlrd()
1479+
1480+
with ensure_clean('.xls') as path:
1481+
rand = np.random.randint(-10, 10, size=(20, 1))
1482+
frame = DataFrame(rand, columns=['A'])
1483+
frame['new'] = frame['A'].apply(lambda x: timedelta(seconds=x))
1484+
frame_expected = DataFrame(rand, columns=['A'])
1485+
delta = frame_expected['A'].apply(lambda x: timedelta(seconds=x))
1486+
1487+
def timedelta_rep(x):
1488+
return (x.days + (float(x.seconds)
1489+
+ float(x.microseconds) / 1E6)
1490+
/ (60 * 60 * 24))
1491+
1492+
frame_expected['new'] = delta.apply(timedelta_rep)
1493+
frame.to_excel(path, 'test1')
1494+
reader = ExcelFile(path)
1495+
recons = read_excel(reader, 'test1')
1496+
1497+
tm.assert_frame_equal(frame_expected, recons)
1498+
14431499
def test_to_excel_periodindex(self):
14441500
_skip_if_no_xlrd()
14451501

0 commit comments

Comments
 (0)