Skip to content

Commit 3f61252

Browse files
committed
do not upcast to float64 everytime
1 parent ae2ca83 commit 3f61252

File tree

4 files changed

+56
-2
lines changed

4 files changed

+56
-2
lines changed

doc/source/whatsnew/v0.18.2.txt

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ Other enhancements
8989

9090
- ``pd.read_html()`` has gained support for the ``decimal`` option (:issue:`12907`)
9191

92+
- ``eval``'s upcasting rules for ``float32`` types have also been updated to be more consistent with numpy's rules. New behavior will not upcast to ``float64`` if you multiply a pandas ``float32`` object by a scalar float64. (:issue:`12388`)
9293

9394

9495
.. _whatsnew_0182.api:

pandas/computation/expr.py

+15
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import tokenize
66

77
from functools import partial
8+
import numpy as np
89

910
import pandas as pd
1011
from pandas import compat
@@ -356,6 +357,19 @@ def _possibly_transform_eq_ne(self, node, left=None, right=None):
356357
right)
357358
return op, op_class, left, right
358359

360+
def _possibly_downcast_constants(self, left, right):
361+
f32 = np.dtype(np.float32)
362+
if left.isscalar and not right.isscalar and right.return_type == f32:
363+
# right is a float32 array, left is a scalar
364+
name = self.env.add_tmp(np.float32(left.value))
365+
left = self.term_type(name, self.env)
366+
if right.isscalar and not left.isscalar and left.return_type == f32:
367+
# left is a float32 array, right is a scalar
368+
name = self.env.add_tmp(np.float32(right.value))
369+
right = self.term_type(name, self.env)
370+
371+
return left, right
372+
359373
def _possibly_eval(self, binop, eval_in_python):
360374
# eval `in` and `not in` (for now) in "partial" python space
361375
# things that can be evaluated in "eval" space will be turned into
@@ -399,6 +413,7 @@ def _possibly_evaluate_binop(self, op, op_class, lhs, rhs,
399413

400414
def visit_BinOp(self, node, **kwargs):
401415
op, op_class, left, right = self._possibly_transform_eq_ne(node)
416+
left, right = self._possibly_downcast_constants(left, right)
402417
return self._possibly_evaluate_binop(op, op_class, left, right)
403418

404419
def visit_Div(self, node, **kwargs):

pandas/computation/ops.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -276,18 +276,25 @@ def _not_in(x, y):
276276
_binary_ops_dict.update(d)
277277

278278

279-
def _cast_inplace(terms, dtype):
279+
def _cast_inplace(terms, acceptable_dtypes, dtype):
280280
"""Cast an expression inplace.
281281
282+
.. versionadded:: 0.18.2
283+
282284
Parameters
283285
----------
284286
terms : Op
285287
The expression that should cast.
288+
acceptable_dtypes : list of acceptable numpy.dtype
289+
Will not cast if term's dtype in this list.
286290
dtype : str or numpy.dtype
287291
The dtype to cast to.
288292
"""
289293
dt = np.dtype(dtype)
290294
for term in terms:
295+
if term.type in acceptable_dtypes:
296+
continue
297+
291298
try:
292299
new_value = term.value.astype(dt)
293300
except AttributeError:
@@ -452,7 +459,9 @@ def __init__(self, lhs, rhs, truediv, *args, **kwargs):
452459
rhs.return_type))
453460

454461
if truediv or PY3:
455-
_cast_inplace(com.flatten(self), np.float_)
462+
# do not upcast float32s to float64 un-necessarily
463+
acceptable_dtypes = [np.float32, np.float_]
464+
_cast_inplace(com.flatten(self), acceptable_dtypes, np.float_)
456465

457466

458467
_unary_ops_syms = '+', '-', '~', 'not'

pandas/computation/tests/test_eval.py

+29
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,35 @@ def check_chained_cmp_op(self, lhs, cmp1, mid, cmp2, rhs):
739739

740740
ENGINES_PARSERS = list(product(_engines, expr._parsers))
741741

742+
#-------------------------------------
743+
# typecasting rules consistency with python
744+
# issue #12388
745+
746+
class TestTypeCasting(tm.TestCase):
747+
748+
def check_binop_typecasting(self, engine, parser, op, dt):
749+
tm.skip_if_no_ne(engine)
750+
df = mkdf(5, 3, data_gen_f=f, dtype=dt)
751+
s = 'df {} 3'.format(op)
752+
res = pd.eval(s, engine=engine, parser=parser)
753+
self.assertTrue(df.values.dtype == dt)
754+
self.assertTrue(res.values.dtype == dt)
755+
assert_frame_equal(res, eval(s))
756+
757+
s = '3 {} df'.format(op)
758+
res = pd.eval(s, engine=engine, parser=parser)
759+
self.assertTrue(df.values.dtype == dt)
760+
self.assertTrue(res.values.dtype == dt)
761+
assert_frame_equal(res, eval(s))
762+
763+
def test_binop_typecasting(self):
764+
for engine, parser in ENGINES_PARSERS:
765+
for op in ['+', '-', '*', '**', '/']:
766+
# maybe someday... numexpr has too many upcasting rules now
767+
#for dt in chain(*(np.sctypes[x] for x in ['uint', 'int', 'float'])):
768+
for dt in [np.float32, np.float64]:
769+
yield self.check_binop_typecasting, engine, parser, op, dt
770+
742771

743772
#-------------------------------------
744773
# basic and complex alignment

0 commit comments

Comments
 (0)