def tree2table(data, transpose=False, head=None): """build table from data. The table will be multi-level (main-rows and sub-rows), if: 1. there is more than one column 2. each cell within a row is a list or tuple If any of the paths contain tuples/lists, these are expanded to extra columns as well. If head is given, only first head rows are output. returns matrix, row_headers, col_headers """ logger = Component.get_logger() labels = getPaths(data) if len(labels) < 2: raise ValueError("expected at least two levels for building table, got %i: %s" % (len(labels), str(labels))) effective_labels = count_levels(labels) # subtract last level (will be expanded) and 1 for row header effective_cols = sum(effective_labels[:-1]) - 1 col_headers = [""] * effective_cols + labels[-1] ncols = len(col_headers) paths = list(itertools.product(*labels[1:-1])) header_offset = effective_cols matrix = [] logger.debug( "Datatree.buildTable: creating table with %i columns" % (len(col_headers))) # the following can be made more efficient # by better use of indices row_offset = 0 row_headers = [] # iterate over main rows for x, row in enumerate(labels[0]): first = True for xx, path in enumerate(paths): # get data - skip if there is None work = getLeaf(data, (row,) + path) if isinstance(work, pandas.DataFrame): if work.empty: continue else: if not work: continue row_data = [""] * ncols # add row header only for first row (if there are sub-rows) if first: if type(row) in Utils.ContainerTypes: row_headers.append(row[0]) for z, p in enumerate(row[1:]): row_data[z] = p else: row_headers.append(row) first = False else: row_headers.append("") # enter data for the first row for z, p in enumerate(path): row_data[z] = p # check for multi-level rows is_container = True max_rows = None for y, column in enumerate(labels[-1]): if column not in work: continue if type(work[column]) not in Utils.ContainerTypes: is_container = False break if max_rows == None: max_rows = len(work[column]) elif max_rows != len(work[column]): raise ValueError("multi-level rows - unequal lengths: %i != %i" % (max_rows, len(work[column]))) # add sub-rows if is_container: # multi-level rows for z in range(max_rows): for y, column in enumerate(labels[-1]): try: row_data[ y + header_offset] = Utils.quote_rst(work[column][z]) except KeyError: pass if z < max_rows - 1: matrix.append(row_data) row_headers.append("") row_data = [""] * ncols else: # single level row for y, column in enumerate(labels[-1]): try: row_data[ y + header_offset] = Utils.quote_rst(work[column]) except KeyError: pass matrix.append(row_data) if head and len(matrix) >= head: break if transpose: row_headers, col_headers = col_headers, row_headers matrix = list(zip(*matrix)) # convert headers to string (might be None) row_headers = [str(x) for x in row_headers] col_headers = [str(x) for x in col_headers] return matrix, row_headers, col_headers