Hide keyboard shortcuts

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` 

2 

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. 

7 

8.. moduleauthor:: Pierre Gerard-Marchant 

9 

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 ? 

15 

16import warnings 

17 

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 ) 

25 

26_byteorderconv = np.core.records._byteorderconv 

27 

28import numpy.ma as ma 

29from numpy.ma import ( 

30 MAError, MaskedArray, masked, nomask, masked_array, getdata, 

31 getmaskarray, filled 

32 ) 

33 

34_check_fill_value = ma.core._check_fill_value 

35 

36 

37__all__ = [ 

38 'MaskedRecords', 'mrecarray', 'fromarrays', 'fromrecords', 

39 'fromtextfile', 'addfield', 

40 ] 

41 

42reserved_fields = ['_data', '_mask', '_fieldmask', 'dtype'] 

43 

44 

45def _checknames(descr, names=None): 

46 """ 

47 Checks that field names ``descr`` are not reserved keywords. 

48 

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. 

51 

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) 

77 

78 

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 

84 

85 

86class MaskedRecords(MaskedArray): 

87 """ 

88 

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. 

101 

102 """ 

103 

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): 

110 

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,) 

115 

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 

144 

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 

164 

165 @property 

166 def _data(self): 

167 """ 

168 Returns the data as a recarray. 

169 

170 """ 

171 return ndarray.view(self, recarray) 

172 

173 @property 

174 def _fieldmask(self): 

175 """ 

176 Alias to mask. 

177 

178 """ 

179 return self._mask 

180 

181 def __len__(self): 

182 """ 

183 Returns the length 

184 

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) 

191 

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 

237 

238 def __setattr__(self, attr, val): 

239 """ 

240 Sets the attribute attr to the value val. 

241 

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) 

278 

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 

292 

293 def __getitem__(self, indx): 

294 """ 

295 Returns all the fields sharing the same fieldname base. 

296 

297 The fieldname base is either `_data` or `_mask`. 

298 

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 

324 

325 def __setitem__(self, indx, value): 

326 """ 

327 Sets the given record to value. 

328 

329 """ 

330 MaskedArray.__setitem__(self, indx, value) 

331 if isinstance(indx, str): 

332 self._mask[indx] = ma.getmaskarray(value) 

333 

334 def __str__(self): 

335 """ 

336 Calculates the string representation. 

337 

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) 

347 

348 def __repr__(self): 

349 """ 

350 Calculates the repr representation. 

351 

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)) 

360 

361 def view(self, dtype=None, type=None): 

362 """ 

363 Returns a view of the mrecarray. 

364 

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 

401 

402 def harden_mask(self): 

403 """ 

404 Forces the mask to hard. 

405 

406 """ 

407 self._hardmask = True 

408 

409 def soften_mask(self): 

410 """ 

411 Forces the mask to soft 

412 

413 """ 

414 self._hardmask = False 

415 

416 def copy(self): 

417 """ 

418 Returns a copy of the masked record. 

419 

420 """ 

421 copied = self._data.copy().view(type(self)) 

422 copied._mask = self._mask.copy() 

423 return copied 

424 

425 def tolist(self, fill_value=None): 

426 """ 

427 Return the data portion of the array as a list. 

428 

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``. 

432 

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() 

440 

441 def __getstate__(self): 

442 """Return the internal state of the masked array. 

443 

444 This is for pickling. 

445 

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 

456 

457 def __setstate__(self, state): 

458 """ 

459 Restore the internal state of the masked array. 

460 

461 This is for pickling. ``state`` is typically the output of the 

462 ``__getstate__`` output, and is a 5-tuple: 

463 

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. 

469 

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 

476 

477 def __reduce__(self): 

478 """ 

479 Return a 3-tuple for pickling a MaskedArray. 

480 

481 """ 

482 return (_mrreconstruct, 

483 (self.__class__, self._baseclass, (0,), 'b',), 

484 self.__getstate__()) 

485 

486def _mrreconstruct(subtype, baseclass, baseshape, basetype,): 

487 """ 

488 Build a new MaskedArray from the information stored in a pickle. 

489 

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,) 

494 

495mrecarray = MaskedRecords 

496 

497 

498############################################################################### 

499# Constructors # 

500############################################################################### 

501 

502 

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. 

508 

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. 

528 

529 Notes 

530 ----- 

531 Lists of tuples should be preferred over lists of lists for faster processing. 

532 

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 

544 

545 

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. 

551 

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. 

573 

574 Notes 

575 ----- 

576 Lists of tuples should be preferred over lists of lists for faster processing. 

577 

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 

609 

610 

611def _guessvartypes(arr): 

612 """ 

613 Tries to guess the dtypes of the str_ ndarray `arr`. 

614 

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. 

619 

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 

646 

647 

648def openfile(fname): 

649 """ 

650 Opens the file handle of file `fname`. 

651 

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") 

666 

667 

668def fromtextfile(fname, delimitor=None, commentchar='#', missingchar='', 

669 varnames=None, vartypes=None): 

670 """ 

671 Creates a mrecarray from data stored in the file `filename`. 

672 

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. 

690 

691 

692 Ultra simple: the varnames are in the header, one line""" 

693 # Try to open the file. 

694 ftext = openfile(fname) 

695 

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 

705 

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() 

711 

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]) 

722 

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] 

726 

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)] 

732 

733 return fromarrays(_datalist, dtype=mdescr) 

734 

735 

736def addfield(mrecord, newfield, newfieldname=None): 

737 """Adds a new field to the masked record array 

738 

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. 

742 

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