tab-dataset.tab_dataset.cfield

The cfield module is part of the tab-dataset package.

It contains the classes Cfield, Cutil for Field entities.

For more information, see the user guide or the github repository.

  1# -*- coding: utf-8 -*-
  2"""
  3The `cfield` module is part of the `tab-dataset` package.
  4
  5It contains the classes `Cfield`, `Cutil` for Field entities.
  6
  7For more information, see the 
  8[user guide](https://loco-philippe.github.io/tab-dataset/docs/user_guide.html) 
  9or the [github repository](https://github.com/loco-philippe/tab-dataset).
 10"""
 11
 12from copy import copy
 13from collections import defaultdict, Counter
 14from itertools import product
 15
 16from json_ntv import Ntv
 17from json_ntv.ntv_util import NtvUtil
 18
 19from tab_analysis import AnaRelation, AnaField
 20
 21
 22@staticmethod
 23def root(leng):
 24    '''return the root Field'''
 25    return Cfield(Cutil.identity(leng), 'root')
 26
 27
 28def identity(*args, **kwargs):
 29    '''return the same value as args or kwargs'''
 30    if len(args) > 0:
 31        return args[0]
 32    if len(kwargs) > 0:
 33        return kwargs[list(kwargs.keys())[0]]
 34    return None
 35
 36
 37class Cutil:
 38    ''' common functions for Field and Dataset class'''
 39
 40    @staticmethod
 41    def identity(leng):
 42        '''return the root_field values'''
 43        return list(range(leng))
 44
 45    @staticmethod
 46    def canonorder(lenidx):
 47        '''return a list of crossed keys from a list of number of values'''
 48        listrange = [range(lidx) for lidx in lenidx]
 49        return Cutil.transpose(Cutil.list(list(product(*listrange))))
 50
 51    @staticmethod
 52    def default(values):
 53        '''return default codec and keys from a list of values'''
 54        codec = list(dict.fromkeys(values))
 55        dic = {codec[i]: i for i in range(len(codec))}
 56        keys = [dic[val] for val in values]
 57        return (codec, keys)
 58
 59    @staticmethod
 60    def dist(key1, key2, distr=False):
 61        '''return default coupling codec between two keys list and optionaly if
 62        the relationship is distributed'''
 63        if not key1 or not key2:
 64            return 0
 65        k1k2 = [tuple((v1, v2)) for v1, v2 in zip(key1, key2)]
 66        dist = len(Cutil.tocodec(k1k2))
 67        if not distr:
 68            return dist
 69        distrib = False
 70        if dist == (max(key1) + 1) * (max(key2) + 1):
 71            distrib = max(Counter(k1k2).values()) == len(key1) // dist
 72            # distrib = min(sum(map(lambda x: (x + i) % (max(a) + 1), a)) == sum(a) for i in range(1, max(a)+1)) 
 73        return [dist, distrib]
 74
 75    @staticmethod
 76    def encode_coef(lis):
 77        '''Generate a repetition coefficient for periodic list'''
 78        if len(lis) < 2:
 79            return 0
 80        coef = 1
 81        while coef != len(lis):
 82            if lis[coef-1] != lis[coef]:
 83                break
 84            coef += 1
 85        if (not len(lis) % (coef * (max(lis) + 1)) and
 86                lis == Cutil.keysfromcoef(coef, max(lis) + 1, len(lis))):
 87            return coef
 88        return 0
 89
 90    @staticmethod
 91    def funclist(value, func, *args, **kwargs):
 92        '''return the function func applied to the object value with parameters args and kwargs'''
 93        if func in (None, []):
 94            return value
 95        lis = []
 96        if not (isinstance(value, list) or value.__class__.__name__ in ['Cfield', 'Cdataset']):
 97            listval = [value]
 98        else:
 99            listval = value
100        for val in listval:
101            try:
102                lis.append(val.func(*args, **kwargs))
103            except:
104                try:
105                    lis.append(func(val, *args, **kwargs))
106                except:
107                    try:
108                        lis.append(listval.func(val, *args, **kwargs))
109                    except:
110                        try:
111                            lis.append(func(listval, val, *args, **kwargs))
112                        except:
113                            raise FieldError("unable to apply func")
114        if len(lis) == 1:
115            return lis[0]
116        return lis
117
118    @staticmethod
119    def idxfull(setidx):
120        '''return additional keys for each index in the setidx list to have crossed setidx'''
121        setcodec = [set(idx.keys) for idx in setidx]
122        lenfull = Cutil.mul([len(codec) for codec in setcodec])
123        if lenfull <= len(setidx[0]):
124            return []
125        complet = Counter(list(product(*setcodec)))
126        complet.subtract(
127            Counter(Cutil.tuple(Cutil.transpose([idx.keys for idx in setidx]))))
128        keysadd = Cutil.transpose(Cutil.list(list(complet.elements())))
129        if not keysadd:
130            return []
131        return keysadd
132
133    @staticmethod
134    def idxlink(ref, lis):
135        ''' return a dict for each different tuple (ref value, lis value)'''
136        return dict(set(zip(ref, lis)))
137        #lis = set(util.tuple(util.transpose([ref, l2])))
138        # if not len(lis) == len(set(ref)):
139        #    return {}
140        # return dict(lis)
141
142    @staticmethod
143    def is_not_equal(value, tovalue=None, **kwargs):
144        ''' return True if value and tovalue are not equal'''
145        return value.__class__.__name__ != tovalue.__class__.__name__ or \
146            value != tovalue
147
148    @staticmethod
149    def keysfromcoef(coef, period, leng=None):
150        ''' return a list of keys with periodic structure'''
151        if not leng:
152            leng = coef * period
153        return None if not (coef and period) else [(ind % (coef * period)) // coef
154                                                   for ind in range(leng)]
155
156    @staticmethod
157    def keysfromderkeys(parentkeys, derkeys):
158        '''return keys from parent keys and derkeys
159
160        *Parameters*
161
162        - **parentkeys** : list of keys from parent
163        - **derkeys** : list of derived keys
164
165        *Returns* : list of keys'''
166        return [derkeys[pkey] for pkey in parentkeys]
167
168    @staticmethod
169    def list(tuplelists):
170        '''transform a list of tuples in a list of lists'''
171        return list(map(list, tuplelists))
172
173    @staticmethod
174    def mul(values):
175        '''return the product of values in a list or tuple (math.prod)'''
176        mul = 1
177        for val in values:
178            mul *= val
179        return mul
180
181    @staticmethod
182    def reindex(oldkeys, oldcodec, newcodec):
183        '''new keys with new order of codec'''
184        dic = {newcodec[i]: i for i in range(len(newcodec))}
185        return [dic[oldcodec[key]] for key in oldkeys]
186
187    @staticmethod
188    def reorder(values, sort=None):
189        '''return a new values list following the order define by sort'''
190        if not sort:
191            return values
192        return [values[ind] for ind in sort]
193
194    @staticmethod
195    def resetidx(values):
196        '''return codec and keys from a list of values'''
197        codec = Cutil.tocodec(values)
198        return (codec, Cutil.tokeys(values, codec))
199
200    @staticmethod
201    def tocodec(values, keys=None):
202        '''extract a list of unique values'''
203        if not keys:
204            # return list(set(values))
205            return list(dict.fromkeys(values))
206        #ind, codec = zip(*sorted(set(zip(keys, values))))
207        return list(list(zip(*sorted(set(zip(keys, values)))))[1])
208
209    @staticmethod
210    def tokeys(values, codec=None):
211        ''' return a list of keys from a list of values'''
212        if not codec:
213            codec = Cutil.tocodec(values)
214        dic = {codec[i]: i for i in range(len(codec))}  # !!!!long
215        keys = [dic[val] for val in values]    # hyper long
216        return keys
217
218    @staticmethod
219    def transpose(idxlist):
220        '''exchange row/column in a list of list'''
221        # if not isinstance(idxlist, list):
222        #    raise FieldError('index not transposable')
223        # if not idxlist:
224        #    return []
225        return list(map(list, zip(*idxlist)))
226        # return [list(elmt) for elmt in zip(*idxlist)]
227        #size = min([len(ix) for ix in idxlist])
228        # return [[ix[ind] for ix in idxlist] for ind in range(size)]
229
230    @staticmethod
231    def tuple(idx):
232        '''transform a list of list in a list of tuple'''
233        return list(map(tuple, idx))
234        # return [val if not isinstance(val, list) else tuple(val) for val in idx]
235
236    @staticmethod
237    def tupled(lis):
238        '''transform a list of list in a tuple of tuple'''
239        #return tuple(val if not isinstance(val, list) else Sfield._tupled(val) for val in lis)
240        return tuple(map(Cutil.tupled, lis)) if isinstance(lis, list) else lis
241
242    @staticmethod
243    def listed(lis):
244        '''transform a tuple of tuple in a list of list'''
245        #return [val if not isinstance(val, tuple) else Cutil.listed(val) for val in lis]
246        return list(map(Cutil.listed, lis)) if isinstance(lis, tuple) else lis
247
248class Cfield:
249    # %% intro
250    '''
251    A `Cfield` is a representation of an Field list .
252
253    *Attributes (for dynamic attributes see @property methods)* :
254
255    - **name** : name of the Field
256    - **_codec** : list of values for each key
257    - **_keys** : list of code values
258
259    The methods defined in this class are :
260
261    *constructor (@classmethod)*
262
263    - `Cfield.bol`
264    - `Cfield.from_ntv`
265    - `Cfield.ntv`
266    - `Cfield.like`
267
268    *conversion static methods*
269
270    - `Cfield.ntv_to_val` (@classmethod)
271    - `Cfield.n_to_i` (@staticmethod)
272
273    *dynamic value (getters @property)*
274
275    - `Cfield.hashf`
276    - `Cfield.to_analysis`
277    - `Cfield.values`
278    - `Cfield.codec`
279    - `Cfield.infos`
280    - `Cfield.keys`
281
282    *add - update methods*
283
284    - `Cfield.add`
285    - `Cfield.append`
286    - `Cfield.setcodecvalue`
287    - `Cfield.setcodeclist`
288    - `Cfield.setname`
289    - `Cfield.set_keys`
290    - `Cfield.set_codec`
291    - `Cfield.setkeys`
292    - `Cfield.setlistvalue`
293    - `Cfield.setvalue`
294
295    *transform methods*
296
297    - `Cfield.coupling`
298    - `Cfield.extendkeys`
299    - `Cfield.full`
300    - `Cfield.reindex`
301    - `Cfield.reorder`
302    - `Cfield.sort`
303    - `Cfield.tocoupled`
304    - `Cfield.tostdcodec`
305
306    *getters methods*
307
308    - `Cfield.couplinginfos`
309    - `Cfield.derkeys`
310    - `Cfield.getduplicates`
311    - `Cfield.iscrossed`
312    - `Cfield.iscoupled`
313    - `Cfield.isderived`
314    - `Cfield.islinked`
315    - `Cfield.isvalue`
316    - `Cfield.iskeysfromderkeys`
317    - `Cfield.keytoval`
318    - `Cfield.loc`
319    - `Cfield.recordfromkeys`
320    - `Cfield.recordfromvalue`
321    - `Cfield.valtokey`
322    '''
323
324    def __init__(self, codec=None, name=None, keys=None, default=False, reindex=False):
325        '''Two modes:
326            - a single attributes : Cfield object to copy
327            - multiple attributes : set codec, name and keys attributes'''
328        if not codec and not keys:
329            self._codec = []
330            self._keys = []
331        elif isinstance(codec, Cfield):
332            self._keys = codec._keys
333            self._codec = codec._codec
334            self.name = codec.name
335            return
336        elif not default:
337            self._keys = keys if keys else Cutil.identity(len(codec))
338            self._codec = codec if codec else Cutil.identity(len(keys))
339        else:
340            self._codec, self._keys = Cutil.default(codec)
341        self.name = name if name else 'field'
342        if reindex:
343            self.reindex()
344        return
345
346    def __repr__(self):
347        '''return classname and number of value'''
348        return self.__class__.__name__ + '[' + str(len(self)) + ']'
349
350    def __str__(self):
351        '''return json string format'''
352        return str({self.name: self.values})
353
354    def __eq__(self, other):
355        ''' equal if class and values are equal'''
356        return self.__class__ .__name__ == other.__class__.__name__ and \
357            self.values == other.values
358
359    def __len__(self):
360        ''' len of values'''
361        return len(self._keys)
362
363    def __contains__(self, item):
364        ''' item of values'''
365        return item in self.values
366
367    def __getitem__(self, ind):
368        ''' return value item (value conversion)'''
369        if isinstance(ind, tuple):
370            return [copy(self.values[i]) for i in ind]
371        # return self.values[ind]
372        return copy(self.values[ind])
373
374    def __setitem__(self, ind, item):
375        ''' modify values item'''
376        if isinstance(ind, slice):
377            start, stop, step = ind.start or 0, ind.stop or len(self), ind.step or 1
378            idxt = list(iter(range(start, stop, step)))
379            if len(idxt) != len(item):
380                raise FieldError("item length not consistent")
381            self.setlistvalue(item, idxt)
382        elif ind < 0 or ind >= len(self):
383            raise FieldError("out of bounds")
384        else: 
385            self.setvalue(ind, item)
386
387    def __delitem__(self, ind):
388        '''remove a record (value and key).'''
389        self._keys.pop(ind)
390        self.reindex()
391
392    def __hash__(self):
393        '''return hash(values)'''
394        return hash(tuple(self.values))
395
396    def _hashe(self):
397        '''return hash(values)'''
398        return hash(tuple(self.values))
399
400    def __add__(self, other):
401        ''' Add other's values to self's values in a new Field'''
402        newiindex = self.__copy__()
403        newiindex.__iadd__(other)
404        return newiindex
405
406    def __iadd__(self, other):
407        ''' Add other's values to self's values'''
408        return self.add(other, solve=False)
409
410    def __copy__(self):
411        ''' Copy all the data '''
412        return self.__class__(self)
413
414    # %% property
415    @property
416    def hashf(self):
417        '''return hash(codec infos and keys)'''
418        return hash(tuple((len(self.codec), len(set(self.codec)), len(self),
419                           self.name, tuple(self._keys))))
420
421    @property
422    def to_analysis(self):
423        '''return data for AnaField module'''
424        return {'maxcodec': len(self), 'lencodec': len(self.codec), 'id': self.name,
425                'mincodec': len(set(self.codec)), 'hashf': self.hashf}
426
427    @property
428    def codec(self):
429        '''return codec  '''
430        return self._codec
431
432    @property
433    def infos(self):
434        '''return dict with lencodec, typecodec, ratecodec, mincodec, maxcodec'''
435        return AnaField(self.to_analysis).to_dict(full=True)
436
437    @property
438    def keys(self):
439        '''return keys  '''
440        return self._keys
441
442    @property
443    def values(self):
444        '''return values (see data model)'''
445        return [self._codec[key] for key in self._keys]
446
447    # %% class methods
448    @classmethod
449    def from_ntv(cls, ntv_value=None, extkeys=None, reindex=True, decode_str=False,
450                 add_type=True, lengkeys=None):
451        '''Generate an Field Object from a Ntv field object'''
452        if isinstance(ntv_value, cls):
453            return copy(ntv_value)
454        if ntv_value is None:
455            return cls()
456        ntv = Ntv.obj(ntv_value, decode_str=decode_str)
457        #ntv = NtvList(ntv_value)
458        name, typ, codec, parent, keys, coef, leng = NtvUtil.decode_ntv_tab(
459            ntv, cls.ntv_to_val)
460        if parent and not extkeys:
461            return None
462        if coef:
463            keys = Cutil.keysfromcoef(coef, leng//coef, lengkeys)
464        elif extkeys and parent:
465            keys = Cutil.keysfromderkeys(extkeys, keys)
466        elif extkeys and not parent:
467            keys = extkeys
468        keys = list(range(len(codec))) if keys is None else keys
469        name = ntv.json_name(string=True) if add_type else name
470        return cls(codec=codec, name=name, keys=keys, reindex=reindex)
471
472    @classmethod
473    def bol(cls, leng, notdef=None, name=None, default=True):
474        '''
475        Field constructor (boolean value).
476
477        *Parameters*
478
479        - **leng** : integer - length of the Field
480        - **notdef** : list (default None) - list of records without default value
481        - **default** : boolean (default True) - default value
482        - **name** : string (default None) - name of Field'''
483        values = [default] * leng
484        if notdef:
485            for item in notdef:
486                values[item] = not default
487        return cls.ntv({name: values})
488
489    @classmethod
490    def like(cls, codec, parent, name=None, reindex=False):
491        '''Generate an Field Object from specific codec and keys from another field.
492
493        *Parameters*
494
495        - **codec** : list of objects
496        - **name** : string (default None) - name of index (see data model)
497        - **parent** : Field, parent of the new Field
498        - **reindex** : boolean (default True) - if True, default codec is apply
499
500        *Returns* : Field '''
501        if isinstance(codec, Cfield):
502            return copy(codec)
503        return cls(codec=codec, name=name, keys=parent.keys, reindex=reindex)
504
505    @classmethod
506    def ntv(cls, ntv_value=None, extkeys=None, reindex=True, decode_str=False):
507        '''Generate an Field Object from a Ntv field object'''
508        return cls.from_ntv(ntv_value, extkeys=extkeys, reindex=reindex, decode_str=decode_str)
509
510    @classmethod
511    def ntv_to_val(cls, ntv):
512        '''conversion in decode_ntv_val method'''
513        return cls.n_to_i(ntv.val)
514
515    # %% static methods
516    @staticmethod
517    def n_to_i(ntv_lis):
518        ''' converting a NtvList value to an internal value'''
519        if isinstance(ntv_lis, list) and len(ntv_lis) == 0:
520            return []
521        if isinstance(ntv_lis, list) and ntv_lis[0].__class__.__name__ in ('NtvSingle', 'NtvList'):
522            return [Cfield.n_to_i(ntv.to_obj()) for ntv in ntv_lis]
523        return ntv_lis
524
525    # %% instance methods
526    def add(self, other, solve=True):
527        ''' Add other's values to self's values
528
529        *Parameters*
530
531        - **other** : Field object to add to self object
532        - **solve** : Boolean (default True) - If True, replace None other's codec value
533        with self codec value.
534
535        *Returns* : self '''
536        if solve:
537            solved = copy(other)
538            for i in range(len(solved.codec)):
539                if solved.codec[i] is None and i in range(len(self.codec)):
540                    solved._codec[i] = self.codec[i]
541            values = self.values + solved.values
542        else:
543            values = self.values + other.values
544        codec = Cutil.tocodec(values)
545        if set(codec) != set(self._codec):
546            self._codec = codec
547        self._keys = Cutil.tokeys(values, self._codec)
548        return self
549
550    def append(self, value, unique=True):
551        '''add a new value
552
553        *Parameters*
554
555        - **value** : new object value
556        - **unique** :  boolean (default True) - If False, duplication codec if value is present
557
558        *Returns* : key of value '''
559        #value = Ntv.obj(value)
560        #value = self.s_to_i(value)
561        if value in self._codec and unique:
562            key = self._codec.index(value)
563        else:
564            key = len(self._codec)
565            self._codec.append(value)
566        self._keys.append(key)
567        return key
568
569    def coupling(self, idx, derived=True, duplicate=True, reindex=False):
570        '''
571        Transform indexes in coupled or derived indexes (codec extension).
572        If derived option is True, self._codec is extended and idx codec not,
573        else, both are coupled and both codec are extended.
574
575        *Parameters*
576
577        - **idx** : single Field or list of Field to be coupled or derived.
578        - **derived** : boolean (default : True) - if True result is derived,
579        if False coupled
580        - **duplicate** : boolean (default: True) - if True, return duplicate records
581        (only for self index)
582        - **reindex** : boolean (default : False). If True self.index is reindexed
583        with default codec. But if not derived, idx indexes MUST to be reindexed.
584
585        *Returns* : tuple with duplicate records (errors) if 'duplicate', None else'''
586        duplic = tuple()
587        if not isinstance(idx, list):
588            index = [idx]
589        else:
590            index = idx
591        idxzip = self.__class__(list(zip(*([self.keys] + [ix.keys for ix in index]))),
592                                reindex=True)
593        self.tocoupled(idxzip)        
594        if not derived:
595            for ind in index:
596                ind.tocoupled(idxzip)
597                duplic += ind.getduplicates(reindex)
598        if duplicate and not duplic:
599            return self.getduplicates(reindex)
600        if duplicate and duplic:
601            return tuple(sorted(list(set(duplic + self.getduplicates(reindex))))) 
602        if reindex:
603            self.reindex()
604        return None
605
606    def couplinginfos(self, other):
607        '''return a dict with the coupling info between other (distance, ratecpl,
608        rateder, dist, disttomin, disttomax, distmin, distmax, diff, typecoupl)
609
610        *Parameters*
611
612        - **other** : other index to compare
613
614        *Returns* : dict'''
615        if min(len(self), len(other)) == 0:
616            null = Cfield()
617            return AnaRelation([AnaField(null.to_analysis), AnaField(null.to_analysis)],
618                               Cutil.dist(null.keys, null.keys, True)
619                               ).to_dict(distances=True, misc=True)
620        return AnaRelation([AnaField(self.to_analysis), AnaField(other.to_analysis)],
621                           Cutil.dist(self.keys, other.keys, True)
622                           ).to_dict(distances=True, misc=True)
623
624    def derkeys(self, parent):
625        '''return keys derived from parent keys
626
627        *Parameters*
628
629        - **parent** : Field - parent
630
631        *Returns* : list of keys'''
632        derkey = [-1] * len(parent.codec)
633        for i in range(len(self)):
634            derkey[parent.keys[i]] = self.keys[i]
635        if min(derkey) < 0:
636            raise FieldError("parent is not a derive Field")
637        return derkey
638
639    def extendkeys(self, keys):
640        '''add keys to the Field
641
642        *Parameters*
643
644        - **keys** : list of int (value lower or equal than actual keys)
645
646        *Returns* : None '''
647        if min(keys) < 0 or max(keys) > len(self._codec) - 1:
648            raise FieldError('keys not consistent with codec')
649        self._keys += keys
650
651    @staticmethod
652    def full(listidx):
653        '''tranform a list of indexes in crossed indexes (value extension).
654
655        *Parameters*
656
657        - **listidx** : list of Field to transform
658
659        *Returns* : tuple of records added '''
660        idx1 = listidx[0]
661        for idx in listidx:
662            if len(idx) != len(idx):
663                return None
664        leninit = len(idx1)
665        keysadd = Cutil.idxfull(listidx)
666        for idx, keys in zip(listidx, keysadd):
667            idx._keys += keys
668        return tuple(range(leninit, len(idx1)))
669
670    def getduplicates(self, reindex=False):
671        ''' calculate items with duplicate codec
672
673        *Parameters*
674
675        - **reindex** : boolean (default : False). If True index is reindexed with default codec
676
677        *Returns* : tuple of items with duplicate codec'''
678        count = Counter(self._codec)
679        defcodec = list(count - Counter(list(count)))
680        dkeys = defaultdict(list)
681        for key, ind in zip(self._keys, range(len(self))):
682            dkeys[key].append(ind)
683        dcodec = defaultdict(list)
684        for key, ind in zip(self._codec, range(len(self._codec))):
685            dcodec[key].append(ind)
686        duplicates = []
687        for item in defcodec:
688            for codecitem in dcodec[item]:
689                duplicates += dkeys[codecitem]
690        if reindex:
691            self.reindex()
692        return tuple(duplicates)
693
694    def iscrossed(self, other):
695        '''return True if self is crossed to other'''
696        return self.couplinginfos(other)['rateder'] == 1.0
697
698    def iscoupled(self, other):
699        '''return True if self is coupled to other'''
700        info = self.couplinginfos(other)
701        return info['diff'] == 0 and info['rateder'] == 0.0
702
703    def isderived(self, other, only=False):
704        '''return True if self is derived from other'''
705        info = self.couplinginfos(other)
706        return not (info['diff'] == 0 and only) and info['rateder'] == 0.0
707
708    def iskeysfromderkeys(self, other):
709        '''return True if self._keys is relative from other._keys'''
710        leng = len(other.codec)
711        if leng % len(self._codec) != 0:
712            return False
713        keys = [(i*len(self._codec))//leng for i in range(leng)]
714        return Cutil.keysfromderkeys(other.keys, keys) == self.keys
715
716    def islinked(self, other):
717        '''return True if self is linked to other'''
718        rate = self.couplinginfos(other)['rateder']
719        return 0.0 < rate < 1.0
720
721    def isvalue(self, value):
722        ''' return True if value is in index values
723
724        *Parameters*
725
726        - **value** : value to check'''
727        return value in self.values
728
729    def keytoval(self, key):
730        ''' return the value of a key
731
732        *Parameters*
733
734        - **key** : key to convert into values
735        - **extern** : if True, return string representation else, internal value
736
737        *Returns*
738
739        - **int** : first key finded (None else)'''
740        if key < 0 or key >= len(self._codec):
741            return None
742        return self._codec[key]
743
744    def loc(self, value):
745        '''return a list of record number with value
746
747        *Parameters*
748
749        - **value** : value to check
750
751        *Returns*
752
753        - **list of int** : list of record number finded (None else)'''
754        return self.recordfromvalue(value)
755
756    def recordfromvalue(self, value):
757        '''return a list of record number with value
758
759        *Parameters*
760
761        - **value** : value to check
762        - **extern** : if True, compare value to external representation of self.value,
763        else, internal
764
765        *Returns*
766
767        - **list of int** : list of record number finded (None else)'''
768
769        if not value in self._codec:
770            return None
771        listkeys = [cod for cod, val in zip(
772            range(len(self._codec)), self._codec) if val == value]
773        return self.recordfromkeys(listkeys)
774
775    def recordfromkeys(self, listkeys):
776        '''return a list of record number with key in listkeys
777
778        *Parameters*
779
780        - **listkeys** : list of keys to check
781
782        *Returns*
783
784        - **list of int** : list of record number finded (None else)'''
785
786        return [rec for rec, key in zip(range(len(self)), self._keys) if key in listkeys]
787
788    def reindex(self, codec=None):
789        '''apply a reordered codec. If None, a new default codec is apply.
790
791        *Parameters*
792
793        - **codec** : list (default None) - reordered codec to apply.
794
795        *Returns* : self'''
796
797        if not codec:
798            codec = Cutil.tocodec(self.values)
799        self._keys = Cutil.reindex(self._keys, self._codec, codec)
800        self._codec = codec
801        return self
802
803    def reorder(self, sort=None, inplace=True):
804        '''Change the Field order with a new order define by sort and reset the codec.
805
806        *Parameters*
807
808        - **sort** : int list (default None)- new record order to apply. If None, no change.
809        - **inplace** : boolean (default True) - if True, new order is apply to self,
810        if False a new Field is created.
811
812        *Returns*
813
814        - **Field** : self if inplace, new Field if not inplace'''
815        values = Cutil.reorder(self.values, sort)
816        codec, keys = Cutil.resetidx(values)
817        if inplace:
818            self._keys = keys
819            self._codec = codec
820            return None
821        return self.__class__(name=self.name, codec=codec, keys=keys)
822
823    def setcodecvalue(self, oldvalue, newvalue):
824        '''update all the oldvalue by newvalue
825
826        *Parameters*
827
828        - **oldvalue** : list of values to replace
829        - **newvalue** : list of new value to apply
830
831        *Returns* : int - last codec rank updated (-1 if None)'''
832
833        rank = -1
834        for i in range(len(self._codec)):
835            if self._codec[i] == oldvalue:
836                self._codec[i] = newvalue
837                rank = i
838        return rank
839
840    def setcodeclist(self, listcodec):
841        '''update codec with listcodec values
842
843        *Parameters*
844
845        - **listcodec** : list of new codec values to apply
846
847        *Returns* : int - last codec rank updated (-1 if None)'''
848        self._codec = listcodec
849
850    def set_keys(self, keys):
851        ''' _keys setters '''
852        self._keys = keys
853
854    def set_codec(self, codec):
855        ''' _codec setters '''
856        self._codec = codec
857
858    def setkeys(self, keys, inplace=True):
859        '''apply new keys (replace codec with extended codec from parent keys)
860
861        *Parameters*
862
863        - **keys** : list of keys to apply
864        - **inplace** : if True, update self data, else create a new Field
865
866        *Returns* : self or new Field'''
867        codec = Cutil.tocodec(self.values, keys)
868        if inplace:
869            self._codec = codec
870            self._keys = keys
871            return self
872        return self.__class__(codec=codec, name=self.name, keys=keys)
873
874    def setname(self, name):
875        '''update the Field name
876
877        *Parameters*
878
879        - **name** : str to set into name
880
881        *Returns* : boolean - True if update'''
882        if isinstance(name, str):
883            self.name = name
884            return True
885        return False
886
887    def setvalue(self, ind, value):
888        '''update a value at the rank ind (and update codec and keys)
889
890        *Parameters*
891
892        - **ind** : rank of the value
893        - **value** : new value
894
895        *Returns* : None'''
896        values = self.values
897        values[ind] = value
898        self._codec, self._keys = Cutil.resetidx(values)
899
900    def setlistvalue(self, listvalue, listind=None):
901        '''update the values (and update codec and keys)
902
903        *Parameters*
904
905        - **listvalue** : list - list of new values
906        - **listind** : list(default None) - list of index
907
908        *Returns* : None'''
909        values = self.values
910        listind = listind if listind else range(len(self))
911        for i, value_i in zip(listind, listvalue):
912            values[i] = value_i
913        self._codec, self._keys = Cutil.resetidx(values)
914
915    def sort(self, reverse=False, inplace=True, func=str):
916        '''Define sorted index with ordered codec.
917
918        *Parameters*
919
920        - **reverse** : boolean (defaut False) - codec is sorted with reverse order
921        - **inplace** : boolean (default True) - if True, new order is apply to self,
922        if False a new Field is created.
923        - **func**    : function (default str) - key used in the sorted function
924
925        *Return*
926
927        - **Field** : self if inplace, new Field if not inplace'''
928        if inplace:
929            self.reindex(codec=sorted(self._codec, reverse=reverse, key=func))
930            self._keys.sort()
931            return self
932        oldcodec = self._codec
933        codec = sorted(oldcodec, reverse=reverse, key=str)
934        return self.__class__(name=self.name, codec=codec,
935                              keys=sorted(Cutil.reindex(self._keys, oldcodec, codec)))
936
937    def tocoupled(self, other, coupling=True):
938        '''
939        Transform a derived index in a coupled index (keys extension) and add
940        new values to have the same length as other.
941
942        *Parameters*
943
944        - **other** : index to be coupled.
945        - **coupling** : boolean (default True) - reindex if False
946
947        *Returns* : None'''
948        dic = Cutil.idxlink(other.keys, self._keys)
949        if not dic:
950            raise FieldError("Field is not coupled or derived from other")
951        self._codec = [self._codec[dic[i]] for i in range(len(dic))]
952        self._keys = other.keys
953        if not coupling:
954            self.reindex()
955
956    def tostdcodec(self, inplace=False, full=True):
957        '''
958        Transform codec in full or in default codec.
959
960        *Parameters*
961
962        - **inplace** : boolean (default True) - if True, new order is apply to self,
963        - **full** : boolean (default True) - if True reindex with full codec
964
965        *Return*
966
967        - **Field** : self if inplace, new Field if not inplace'''
968        if full:
969            codec = self.values
970            keys = list(range(len(codec)))
971        else:
972            codec = Cutil.tocodec(self.values)
973            keys = Cutil.reindex(self._keys, self._codec, codec)
974        if inplace:
975            self._codec = codec
976            self._keys = keys
977            return self
978        return self.__class__(codec=codec, name=self.name, keys=keys)
979
980    def valtokey(self, value):
981        '''convert a value to a key
982
983        *Parameters*
984
985        - **value** : value to convert
986
987        *Returns*
988
989        - **int** : first key finded (None else)'''
990        if value in self._codec:
991            return self._codec.index(value)
992        return None
993
994
995class FieldError(Exception):
996    ''' Field Exception'''
997    # pass
@staticmethod
def root(leng):
23@staticmethod
24def root(leng):
25    '''return the root Field'''
26    return Cfield(Cutil.identity(leng), 'root')

return the root Field

def identity(*args, **kwargs):
29def identity(*args, **kwargs):
30    '''return the same value as args or kwargs'''
31    if len(args) > 0:
32        return args[0]
33    if len(kwargs) > 0:
34        return kwargs[list(kwargs.keys())[0]]
35    return None

return the same value as args or kwargs

class Cutil:
 38class Cutil:
 39    ''' common functions for Field and Dataset class'''
 40
 41    @staticmethod
 42    def identity(leng):
 43        '''return the root_field values'''
 44        return list(range(leng))
 45
 46    @staticmethod
 47    def canonorder(lenidx):
 48        '''return a list of crossed keys from a list of number of values'''
 49        listrange = [range(lidx) for lidx in lenidx]
 50        return Cutil.transpose(Cutil.list(list(product(*listrange))))
 51
 52    @staticmethod
 53    def default(values):
 54        '''return default codec and keys from a list of values'''
 55        codec = list(dict.fromkeys(values))
 56        dic = {codec[i]: i for i in range(len(codec))}
 57        keys = [dic[val] for val in values]
 58        return (codec, keys)
 59
 60    @staticmethod
 61    def dist(key1, key2, distr=False):
 62        '''return default coupling codec between two keys list and optionaly if
 63        the relationship is distributed'''
 64        if not key1 or not key2:
 65            return 0
 66        k1k2 = [tuple((v1, v2)) for v1, v2 in zip(key1, key2)]
 67        dist = len(Cutil.tocodec(k1k2))
 68        if not distr:
 69            return dist
 70        distrib = False
 71        if dist == (max(key1) + 1) * (max(key2) + 1):
 72            distrib = max(Counter(k1k2).values()) == len(key1) // dist
 73            # distrib = min(sum(map(lambda x: (x + i) % (max(a) + 1), a)) == sum(a) for i in range(1, max(a)+1)) 
 74        return [dist, distrib]
 75
 76    @staticmethod
 77    def encode_coef(lis):
 78        '''Generate a repetition coefficient for periodic list'''
 79        if len(lis) < 2:
 80            return 0
 81        coef = 1
 82        while coef != len(lis):
 83            if lis[coef-1] != lis[coef]:
 84                break
 85            coef += 1
 86        if (not len(lis) % (coef * (max(lis) + 1)) and
 87                lis == Cutil.keysfromcoef(coef, max(lis) + 1, len(lis))):
 88            return coef
 89        return 0
 90
 91    @staticmethod
 92    def funclist(value, func, *args, **kwargs):
 93        '''return the function func applied to the object value with parameters args and kwargs'''
 94        if func in (None, []):
 95            return value
 96        lis = []
 97        if not (isinstance(value, list) or value.__class__.__name__ in ['Cfield', 'Cdataset']):
 98            listval = [value]
 99        else:
100            listval = value
101        for val in listval:
102            try:
103                lis.append(val.func(*args, **kwargs))
104            except:
105                try:
106                    lis.append(func(val, *args, **kwargs))
107                except:
108                    try:
109                        lis.append(listval.func(val, *args, **kwargs))
110                    except:
111                        try:
112                            lis.append(func(listval, val, *args, **kwargs))
113                        except:
114                            raise FieldError("unable to apply func")
115        if len(lis) == 1:
116            return lis[0]
117        return lis
118
119    @staticmethod
120    def idxfull(setidx):
121        '''return additional keys for each index in the setidx list to have crossed setidx'''
122        setcodec = [set(idx.keys) for idx in setidx]
123        lenfull = Cutil.mul([len(codec) for codec in setcodec])
124        if lenfull <= len(setidx[0]):
125            return []
126        complet = Counter(list(product(*setcodec)))
127        complet.subtract(
128            Counter(Cutil.tuple(Cutil.transpose([idx.keys for idx in setidx]))))
129        keysadd = Cutil.transpose(Cutil.list(list(complet.elements())))
130        if not keysadd:
131            return []
132        return keysadd
133
134    @staticmethod
135    def idxlink(ref, lis):
136        ''' return a dict for each different tuple (ref value, lis value)'''
137        return dict(set(zip(ref, lis)))
138        #lis = set(util.tuple(util.transpose([ref, l2])))
139        # if not len(lis) == len(set(ref)):
140        #    return {}
141        # return dict(lis)
142
143    @staticmethod
144    def is_not_equal(value, tovalue=None, **kwargs):
145        ''' return True if value and tovalue are not equal'''
146        return value.__class__.__name__ != tovalue.__class__.__name__ or \
147            value != tovalue
148
149    @staticmethod
150    def keysfromcoef(coef, period, leng=None):
151        ''' return a list of keys with periodic structure'''
152        if not leng:
153            leng = coef * period
154        return None if not (coef and period) else [(ind % (coef * period)) // coef
155                                                   for ind in range(leng)]
156
157    @staticmethod
158    def keysfromderkeys(parentkeys, derkeys):
159        '''return keys from parent keys and derkeys
160
161        *Parameters*
162
163        - **parentkeys** : list of keys from parent
164        - **derkeys** : list of derived keys
165
166        *Returns* : list of keys'''
167        return [derkeys[pkey] for pkey in parentkeys]
168
169    @staticmethod
170    def list(tuplelists):
171        '''transform a list of tuples in a list of lists'''
172        return list(map(list, tuplelists))
173
174    @staticmethod
175    def mul(values):
176        '''return the product of values in a list or tuple (math.prod)'''
177        mul = 1
178        for val in values:
179            mul *= val
180        return mul
181
182    @staticmethod
183    def reindex(oldkeys, oldcodec, newcodec):
184        '''new keys with new order of codec'''
185        dic = {newcodec[i]: i for i in range(len(newcodec))}
186        return [dic[oldcodec[key]] for key in oldkeys]
187
188    @staticmethod
189    def reorder(values, sort=None):
190        '''return a new values list following the order define by sort'''
191        if not sort:
192            return values
193        return [values[ind] for ind in sort]
194
195    @staticmethod
196    def resetidx(values):
197        '''return codec and keys from a list of values'''
198        codec = Cutil.tocodec(values)
199        return (codec, Cutil.tokeys(values, codec))
200
201    @staticmethod
202    def tocodec(values, keys=None):
203        '''extract a list of unique values'''
204        if not keys:
205            # return list(set(values))
206            return list(dict.fromkeys(values))
207        #ind, codec = zip(*sorted(set(zip(keys, values))))
208        return list(list(zip(*sorted(set(zip(keys, values)))))[1])
209
210    @staticmethod
211    def tokeys(values, codec=None):
212        ''' return a list of keys from a list of values'''
213        if not codec:
214            codec = Cutil.tocodec(values)
215        dic = {codec[i]: i for i in range(len(codec))}  # !!!!long
216        keys = [dic[val] for val in values]    # hyper long
217        return keys
218
219    @staticmethod
220    def transpose(idxlist):
221        '''exchange row/column in a list of list'''
222        # if not isinstance(idxlist, list):
223        #    raise FieldError('index not transposable')
224        # if not idxlist:
225        #    return []
226        return list(map(list, zip(*idxlist)))
227        # return [list(elmt) for elmt in zip(*idxlist)]
228        #size = min([len(ix) for ix in idxlist])
229        # return [[ix[ind] for ix in idxlist] for ind in range(size)]
230
231    @staticmethod
232    def tuple(idx):
233        '''transform a list of list in a list of tuple'''
234        return list(map(tuple, idx))
235        # return [val if not isinstance(val, list) else tuple(val) for val in idx]
236
237    @staticmethod
238    def tupled(lis):
239        '''transform a list of list in a tuple of tuple'''
240        #return tuple(val if not isinstance(val, list) else Sfield._tupled(val) for val in lis)
241        return tuple(map(Cutil.tupled, lis)) if isinstance(lis, list) else lis
242
243    @staticmethod
244    def listed(lis):
245        '''transform a tuple of tuple in a list of list'''
246        #return [val if not isinstance(val, tuple) else Cutil.listed(val) for val in lis]
247        return list(map(Cutil.listed, lis)) if isinstance(lis, tuple) else lis

common functions for Field and Dataset class

@staticmethod
def identity(leng):
41    @staticmethod
42    def identity(leng):
43        '''return the root_field values'''
44        return list(range(leng))

return the root_field values

@staticmethod
def canonorder(lenidx):
46    @staticmethod
47    def canonorder(lenidx):
48        '''return a list of crossed keys from a list of number of values'''
49        listrange = [range(lidx) for lidx in lenidx]
50        return Cutil.transpose(Cutil.list(list(product(*listrange))))

return a list of crossed keys from a list of number of values

@staticmethod
def default(values):
52    @staticmethod
53    def default(values):
54        '''return default codec and keys from a list of values'''
55        codec = list(dict.fromkeys(values))
56        dic = {codec[i]: i for i in range(len(codec))}
57        keys = [dic[val] for val in values]
58        return (codec, keys)

return default codec and keys from a list of values

@staticmethod
def dist(key1, key2, distr=False):
60    @staticmethod
61    def dist(key1, key2, distr=False):
62        '''return default coupling codec between two keys list and optionaly if
63        the relationship is distributed'''
64        if not key1 or not key2:
65            return 0
66        k1k2 = [tuple((v1, v2)) for v1, v2 in zip(key1, key2)]
67        dist = len(Cutil.tocodec(k1k2))
68        if not distr:
69            return dist
70        distrib = False
71        if dist == (max(key1) + 1) * (max(key2) + 1):
72            distrib = max(Counter(k1k2).values()) == len(key1) // dist
73            # distrib = min(sum(map(lambda x: (x + i) % (max(a) + 1), a)) == sum(a) for i in range(1, max(a)+1)) 
74        return [dist, distrib]

return default coupling codec between two keys list and optionaly if the relationship is distributed

@staticmethod
def encode_coef(lis):
76    @staticmethod
77    def encode_coef(lis):
78        '''Generate a repetition coefficient for periodic list'''
79        if len(lis) < 2:
80            return 0
81        coef = 1
82        while coef != len(lis):
83            if lis[coef-1] != lis[coef]:
84                break
85            coef += 1
86        if (not len(lis) % (coef * (max(lis) + 1)) and
87                lis == Cutil.keysfromcoef(coef, max(lis) + 1, len(lis))):
88            return coef
89        return 0

Generate a repetition coefficient for periodic list

@staticmethod
def funclist(value, func, *args, **kwargs):
 91    @staticmethod
 92    def funclist(value, func, *args, **kwargs):
 93        '''return the function func applied to the object value with parameters args and kwargs'''
 94        if func in (None, []):
 95            return value
 96        lis = []
 97        if not (isinstance(value, list) or value.__class__.__name__ in ['Cfield', 'Cdataset']):
 98            listval = [value]
 99        else:
100            listval = value
101        for val in listval:
102            try:
103                lis.append(val.func(*args, **kwargs))
104            except:
105                try:
106                    lis.append(func(val, *args, **kwargs))
107                except:
108                    try:
109                        lis.append(listval.func(val, *args, **kwargs))
110                    except:
111                        try:
112                            lis.append(func(listval, val, *args, **kwargs))
113                        except:
114                            raise FieldError("unable to apply func")
115        if len(lis) == 1:
116            return lis[0]
117        return lis

return the function func applied to the object value with parameters args and kwargs

@staticmethod
def idxfull(setidx):
119    @staticmethod
120    def idxfull(setidx):
121        '''return additional keys for each index in the setidx list to have crossed setidx'''
122        setcodec = [set(idx.keys) for idx in setidx]
123        lenfull = Cutil.mul([len(codec) for codec in setcodec])
124        if lenfull <= len(setidx[0]):
125            return []
126        complet = Counter(list(product(*setcodec)))
127        complet.subtract(
128            Counter(Cutil.tuple(Cutil.transpose([idx.keys for idx in setidx]))))
129        keysadd = Cutil.transpose(Cutil.list(list(complet.elements())))
130        if not keysadd:
131            return []
132        return keysadd

return additional keys for each index in the setidx list to have crossed setidx

@staticmethod
def is_not_equal(value, tovalue=None, **kwargs):
143    @staticmethod
144    def is_not_equal(value, tovalue=None, **kwargs):
145        ''' return True if value and tovalue are not equal'''
146        return value.__class__.__name__ != tovalue.__class__.__name__ or \
147            value != tovalue

return True if value and tovalue are not equal

@staticmethod
def keysfromcoef(coef, period, leng=None):
149    @staticmethod
150    def keysfromcoef(coef, period, leng=None):
151        ''' return a list of keys with periodic structure'''
152        if not leng:
153            leng = coef * period
154        return None if not (coef and period) else [(ind % (coef * period)) // coef
155                                                   for ind in range(leng)]

return a list of keys with periodic structure

@staticmethod
def keysfromderkeys(parentkeys, derkeys):
157    @staticmethod
158    def keysfromderkeys(parentkeys, derkeys):
159        '''return keys from parent keys and derkeys
160
161        *Parameters*
162
163        - **parentkeys** : list of keys from parent
164        - **derkeys** : list of derived keys
165
166        *Returns* : list of keys'''
167        return [derkeys[pkey] for pkey in parentkeys]

return keys from parent keys and derkeys

Parameters

  • parentkeys : list of keys from parent
  • derkeys : list of derived keys

Returns : list of keys

@staticmethod
def list(tuplelists):
169    @staticmethod
170    def list(tuplelists):
171        '''transform a list of tuples in a list of lists'''
172        return list(map(list, tuplelists))

transform a list of tuples in a list of lists

@staticmethod
def mul(values):
174    @staticmethod
175    def mul(values):
176        '''return the product of values in a list or tuple (math.prod)'''
177        mul = 1
178        for val in values:
179            mul *= val
180        return mul

return the product of values in a list or tuple (math.prod)

@staticmethod
def reindex(oldkeys, oldcodec, newcodec):
182    @staticmethod
183    def reindex(oldkeys, oldcodec, newcodec):
184        '''new keys with new order of codec'''
185        dic = {newcodec[i]: i for i in range(len(newcodec))}
186        return [dic[oldcodec[key]] for key in oldkeys]

new keys with new order of codec

@staticmethod
def reorder(values, sort=None):
188    @staticmethod
189    def reorder(values, sort=None):
190        '''return a new values list following the order define by sort'''
191        if not sort:
192            return values
193        return [values[ind] for ind in sort]

return a new values list following the order define by sort

@staticmethod
def resetidx(values):
195    @staticmethod
196    def resetidx(values):
197        '''return codec and keys from a list of values'''
198        codec = Cutil.tocodec(values)
199        return (codec, Cutil.tokeys(values, codec))

return codec and keys from a list of values

@staticmethod
def tocodec(values, keys=None):
201    @staticmethod
202    def tocodec(values, keys=None):
203        '''extract a list of unique values'''
204        if not keys:
205            # return list(set(values))
206            return list(dict.fromkeys(values))
207        #ind, codec = zip(*sorted(set(zip(keys, values))))
208        return list(list(zip(*sorted(set(zip(keys, values)))))[1])

extract a list of unique values

@staticmethod
def tokeys(values, codec=None):
210    @staticmethod
211    def tokeys(values, codec=None):
212        ''' return a list of keys from a list of values'''
213        if not codec:
214            codec = Cutil.tocodec(values)
215        dic = {codec[i]: i for i in range(len(codec))}  # !!!!long
216        keys = [dic[val] for val in values]    # hyper long
217        return keys

return a list of keys from a list of values

@staticmethod
def transpose(idxlist):
219    @staticmethod
220    def transpose(idxlist):
221        '''exchange row/column in a list of list'''
222        # if not isinstance(idxlist, list):
223        #    raise FieldError('index not transposable')
224        # if not idxlist:
225        #    return []
226        return list(map(list, zip(*idxlist)))
227        # return [list(elmt) for elmt in zip(*idxlist)]
228        #size = min([len(ix) for ix in idxlist])
229        # return [[ix[ind] for ix in idxlist] for ind in range(size)]

exchange row/column in a list of list

@staticmethod
def tuple(idx):
231    @staticmethod
232    def tuple(idx):
233        '''transform a list of list in a list of tuple'''
234        return list(map(tuple, idx))
235        # return [val if not isinstance(val, list) else tuple(val) for val in idx]

transform a list of list in a list of tuple

@staticmethod
def tupled(lis):
237    @staticmethod
238    def tupled(lis):
239        '''transform a list of list in a tuple of tuple'''
240        #return tuple(val if not isinstance(val, list) else Sfield._tupled(val) for val in lis)
241        return tuple(map(Cutil.tupled, lis)) if isinstance(lis, list) else lis

transform a list of list in a tuple of tuple

@staticmethod
def listed(lis):
243    @staticmethod
244    def listed(lis):
245        '''transform a tuple of tuple in a list of list'''
246        #return [val if not isinstance(val, tuple) else Cutil.listed(val) for val in lis]
247        return list(map(Cutil.listed, lis)) if isinstance(lis, tuple) else lis

transform a tuple of tuple in a list of list

class Cfield:
249class Cfield:
250    # %% intro
251    '''
252    A `Cfield` is a representation of an Field list .
253
254    *Attributes (for dynamic attributes see @property methods)* :
255
256    - **name** : name of the Field
257    - **_codec** : list of values for each key
258    - **_keys** : list of code values
259
260    The methods defined in this class are :
261
262    *constructor (@classmethod)*
263
264    - `Cfield.bol`
265    - `Cfield.from_ntv`
266    - `Cfield.ntv`
267    - `Cfield.like`
268
269    *conversion static methods*
270
271    - `Cfield.ntv_to_val` (@classmethod)
272    - `Cfield.n_to_i` (@staticmethod)
273
274    *dynamic value (getters @property)*
275
276    - `Cfield.hashf`
277    - `Cfield.to_analysis`
278    - `Cfield.values`
279    - `Cfield.codec`
280    - `Cfield.infos`
281    - `Cfield.keys`
282
283    *add - update methods*
284
285    - `Cfield.add`
286    - `Cfield.append`
287    - `Cfield.setcodecvalue`
288    - `Cfield.setcodeclist`
289    - `Cfield.setname`
290    - `Cfield.set_keys`
291    - `Cfield.set_codec`
292    - `Cfield.setkeys`
293    - `Cfield.setlistvalue`
294    - `Cfield.setvalue`
295
296    *transform methods*
297
298    - `Cfield.coupling`
299    - `Cfield.extendkeys`
300    - `Cfield.full`
301    - `Cfield.reindex`
302    - `Cfield.reorder`
303    - `Cfield.sort`
304    - `Cfield.tocoupled`
305    - `Cfield.tostdcodec`
306
307    *getters methods*
308
309    - `Cfield.couplinginfos`
310    - `Cfield.derkeys`
311    - `Cfield.getduplicates`
312    - `Cfield.iscrossed`
313    - `Cfield.iscoupled`
314    - `Cfield.isderived`
315    - `Cfield.islinked`
316    - `Cfield.isvalue`
317    - `Cfield.iskeysfromderkeys`
318    - `Cfield.keytoval`
319    - `Cfield.loc`
320    - `Cfield.recordfromkeys`
321    - `Cfield.recordfromvalue`
322    - `Cfield.valtokey`
323    '''
324
325    def __init__(self, codec=None, name=None, keys=None, default=False, reindex=False):
326        '''Two modes:
327            - a single attributes : Cfield object to copy
328            - multiple attributes : set codec, name and keys attributes'''
329        if not codec and not keys:
330            self._codec = []
331            self._keys = []
332        elif isinstance(codec, Cfield):
333            self._keys = codec._keys
334            self._codec = codec._codec
335            self.name = codec.name
336            return
337        elif not default:
338            self._keys = keys if keys else Cutil.identity(len(codec))
339            self._codec = codec if codec else Cutil.identity(len(keys))
340        else:
341            self._codec, self._keys = Cutil.default(codec)
342        self.name = name if name else 'field'
343        if reindex:
344            self.reindex()
345        return
346
347    def __repr__(self):
348        '''return classname and number of value'''
349        return self.__class__.__name__ + '[' + str(len(self)) + ']'
350
351    def __str__(self):
352        '''return json string format'''
353        return str({self.name: self.values})
354
355    def __eq__(self, other):
356        ''' equal if class and values are equal'''
357        return self.__class__ .__name__ == other.__class__.__name__ and \
358            self.values == other.values
359
360    def __len__(self):
361        ''' len of values'''
362        return len(self._keys)
363
364    def __contains__(self, item):
365        ''' item of values'''
366        return item in self.values
367
368    def __getitem__(self, ind):
369        ''' return value item (value conversion)'''
370        if isinstance(ind, tuple):
371            return [copy(self.values[i]) for i in ind]
372        # return self.values[ind]
373        return copy(self.values[ind])
374
375    def __setitem__(self, ind, item):
376        ''' modify values item'''
377        if isinstance(ind, slice):
378            start, stop, step = ind.start or 0, ind.stop or len(self), ind.step or 1
379            idxt = list(iter(range(start, stop, step)))
380            if len(idxt) != len(item):
381                raise FieldError("item length not consistent")
382            self.setlistvalue(item, idxt)
383        elif ind < 0 or ind >= len(self):
384            raise FieldError("out of bounds")
385        else: 
386            self.setvalue(ind, item)
387
388    def __delitem__(self, ind):
389        '''remove a record (value and key).'''
390        self._keys.pop(ind)
391        self.reindex()
392
393    def __hash__(self):
394        '''return hash(values)'''
395        return hash(tuple(self.values))
396
397    def _hashe(self):
398        '''return hash(values)'''
399        return hash(tuple(self.values))
400
401    def __add__(self, other):
402        ''' Add other's values to self's values in a new Field'''
403        newiindex = self.__copy__()
404        newiindex.__iadd__(other)
405        return newiindex
406
407    def __iadd__(self, other):
408        ''' Add other's values to self's values'''
409        return self.add(other, solve=False)
410
411    def __copy__(self):
412        ''' Copy all the data '''
413        return self.__class__(self)
414
415    # %% property
416    @property
417    def hashf(self):
418        '''return hash(codec infos and keys)'''
419        return hash(tuple((len(self.codec), len(set(self.codec)), len(self),
420                           self.name, tuple(self._keys))))
421
422    @property
423    def to_analysis(self):
424        '''return data for AnaField module'''
425        return {'maxcodec': len(self), 'lencodec': len(self.codec), 'id': self.name,
426                'mincodec': len(set(self.codec)), 'hashf': self.hashf}
427
428    @property
429    def codec(self):
430        '''return codec  '''
431        return self._codec
432
433    @property
434    def infos(self):
435        '''return dict with lencodec, typecodec, ratecodec, mincodec, maxcodec'''
436        return AnaField(self.to_analysis).to_dict(full=True)
437
438    @property
439    def keys(self):
440        '''return keys  '''
441        return self._keys
442
443    @property
444    def values(self):
445        '''return values (see data model)'''
446        return [self._codec[key] for key in self._keys]
447
448    # %% class methods
449    @classmethod
450    def from_ntv(cls, ntv_value=None, extkeys=None, reindex=True, decode_str=False,
451                 add_type=True, lengkeys=None):
452        '''Generate an Field Object from a Ntv field object'''
453        if isinstance(ntv_value, cls):
454            return copy(ntv_value)
455        if ntv_value is None:
456            return cls()
457        ntv = Ntv.obj(ntv_value, decode_str=decode_str)
458        #ntv = NtvList(ntv_value)
459        name, typ, codec, parent, keys, coef, leng = NtvUtil.decode_ntv_tab(
460            ntv, cls.ntv_to_val)
461        if parent and not extkeys:
462            return None
463        if coef:
464            keys = Cutil.keysfromcoef(coef, leng//coef, lengkeys)
465        elif extkeys and parent:
466            keys = Cutil.keysfromderkeys(extkeys, keys)
467        elif extkeys and not parent:
468            keys = extkeys
469        keys = list(range(len(codec))) if keys is None else keys
470        name = ntv.json_name(string=True) if add_type else name
471        return cls(codec=codec, name=name, keys=keys, reindex=reindex)
472
473    @classmethod
474    def bol(cls, leng, notdef=None, name=None, default=True):
475        '''
476        Field constructor (boolean value).
477
478        *Parameters*
479
480        - **leng** : integer - length of the Field
481        - **notdef** : list (default None) - list of records without default value
482        - **default** : boolean (default True) - default value
483        - **name** : string (default None) - name of Field'''
484        values = [default] * leng
485        if notdef:
486            for item in notdef:
487                values[item] = not default
488        return cls.ntv({name: values})
489
490    @classmethod
491    def like(cls, codec, parent, name=None, reindex=False):
492        '''Generate an Field Object from specific codec and keys from another field.
493
494        *Parameters*
495
496        - **codec** : list of objects
497        - **name** : string (default None) - name of index (see data model)
498        - **parent** : Field, parent of the new Field
499        - **reindex** : boolean (default True) - if True, default codec is apply
500
501        *Returns* : Field '''
502        if isinstance(codec, Cfield):
503            return copy(codec)
504        return cls(codec=codec, name=name, keys=parent.keys, reindex=reindex)
505
506    @classmethod
507    def ntv(cls, ntv_value=None, extkeys=None, reindex=True, decode_str=False):
508        '''Generate an Field Object from a Ntv field object'''
509        return cls.from_ntv(ntv_value, extkeys=extkeys, reindex=reindex, decode_str=decode_str)
510
511    @classmethod
512    def ntv_to_val(cls, ntv):
513        '''conversion in decode_ntv_val method'''
514        return cls.n_to_i(ntv.val)
515
516    # %% static methods
517    @staticmethod
518    def n_to_i(ntv_lis):
519        ''' converting a NtvList value to an internal value'''
520        if isinstance(ntv_lis, list) and len(ntv_lis) == 0:
521            return []
522        if isinstance(ntv_lis, list) and ntv_lis[0].__class__.__name__ in ('NtvSingle', 'NtvList'):
523            return [Cfield.n_to_i(ntv.to_obj()) for ntv in ntv_lis]
524        return ntv_lis
525
526    # %% instance methods
527    def add(self, other, solve=True):
528        ''' Add other's values to self's values
529
530        *Parameters*
531
532        - **other** : Field object to add to self object
533        - **solve** : Boolean (default True) - If True, replace None other's codec value
534        with self codec value.
535
536        *Returns* : self '''
537        if solve:
538            solved = copy(other)
539            for i in range(len(solved.codec)):
540                if solved.codec[i] is None and i in range(len(self.codec)):
541                    solved._codec[i] = self.codec[i]
542            values = self.values + solved.values
543        else:
544            values = self.values + other.values
545        codec = Cutil.tocodec(values)
546        if set(codec) != set(self._codec):
547            self._codec = codec
548        self._keys = Cutil.tokeys(values, self._codec)
549        return self
550
551    def append(self, value, unique=True):
552        '''add a new value
553
554        *Parameters*
555
556        - **value** : new object value
557        - **unique** :  boolean (default True) - If False, duplication codec if value is present
558
559        *Returns* : key of value '''
560        #value = Ntv.obj(value)
561        #value = self.s_to_i(value)
562        if value in self._codec and unique:
563            key = self._codec.index(value)
564        else:
565            key = len(self._codec)
566            self._codec.append(value)
567        self._keys.append(key)
568        return key
569
570    def coupling(self, idx, derived=True, duplicate=True, reindex=False):
571        '''
572        Transform indexes in coupled or derived indexes (codec extension).
573        If derived option is True, self._codec is extended and idx codec not,
574        else, both are coupled and both codec are extended.
575
576        *Parameters*
577
578        - **idx** : single Field or list of Field to be coupled or derived.
579        - **derived** : boolean (default : True) - if True result is derived,
580        if False coupled
581        - **duplicate** : boolean (default: True) - if True, return duplicate records
582        (only for self index)
583        - **reindex** : boolean (default : False). If True self.index is reindexed
584        with default codec. But if not derived, idx indexes MUST to be reindexed.
585
586        *Returns* : tuple with duplicate records (errors) if 'duplicate', None else'''
587        duplic = tuple()
588        if not isinstance(idx, list):
589            index = [idx]
590        else:
591            index = idx
592        idxzip = self.__class__(list(zip(*([self.keys] + [ix.keys for ix in index]))),
593                                reindex=True)
594        self.tocoupled(idxzip)        
595        if not derived:
596            for ind in index:
597                ind.tocoupled(idxzip)
598                duplic += ind.getduplicates(reindex)
599        if duplicate and not duplic:
600            return self.getduplicates(reindex)
601        if duplicate and duplic:
602            return tuple(sorted(list(set(duplic + self.getduplicates(reindex))))) 
603        if reindex:
604            self.reindex()
605        return None
606
607    def couplinginfos(self, other):
608        '''return a dict with the coupling info between other (distance, ratecpl,
609        rateder, dist, disttomin, disttomax, distmin, distmax, diff, typecoupl)
610
611        *Parameters*
612
613        - **other** : other index to compare
614
615        *Returns* : dict'''
616        if min(len(self), len(other)) == 0:
617            null = Cfield()
618            return AnaRelation([AnaField(null.to_analysis), AnaField(null.to_analysis)],
619                               Cutil.dist(null.keys, null.keys, True)
620                               ).to_dict(distances=True, misc=True)
621        return AnaRelation([AnaField(self.to_analysis), AnaField(other.to_analysis)],
622                           Cutil.dist(self.keys, other.keys, True)
623                           ).to_dict(distances=True, misc=True)
624
625    def derkeys(self, parent):
626        '''return keys derived from parent keys
627
628        *Parameters*
629
630        - **parent** : Field - parent
631
632        *Returns* : list of keys'''
633        derkey = [-1] * len(parent.codec)
634        for i in range(len(self)):
635            derkey[parent.keys[i]] = self.keys[i]
636        if min(derkey) < 0:
637            raise FieldError("parent is not a derive Field")
638        return derkey
639
640    def extendkeys(self, keys):
641        '''add keys to the Field
642
643        *Parameters*
644
645        - **keys** : list of int (value lower or equal than actual keys)
646
647        *Returns* : None '''
648        if min(keys) < 0 or max(keys) > len(self._codec) - 1:
649            raise FieldError('keys not consistent with codec')
650        self._keys += keys
651
652    @staticmethod
653    def full(listidx):
654        '''tranform a list of indexes in crossed indexes (value extension).
655
656        *Parameters*
657
658        - **listidx** : list of Field to transform
659
660        *Returns* : tuple of records added '''
661        idx1 = listidx[0]
662        for idx in listidx:
663            if len(idx) != len(idx):
664                return None
665        leninit = len(idx1)
666        keysadd = Cutil.idxfull(listidx)
667        for idx, keys in zip(listidx, keysadd):
668            idx._keys += keys
669        return tuple(range(leninit, len(idx1)))
670
671    def getduplicates(self, reindex=False):
672        ''' calculate items with duplicate codec
673
674        *Parameters*
675
676        - **reindex** : boolean (default : False). If True index is reindexed with default codec
677
678        *Returns* : tuple of items with duplicate codec'''
679        count = Counter(self._codec)
680        defcodec = list(count - Counter(list(count)))
681        dkeys = defaultdict(list)
682        for key, ind in zip(self._keys, range(len(self))):
683            dkeys[key].append(ind)
684        dcodec = defaultdict(list)
685        for key, ind in zip(self._codec, range(len(self._codec))):
686            dcodec[key].append(ind)
687        duplicates = []
688        for item in defcodec:
689            for codecitem in dcodec[item]:
690                duplicates += dkeys[codecitem]
691        if reindex:
692            self.reindex()
693        return tuple(duplicates)
694
695    def iscrossed(self, other):
696        '''return True if self is crossed to other'''
697        return self.couplinginfos(other)['rateder'] == 1.0
698
699    def iscoupled(self, other):
700        '''return True if self is coupled to other'''
701        info = self.couplinginfos(other)
702        return info['diff'] == 0 and info['rateder'] == 0.0
703
704    def isderived(self, other, only=False):
705        '''return True if self is derived from other'''
706        info = self.couplinginfos(other)
707        return not (info['diff'] == 0 and only) and info['rateder'] == 0.0
708
709    def iskeysfromderkeys(self, other):
710        '''return True if self._keys is relative from other._keys'''
711        leng = len(other.codec)
712        if leng % len(self._codec) != 0:
713            return False
714        keys = [(i*len(self._codec))//leng for i in range(leng)]
715        return Cutil.keysfromderkeys(other.keys, keys) == self.keys
716
717    def islinked(self, other):
718        '''return True if self is linked to other'''
719        rate = self.couplinginfos(other)['rateder']
720        return 0.0 < rate < 1.0
721
722    def isvalue(self, value):
723        ''' return True if value is in index values
724
725        *Parameters*
726
727        - **value** : value to check'''
728        return value in self.values
729
730    def keytoval(self, key):
731        ''' return the value of a key
732
733        *Parameters*
734
735        - **key** : key to convert into values
736        - **extern** : if True, return string representation else, internal value
737
738        *Returns*
739
740        - **int** : first key finded (None else)'''
741        if key < 0 or key >= len(self._codec):
742            return None
743        return self._codec[key]
744
745    def loc(self, value):
746        '''return a list of record number with value
747
748        *Parameters*
749
750        - **value** : value to check
751
752        *Returns*
753
754        - **list of int** : list of record number finded (None else)'''
755        return self.recordfromvalue(value)
756
757    def recordfromvalue(self, value):
758        '''return a list of record number with value
759
760        *Parameters*
761
762        - **value** : value to check
763        - **extern** : if True, compare value to external representation of self.value,
764        else, internal
765
766        *Returns*
767
768        - **list of int** : list of record number finded (None else)'''
769
770        if not value in self._codec:
771            return None
772        listkeys = [cod for cod, val in zip(
773            range(len(self._codec)), self._codec) if val == value]
774        return self.recordfromkeys(listkeys)
775
776    def recordfromkeys(self, listkeys):
777        '''return a list of record number with key in listkeys
778
779        *Parameters*
780
781        - **listkeys** : list of keys to check
782
783        *Returns*
784
785        - **list of int** : list of record number finded (None else)'''
786
787        return [rec for rec, key in zip(range(len(self)), self._keys) if key in listkeys]
788
789    def reindex(self, codec=None):
790        '''apply a reordered codec. If None, a new default codec is apply.
791
792        *Parameters*
793
794        - **codec** : list (default None) - reordered codec to apply.
795
796        *Returns* : self'''
797
798        if not codec:
799            codec = Cutil.tocodec(self.values)
800        self._keys = Cutil.reindex(self._keys, self._codec, codec)
801        self._codec = codec
802        return self
803
804    def reorder(self, sort=None, inplace=True):
805        '''Change the Field order with a new order define by sort and reset the codec.
806
807        *Parameters*
808
809        - **sort** : int list (default None)- new record order to apply. If None, no change.
810        - **inplace** : boolean (default True) - if True, new order is apply to self,
811        if False a new Field is created.
812
813        *Returns*
814
815        - **Field** : self if inplace, new Field if not inplace'''
816        values = Cutil.reorder(self.values, sort)
817        codec, keys = Cutil.resetidx(values)
818        if inplace:
819            self._keys = keys
820            self._codec = codec
821            return None
822        return self.__class__(name=self.name, codec=codec, keys=keys)
823
824    def setcodecvalue(self, oldvalue, newvalue):
825        '''update all the oldvalue by newvalue
826
827        *Parameters*
828
829        - **oldvalue** : list of values to replace
830        - **newvalue** : list of new value to apply
831
832        *Returns* : int - last codec rank updated (-1 if None)'''
833
834        rank = -1
835        for i in range(len(self._codec)):
836            if self._codec[i] == oldvalue:
837                self._codec[i] = newvalue
838                rank = i
839        return rank
840
841    def setcodeclist(self, listcodec):
842        '''update codec with listcodec values
843
844        *Parameters*
845
846        - **listcodec** : list of new codec values to apply
847
848        *Returns* : int - last codec rank updated (-1 if None)'''
849        self._codec = listcodec
850
851    def set_keys(self, keys):
852        ''' _keys setters '''
853        self._keys = keys
854
855    def set_codec(self, codec):
856        ''' _codec setters '''
857        self._codec = codec
858
859    def setkeys(self, keys, inplace=True):
860        '''apply new keys (replace codec with extended codec from parent keys)
861
862        *Parameters*
863
864        - **keys** : list of keys to apply
865        - **inplace** : if True, update self data, else create a new Field
866
867        *Returns* : self or new Field'''
868        codec = Cutil.tocodec(self.values, keys)
869        if inplace:
870            self._codec = codec
871            self._keys = keys
872            return self
873        return self.__class__(codec=codec, name=self.name, keys=keys)
874
875    def setname(self, name):
876        '''update the Field name
877
878        *Parameters*
879
880        - **name** : str to set into name
881
882        *Returns* : boolean - True if update'''
883        if isinstance(name, str):
884            self.name = name
885            return True
886        return False
887
888    def setvalue(self, ind, value):
889        '''update a value at the rank ind (and update codec and keys)
890
891        *Parameters*
892
893        - **ind** : rank of the value
894        - **value** : new value
895
896        *Returns* : None'''
897        values = self.values
898        values[ind] = value
899        self._codec, self._keys = Cutil.resetidx(values)
900
901    def setlistvalue(self, listvalue, listind=None):
902        '''update the values (and update codec and keys)
903
904        *Parameters*
905
906        - **listvalue** : list - list of new values
907        - **listind** : list(default None) - list of index
908
909        *Returns* : None'''
910        values = self.values
911        listind = listind if listind else range(len(self))
912        for i, value_i in zip(listind, listvalue):
913            values[i] = value_i
914        self._codec, self._keys = Cutil.resetidx(values)
915
916    def sort(self, reverse=False, inplace=True, func=str):
917        '''Define sorted index with ordered codec.
918
919        *Parameters*
920
921        - **reverse** : boolean (defaut False) - codec is sorted with reverse order
922        - **inplace** : boolean (default True) - if True, new order is apply to self,
923        if False a new Field is created.
924        - **func**    : function (default str) - key used in the sorted function
925
926        *Return*
927
928        - **Field** : self if inplace, new Field if not inplace'''
929        if inplace:
930            self.reindex(codec=sorted(self._codec, reverse=reverse, key=func))
931            self._keys.sort()
932            return self
933        oldcodec = self._codec
934        codec = sorted(oldcodec, reverse=reverse, key=str)
935        return self.__class__(name=self.name, codec=codec,
936                              keys=sorted(Cutil.reindex(self._keys, oldcodec, codec)))
937
938    def tocoupled(self, other, coupling=True):
939        '''
940        Transform a derived index in a coupled index (keys extension) and add
941        new values to have the same length as other.
942
943        *Parameters*
944
945        - **other** : index to be coupled.
946        - **coupling** : boolean (default True) - reindex if False
947
948        *Returns* : None'''
949        dic = Cutil.idxlink(other.keys, self._keys)
950        if not dic:
951            raise FieldError("Field is not coupled or derived from other")
952        self._codec = [self._codec[dic[i]] for i in range(len(dic))]
953        self._keys = other.keys
954        if not coupling:
955            self.reindex()
956
957    def tostdcodec(self, inplace=False, full=True):
958        '''
959        Transform codec in full or in default codec.
960
961        *Parameters*
962
963        - **inplace** : boolean (default True) - if True, new order is apply to self,
964        - **full** : boolean (default True) - if True reindex with full codec
965
966        *Return*
967
968        - **Field** : self if inplace, new Field if not inplace'''
969        if full:
970            codec = self.values
971            keys = list(range(len(codec)))
972        else:
973            codec = Cutil.tocodec(self.values)
974            keys = Cutil.reindex(self._keys, self._codec, codec)
975        if inplace:
976            self._codec = codec
977            self._keys = keys
978            return self
979        return self.__class__(codec=codec, name=self.name, keys=keys)
980
981    def valtokey(self, value):
982        '''convert a value to a key
983
984        *Parameters*
985
986        - **value** : value to convert
987
988        *Returns*
989
990        - **int** : first key finded (None else)'''
991        if value in self._codec:
992            return self._codec.index(value)
993        return None

A Cfield is a representation of an Field list .

Attributes (for dynamic attributes see @property methods) :

  • name : name of the Field
  • _codec : list of values for each key
  • _keys : list of code values

The methods defined in this class are :

constructor (@classmethod)

conversion static methods

dynamic value (getters @property)

add - update methods

transform methods

getters methods

Cfield(codec=None, name=None, keys=None, default=False, reindex=False)
325    def __init__(self, codec=None, name=None, keys=None, default=False, reindex=False):
326        '''Two modes:
327            - a single attributes : Cfield object to copy
328            - multiple attributes : set codec, name and keys attributes'''
329        if not codec and not keys:
330            self._codec = []
331            self._keys = []
332        elif isinstance(codec, Cfield):
333            self._keys = codec._keys
334            self._codec = codec._codec
335            self.name = codec.name
336            return
337        elif not default:
338            self._keys = keys if keys else Cutil.identity(len(codec))
339            self._codec = codec if codec else Cutil.identity(len(keys))
340        else:
341            self._codec, self._keys = Cutil.default(codec)
342        self.name = name if name else 'field'
343        if reindex:
344            self.reindex()
345        return

Two modes:

  • a single attributes : Cfield object to copy
  • multiple attributes : set codec, name and keys attributes
hashf

return hash(codec infos and keys)

to_analysis

return data for AnaField module

codec

return codec

infos

return dict with lencodec, typecodec, ratecodec, mincodec, maxcodec

keys

return keys

values

return values (see data model)

@classmethod
def from_ntv( cls, ntv_value=None, extkeys=None, reindex=True, decode_str=False, add_type=True, lengkeys=None):
449    @classmethod
450    def from_ntv(cls, ntv_value=None, extkeys=None, reindex=True, decode_str=False,
451                 add_type=True, lengkeys=None):
452        '''Generate an Field Object from a Ntv field object'''
453        if isinstance(ntv_value, cls):
454            return copy(ntv_value)
455        if ntv_value is None:
456            return cls()
457        ntv = Ntv.obj(ntv_value, decode_str=decode_str)
458        #ntv = NtvList(ntv_value)
459        name, typ, codec, parent, keys, coef, leng = NtvUtil.decode_ntv_tab(
460            ntv, cls.ntv_to_val)
461        if parent and not extkeys:
462            return None
463        if coef:
464            keys = Cutil.keysfromcoef(coef, leng//coef, lengkeys)
465        elif extkeys and parent:
466            keys = Cutil.keysfromderkeys(extkeys, keys)
467        elif extkeys and not parent:
468            keys = extkeys
469        keys = list(range(len(codec))) if keys is None else keys
470        name = ntv.json_name(string=True) if add_type else name
471        return cls(codec=codec, name=name, keys=keys, reindex=reindex)

Generate an Field Object from a Ntv field object

@classmethod
def bol(cls, leng, notdef=None, name=None, default=True):
473    @classmethod
474    def bol(cls, leng, notdef=None, name=None, default=True):
475        '''
476        Field constructor (boolean value).
477
478        *Parameters*
479
480        - **leng** : integer - length of the Field
481        - **notdef** : list (default None) - list of records without default value
482        - **default** : boolean (default True) - default value
483        - **name** : string (default None) - name of Field'''
484        values = [default] * leng
485        if notdef:
486            for item in notdef:
487                values[item] = not default
488        return cls.ntv({name: values})

Field constructor (boolean value).

Parameters

  • leng : integer - length of the Field
  • notdef : list (default None) - list of records without default value
  • default : boolean (default True) - default value
  • name : string (default None) - name of Field
@classmethod
def like(cls, codec, parent, name=None, reindex=False):
490    @classmethod
491    def like(cls, codec, parent, name=None, reindex=False):
492        '''Generate an Field Object from specific codec and keys from another field.
493
494        *Parameters*
495
496        - **codec** : list of objects
497        - **name** : string (default None) - name of index (see data model)
498        - **parent** : Field, parent of the new Field
499        - **reindex** : boolean (default True) - if True, default codec is apply
500
501        *Returns* : Field '''
502        if isinstance(codec, Cfield):
503            return copy(codec)
504        return cls(codec=codec, name=name, keys=parent.keys, reindex=reindex)

Generate an Field Object from specific codec and keys from another field.

Parameters

  • codec : list of objects
  • name : string (default None) - name of index (see data model)
  • parent : Field, parent of the new Field
  • reindex : boolean (default True) - if True, default codec is apply

Returns : Field

@classmethod
def ntv(cls, ntv_value=None, extkeys=None, reindex=True, decode_str=False):
506    @classmethod
507    def ntv(cls, ntv_value=None, extkeys=None, reindex=True, decode_str=False):
508        '''Generate an Field Object from a Ntv field object'''
509        return cls.from_ntv(ntv_value, extkeys=extkeys, reindex=reindex, decode_str=decode_str)

Generate an Field Object from a Ntv field object

@classmethod
def ntv_to_val(cls, ntv):
511    @classmethod
512    def ntv_to_val(cls, ntv):
513        '''conversion in decode_ntv_val method'''
514        return cls.n_to_i(ntv.val)

conversion in decode_ntv_val method

@staticmethod
def n_to_i(ntv_lis):
517    @staticmethod
518    def n_to_i(ntv_lis):
519        ''' converting a NtvList value to an internal value'''
520        if isinstance(ntv_lis, list) and len(ntv_lis) == 0:
521            return []
522        if isinstance(ntv_lis, list) and ntv_lis[0].__class__.__name__ in ('NtvSingle', 'NtvList'):
523            return [Cfield.n_to_i(ntv.to_obj()) for ntv in ntv_lis]
524        return ntv_lis

converting a NtvList value to an internal value

def add(self, other, solve=True):
527    def add(self, other, solve=True):
528        ''' Add other's values to self's values
529
530        *Parameters*
531
532        - **other** : Field object to add to self object
533        - **solve** : Boolean (default True) - If True, replace None other's codec value
534        with self codec value.
535
536        *Returns* : self '''
537        if solve:
538            solved = copy(other)
539            for i in range(len(solved.codec)):
540                if solved.codec[i] is None and i in range(len(self.codec)):
541                    solved._codec[i] = self.codec[i]
542            values = self.values + solved.values
543        else:
544            values = self.values + other.values
545        codec = Cutil.tocodec(values)
546        if set(codec) != set(self._codec):
547            self._codec = codec
548        self._keys = Cutil.tokeys(values, self._codec)
549        return self

Add other's values to self's values

Parameters

  • other : Field object to add to self object
  • solve : Boolean (default True) - If True, replace None other's codec value with self codec value.

Returns : self

def append(self, value, unique=True):
551    def append(self, value, unique=True):
552        '''add a new value
553
554        *Parameters*
555
556        - **value** : new object value
557        - **unique** :  boolean (default True) - If False, duplication codec if value is present
558
559        *Returns* : key of value '''
560        #value = Ntv.obj(value)
561        #value = self.s_to_i(value)
562        if value in self._codec and unique:
563            key = self._codec.index(value)
564        else:
565            key = len(self._codec)
566            self._codec.append(value)
567        self._keys.append(key)
568        return key

add a new value

Parameters

  • value : new object value
  • unique : boolean (default True) - If False, duplication codec if value is present

Returns : key of value

def coupling(self, idx, derived=True, duplicate=True, reindex=False):
570    def coupling(self, idx, derived=True, duplicate=True, reindex=False):
571        '''
572        Transform indexes in coupled or derived indexes (codec extension).
573        If derived option is True, self._codec is extended and idx codec not,
574        else, both are coupled and both codec are extended.
575
576        *Parameters*
577
578        - **idx** : single Field or list of Field to be coupled or derived.
579        - **derived** : boolean (default : True) - if True result is derived,
580        if False coupled
581        - **duplicate** : boolean (default: True) - if True, return duplicate records
582        (only for self index)
583        - **reindex** : boolean (default : False). If True self.index is reindexed
584        with default codec. But if not derived, idx indexes MUST to be reindexed.
585
586        *Returns* : tuple with duplicate records (errors) if 'duplicate', None else'''
587        duplic = tuple()
588        if not isinstance(idx, list):
589            index = [idx]
590        else:
591            index = idx
592        idxzip = self.__class__(list(zip(*([self.keys] + [ix.keys for ix in index]))),
593                                reindex=True)
594        self.tocoupled(idxzip)        
595        if not derived:
596            for ind in index:
597                ind.tocoupled(idxzip)
598                duplic += ind.getduplicates(reindex)
599        if duplicate and not duplic:
600            return self.getduplicates(reindex)
601        if duplicate and duplic:
602            return tuple(sorted(list(set(duplic + self.getduplicates(reindex))))) 
603        if reindex:
604            self.reindex()
605        return None

Transform indexes in coupled or derived indexes (codec extension). If derived option is True, self._codec is extended and idx codec not, else, both are coupled and both codec are extended.

Parameters

  • idx : single Field or list of Field to be coupled or derived.
  • derived : boolean (default : True) - if True result is derived, if False coupled
  • duplicate : boolean (default: True) - if True, return duplicate records (only for self index)
  • reindex : boolean (default : False). If True self.index is reindexed with default codec. But if not derived, idx indexes MUST to be reindexed.

Returns : tuple with duplicate records (errors) if 'duplicate', None else

def couplinginfos(self, other):
607    def couplinginfos(self, other):
608        '''return a dict with the coupling info between other (distance, ratecpl,
609        rateder, dist, disttomin, disttomax, distmin, distmax, diff, typecoupl)
610
611        *Parameters*
612
613        - **other** : other index to compare
614
615        *Returns* : dict'''
616        if min(len(self), len(other)) == 0:
617            null = Cfield()
618            return AnaRelation([AnaField(null.to_analysis), AnaField(null.to_analysis)],
619                               Cutil.dist(null.keys, null.keys, True)
620                               ).to_dict(distances=True, misc=True)
621        return AnaRelation([AnaField(self.to_analysis), AnaField(other.to_analysis)],
622                           Cutil.dist(self.keys, other.keys, True)
623                           ).to_dict(distances=True, misc=True)

return a dict with the coupling info between other (distance, ratecpl, rateder, dist, disttomin, disttomax, distmin, distmax, diff, typecoupl)

Parameters

  • other : other index to compare

Returns : dict

def derkeys(self, parent):
625    def derkeys(self, parent):
626        '''return keys derived from parent keys
627
628        *Parameters*
629
630        - **parent** : Field - parent
631
632        *Returns* : list of keys'''
633        derkey = [-1] * len(parent.codec)
634        for i in range(len(self)):
635            derkey[parent.keys[i]] = self.keys[i]
636        if min(derkey) < 0:
637            raise FieldError("parent is not a derive Field")
638        return derkey

return keys derived from parent keys

Parameters

  • parent : Field - parent

Returns : list of keys

def extendkeys(self, keys):
640    def extendkeys(self, keys):
641        '''add keys to the Field
642
643        *Parameters*
644
645        - **keys** : list of int (value lower or equal than actual keys)
646
647        *Returns* : None '''
648        if min(keys) < 0 or max(keys) > len(self._codec) - 1:
649            raise FieldError('keys not consistent with codec')
650        self._keys += keys

add keys to the Field

Parameters

  • keys : list of int (value lower or equal than actual keys)

Returns : None

@staticmethod
def full(listidx):
652    @staticmethod
653    def full(listidx):
654        '''tranform a list of indexes in crossed indexes (value extension).
655
656        *Parameters*
657
658        - **listidx** : list of Field to transform
659
660        *Returns* : tuple of records added '''
661        idx1 = listidx[0]
662        for idx in listidx:
663            if len(idx) != len(idx):
664                return None
665        leninit = len(idx1)
666        keysadd = Cutil.idxfull(listidx)
667        for idx, keys in zip(listidx, keysadd):
668            idx._keys += keys
669        return tuple(range(leninit, len(idx1)))

tranform a list of indexes in crossed indexes (value extension).

Parameters

  • listidx : list of Field to transform

Returns : tuple of records added

def getduplicates(self, reindex=False):
671    def getduplicates(self, reindex=False):
672        ''' calculate items with duplicate codec
673
674        *Parameters*
675
676        - **reindex** : boolean (default : False). If True index is reindexed with default codec
677
678        *Returns* : tuple of items with duplicate codec'''
679        count = Counter(self._codec)
680        defcodec = list(count - Counter(list(count)))
681        dkeys = defaultdict(list)
682        for key, ind in zip(self._keys, range(len(self))):
683            dkeys[key].append(ind)
684        dcodec = defaultdict(list)
685        for key, ind in zip(self._codec, range(len(self._codec))):
686            dcodec[key].append(ind)
687        duplicates = []
688        for item in defcodec:
689            for codecitem in dcodec[item]:
690                duplicates += dkeys[codecitem]
691        if reindex:
692            self.reindex()
693        return tuple(duplicates)

calculate items with duplicate codec

Parameters

  • reindex : boolean (default : False). If True index is reindexed with default codec

Returns : tuple of items with duplicate codec

def iscrossed(self, other):
695    def iscrossed(self, other):
696        '''return True if self is crossed to other'''
697        return self.couplinginfos(other)['rateder'] == 1.0

return True if self is crossed to other

def iscoupled(self, other):
699    def iscoupled(self, other):
700        '''return True if self is coupled to other'''
701        info = self.couplinginfos(other)
702        return info['diff'] == 0 and info['rateder'] == 0.0

return True if self is coupled to other

def isderived(self, other, only=False):
704    def isderived(self, other, only=False):
705        '''return True if self is derived from other'''
706        info = self.couplinginfos(other)
707        return not (info['diff'] == 0 and only) and info['rateder'] == 0.0

return True if self is derived from other

def iskeysfromderkeys(self, other):
709    def iskeysfromderkeys(self, other):
710        '''return True if self._keys is relative from other._keys'''
711        leng = len(other.codec)
712        if leng % len(self._codec) != 0:
713            return False
714        keys = [(i*len(self._codec))//leng for i in range(leng)]
715        return Cutil.keysfromderkeys(other.keys, keys) == self.keys

return True if self._keys is relative from other._keys

def islinked(self, other):
717    def islinked(self, other):
718        '''return True if self is linked to other'''
719        rate = self.couplinginfos(other)['rateder']
720        return 0.0 < rate < 1.0

return True if self is linked to other

def isvalue(self, value):
722    def isvalue(self, value):
723        ''' return True if value is in index values
724
725        *Parameters*
726
727        - **value** : value to check'''
728        return value in self.values

return True if value is in index values

Parameters

  • value : value to check
def keytoval(self, key):
730    def keytoval(self, key):
731        ''' return the value of a key
732
733        *Parameters*
734
735        - **key** : key to convert into values
736        - **extern** : if True, return string representation else, internal value
737
738        *Returns*
739
740        - **int** : first key finded (None else)'''
741        if key < 0 or key >= len(self._codec):
742            return None
743        return self._codec[key]

return the value of a key

Parameters

  • key : key to convert into values
  • extern : if True, return string representation else, internal value

Returns

  • int : first key finded (None else)
def loc(self, value):
745    def loc(self, value):
746        '''return a list of record number with value
747
748        *Parameters*
749
750        - **value** : value to check
751
752        *Returns*
753
754        - **list of int** : list of record number finded (None else)'''
755        return self.recordfromvalue(value)

return a list of record number with value

Parameters

  • value : value to check

Returns

  • list of int : list of record number finded (None else)
def recordfromvalue(self, value):
757    def recordfromvalue(self, value):
758        '''return a list of record number with value
759
760        *Parameters*
761
762        - **value** : value to check
763        - **extern** : if True, compare value to external representation of self.value,
764        else, internal
765
766        *Returns*
767
768        - **list of int** : list of record number finded (None else)'''
769
770        if not value in self._codec:
771            return None
772        listkeys = [cod for cod, val in zip(
773            range(len(self._codec)), self._codec) if val == value]
774        return self.recordfromkeys(listkeys)

return a list of record number with value

Parameters

  • value : value to check
  • extern : if True, compare value to external representation of self.value, else, internal

Returns

  • list of int : list of record number finded (None else)
def recordfromkeys(self, listkeys):
776    def recordfromkeys(self, listkeys):
777        '''return a list of record number with key in listkeys
778
779        *Parameters*
780
781        - **listkeys** : list of keys to check
782
783        *Returns*
784
785        - **list of int** : list of record number finded (None else)'''
786
787        return [rec for rec, key in zip(range(len(self)), self._keys) if key in listkeys]

return a list of record number with key in listkeys

Parameters

  • listkeys : list of keys to check

Returns

  • list of int : list of record number finded (None else)
def reindex(self, codec=None):
789    def reindex(self, codec=None):
790        '''apply a reordered codec. If None, a new default codec is apply.
791
792        *Parameters*
793
794        - **codec** : list (default None) - reordered codec to apply.
795
796        *Returns* : self'''
797
798        if not codec:
799            codec = Cutil.tocodec(self.values)
800        self._keys = Cutil.reindex(self._keys, self._codec, codec)
801        self._codec = codec
802        return self

apply a reordered codec. If None, a new default codec is apply.

Parameters

  • codec : list (default None) - reordered codec to apply.

Returns : self

def reorder(self, sort=None, inplace=True):
804    def reorder(self, sort=None, inplace=True):
805        '''Change the Field order with a new order define by sort and reset the codec.
806
807        *Parameters*
808
809        - **sort** : int list (default None)- new record order to apply. If None, no change.
810        - **inplace** : boolean (default True) - if True, new order is apply to self,
811        if False a new Field is created.
812
813        *Returns*
814
815        - **Field** : self if inplace, new Field if not inplace'''
816        values = Cutil.reorder(self.values, sort)
817        codec, keys = Cutil.resetidx(values)
818        if inplace:
819            self._keys = keys
820            self._codec = codec
821            return None
822        return self.__class__(name=self.name, codec=codec, keys=keys)

Change the Field order with a new order define by sort and reset the codec.

Parameters

  • sort : int list (default None)- new record order to apply. If None, no change.
  • inplace : boolean (default True) - if True, new order is apply to self, if False a new Field is created.

Returns

  • Field : self if inplace, new Field if not inplace
def setcodecvalue(self, oldvalue, newvalue):
824    def setcodecvalue(self, oldvalue, newvalue):
825        '''update all the oldvalue by newvalue
826
827        *Parameters*
828
829        - **oldvalue** : list of values to replace
830        - **newvalue** : list of new value to apply
831
832        *Returns* : int - last codec rank updated (-1 if None)'''
833
834        rank = -1
835        for i in range(len(self._codec)):
836            if self._codec[i] == oldvalue:
837                self._codec[i] = newvalue
838                rank = i
839        return rank

update all the oldvalue by newvalue

Parameters

  • oldvalue : list of values to replace
  • newvalue : list of new value to apply

Returns : int - last codec rank updated (-1 if None)

def setcodeclist(self, listcodec):
841    def setcodeclist(self, listcodec):
842        '''update codec with listcodec values
843
844        *Parameters*
845
846        - **listcodec** : list of new codec values to apply
847
848        *Returns* : int - last codec rank updated (-1 if None)'''
849        self._codec = listcodec

update codec with listcodec values

Parameters

  • listcodec : list of new codec values to apply

Returns : int - last codec rank updated (-1 if None)

def set_keys(self, keys):
851    def set_keys(self, keys):
852        ''' _keys setters '''
853        self._keys = keys

_keys setters

def set_codec(self, codec):
855    def set_codec(self, codec):
856        ''' _codec setters '''
857        self._codec = codec

_codec setters

def setkeys(self, keys, inplace=True):
859    def setkeys(self, keys, inplace=True):
860        '''apply new keys (replace codec with extended codec from parent keys)
861
862        *Parameters*
863
864        - **keys** : list of keys to apply
865        - **inplace** : if True, update self data, else create a new Field
866
867        *Returns* : self or new Field'''
868        codec = Cutil.tocodec(self.values, keys)
869        if inplace:
870            self._codec = codec
871            self._keys = keys
872            return self
873        return self.__class__(codec=codec, name=self.name, keys=keys)

apply new keys (replace codec with extended codec from parent keys)

Parameters

  • keys : list of keys to apply
  • inplace : if True, update self data, else create a new Field

Returns : self or new Field

def setname(self, name):
875    def setname(self, name):
876        '''update the Field name
877
878        *Parameters*
879
880        - **name** : str to set into name
881
882        *Returns* : boolean - True if update'''
883        if isinstance(name, str):
884            self.name = name
885            return True
886        return False

update the Field name

Parameters

  • name : str to set into name

Returns : boolean - True if update

def setvalue(self, ind, value):
888    def setvalue(self, ind, value):
889        '''update a value at the rank ind (and update codec and keys)
890
891        *Parameters*
892
893        - **ind** : rank of the value
894        - **value** : new value
895
896        *Returns* : None'''
897        values = self.values
898        values[ind] = value
899        self._codec, self._keys = Cutil.resetidx(values)

update a value at the rank ind (and update codec and keys)

Parameters

  • ind : rank of the value
  • value : new value

Returns : None

def setlistvalue(self, listvalue, listind=None):
901    def setlistvalue(self, listvalue, listind=None):
902        '''update the values (and update codec and keys)
903
904        *Parameters*
905
906        - **listvalue** : list - list of new values
907        - **listind** : list(default None) - list of index
908
909        *Returns* : None'''
910        values = self.values
911        listind = listind if listind else range(len(self))
912        for i, value_i in zip(listind, listvalue):
913            values[i] = value_i
914        self._codec, self._keys = Cutil.resetidx(values)

update the values (and update codec and keys)

Parameters

  • listvalue : list - list of new values
  • listind : list(default None) - list of index

Returns : None

def sort(self, reverse=False, inplace=True, func=<class 'str'>):
916    def sort(self, reverse=False, inplace=True, func=str):
917        '''Define sorted index with ordered codec.
918
919        *Parameters*
920
921        - **reverse** : boolean (defaut False) - codec is sorted with reverse order
922        - **inplace** : boolean (default True) - if True, new order is apply to self,
923        if False a new Field is created.
924        - **func**    : function (default str) - key used in the sorted function
925
926        *Return*
927
928        - **Field** : self if inplace, new Field if not inplace'''
929        if inplace:
930            self.reindex(codec=sorted(self._codec, reverse=reverse, key=func))
931            self._keys.sort()
932            return self
933        oldcodec = self._codec
934        codec = sorted(oldcodec, reverse=reverse, key=str)
935        return self.__class__(name=self.name, codec=codec,
936                              keys=sorted(Cutil.reindex(self._keys, oldcodec, codec)))

Define sorted index with ordered codec.

Parameters

  • reverse : boolean (defaut False) - codec is sorted with reverse order
  • inplace : boolean (default True) - if True, new order is apply to self, if False a new Field is created.
  • func : function (default str) - key used in the sorted function

Return

  • Field : self if inplace, new Field if not inplace
def tocoupled(self, other, coupling=True):
938    def tocoupled(self, other, coupling=True):
939        '''
940        Transform a derived index in a coupled index (keys extension) and add
941        new values to have the same length as other.
942
943        *Parameters*
944
945        - **other** : index to be coupled.
946        - **coupling** : boolean (default True) - reindex if False
947
948        *Returns* : None'''
949        dic = Cutil.idxlink(other.keys, self._keys)
950        if not dic:
951            raise FieldError("Field is not coupled or derived from other")
952        self._codec = [self._codec[dic[i]] for i in range(len(dic))]
953        self._keys = other.keys
954        if not coupling:
955            self.reindex()

Transform a derived index in a coupled index (keys extension) and add new values to have the same length as other.

Parameters

  • other : index to be coupled.
  • coupling : boolean (default True) - reindex if False

Returns : None

def tostdcodec(self, inplace=False, full=True):
957    def tostdcodec(self, inplace=False, full=True):
958        '''
959        Transform codec in full or in default codec.
960
961        *Parameters*
962
963        - **inplace** : boolean (default True) - if True, new order is apply to self,
964        - **full** : boolean (default True) - if True reindex with full codec
965
966        *Return*
967
968        - **Field** : self if inplace, new Field if not inplace'''
969        if full:
970            codec = self.values
971            keys = list(range(len(codec)))
972        else:
973            codec = Cutil.tocodec(self.values)
974            keys = Cutil.reindex(self._keys, self._codec, codec)
975        if inplace:
976            self._codec = codec
977            self._keys = keys
978            return self
979        return self.__class__(codec=codec, name=self.name, keys=keys)

Transform codec in full or in default codec.

Parameters

  • inplace : boolean (default True) - if True, new order is apply to self,
  • full : boolean (default True) - if True reindex with full codec

Return

  • Field : self if inplace, new Field if not inplace
def valtokey(self, value):
981    def valtokey(self, value):
982        '''convert a value to a key
983
984        *Parameters*
985
986        - **value** : value to convert
987
988        *Returns*
989
990        - **int** : first key finded (None else)'''
991        if value in self._codec:
992            return self._codec.index(value)
993        return None

convert a value to a key

Parameters

  • value : value to convert

Returns

  • int : first key finded (None else)
class FieldError(builtins.Exception):
996class FieldError(Exception):
997    ''' Field Exception'''
998    # pass

Field Exception

Inherited Members
builtins.Exception
Exception
builtins.BaseException
with_traceback