Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/numpy/ma/mrecords.py : 11%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1""":mod:`numpy.ma..mrecords`
3Defines the equivalent of :class:`numpy.recarrays` for masked arrays,
4where fields can be accessed as attributes.
5Note that :class:`numpy.ma.MaskedArray` already supports structured datatypes
6and the masking of individual fields.
8.. moduleauthor:: Pierre Gerard-Marchant
10"""
11# We should make sure that no field is called '_mask','mask','_fieldmask',
12# or whatever restricted keywords. An idea would be to no bother in the
13# first place, and then rename the invalid fields with a trailing
14# underscore. Maybe we could just overload the parser function ?
16import warnings
18import numpy as np
19from numpy import (
20 bool_, dtype, ndarray, recarray, array as narray
21 )
22from numpy.core.records import (
23 fromarrays as recfromarrays, fromrecords as recfromrecords
24 )
26_byteorderconv = np.core.records._byteorderconv
28import numpy.ma as ma
29from numpy.ma import (
30 MAError, MaskedArray, masked, nomask, masked_array, getdata,
31 getmaskarray, filled
32 )
34_check_fill_value = ma.core._check_fill_value
37__all__ = [
38 'MaskedRecords', 'mrecarray', 'fromarrays', 'fromrecords',
39 'fromtextfile', 'addfield',
40 ]
42reserved_fields = ['_data', '_mask', '_fieldmask', 'dtype']
45def _checknames(descr, names=None):
46 """
47 Checks that field names ``descr`` are not reserved keywords.
49 If this is the case, a default 'f%i' is substituted. If the argument
50 `names` is not None, updates the field names to valid names.
52 """
53 ndescr = len(descr)
54 default_names = ['f%i' % i for i in range(ndescr)]
55 if names is None:
56 new_names = default_names
57 else:
58 if isinstance(names, (tuple, list)):
59 new_names = names
60 elif isinstance(names, str):
61 new_names = names.split(',')
62 else:
63 raise NameError("illegal input names %s" % repr(names))
64 nnames = len(new_names)
65 if nnames < ndescr:
66 new_names += default_names[nnames:]
67 ndescr = []
68 for (n, d, t) in zip(new_names, default_names, descr.descr):
69 if n in reserved_fields:
70 if t[0] in reserved_fields:
71 ndescr.append((d, t[1]))
72 else:
73 ndescr.append(t)
74 else:
75 ndescr.append((n, t[1]))
76 return np.dtype(ndescr)
79def _get_fieldmask(self):
80 mdescr = [(n, '|b1') for n in self.dtype.names]
81 fdmask = np.empty(self.shape, dtype=mdescr)
82 fdmask.flat = tuple([False] * len(mdescr))
83 return fdmask
86class MaskedRecords(MaskedArray):
87 """
89 Attributes
90 ----------
91 _data : recarray
92 Underlying data, as a record array.
93 _mask : boolean array
94 Mask of the records. A record is masked when all its fields are
95 masked.
96 _fieldmask : boolean recarray
97 Record array of booleans, setting the mask of each individual field
98 of each record.
99 _fill_value : record
100 Filling values for each field.
102 """
104 def __new__(cls, shape, dtype=None, buf=None, offset=0, strides=None,
105 formats=None, names=None, titles=None,
106 byteorder=None, aligned=False,
107 mask=nomask, hard_mask=False, fill_value=None, keep_mask=True,
108 copy=False,
109 **options):
111 self = recarray.__new__(cls, shape, dtype=dtype, buf=buf, offset=offset,
112 strides=strides, formats=formats, names=names,
113 titles=titles, byteorder=byteorder,
114 aligned=aligned,)
116 mdtype = ma.make_mask_descr(self.dtype)
117 if mask is nomask or not np.size(mask):
118 if not keep_mask:
119 self._mask = tuple([False] * len(mdtype))
120 else:
121 mask = np.array(mask, copy=copy)
122 if mask.shape != self.shape:
123 (nd, nm) = (self.size, mask.size)
124 if nm == 1:
125 mask = np.resize(mask, self.shape)
126 elif nm == nd:
127 mask = np.reshape(mask, self.shape)
128 else:
129 msg = "Mask and data not compatible: data size is %i, " + \
130 "mask size is %i."
131 raise MAError(msg % (nd, nm))
132 copy = True
133 if not keep_mask:
134 self.__setmask__(mask)
135 self._sharedmask = True
136 else:
137 if mask.dtype == mdtype:
138 _mask = mask
139 else:
140 _mask = np.array([tuple([m] * len(mdtype)) for m in mask],
141 dtype=mdtype)
142 self._mask = _mask
143 return self
145 def __array_finalize__(self, obj):
146 # Make sure we have a _fieldmask by default
147 _mask = getattr(obj, '_mask', None)
148 if _mask is None:
149 objmask = getattr(obj, '_mask', nomask)
150 _dtype = ndarray.__getattribute__(self, 'dtype')
151 if objmask is nomask:
152 _mask = ma.make_mask_none(self.shape, dtype=_dtype)
153 else:
154 mdescr = ma.make_mask_descr(_dtype)
155 _mask = narray([tuple([m] * len(mdescr)) for m in objmask],
156 dtype=mdescr).view(recarray)
157 # Update some of the attributes
158 _dict = self.__dict__
159 _dict.update(_mask=_mask)
160 self._update_from(obj)
161 if _dict['_baseclass'] == ndarray:
162 _dict['_baseclass'] = recarray
163 return
165 @property
166 def _data(self):
167 """
168 Returns the data as a recarray.
170 """
171 return ndarray.view(self, recarray)
173 @property
174 def _fieldmask(self):
175 """
176 Alias to mask.
178 """
179 return self._mask
181 def __len__(self):
182 """
183 Returns the length
185 """
186 # We have more than one record
187 if self.ndim:
188 return len(self._data)
189 # We have only one record: return the nb of fields
190 return len(self.dtype)
192 def __getattribute__(self, attr):
193 try:
194 return object.__getattribute__(self, attr)
195 except AttributeError:
196 # attr must be a fieldname
197 pass
198 fielddict = ndarray.__getattribute__(self, 'dtype').fields
199 try:
200 res = fielddict[attr][:2]
201 except (TypeError, KeyError):
202 raise AttributeError("record array has no attribute %s" % attr)
203 # So far, so good
204 _localdict = ndarray.__getattribute__(self, '__dict__')
205 _data = ndarray.view(self, _localdict['_baseclass'])
206 obj = _data.getfield(*res)
207 if obj.dtype.names is not None:
208 raise NotImplementedError("MaskedRecords is currently limited to"
209 "simple records.")
210 # Get some special attributes
211 # Reset the object's mask
212 hasmasked = False
213 _mask = _localdict.get('_mask', None)
214 if _mask is not None:
215 try:
216 _mask = _mask[attr]
217 except IndexError:
218 # Couldn't find a mask: use the default (nomask)
219 pass
220 tp_len = len(_mask.dtype)
221 hasmasked = _mask.view((bool, ((tp_len,) if tp_len else ()))).any()
222 if (obj.shape or hasmasked):
223 obj = obj.view(MaskedArray)
224 obj._baseclass = ndarray
225 obj._isfield = True
226 obj._mask = _mask
227 # Reset the field values
228 _fill_value = _localdict.get('_fill_value', None)
229 if _fill_value is not None:
230 try:
231 obj._fill_value = _fill_value[attr]
232 except ValueError:
233 obj._fill_value = None
234 else:
235 obj = obj.item()
236 return obj
238 def __setattr__(self, attr, val):
239 """
240 Sets the attribute attr to the value val.
242 """
243 # Should we call __setmask__ first ?
244 if attr in ['mask', 'fieldmask']:
245 self.__setmask__(val)
246 return
247 # Create a shortcut (so that we don't have to call getattr all the time)
248 _localdict = object.__getattribute__(self, '__dict__')
249 # Check whether we're creating a new field
250 newattr = attr not in _localdict
251 try:
252 # Is attr a generic attribute ?
253 ret = object.__setattr__(self, attr, val)
254 except Exception:
255 # Not a generic attribute: exit if it's not a valid field
256 fielddict = ndarray.__getattribute__(self, 'dtype').fields or {}
257 optinfo = ndarray.__getattribute__(self, '_optinfo') or {}
258 if not (attr in fielddict or attr in optinfo):
259 raise
260 else:
261 # Get the list of names
262 fielddict = ndarray.__getattribute__(self, 'dtype').fields or {}
263 # Check the attribute
264 if attr not in fielddict:
265 return ret
266 if newattr:
267 # We just added this one or this setattr worked on an
268 # internal attribute.
269 try:
270 object.__delattr__(self, attr)
271 except Exception:
272 return ret
273 # Let's try to set the field
274 try:
275 res = fielddict[attr][:2]
276 except (TypeError, KeyError):
277 raise AttributeError("record array has no attribute %s" % attr)
279 if val is masked:
280 _fill_value = _localdict['_fill_value']
281 if _fill_value is not None:
282 dval = _localdict['_fill_value'][attr]
283 else:
284 dval = val
285 mval = True
286 else:
287 dval = filled(val)
288 mval = getmaskarray(val)
289 obj = ndarray.__getattribute__(self, '_data').setfield(dval, *res)
290 _localdict['_mask'].__setitem__(attr, mval)
291 return obj
293 def __getitem__(self, indx):
294 """
295 Returns all the fields sharing the same fieldname base.
297 The fieldname base is either `_data` or `_mask`.
299 """
300 _localdict = self.__dict__
301 _mask = ndarray.__getattribute__(self, '_mask')
302 _data = ndarray.view(self, _localdict['_baseclass'])
303 # We want a field
304 if isinstance(indx, str):
305 # Make sure _sharedmask is True to propagate back to _fieldmask
306 # Don't use _set_mask, there are some copies being made that
307 # break propagation Don't force the mask to nomask, that wreaks
308 # easy masking
309 obj = _data[indx].view(MaskedArray)
310 obj._mask = _mask[indx]
311 obj._sharedmask = True
312 fval = _localdict['_fill_value']
313 if fval is not None:
314 obj._fill_value = fval[indx]
315 # Force to masked if the mask is True
316 if not obj.ndim and obj._mask:
317 return masked
318 return obj
319 # We want some elements.
320 # First, the data.
321 obj = np.array(_data[indx], copy=False).view(mrecarray)
322 obj._mask = np.array(_mask[indx], copy=False).view(recarray)
323 return obj
325 def __setitem__(self, indx, value):
326 """
327 Sets the given record to value.
329 """
330 MaskedArray.__setitem__(self, indx, value)
331 if isinstance(indx, str):
332 self._mask[indx] = ma.getmaskarray(value)
334 def __str__(self):
335 """
336 Calculates the string representation.
338 """
339 if self.size > 1:
340 mstr = ["(%s)" % ",".join([str(i) for i in s])
341 for s in zip(*[getattr(self, f) for f in self.dtype.names])]
342 return "[%s]" % ", ".join(mstr)
343 else:
344 mstr = ["%s" % ",".join([str(i) for i in s])
345 for s in zip([getattr(self, f) for f in self.dtype.names])]
346 return "(%s)" % ", ".join(mstr)
348 def __repr__(self):
349 """
350 Calculates the repr representation.
352 """
353 _names = self.dtype.names
354 fmt = "%%%is : %%s" % (max([len(n) for n in _names]) + 4,)
355 reprstr = [fmt % (f, getattr(self, f)) for f in self.dtype.names]
356 reprstr.insert(0, 'masked_records(')
357 reprstr.extend([fmt % (' fill_value', self.fill_value),
358 ' )'])
359 return str("\n".join(reprstr))
361 def view(self, dtype=None, type=None):
362 """
363 Returns a view of the mrecarray.
365 """
366 # OK, basic copy-paste from MaskedArray.view.
367 if dtype is None:
368 if type is None:
369 output = ndarray.view(self)
370 else:
371 output = ndarray.view(self, type)
372 # Here again.
373 elif type is None:
374 try:
375 if issubclass(dtype, ndarray):
376 output = ndarray.view(self, dtype)
377 dtype = None
378 else:
379 output = ndarray.view(self, dtype)
380 # OK, there's the change
381 except TypeError:
382 dtype = np.dtype(dtype)
383 # we need to revert to MaskedArray, but keeping the possibility
384 # of subclasses (eg, TimeSeriesRecords), so we'll force a type
385 # set to the first parent
386 if dtype.fields is None:
387 basetype = self.__class__.__bases__[0]
388 output = self.__array__().view(dtype, basetype)
389 output._update_from(self)
390 else:
391 output = ndarray.view(self, dtype)
392 output._fill_value = None
393 else:
394 output = ndarray.view(self, dtype, type)
395 # Update the mask, just like in MaskedArray.view
396 if (getattr(output, '_mask', nomask) is not nomask):
397 mdtype = ma.make_mask_descr(output.dtype)
398 output._mask = self._mask.view(mdtype, ndarray)
399 output._mask.shape = output.shape
400 return output
402 def harden_mask(self):
403 """
404 Forces the mask to hard.
406 """
407 self._hardmask = True
409 def soften_mask(self):
410 """
411 Forces the mask to soft
413 """
414 self._hardmask = False
416 def copy(self):
417 """
418 Returns a copy of the masked record.
420 """
421 copied = self._data.copy().view(type(self))
422 copied._mask = self._mask.copy()
423 return copied
425 def tolist(self, fill_value=None):
426 """
427 Return the data portion of the array as a list.
429 Data items are converted to the nearest compatible Python type.
430 Masked values are converted to fill_value. If fill_value is None,
431 the corresponding entries in the output list will be ``None``.
433 """
434 if fill_value is not None:
435 return self.filled(fill_value).tolist()
436 result = narray(self.filled().tolist(), dtype=object)
437 mask = narray(self._mask.tolist())
438 result[mask] = None
439 return result.tolist()
441 def __getstate__(self):
442 """Return the internal state of the masked array.
444 This is for pickling.
446 """
447 state = (1,
448 self.shape,
449 self.dtype,
450 self.flags.fnc,
451 self._data.tobytes(),
452 self._mask.tobytes(),
453 self._fill_value,
454 )
455 return state
457 def __setstate__(self, state):
458 """
459 Restore the internal state of the masked array.
461 This is for pickling. ``state`` is typically the output of the
462 ``__getstate__`` output, and is a 5-tuple:
464 - class name
465 - a tuple giving the shape of the data
466 - a typecode for the data
467 - a binary string for the data
468 - a binary string for the mask.
470 """
471 (ver, shp, typ, isf, raw, msk, flv) = state
472 ndarray.__setstate__(self, (shp, typ, isf, raw))
473 mdtype = dtype([(k, bool_) for (k, _) in self.dtype.descr])
474 self.__dict__['_mask'].__setstate__((shp, mdtype, isf, msk))
475 self.fill_value = flv
477 def __reduce__(self):
478 """
479 Return a 3-tuple for pickling a MaskedArray.
481 """
482 return (_mrreconstruct,
483 (self.__class__, self._baseclass, (0,), 'b',),
484 self.__getstate__())
486def _mrreconstruct(subtype, baseclass, baseshape, basetype,):
487 """
488 Build a new MaskedArray from the information stored in a pickle.
490 """
491 _data = ndarray.__new__(baseclass, baseshape, basetype).view(subtype)
492 _mask = ndarray.__new__(ndarray, baseshape, 'b1')
493 return subtype.__new__(subtype, _data, mask=_mask, dtype=basetype,)
495mrecarray = MaskedRecords
498###############################################################################
499# Constructors #
500###############################################################################
503def fromarrays(arraylist, dtype=None, shape=None, formats=None,
504 names=None, titles=None, aligned=False, byteorder=None,
505 fill_value=None):
506 """
507 Creates a mrecarray from a (flat) list of masked arrays.
509 Parameters
510 ----------
511 arraylist : sequence
512 A list of (masked) arrays. Each element of the sequence is first converted
513 to a masked array if needed. If a 2D array is passed as argument, it is
514 processed line by line
515 dtype : {None, dtype}, optional
516 Data type descriptor.
517 shape : {None, integer}, optional
518 Number of records. If None, shape is defined from the shape of the
519 first array in the list.
520 formats : {None, sequence}, optional
521 Sequence of formats for each individual field. If None, the formats will
522 be autodetected by inspecting the fields and selecting the highest dtype
523 possible.
524 names : {None, sequence}, optional
525 Sequence of the names of each field.
526 fill_value : {None, sequence}, optional
527 Sequence of data to be used as filling values.
529 Notes
530 -----
531 Lists of tuples should be preferred over lists of lists for faster processing.
533 """
534 datalist = [getdata(x) for x in arraylist]
535 masklist = [np.atleast_1d(getmaskarray(x)) for x in arraylist]
536 _array = recfromarrays(datalist,
537 dtype=dtype, shape=shape, formats=formats,
538 names=names, titles=titles, aligned=aligned,
539 byteorder=byteorder).view(mrecarray)
540 _array._mask.flat = list(zip(*masklist))
541 if fill_value is not None:
542 _array.fill_value = fill_value
543 return _array
546def fromrecords(reclist, dtype=None, shape=None, formats=None, names=None,
547 titles=None, aligned=False, byteorder=None,
548 fill_value=None, mask=nomask):
549 """
550 Creates a MaskedRecords from a list of records.
552 Parameters
553 ----------
554 reclist : sequence
555 A list of records. Each element of the sequence is first converted
556 to a masked array if needed. If a 2D array is passed as argument, it is
557 processed line by line
558 dtype : {None, dtype}, optional
559 Data type descriptor.
560 shape : {None,int}, optional
561 Number of records. If None, ``shape`` is defined from the shape of the
562 first array in the list.
563 formats : {None, sequence}, optional
564 Sequence of formats for each individual field. If None, the formats will
565 be autodetected by inspecting the fields and selecting the highest dtype
566 possible.
567 names : {None, sequence}, optional
568 Sequence of the names of each field.
569 fill_value : {None, sequence}, optional
570 Sequence of data to be used as filling values.
571 mask : {nomask, sequence}, optional.
572 External mask to apply on the data.
574 Notes
575 -----
576 Lists of tuples should be preferred over lists of lists for faster processing.
578 """
579 # Grab the initial _fieldmask, if needed:
580 _mask = getattr(reclist, '_mask', None)
581 # Get the list of records.
582 if isinstance(reclist, ndarray):
583 # Make sure we don't have some hidden mask
584 if isinstance(reclist, MaskedArray):
585 reclist = reclist.filled().view(ndarray)
586 # Grab the initial dtype, just in case
587 if dtype is None:
588 dtype = reclist.dtype
589 reclist = reclist.tolist()
590 mrec = recfromrecords(reclist, dtype=dtype, shape=shape, formats=formats,
591 names=names, titles=titles,
592 aligned=aligned, byteorder=byteorder).view(mrecarray)
593 # Set the fill_value if needed
594 if fill_value is not None:
595 mrec.fill_value = fill_value
596 # Now, let's deal w/ the mask
597 if mask is not nomask:
598 mask = np.array(mask, copy=False)
599 maskrecordlength = len(mask.dtype)
600 if maskrecordlength:
601 mrec._mask.flat = mask
602 elif mask.ndim == 2:
603 mrec._mask.flat = [tuple(m) for m in mask]
604 else:
605 mrec.__setmask__(mask)
606 if _mask is not None:
607 mrec._mask[:] = _mask
608 return mrec
611def _guessvartypes(arr):
612 """
613 Tries to guess the dtypes of the str_ ndarray `arr`.
615 Guesses by testing element-wise conversion. Returns a list of dtypes.
616 The array is first converted to ndarray. If the array is 2D, the test
617 is performed on the first line. An exception is raised if the file is
618 3D or more.
620 """
621 vartypes = []
622 arr = np.asarray(arr)
623 if arr.ndim == 2:
624 arr = arr[0]
625 elif arr.ndim > 2:
626 raise ValueError("The array should be 2D at most!")
627 # Start the conversion loop.
628 for f in arr:
629 try:
630 int(f)
631 except (ValueError, TypeError):
632 try:
633 float(f)
634 except (ValueError, TypeError):
635 try:
636 complex(f)
637 except (ValueError, TypeError):
638 vartypes.append(arr.dtype)
639 else:
640 vartypes.append(np.dtype(complex))
641 else:
642 vartypes.append(np.dtype(float))
643 else:
644 vartypes.append(np.dtype(int))
645 return vartypes
648def openfile(fname):
649 """
650 Opens the file handle of file `fname`.
652 """
653 # A file handle
654 if hasattr(fname, 'readline'):
655 return fname
656 # Try to open the file and guess its type
657 try:
658 f = open(fname)
659 except IOError:
660 raise IOError("No such file: '%s'" % fname)
661 if f.readline()[:2] != "\\x":
662 f.seek(0, 0)
663 return f
664 f.close()
665 raise NotImplementedError("Wow, binary file")
668def fromtextfile(fname, delimitor=None, commentchar='#', missingchar='',
669 varnames=None, vartypes=None):
670 """
671 Creates a mrecarray from data stored in the file `filename`.
673 Parameters
674 ----------
675 fname : {file name/handle}
676 Handle of an opened file.
677 delimitor : {None, string}, optional
678 Alphanumeric character used to separate columns in the file.
679 If None, any (group of) white spacestring(s) will be used.
680 commentchar : {'#', string}, optional
681 Alphanumeric character used to mark the start of a comment.
682 missingchar : {'', string}, optional
683 String indicating missing data, and used to create the masks.
684 varnames : {None, sequence}, optional
685 Sequence of the variable names. If None, a list will be created from
686 the first non empty line of the file.
687 vartypes : {None, sequence}, optional
688 Sequence of the variables dtypes. If None, it will be estimated from
689 the first non-commented line.
692 Ultra simple: the varnames are in the header, one line"""
693 # Try to open the file.
694 ftext = openfile(fname)
696 # Get the first non-empty line as the varnames
697 while True:
698 line = ftext.readline()
699 firstline = line[:line.find(commentchar)].strip()
700 _varnames = firstline.split(delimitor)
701 if len(_varnames) > 1:
702 break
703 if varnames is None:
704 varnames = _varnames
706 # Get the data.
707 _variables = masked_array([line.strip().split(delimitor) for line in ftext
708 if line[0] != commentchar and len(line) > 1])
709 (_, nfields) = _variables.shape
710 ftext.close()
712 # Try to guess the dtype.
713 if vartypes is None:
714 vartypes = _guessvartypes(_variables[0])
715 else:
716 vartypes = [np.dtype(v) for v in vartypes]
717 if len(vartypes) != nfields:
718 msg = "Attempting to %i dtypes for %i fields!"
719 msg += " Reverting to default."
720 warnings.warn(msg % (len(vartypes), nfields), stacklevel=2)
721 vartypes = _guessvartypes(_variables[0])
723 # Construct the descriptor.
724 mdescr = [(n, f) for (n, f) in zip(varnames, vartypes)]
725 mfillv = [ma.default_fill_value(f) for f in vartypes]
727 # Get the data and the mask.
728 # We just need a list of masked_arrays. It's easier to create it like that:
729 _mask = (_variables.T == missingchar)
730 _datalist = [masked_array(a, mask=m, dtype=t, fill_value=f)
731 for (a, m, t, f) in zip(_variables.T, _mask, vartypes, mfillv)]
733 return fromarrays(_datalist, dtype=mdescr)
736def addfield(mrecord, newfield, newfieldname=None):
737 """Adds a new field to the masked record array
739 Uses `newfield` as data and `newfieldname` as name. If `newfieldname`
740 is None, the new field name is set to 'fi', where `i` is the number of
741 existing fields.
743 """
744 _data = mrecord._data
745 _mask = mrecord._mask
746 if newfieldname is None or newfieldname in reserved_fields:
747 newfieldname = 'f%i' % len(_data.dtype)
748 newfield = ma.array(newfield)
749 # Get the new data.
750 # Create a new empty recarray
751 newdtype = np.dtype(_data.dtype.descr + [(newfieldname, newfield.dtype)])
752 newdata = recarray(_data.shape, newdtype)
753 # Add the existing field
754 [newdata.setfield(_data.getfield(*f), *f)
755 for f in _data.dtype.fields.values()]
756 # Add the new field
757 newdata.setfield(newfield._data, *newdata.dtype.fields[newfieldname])
758 newdata = newdata.view(MaskedRecords)
759 # Get the new mask
760 # Create a new empty recarray
761 newmdtype = np.dtype([(n, bool_) for n in newdtype.names])
762 newmask = recarray(_data.shape, newmdtype)
763 # Add the old masks
764 [newmask.setfield(_mask.getfield(*f), *f)
765 for f in _mask.dtype.fields.values()]
766 # Add the mask of the new field
767 newmask.setfield(getmaskarray(newfield),
768 *newmask.dtype.fields[newfieldname])
769 newdata._mask = newmask
770 return newdata