Skip to content

Commit ba82b51

Browse files
haleemurjorisvandenbossche
authored andcommitted
BUG: Datetime64Formatter not respecting formatter
- [x] closes pandas-dev#10690 - [x] tests added / passed - [x] passes ``git diff upstream/master | flake8 --diff`` - [x] whatsnew entry the Datetime64Formatter class did not accept a `formatter` argument, so custom formatters passed in through `df.to_string` or `df.to_html` were silently ignored. Author: Haleemur Ali <[email protected]> This patch had conflicts when merged, resolved by Committer: Joris Van den Bossche <[email protected]> Closes pandas-dev#13567 from haleemur/fix/dt64_outputformat and squashes the following commits: 8d84283 [Haleemur Ali] fix bug in Datetime64Formatter, which affected custom date formatted output for df.to_string, df.to_html methods
1 parent f11b9c1 commit ba82b51

File tree

3 files changed

+133
-0
lines changed

3 files changed

+133
-0
lines changed

doc/source/whatsnew/v0.19.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ Bug Fixes
522522
- Bug in ``Series`` comparison operators when dealing with zero dim NumPy arrays (:issue:`13006`)
523523
- Bug in ``groupby`` where ``apply`` returns different result depending on whether first result is ``None`` or not (:issue:`12824`)
524524
- Bug in ``groupby(..).nth()`` where the group key is included inconsistently if called after ``.head()/.tail()`` (:issue:`12839`)
525+
- Bug in ``.to_html``, ``.to_latex`` and ``.to_string`` silently ignore custom datetime formatter passed through the ``formatters`` key word (:issue:`10690`)
525526

526527
- Bug in ``pd.to_numeric`` when ``errors='coerce'`` and input contains non-hashable objects (:issue:`13324`)
527528

pandas/formats/format.py

+4
Original file line numberDiff line numberDiff line change
@@ -2239,9 +2239,13 @@ def _format_strings(self):
22392239
""" we by definition have DO NOT have a TZ """
22402240

22412241
values = self.values
2242+
22422243
if not isinstance(values, DatetimeIndex):
22432244
values = DatetimeIndex(values)
22442245

2246+
if self.formatter is not None and callable(self.formatter):
2247+
return [self.formatter(x) for x in values]
2248+
22452249
fmt_values = format_array_from_datetime(
22462250
values.asi8.ravel(),
22472251
format=_get_format_datetime64_from_values(values,

pandas/tests/formats/test_format.py

+128
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,28 @@ def test_to_string_with_formatters(self):
456456
'2 0x3 [ 3.0] -False-'))
457457
self.assertEqual(result, result2)
458458

459+
def test_to_string_with_datetime64_monthformatter(self):
460+
months = [datetime(2016, 1, 1), datetime(2016, 2, 2)]
461+
x = DataFrame({'months': months})
462+
463+
def format_func(x):
464+
return x.strftime('%Y-%m')
465+
result = x.to_string(formatters={'months': format_func})
466+
expected = 'months\n0 2016-01\n1 2016-02'
467+
self.assertEqual(result.strip(), expected)
468+
469+
def test_to_string_with_datetime64_hourformatter(self):
470+
471+
x = DataFrame({'hod': pd.to_datetime(['10:10:10.100', '12:12:12.120'],
472+
format='%H:%M:%S.%f')})
473+
474+
def format_func(x):
475+
return x.strftime('%H:%M')
476+
477+
result = x.to_string(formatters={'hod': format_func})
478+
expected = 'hod\n0 10:10\n1 12:12'
479+
self.assertEqual(result.strip(), expected)
480+
459481
def test_to_string_with_formatters_unicode(self):
460482
df = DataFrame({u('c/\u03c3'): [1, 2, 3]})
461483
result = df.to_string(formatters={u('c/\u03c3'): lambda x: '%s' % x})
@@ -1233,6 +1255,63 @@ def test_to_html_index_formatter(self):
12331255

12341256
self.assertEqual(result, expected)
12351257

1258+
def test_to_html_datetime64_monthformatter(self):
1259+
months = [datetime(2016, 1, 1), datetime(2016, 2, 2)]
1260+
x = DataFrame({'months': months})
1261+
1262+
def format_func(x):
1263+
return x.strftime('%Y-%m')
1264+
result = x.to_html(formatters={'months': format_func})
1265+
expected = """\
1266+
<table border="1" class="dataframe">
1267+
<thead>
1268+
<tr style="text-align: right;">
1269+
<th></th>
1270+
<th>months</th>
1271+
</tr>
1272+
</thead>
1273+
<tbody>
1274+
<tr>
1275+
<th>0</th>
1276+
<td>2016-01</td>
1277+
</tr>
1278+
<tr>
1279+
<th>1</th>
1280+
<td>2016-02</td>
1281+
</tr>
1282+
</tbody>
1283+
</table>"""
1284+
self.assertEqual(result, expected)
1285+
1286+
def test_to_html_datetime64_hourformatter(self):
1287+
1288+
x = DataFrame({'hod': pd.to_datetime(['10:10:10.100', '12:12:12.120'],
1289+
format='%H:%M:%S.%f')})
1290+
1291+
def format_func(x):
1292+
return x.strftime('%H:%M')
1293+
result = x.to_html(formatters={'hod': format_func})
1294+
expected = """\
1295+
<table border="1" class="dataframe">
1296+
<thead>
1297+
<tr style="text-align: right;">
1298+
<th></th>
1299+
<th>hod</th>
1300+
</tr>
1301+
</thead>
1302+
<tbody>
1303+
<tr>
1304+
<th>0</th>
1305+
<td>10:10</td>
1306+
</tr>
1307+
<tr>
1308+
<th>1</th>
1309+
<td>12:12</td>
1310+
</tr>
1311+
</tbody>
1312+
</table>"""
1313+
self.assertEqual(result, expected)
1314+
12361315
def test_to_html_regression_GH6098(self):
12371316
df = DataFrame({u('clé1'): [u('a'), u('a'), u('b'), u('b'), u('a')],
12381317
u('clé2'): [u('1er'), u('2ème'), u('1er'), u('2ème'),
@@ -2775,6 +2854,33 @@ def test_to_latex_format(self):
27752854

27762855
self.assertEqual(withindex_result, withindex_expected)
27772856

2857+
def test_to_latex_with_formatters(self):
2858+
df = DataFrame({'int': [1, 2, 3],
2859+
'float': [1.0, 2.0, 3.0],
2860+
'object': [(1, 2), True, False],
2861+
'datetime64': [datetime(2016, 1, 1),
2862+
datetime(2016, 2, 5),
2863+
datetime(2016, 3, 3)]})
2864+
2865+
formatters = {'int': lambda x: '0x%x' % x,
2866+
'float': lambda x: '[% 4.1f]' % x,
2867+
'object': lambda x: '-%s-' % str(x),
2868+
'datetime64': lambda x: x.strftime('%Y-%m'),
2869+
'__index__': lambda x: 'index: %s' % x}
2870+
result = df.to_latex(formatters=dict(formatters))
2871+
2872+
expected = r"""\begin{tabular}{llrrl}
2873+
\toprule
2874+
{} & datetime64 & float & int & object \\
2875+
\midrule
2876+
index: 0 & 2016-01 & [ 1.0] & 0x1 & -(1, 2)- \\
2877+
index: 1 & 2016-02 & [ 2.0] & 0x2 & -True- \\
2878+
index: 2 & 2016-03 & [ 3.0] & 0x3 & -False- \\
2879+
\bottomrule
2880+
\end{tabular}
2881+
"""
2882+
self.assertEqual(result, expected)
2883+
27782884
def test_to_latex_multiindex(self):
27792885
df = DataFrame({('x', 'y'): ['a']})
27802886
result = df.to_latex()
@@ -4161,6 +4267,28 @@ def test_dates_display(self):
41614267
self.assertEqual(result[1].strip(), "NaT")
41624268
self.assertEqual(result[4].strip(), "2013-01-01 09:00:00.000000004")
41634269

4270+
def test_datetime64formatter_yearmonth(self):
4271+
x = Series([datetime(2016, 1, 1), datetime(2016, 2, 2)])
4272+
4273+
def format_func(x):
4274+
return x.strftime('%Y-%m')
4275+
4276+
formatter = fmt.Datetime64Formatter(x, formatter=format_func)
4277+
result = formatter.get_result()
4278+
self.assertEqual(result, ['2016-01', '2016-02'])
4279+
4280+
def test_datetime64formatter_hoursecond(self):
4281+
4282+
x = Series(pd.to_datetime(['10:10:10.100', '12:12:12.120'],
4283+
format='%H:%M:%S.%f'))
4284+
4285+
def format_func(x):
4286+
return x.strftime('%H:%M')
4287+
4288+
formatter = fmt.Datetime64Formatter(x, formatter=format_func)
4289+
result = formatter.get_result()
4290+
self.assertEqual(result, ['10:10', '12:12'])
4291+
41644292

41654293
class TestNaTFormatting(tm.TestCase):
41664294

0 commit comments

Comments
 (0)