-
-
Notifications
You must be signed in to change notification settings - Fork 394
/
tabular.py
319 lines (251 loc) · 11.4 KB
/
tabular.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
import numpy as np
import param
from ..core import OrderedDict, Dimension, Element, NdElement, HoloMap
class ItemTable(Element):
"""
A tabular element type to allow convenient visualization of either
a standard Python dictionary, an OrderedDict or a list of tuples
(i.e. input suitable for an OrderedDict constructor). If an
OrderedDict is used, the headings will be kept in the correct
order. Tables store heterogeneous data with different labels.
Dimension objects are also accepted as keys, allowing dimensional
information (e.g type and units) to be associated per heading.
"""
kdims = param.List(default=[], bounds=(0, 0), doc="""
ItemTables hold an index Dimension for each value they contain, i.e.
they are equivalent to the keys.""")
vdims = param.List(default=[Dimension('Default')], bounds=(1, None), doc="""
ItemTables should have only index Dimensions.""")
group = param.String(default="ItemTable", constant=True)
@property
def rows(self):
return len(self.vdims)
@property
def cols(self):
return 2
def __init__(self, data, **params):
if type(data) == dict:
raise ValueError("ItemTable cannot accept a standard Python dictionary "
"as a well-defined item ordering is required.")
elif isinstance(data, dict): pass
elif isinstance(data, list):
data = OrderedDict(data)
else:
data = OrderedDict(list(data)) # Python 3
if not 'vdims' in params:
params['vdims'] = list(data.keys())
str_keys = OrderedDict((k.name if isinstance(k, Dimension)
else k ,v) for (k,v) in data.items())
super(ItemTable, self).__init__(str_keys, **params)
def __getitem__(self, heading):
"""
Get the value associated with the given heading (key).
"""
if heading is ():
return self
if heading not in self._cached_value_names:
raise IndexError("%r not in available headings." % heading)
return self.data.get(heading, np.NaN)
@classmethod
def collapse_data(cls, data, function, **kwargs):
groups = np.vstack([np.array(odict.values()) for odict in data]).T
return OrderedDict(zip(data[0].keys(), function(groups, axis=-1, **kwargs)))
def dimension_values(self, dimension):
if isinstance(dimension, int):
dimension = self._cached_index_names[dimension]
if dimension in self.dimensions('value', label=True):
return [self.data.get(dimension, np.NaN)]
else:
return super(ItemTable, self).dimension_values(dimension)
def sample(self, samples=[]):
if callable(samples):
sampled_data = OrderedDict(item for item in self.data.items()
if samples(item))
else:
sampled_data = OrderedDict((s, self.data.get(s, np.NaN)) for s in samples)
return self.clone(sampled_data)
def reduce(self, dimensions=None, function=None, **reduce_map):
raise NotImplementedError('ItemTables are for heterogeneous data, which'
'cannot be reduced.')
def pprint_cell(self, row, col):
"""
Get the formatted cell value for the given row and column indices.
"""
if col > 2:
raise Exception("Only two columns available in a ItemTable.")
elif row >= self.rows:
raise Exception("Maximum row index is %d" % self.rows-1)
elif col == 0:
return str(self.dimensions('value')[row])
else:
dim = self.get_dimension(row)
heading = self._cached_value_names[row]
return dim.pprint_value(self.data.get(heading, np.NaN))
def hist(self, *args, **kwargs):
raise NotImplementedError("ItemTables are not homogenous and "
"don't support histograms.")
def cell_type(self, row, col):
"""
Returns the cell type given a row and column index. The common
basic cell types are 'data' and 'heading'.
"""
if col == 0: return 'heading'
else: return 'data'
def dframe(self):
"""
Generates a Pandas dframe from the ItemTable.
"""
from pandas import DataFrame
return DataFrame({(k.name if isinstance(k, Dimension)
else k): [v] for k, v in self.data.items()})
def table(self):
return Table(OrderedDict([((), self.values())]), kdims=[],
vdims=self.vdims)
def values(self):
return tuple(self.data.get(k, np.NaN)
for k in self._cached_value_names)
class Table(NdElement):
"""
Table is an NdElement type, which gets displayed in a tabular
format and is convertible to most other Element types.
"""
kdims = param.List(default=[Dimension(name="Row")], doc="""
One or more key dimensions. By default, the special 'Row'
dimension ensures that the table is always indexed by the row
number.
If no key dimensions are set, only one entry can be stored
using the empty key ().""")
group = param.String(default='Table', constant=True, doc="""
The group is used to describe the Table.""")
def _add_item(self, key, value, sort=True):
if self.indexed and ((key != len(self)) and (key != (len(self),))):
raise Exception("Supplied key %s does not correspond to the items row number." % key)
if isinstance(value, (dict, OrderedDict)):
if all(isinstance(k, str) for k in key):
value = ItemTable(value)
else:
raise ValueError("Tables only supports string inner"
"keys when supplied nested dictionary")
if isinstance(value, ItemTable):
if value.vdims != self.vdims:
raise Exception("Input ItemTables dimensions must match value dimensions.")
value = value.data.values()
super(Table, self)._add_item(key, value, sort)
@property
def indexed(self):
"""
Whether this is an indexed table: a table that has a single
key dimension called 'Row' corresponds to the row number.
"""
return self.ndims == 1 and self.kdims[0].name == 'Row'
@property
def rows(self):
return len(self.data) + 1
@property
def cols(self):
return self.ndims + len(self.vdims)
def pprint_cell(self, row, col):
"""
Get the formatted cell value for the given row and column indices.
"""
ndims = self.ndims
if col >= self.cols:
raise Exception("Maximum column index is %d" % self.cols-1)
elif row >= self.rows:
raise Exception("Maximum row index is %d" % self.rows-1)
elif row == 0:
if col >= ndims:
return str(self.vdims[col - ndims])
return str(self.kdims[col])
else:
dim = self.get_dimension(col)
if col >= ndims:
row_values = self.values()[row-1]
val = row_values[col - ndims]
else:
row_data = list(self.data.keys())[row-1]
val = row_data[col]
return dim.pprint_value(val)
def cell_type(self, row, col):
"""
Returns the cell type given a row and column index. The common
basic cell types are 'data' and 'heading'.
"""
return 'heading' if row == 0 else 'data'
@property
def to(self):
"""
Property to create a conversion table with methods to convert
to any type.
"""
return TableConversion(self)
def dframe(self, value_label='data'):
dframe = super(Table, self).dframe(value_label=value_label)
# Drop 'Row' column as it is redundant with dframe index
if self.indexed: del dframe['Row']
return dframe
class TableConversion(object):
"""
TableConversion is a very simple container object which can
be given an existing Table and provides methods to convert
the Table into most other Element types.
"""
def __init__(self, table):
self._table = table
def _conversion(self, kdims=None, vdims=None, new_type=None, **kwargs):
if kdims is None:
kdims = self._table._cached_index_names
elif kdims and not isinstance(kdims, list): kdims = [kdims]
if vdims is None:
vdims = self._table._cached_value_names
elif vdims and not isinstance(vdims, list): vdims = [vdims]
if (any(kd in self._table._cached_value_names for kd in kdims) or
any(vd in self._table._cached_index_names for vd in vdims)):
new_kdims = [kd for kd in self._table._cached_index_names
if kd not in kdims and kd not in vdims] + kdims
selected = self._table.reindex(new_kdims, vdims)
else:
selected = self._table.select(**{'value': vdims})
if kdims != self._table._cached_index_names:
selected = self._table.reindex(kdims)
all_dims = selected.dimensions(label=True)
invalid = [dim for dim in kdims+vdims if dim not in all_dims]
if invalid:
raise Exception("Dimensions %r could not be found during conversion to %s new_type" %
(invalid, new_type.__name__))
group_dims = [dim for dim in selected._cached_index_names if not dim in kdims+vdims]
params = dict({'kdims': [selected.get_dimension(kd) for kd in kdims],
'vdims': [selected.get_dimension(vd) for vd in vdims]},
**kwargs)
if len(kdims) == selected.ndims:
return new_type(selected, **params)
return selected.groupby(group_dims, container_type=HoloMap, group_type=new_type, **params)
def bars(self, kdims=None, vdims=None, **kwargs):
from .chart import Bars
return self._conversion(kdims, vdims, Bars, **kwargs)
def curve(self, kdims=None, vdims=None, **kwargs):
from .chart import Curve
return self._conversion(kdims, vdims, Curve, **kwargs)
def heatmap(self, kdims=None, vdims=None, **kwargs):
from .raster import HeatMap
return self._conversion(kdims, vdims, HeatMap, **kwargs)
def points(self, kdims=None, vdims=None, **kwargs):
from .chart import Points
return self._conversion(kdims, vdims, Points, **kwargs)
def scatter(self, kdims=None, vdims=None, **kwargs):
from .chart import Scatter
return self._conversion(kdims, vdims, Scatter, **kwargs)
def scatter3d(self, kdims=None, vdims=None, **kwargs):
from .chart3d import Scatter3D
return self._conversion(kdims, vdims, Scatter3D, **kwargs)
def raster(self, kdims=None, vdims=None, **kwargs):
from .raster import Raster
heatmap = self.heatmap(kdims, vdims, **kwargs)
return Raster(heatmap.data, **dict(self._table.get_param_values(onlychanged=True)))
def surface(self, kdims=None, vdims=None, **kwargs):
from .chart3d import Surface
heatmap = self.heatmap(kdims, vdims, **kwargs)
return Surface(heatmap.data, **dict(self._table.get_param_values(onlychanged=True)))
def vectorfield(self, kdims=None, vdims=None, **kwargs):
from .chart import VectorField
return self._conversion(kdims, vdims, VectorField, **kwargs)