Skip to content

Commit 9a6354e

Browse files
committed
Merge pull request #494 from shoyer/safe-encoding
Raise an error when encoding floats to integers without a fill value
2 parents 03990f1 + fb0ba73 commit 9a6354e

File tree

2 files changed

+26
-10
lines changed

2 files changed

+26
-10
lines changed

xray/conventions.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,13 @@ def encode_datetime(d):
244244
return np.vectorize(encode_datetime)(dates)
245245

246246

247+
def cast_to_int_if_safe(num):
248+
int_num = np.array(num, dtype=np.int64)
249+
if (num == int_num).all():
250+
num = int_num
251+
return num
252+
253+
247254
def encode_cf_datetime(dates, units=None, calendar=None):
248255
"""Given an array of datetime objects, returns the tuple `(num, units,
249256
calendar)` suitable for a CF complient time variable.
@@ -279,6 +286,7 @@ def encode_cf_datetime(dates, units=None, calendar=None):
279286
except (OutOfBoundsDatetime, ValueError, OverflowError):
280287
num = _encode_datetime_with_netcdf4(dates, units, calendar)
281288

289+
num = cast_to_int_if_safe(num)
282290
return (num, units, calendar)
283291

284292

@@ -289,11 +297,7 @@ def encode_cf_timedelta(timedeltas, units=None):
289297
np_unit = _netcdf_to_numpy_timeunit(units)
290298
num = 1.0 * timedeltas / np.timedelta64(1, np_unit)
291299
num = np.where(pd.isnull(timedeltas), np.nan, num)
292-
293-
int_num = np.asarray(num, dtype=np.int64)
294-
if (num == int_num).all():
295-
num = int_num
296-
300+
num = cast_to_int_if_safe(num)
297301
return (num, units)
298302

299303

@@ -575,12 +579,18 @@ def maybe_encode_fill_value(var, needs_copy=True):
575579
return var, needs_copy
576580

577581

578-
def maybe_encode_dtype(var):
582+
def maybe_encode_dtype(var, name=None):
579583
if 'dtype' in var.encoding:
580584
dims, data, attrs, encoding = _var_as_tuple(var)
581585
dtype = np.dtype(encoding.pop('dtype'))
582586
if dtype != var.dtype and dtype.kind != 'O':
583-
if np.issubdtype(dtype, int):
587+
if np.issubdtype(dtype, np.integer):
588+
if (np.issubdtype(var.dtype, np.floating)
589+
and '_FillValue' not in var.attrs):
590+
warnings.warn('saving variable %s with floating '
591+
'point data as an integer dtype without '
592+
'any _FillValue to use for NaNs' % name,
593+
RuntimeWarning, stacklevel=3)
584594
data = ops.around(data)[...]
585595
if dtype == 'S1' and data.dtype != 'S1':
586596
data = string_to_char(np.asarray(data, 'S'))
@@ -638,7 +648,7 @@ def ensure_dtype_not_object(var):
638648
return var
639649

640650

641-
def encode_cf_variable(var, needs_copy=True):
651+
def encode_cf_variable(var, needs_copy=True, name=None):
642652
"""
643653
Converts an Variable into an Variable which follows some
644654
of the CF conventions:
@@ -662,7 +672,7 @@ def encode_cf_variable(var, needs_copy=True):
662672
var = maybe_encode_timedelta(var)
663673
var, needs_copy = maybe_encode_offset_and_scale(var, needs_copy)
664674
var, needs_copy = maybe_encode_fill_value(var, needs_copy)
665-
var = maybe_encode_dtype(var)
675+
var = maybe_encode_dtype(var, name)
666676
var = ensure_dtype_not_object(var)
667677
return var
668678

@@ -989,6 +999,6 @@ def cf_encoder(variables, attributes):
989999
9901000
See also: encode_cf_variable
9911001
"""
992-
new_vars = OrderedDict((k, encode_cf_variable(v))
1002+
new_vars = OrderedDict((k, encode_cf_variable(v, name=k))
9931003
for k, v in iteritems(variables))
9941004
return new_vars, attributes

xray/test/test_conventions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,12 @@ def test_incompatible_attributes(self):
445445
with self.assertRaises(ValueError):
446446
conventions.encode_cf_variable(var)
447447

448+
def test_missing_fillvalue(self):
449+
v = Variable(['x'], np.array([np.nan, 1, 2, 3]))
450+
v.encoding = {'dtype': 'int16'}
451+
with self.assertWarns('floating point data as an integer'):
452+
conventions.encode_cf_variable(v)
453+
448454

449455
@requires_netCDF4
450456
class TestDecodeCF(TestCase):

0 commit comments

Comments
 (0)