diff --git a/python/damask/table.py b/python/damask/table.py index 3c867fcf5..6181fdb1f 100644 --- a/python/damask/table.py +++ b/python/damask/table.py @@ -1,4 +1,3 @@ -import random import re import pandas as pd @@ -21,27 +20,36 @@ class Table(): Additional, human-readable information. """ + self.comments = [] if comments is None else [c for c in comments] self.data = pd.DataFrame(data=data) - - labels = {} - i = 0 - for label in shapes.keys(): - for components in range(np.prod(shapes[label])): - labels[i] = label - i+=1 - - if i != self.data.shape[1]: - raise IndexError('Shape mismatch between shapes and data') - - self.data.rename(columns=labels,inplace=True) - - if comments is None: - self.comments = [] - else: - self.comments = [c for c in comments] - self.shapes = shapes + self.__label_condensed() + + def __label_flat(self): + """Label data individually, e.g. v v v ==> 1_v 2_v 3_v.""" + labels = [] + for label,shape in self.shapes.items(): + size = np.prod(shape) + labels += ['{}{}'.format('' if size == 1 else '{}_'.format(i+1),label) for i in range(size)] + self.data.columns = labels + + + def __label_condensed(self): + """Label data condensed, e.g. 1_v 2_v 3_v ==> v v v.""" + labels = [] + for label,shape in self.shapes.items(): + labels += [label] * np.prod(shape) + self.data.columns = labels + + + def __add_comment(self,label,shape,info): + if info is not None: + self.comments.append('{}{}: {}'.format(label, + ' '+str(shape) if np.prod(shape,dtype=int) > 1 else '', + info)) + + @staticmethod def from_ASCII(fname): """ @@ -67,7 +75,7 @@ class Table(): header = int(header) else: raise Exception - comments = [f.readline()[:-1] for i in range(header-1)] + comments = [f.readline()[:-1] for i in range(1,header)] labels = f.readline().split() shapes = {} @@ -81,13 +89,13 @@ class Table(): if vector_column: shapes[label.split('_',1)[1]] = (int(label.split('_',1)[0]),) else: - shapes[label]=(1,) - - data = pd.read_csv(f,names=[i for i in range(len(labels))],sep=r'\s+').to_numpy() + shapes[label] = (1,) + data = pd.read_csv(f,names=list(range(len(labels))),sep=r'\s+').to_numpy() + return Table(data,shapes,comments) - + @property def labels(self): """Return the labels of all columns.""" return list(self.shapes.keys()) @@ -105,9 +113,12 @@ class Table(): """ if re.match(r'[0-9]*?_',label): idx,key = label.split('_',1) - return self.data[key].to_numpy()[:,int(idx)-1].reshape((-1,1)) + data = self.data[key].to_numpy()[:,int(idx)-1].reshape((-1,1)) else: - return self.data[label].to_numpy().reshape((-1,)+self.shapes[label]) + data = self.data[label].to_numpy().reshape((-1,)+self.shapes[label]) + + return data.astype(type(data.flatten()[0])) + def set(self,label,data,info=None): """ @@ -123,11 +134,7 @@ class Table(): Human-readable information about the new data. """ - if info is not None: - if np.prod(data.shape[1:],dtype=int) == 1: - self.comments.append('{}: {}'.format(label,info)) - else: - self.comments.append('{} {}: {}'.format(label,data.shape[1:],info)) + self.__add_comment(label,data.shape[1:],info) if re.match(r'[0-9]*?_',label): idx,key = label.split('_',1) @@ -136,6 +143,7 @@ class Table(): else: self.data[label] = data.reshape(self.data[label].shape) + def add(self,label,data,info=None): """ Add column data. @@ -150,17 +158,16 @@ class Table(): Human-readable information about the modified data. """ - if info is not None: - if np.prod(data.shape[1:],dtype=int) == 1: - self.comments.append('{}: {}'.format(label,info)) - else: - self.comments.append('{} {}: {}'.format(label,data.shape[1:],info)) - + self.__add_comment(label,data.shape[1:],info) + self.shapes[label] = data.shape[1:] if len(data.shape) > 1 else (1,) - size = np.prod(data.shape[1:],dtype=int) - new_data = pd.DataFrame(data=data.reshape(-1,size), - columns=[label for l in range(size)]) - self.data = pd.concat([self.data,new_data],axis=1) + size = np.prod(data.shape[1:],dtype=int) + new = pd.DataFrame(data=data.reshape(-1,size), + columns=[label]*size, + ) + new.index = self.data.index + self.data = pd.concat([self.data,new],axis=1) + def delete(self,label): """ @@ -176,6 +183,7 @@ class Table(): del self.shapes[label] + def rename(self,label_old,label_new,info=None): """ Rename column data. @@ -190,9 +198,10 @@ class Table(): """ self.data.rename(columns={label_old:label_new},inplace=True) - comments = '{} => {}'.format(label_old,label_new) - comments += ': {}'.format(info) if info is not None else '' - self.comments.append(comments) + self.comments.append('{} => {}{}'.format(label_old, + label_new, + '' if info is None else ': {}'.format(info), + )) self.shapes[label_new] = self.shapes.pop(label_old) @@ -203,26 +212,18 @@ class Table(): Parameters ---------- - label : list of str or str + label : str or list Column labels. - ascending : bool, optional + ascending : bool or list, optional Set sort order. """ - _temp = [] - _labels = [] - for label in labels if isinstance(labels,list) else [labels]: - if re.match(r'[0-9]*?_',label): - _temp.append(str(random.getrandbits(128))) - self.add(_temp[-1],self.get(label)) - _labels.append(_temp[-1]) - else: - _labels.append(label) - - self.data.sort_values(_labels,axis=0,inplace=True,ascending=ascending) - for t in _temp: self.delete(t) + self.__label_flat() + self.data.sort_values(labels,axis=0,inplace=True,ascending=ascending) + self.__label_condensed() self.comments.append('sorted by [{}]'.format(', '.join(labels))) + def to_ASCII(self,fname): """ Store as plain text file. @@ -238,14 +239,14 @@ class Table(): if(self.shapes[l] == (1,)): labels.append('{}'.format(l)) elif(len(self.shapes[l]) == 1): - labels+=['{}_{}'.format(i+1,l)\ - for i in range(self.shapes[l][0])] + labels += ['{}_{}'.format(i+1,l) \ + for i in range(self.shapes[l][0])] else: - labels+=['{}:{}_{}'.format('x'.join([str(d) for d in self.shapes[l]]),i+1,l)\ - for i in range(np.prod(self.shapes[l],dtype=int))] + labels += ['{}:{}_{}'.format('x'.join([str(d) for d in self.shapes[l]]),i+1,l) \ + for i in range(np.prod(self.shapes[l],dtype=int))] - header = ['{} header'.format(len(self.comments)+1)]\ - + self.comments\ + header = ['{} header'.format(len(self.comments)+1)] \ + + self.comments \ + [' '.join(labels)] try: diff --git a/python/tests/test_Table.py b/python/tests/test_Table.py index b17348881..a0dc31975 100644 --- a/python/tests/test_Table.py +++ b/python/tests/test_Table.py @@ -9,7 +9,7 @@ from damask import Table @pytest.fixture def default(): """Simple Table.""" - x = np.ones((5,13)) + x = np.ones((5,13),dtype=float) return Table(x,{'F':(3,3),'v':(3,),'s':(1,)},['test data','contains only ones']) @pytest.fixture @@ -58,7 +58,7 @@ class TestTable: assert np.allclose(d,0.0) and d.shape[1:] == (3,3) def test_labels(self,default): - assert default.labels() == ['F','v','s'] + assert default.labels == ['F','v','s'] def test_add(self,default): d = np.random.random((5,9)) @@ -82,9 +82,9 @@ class TestTable: default.get('v') - def test_invalid_initialization(self,default): - x = default.get('v') - with pytest.raises(IndexError): + def test_invalid_initialization(self): + x = np.random.random((5,10)) + with pytest.raises(ValueError): Table(x,{'F':(3,3)}) def test_invalid_set(self,default): @@ -115,7 +115,14 @@ class TestTable: def test_sort_revert(self): x = np.random.random((5,12)) t = Table(x,{'F':(3,3),'v':(3,)},['random test data']) - t.sort_by('4_F',False) + t.sort_by('4_F',ascending=False) sort = t.get('4_F') assert np.all(np.sort(sort,0)==sort[::-1,:]) + def test_sort(self): + t = Table(np.array([[0,1,],[2,1,]]), + {'v':(2,)}, + ['test data']) + t.add('s',np.array(['b','a'])) + t.sort_by('s') + assert np.all(t.get('1_v') == np.array([2,0]).reshape((2,1)))