Coverage for src/thunderlab/tabledata.py: 89%

1560 statements  

« prev     ^ index     » next       coverage.py v7.6.2, created at 2024-10-09 16:02 +0000

1""" 

2Tables with hierarchical headers and units 

3 

4## Classes 

5 

6- `class TableData`: tables with a rich hierarchical header 

7 including units and column-specific formats. Kind of similar to a 

8 pandas data frame, but with intuitive numpy-style indexing and nicely 

9 formatted output to csv, html, and latex. 

10 

11 

12## Helper functions 

13 

14- `write()`: shortcut for constructing and writing a TableData. 

15- `latex_unit()`: translate unit string into SIunit LaTeX code. 

16- `index2aa()`: convert an integer into an alphabetical representation. 

17- `aa2index()`: convert an alphabetical representation to an index. 

18 

19 

20## Configuration 

21 

22- `add_write_table_config()`: add parameter specifying how to write a table to a file as a new section to a configuration. 

23- `write_table_args()`: translates a configuration to the respective parameter names for writing a table to a file. 

24""" 

25 

26import sys 

27import os 

28import re 

29import math as m 

30import numpy as np 

31from io import StringIO 

32try: 

33 import pandas as pd 

34except ImportError: 

35 pass 

36 

37 

38__pdoc__ = {} 

39__pdoc__['TableData.__contains__'] = True 

40__pdoc__['TableData.__len__'] = True 

41__pdoc__['TableData.__iter__'] = True 

42__pdoc__['TableData.__next__'] = True 

43__pdoc__['TableData.__setupkey__'] = True 

44__pdoc__['TableData.__call__'] = True 

45__pdoc__['TableData.__getitem__'] = True 

46__pdoc__['TableData.__setitem__'] = True 

47__pdoc__['TableData.__delitem__'] = True 

48__pdoc__['TableData.__str__'] = True 

49 

50 

51default_missing_str = '-' 

52"""Default string indicating nan data elements when outputting data.""" 

53 

54default_missing_inputs = ['na', 'NA', 'nan', 'NAN', '-'] 

55"""Default strings that are translated to nan when loading table data.""" 

56 

57 

58class TableData(object): 

59 """Table with numpy-style indexing and a rich hierarchical header including units and formats. 

60  

61 Parameters 

62 ---------- 

63 data: str, stream, ndarray 

64 - a filename: load table from file with name `data`. 

65 - a stream/file handle: load table from that stream. 

66 - 1-D or 2-D ndarray of data: the data of the table. 

67 Requires als a specified `header`. 

68 header: list of str 

69 Header labels for each column. 

70 units: list of str, optional 

71 Unit strings for each column. 

72 formats: str or list of str, optional 

73 Format strings for each column. If only a single format string is 

74 given, then all columns are initialized with this format string. 

75 missing: list of str 

76 Missing data are indicated by one of these strings. 

77 

78 Manipulate table header 

79 ----------------------- 

80 

81 Each column of the table has a label (the name of the column), a 

82 unit, and a format specifier. Sections group columns into a hierarchy. 

83 

84 - `__init__()`: initialize a TableData from data or a file. 

85 - `append()`: append column to the table. 

86 - `insert()`: insert a table column at a given position. 

87 - `remove()`: remove columns from the table. 

88 - `section()`: the section name of a specified column. 

89 - `set_section()`: set a section name. 

90 - `append_section()`: add sections to the table header. 

91 - `insert_section()`: insert a section at a given position of the table header. 

92 - `label()`: the name of a column. 

93 - `set_label()`: set the name of a column. 

94 - `unit()`: the unit of a column. 

95 - `set_unit()`: set the unit of a column. 

96 - `set_units()`: set the units of all columns. 

97 - `format()`: the format string of the column. 

98 - `set_format()`: set the format string of a column. 

99 - `set_formats()`: set the format strings of all columns. 

100 

101 For example: 

102 ``` 

103 tf = TableData('data.csv') 

104 ``` 

105 loads a table directly from a file. See `load()` for details. 

106 ``` 

107 tf = TableData(np.random.randn(4,3), header=['aaa', 'bbb', 'ccc'], units=['m', 's', 'g'], formats='%.2f')  

108 ``` 

109 results in 

110 ``` plain 

111 aaa bbb ccc 

112 m s g  

113 1.45 0.01 0.16 

114 -0.74 -0.58 -1.34 

115 -2.06 0.08 1.47 

116 -0.43 0.60 1.38 

117 ``` 

118 

119 A more elaborate way to construct a table is: 

120 ``` 

121 df = TableData() 

122 # first column with section names and 3 data values: 

123 df.append(["data", "partial information", "size"], "m", "%6.2f", 

124 [2.34, 56.7, 8.9]) 

125 # next columns with single data values: 

126 df.append("full weight", "kg", "%.0f", 122.8) 

127 df.append_section("complete reaction") 

128 df.append("speed", "m/s", "%.3g", 98.7) 

129 df.append("median jitter", "mm", "%.1f", 23) 

130 df.append("size", "g", "%.2e", 1.234) 

131 # add a missing value to the second column: 

132 df.append_data(np.nan, 1) 

133 # fill up the remaining columns of the row: 

134 df.append_data((0.543, 45, 1.235e2)) 

135 # append data to the next row starting at the second column: 

136 df.append_data((43.21, 6789.1, 3405, 1.235e-4), 1) # next row 

137 ``` 

138 results in 

139 ``` plain 

140 data 

141 partial information complete reaction 

142 size full weight speed median jitter size 

143 m kg m/s mm g  

144 2.34 123 98.7 23.0 1.23e+00 

145 56.70 - 0.543 45.0 1.24e+02 

146 8.90 43 6.79e+03 3405.0 1.23e-04 

147 ``` 

148  

149 Table columns 

150 ------------- 

151 

152 Columns can be specified by an index or by the name of a column. In 

153 table headers with sections the colum can be specified by the 

154 section names and the column name separated by '>'. 

155  

156 - `index()`: the index of a column. 

157 - `__contains__()`: check for existence of a column. 

158 - `find_col()`: find the start and end index of a column specification. 

159 - `column_spec()`: full specification of a column with all its section names. 

160 - `column_head()`: the name, unit, and format of a column. 

161 - `table_header()`: the header of the table without content. 

162 

163 For example: 

164 ``` 

165 df.index('complete reaction>size) # returns 4 

166 'speed' in df # is True 

167 ``` 

168 

169 Iterating over columns 

170 ---------------------- 

171 

172 A table behaves like an ordered dictionary with column names as 

173 keys and the data of each column as values. 

174 Iterating over a table goes over columns. 

175  

176 - `keys()`: list of unique column keys for all available columns. 

177 - `values()`: list of column data corresponding to keys(). 

178 - `items()`: list of tuples with unique column specifications and the corresponding data. 

179 - `__len__()`: the number of columns. 

180 - `__iter__()`: initialize iteration over data columns. 

181 - `__next__()`: return data of next column as a list. 

182 - `data`: the table data as a list over columns each containing a list of data elements. 

183 

184 For example: 

185 ``` 

186 print('column specifications:') 

187 for c in range(df.columns()): 

188 print(df.column_spec(c)) 

189 print('keys():') 

190 for c, k in enumerate(df.keys()): 

191 print('%d: %s' % (c, k)) 

192 print('values():') 

193 for c, v in enumerate(df.values()): 

194 print(v) 

195 print('iterating over the table:') 

196 for v in df: 

197 print(v) 

198 ``` 

199 results in 

200 ``` plain 

201 column specifications: 

202 data>partial information>size 

203 data>partial information>full weight 

204 data>complete reaction>speed 

205 data>complete reaction>median jitter 

206 data>complete reaction>size 

207 keys(): 

208 0: data>partial information>size 

209 1: data>partial information>full weight 

210 2: data>complete reaction>speed 

211 3: data>complete reaction>median jitter 

212 4: data>complete reaction>size 

213 values(): 

214 [2.34, 56.7, 8.9] 

215 [122.8, nan, 43.21] 

216 [98.7, 0.543, 6789.1] 

217 [23, 45, 3405] 

218 [1.234, 123.5, 0.0001235] 

219 iterating over the table: 

220 [2.34, 56.7, 8.9] 

221 [122.8, nan, 43.21] 

222 [98.7, 0.543, 6789.1] 

223 [23, 45, 3405] 

224 [1.234, 123.5, 0.0001235] 

225 ``` 

226 

227 Accessing data 

228 -------------- 

229 

230 In contrast to the iterator functions the [] operator treats the 

231 table as a 2D-array where the first index indicates the row and 

232 the second index the column. 

233 

234 Like a numpy aray the table can be sliced, and logical indexing can 

235 be used to select specific parts of the table. 

236  

237 As for any function, columns can be specified as indices or strings. 

238  

239 - `rows()`: the number of rows. 

240 - `columns()`: the number of columns. 

241 - `shape`: number of rows and columns. 

242 - `row()`: a single row of the table as TableData. 

243 - `row_dict()`: a single row of the table as dictionary. 

244 - `col()`: a single column of the table as TableData. 

245 - `__call__()`: a single column of the table as ndarray. 

246 - `__getitem__()`: data elements specified by slice. 

247 - `__setitem__()`: assign values to data elements specified by slice. 

248 - `__delitem__()`: delete data elements or whole columns or rows. 

249 - `array()`: the table data as a ndarray. 

250 - `data_frame()`: the table data as a pandas DataFrame. 

251 - `dicts()`: the table as a list of dictionaries. 

252 - `dict()`: the table as a dictionary. 

253 - `append_data()`: append data elements to successive columns. 

254 - `append_data_column()`: append data elements to a column. 

255 - `set_column()`: set the column where to add data. 

256 - `fill_data()`: fill up all columns with missing data. 

257 - `clear_data()`: clear content of the table but keep header. 

258 - `key_value()`: a data element returned as a key-value pair. 

259  

260 - `sort()`: sort the table rows in place. 

261 - `statistics()`: descriptive statistics of each column. 

262 

263 For example: 

264 ``` 

265 # single column:  

266 df('size') # data of 'size' column as ndarray 

267 df[:,'size'] # data of 'size' column as ndarray 

268 df.col('size') # table with the single column 'size' 

269 

270 # single row:  

271 df[2,:] # table with data of only the third row 

272 df.row(2) # table with data of only the third row 

273 

274 # slices: 

275 df[2:5,['size','jitter']] # sub-table 

276 df[2:5,['size','jitter']].array() # ndarray with data only 

277 

278 # logical indexing: 

279 df[df('speed') > 100.0, 'size'] = 0.0 # set size to 0 if speed is > 100 

280 

281 # delete: 

282 del df[3:6, 'weight'] # delete rows 3-6 from column 'weight' 

283 del df[3:5,:] # delete rows 3-5 completeley 

284 del df[:,'speed'] # remove column 'speed' from table 

285 df.remove('weight') # remove column 'weigth' from table 

286 

287 # sort and statistics: 

288 df.sort(['weight', 'jitter']) 

289 df.statistics() 

290 ``` 

291 statistics() returns a table with standard descriptive statistics: 

292 ``` plain 

293 statistics data 

294 - partial information complete reaction 

295 - size full weight speed median jitter size 

296 - m kg m/s mm g  

297 mean 22.65 83 2.3e+03 1157.7 4.16e+01 

298 std 24.23 40 3.18e+03 1589.1 5.79e+01 

299 min 2.34 43 0.543 23.0 1.23e-04 

300 quartile1 5.62 83 49.6 34.0 6.17e-01 

301 median 8.90 123 98.7 45.0 1.23e+00 

302 quartile3 32.80 - 3.44e+03 1725.0 6.24e+01 

303 max 56.70 123 6.79e+03 3405.0 1.24e+02 

304 count 3.00 2 3 3.0 3.00e+00 

305 ``` 

306 

307 Write and load tables 

308 --------------------- 

309 

310 Table data can be written to a variety of text-based formats 

311 including comma separated values, latex and html files. Which 

312 columns are written can be controlled by the hide() and show() 

313 functions. TableData can be loaded from all the written file formats 

314 (except html), also directly via the constructor. 

315  

316 - `hide()`: hide a column or a range of columns. 

317 - `hide_all()`: hide all columns. 

318 - `hide_empty_columns()`: hide all columns that do not contain data. 

319 - `show()`: show a column or a range of columns. 

320 - `write()`: write table to a file or stream. 

321 - `write_file_stream()`: write table to file or stream and return appropriate file name. 

322 - `__str__()`: write table to a string. 

323 - `load()`: load table from file or stream. 

324 - `formats`: list of supported file formats for writing. 

325 - `descriptions`: dictionary with descriptions of the supported file formats. 

326 - `extensions`: dictionary with default filename extensions for each of the file formats. 

327 - `ext_formats`: dictionary mapping filename extensions to file formats. 

328 

329 See documentation of the `write()` function for examples of the supported file formats. 

330 

331 """ 

332 

333 formats = ['dat', 'ascii', 'csv', 'rtai', 'md', 'tex', 'html'] 

334 """list of strings: Supported output formats.""" 

335 descriptions = {'dat': 'data text file', 'ascii': 'ascii-art table', 

336 'csv': 'comma separated values', 'rtai': 'rtai-style table', 

337 'md': 'markdown', 'tex': 'latex tabular', 

338 'html': 'html markup'} 

339 """dict: Decription of output formats corresponding to `formats`.""" 

340 extensions = {'dat': 'dat', 'ascii': 'txt', 'csv': 'csv', 'rtai': 'dat', 

341 'md': 'md', 'tex': 'tex', 'html': 'html'} 

342 """dict: Default file extensions for the output `formats`. """ 

343 ext_formats = {'dat': 'dat', 'DAT': 'dat', 'txt': 'dat', 'TXT': 'dat', 

344 'csv': 'csv', 'CSV': 'csv', 'md': 'md', 'MD': 'md', 

345 'tex': 'tex', 'TEX': 'tex', 'html': 'html', 'HTML': 'html'} 

346 """dict: Mapping of file extensions to the output formats.""" 

347 

348 def __init__(self, data=None, header=None, units=None, formats=None, 

349 missing=default_missing_inputs, stop=None): 

350 self.data = [] 

351 self.shape = (0, 0) 

352 self.header = [] 

353 self.nsecs = 0 

354 self.units = [] 

355 self.formats = [] 

356 self.hidden = [] 

357 self.setcol = 0 

358 self.addcol = 0 

359 if header is not None: 

360 if units is None: 

361 units = ['']*len(header) 

362 if formats is None: 

363 formats = ['%g']*len(header) 

364 elif not isinstance(formats, (list, tuple, np.ndarray)): 

365 formats = [formats]*len(header) 

366 for h, u, f in zip(header, units, formats): 

367 self.append(h, u, f) 

368 if data is not None: 

369 if isinstance(data, TableData): 

370 self.shape = data.shape 

371 self.nsecs = data.nsecs 

372 self.setcol = data.setcol 

373 self.addcol = data.addcol 

374 for c in range(data.columns()): 

375 self.header.append([]) 

376 for h in data.header[c]: 

377 self.header[c].append(h) 

378 self.units.append(data.units[c]) 

379 self.formats.append(data.formats[c]) 

380 self.hidden.append(data.hidden[c]) 

381 self.data.append([]) 

382 for d in data.data[c]: 

383 self.data[c].append(d) 

384 elif isinstance(data, (list, tuple, np.ndarray)): 

385 if isinstance(data[0], (list, tuple, np.ndarray)): 

386 # 2D list, rows first: 

387 for row in data: 

388 for c, val in enumerate(row): 

389 self.data[c].append(val) 

390 else: 

391 # 1D list: 

392 for c, val in enumerate(data): 

393 self.data[c].append(val) 

394 else: 

395 self.load(data, missing, stop) 

396 

397 def append(self, label, unit=None, formats=None, value=None, 

398 fac=None, key=None): 

399 """Append column to the table. 

400 

401 Parameters 

402 ---------- 

403 label: str or list of str 

404 Optional section titles and the name of the column. 

405 unit: str or None 

406 The unit of the column contents. 

407 formats: str or None 

408 The C-style format string used for printing out the column content, e.g. 

409 '%g', '%.2f', '%s', etc. 

410 If None, the format is set to '%g'. 

411 value: None, float, int, str, etc. or list thereof, or list of dict 

412 If not None, data for the column. 

413 If list of dictionaries, extract from each dictionary in the list 

414 the value specified by `key`. If `key` is `None` use `label` as 

415 the key. 

416 fac: float 

417 If not None, multiply the data values by this number. 

418 key: None or key of a dictionary 

419 If not None and `value` is a list of dictionaries, 

420 extract from each dictionary in the list the value specified 

421 by `key` and assign the resulting list as data to the column. 

422 

423 Returns 

424 ------- 

425 index: int 

426 The index of the new column. 

427 """ 

428 if self.addcol >= len(self.data): 

429 if isinstance(label, (list, tuple, np.ndarray)): 

430 self.header.append(list(reversed(label))) 

431 label = label[-1] 

432 else: 

433 self.header.append([label]) 

434 self.formats.append(formats or '%g') 

435 self.units.append(unit or '') 

436 self.hidden.append(False) 

437 self.data.append([]) 

438 if self.nsecs < len(self.header[-1])-1: 

439 self.nsecs = len(self.header[-1])-1 

440 else: 

441 if isinstance(label, (list, tuple, np.ndarray)): 

442 self.header[self.addcol] = list(reversed(label)) + self.header[self.addcol] 

443 label = label[-1] 

444 else: 

445 self.header[self.addcol] = [label] + self.header[self.addcol] 

446 self.units[self.addcol] = unit or '' 

447 self.formats[self.addcol] = formats or '%g' 

448 if self.nsecs < len(self.header[self.addcol])-1: 

449 self.nsecs = len(self.header[self.addcol])-1 

450 if not key: 

451 key = label 

452 if value is not None: 

453 if isinstance(value, (list, tuple, np.ndarray)): 

454 if key and len(value) > 0 and isinstance(value[0], dict): 

455 value = [d[key] if key in d else float('nan') for d in value] 

456 self.data[-1].extend(value) 

457 else: 

458 self.data[-1].append(value) 

459 if fac: 

460 for k in range(len(self.data[-1])): 

461 self.data[-1][k] *= fac 

462 self.addcol = len(self.data) 

463 self.shape = (self.rows(), self.columns()) 

464 return self.addcol-1 

465 

466 def insert(self, column, label, unit=None, formats=None, value=None): 

467 """Insert a table column at a given position. 

468 

469 .. WARNING:: 

470 If no `value` is given, the inserted column is an empty list. 

471 

472 Parameters 

473 ---------- 

474 columns int or str 

475 Column before which to insert the new column. 

476 Column can be specified by index or name, 

477 see `index()` for details. 

478 label: str or list of str 

479 Optional section titles and the name of the column. 

480 unit: str or None 

481 The unit of the column contents. 

482 formats: str or None 

483 The C-style format string used for printing out the column content, e.g. 

484 '%g', '%.2f', '%s', etc. 

485 If None, the format is set to '%g'. 

486 value: None, float, int, str, etc. or list thereof 

487 If not None, data for the column. 

488 

489 Returns 

490 ------- 

491 index: int 

492 The index of the inserted column. 

493  

494 Raises 

495 ------ 

496 IndexError: 

497 If an invalid column was specified. 

498 """ 

499 col = self.index(column) 

500 if col is None: 

501 if isinstance(column, (np.integer, int)): 

502 column = '%d' % column 

503 raise IndexError('Cannot insert before non-existing column ' + column) 

504 if isinstance(label, (list, tuple, np.ndarray)): 

505 self.header.insert(col, list(reversed(label))) 

506 else: 

507 self.header.insert(col, [label]) 

508 self.formats.insert(col, formats or '%g') 

509 self.units.insert(col, unit or '') 

510 self.hidden.insert(col, False) 

511 self.data.insert(col, []) 

512 if self.nsecs < len(self.header[col])-1: 

513 self.nsecs = len(self.header[col])-1 

514 if value is not None: 

515 if isinstance(value, (list, tuple, np.ndarray)): 

516 self.data[col].extend(value) 

517 else: 

518 self.data[col].append(value) 

519 self.addcol = len(self.data) 

520 self.shape = (self.rows(), self.columns()) 

521 return col 

522 

523 def remove(self, columns): 

524 """Remove columns from the table. 

525 

526 Parameters 

527 ----------- 

528 columns: int or str or list of int of str 

529 Columns can be specified by index or name, 

530 see `index()` for details. 

531 

532 Raises 

533 ------ 

534 IndexError: 

535 If an invalid column was specified. 

536 """ 

537 # fix columns: 

538 if not isinstance(columns, (list, tuple, np.ndarray)): 

539 columns = [ columns ] 

540 if not columns: 

541 return 

542 # remove: 

543 for col in columns: 

544 c = self.index(col) 

545 if c is None: 

546 if isinstance(col, (np.integer, int)): 

547 col = '%d' % col 

548 raise IndexError('Cannot remove non-existing column ' + col) 

549 continue 

550 if c+1 < len(self.header): 

551 self.header[c+1].extend(self.header[c][len(self.header[c+1]):]) 

552 del self.header[c] 

553 del self.units[c] 

554 del self.formats[c] 

555 del self.hidden[c] 

556 del self.data[c] 

557 if self.setcol >= len(self.data): 

558 self.setcol = 0 

559 self.shape = (self.rows(), self.columns()) 

560 

561 def section(self, column, level): 

562 """The section name of a specified column. 

563 

564 Parameters 

565 ---------- 

566 column: None, int, or str 

567 A specification of a column. 

568 See self.index() for more information on how to specify a column. 

569 level: int 

570 The level of the section to be returned. The column label itself is level=0. 

571 

572 Returns 

573 ------- 

574 name: str 

575 The name of the section at the specified level containing 

576 the column. 

577 index: int 

578 The column index that contains this section 

579 (equal or smaller thant `column`). 

580 

581 Raises 

582 ------ 

583 IndexError: 

584 If `level` exceeds the maximum possible level. 

585 """ 

586 if level < 0 or level > self.nsecs: 

587 raise IndexError('Invalid section level') 

588 column = self.index(column) 

589 while len(self.header[column]) <= level: 

590 column -= 1 

591 return self.header[column][level], column 

592 

593 def set_section(self, label, column, level): 

594 """Set a section name. 

595 

596 Parameters 

597 ---------- 

598 label: str 

599 The new name to be used for the section. 

600 column: None, int, or str 

601 A specification of a column. 

602 See self.index() for more information on how to specify a column. 

603 level: int 

604 The level of the section to be set. The column label itself is level=0. 

605 """ 

606 column = self.index(column) 

607 self.header[column][level] = label 

608 return column 

609 

610 def append_section(self, label): 

611 """Add sections to the table header. 

612 

613 Each column of the table has a header label. Columns can be 

614 grouped into sections. Sections can be nested arbitrarily. 

615 

616 Parameters 

617 ---------- 

618 label: stri or list of str 

619 The name(s) of the section(s). 

620 

621 Returns 

622 ------- 

623 index: int 

624 The column index where the section was appended. 

625 """ 

626 if self.addcol >= len(self.data): 

627 if isinstance(label, (list, tuple, np.ndarray)): 

628 self.header.append(list(reversed(label))) 

629 else: 

630 self.header.append([label]) 

631 self.units.append('') 

632 self.formats.append('') 

633 self.hidden.append(False) 

634 self.data.append([]) 

635 else: 

636 if isinstance(label, (list, tuple, np.ndarray)): 

637 self.header[self.addcol] = list(reversed(label)) + self.header[self.addcol] 

638 else: 

639 self.header[self.addcol] = [label] + self.header[self.addcol] 

640 if self.nsecs < len(self.header[self.addcol]): 

641 self.nsecs = len(self.header[self.addcol]) 

642 self.addcol = len(self.data)-1 

643 self.shape = (self.rows(), self.columns()) 

644 return self.addcol 

645 

646 def insert_section(self, column, section): 

647 """Insert a section at a given position of the table header. 

648 

649 Parameters 

650 ---------- 

651 columns int or str 

652 Column before which to insert the new section. 

653 Column can be specified by index or name, 

654 see `index()` for details. 

655 section: str 

656 The name of the section. 

657 

658 Returns 

659 ------- 

660 index: int 

661 The index of the column where the section was inserted. 

662  

663 Raises 

664 ------ 

665 IndexError: 

666 If an invalid column was specified. 

667 """ 

668 col = self.index(column) 

669 if col is None: 

670 if isinstance(column, (np.integer, int)): 

671 column = '%d' % column 

672 raise IndexError('Cannot insert at non-existing column ' + column) 

673 self.header[col].append(section) 

674 if self.nsecs < len(self.header[col])-1: 

675 self.nsecs = len(self.header[col])-1 

676 return col 

677 

678 def label(self, column): 

679 """The name of a column. 

680 

681 Parameters 

682 ---------- 

683 column: None, int, or str 

684 A specification of a column. 

685 See self.index() for more information on how to specify a column. 

686 

687 Returns 

688 ------- 

689 name: str 

690 The column label. 

691 """ 

692 column = self.index(column) 

693 return self.header[column][0] 

694 

695 def set_label(self, label, column): 

696 """Set the name of a column. 

697 

698 Parameters 

699 ---------- 

700 label: str 

701 The new name to be used for the column. 

702 column: None, int, or str 

703 A specification of a column. 

704 See self.index() for more information on how to specify a column. 

705 """ 

706 column = self.index(column) 

707 self.header[column][0] = label 

708 return column 

709 

710 def unit(self, column): 

711 """The unit of a column. 

712 

713 Parameters 

714 ---------- 

715 column: None, int, or str 

716 A specification of a column. 

717 See self.index() for more information on how to specify a column. 

718 

719 Returns 

720 ------- 

721 unit: str 

722 The unit. 

723 """ 

724 column = self.index(column) 

725 return self.units[column] 

726 

727 def set_unit(self, unit, column): 

728 """Set the unit of a column. 

729 

730 Parameters 

731 ---------- 

732 unit: str 

733 The new unit to be used for the column. 

734 column: None, int, or str 

735 A specification of a column. 

736 See self.index() for more information on how to specify a column. 

737 """ 

738 column = self.index(column) 

739 self.units[column] = unit 

740 return column 

741 

742 def set_units(self, units): 

743 """Set the units of all columns. 

744 

745 Parameters 

746 ---------- 

747 units: list of str 

748 The new units to be used. 

749 """ 

750 for c, u in enumerate(units): 

751 self.units[c] = u 

752 

753 def format(self, column): 

754 """The format string of the column. 

755 

756 Parameters 

757 ---------- 

758 column: None, int, or str 

759 A specification of a column. 

760 See self.index() for more information on how to specify a column. 

761 

762 Returns 

763 ------- 

764 format: str 

765 The format string. 

766 """ 

767 column = self.index(column) 

768 return self.formats[column] 

769 

770 def set_format(self, format, column): 

771 """Set the format string of a column. 

772 

773 Parameters 

774 ---------- 

775 format: str 

776 The new format string to be used for the column. 

777 column: None, int, or str 

778 A specification of a column. 

779 See self.index() for more information on how to specify a column. 

780 """ 

781 column = self.index(column) 

782 self.formats[column] = format 

783 return column 

784 

785 def set_formats(self, formats): 

786 """Set the format strings of all columns. 

787 

788 Parameters 

789 ---------- 

790 formats: str or list of str 

791 The new format strings to be used. 

792 If only a single format is specified, 

793 then all columns get the same format. 

794 """ 

795 if isinstance(formats, (list, tuple, np.ndarray)): 

796 for c, f in enumerate(formats): 

797 self.formats[c] = f or '%g' 

798 else: 

799 for c in range(len(self.formats)): 

800 self.formats[c] = formats or '%g' 

801 

802 def table_header(self): 

803 """The header of the table without content. 

804 

805 Returns 

806 ------- 

807 data: TableData 

808 A TableData object with the same header but empty data. 

809 """ 

810 data = TableData() 

811 sec_indices = [-1] * self.nsecs 

812 for c in range(self.columns()): 

813 data.append(*self.column_head(c)) 

814 for l in range(self.nsecs): 

815 s, i = self.section(c, l+1) 

816 if i != sec_indices[l]: 

817 data.header[-1].append(s) 

818 sec_indices[l] = i 

819 data.nsecs = self.nsecs 

820 return data 

821 

822 def column_head(self, column): 

823 """The name, unit, and format of a column. 

824 

825 Parameters 

826 ---------- 

827 column: None, int, or str 

828 A specification of a column. 

829 See self.index() for more information on how to specify a column. 

830 

831 Returns 

832 ------- 

833 name: str 

834 The column label. 

835 unit: str 

836 The unit. 

837 format: str 

838 The format string. 

839 """ 

840 column = self.index(column) 

841 return self.header[column][0], self.units[column], self.formats[column] 

842 

843 def column_spec(self, column): 

844 """Full specification of a column with all its section names. 

845 

846 Parameters 

847 ---------- 

848 column: int or str 

849 Specifies the column. 

850 See self.index() for more information on how to specify a column. 

851 

852 Returns 

853 ------- 

854 s: str 

855 Full specification of the column by all its section names and its header name. 

856 """ 

857 c = self.index(column) 

858 fh = [self.header[c][0]] 

859 for l in range(self.nsecs): 

860 fh.append(self.section(c, l+1)[0]) 

861 return '>'.join(reversed(fh)) 

862 

863 def find_col(self, column): 

864 """Find the start and end index of a column specification. 

865  

866 Parameters 

867 ---------- 

868 column: None, int, or str 

869 A specification of a column. 

870 See self.index() for more information on how to specify a column. 

871 

872 Returns 

873 ------- 

874 c0: int or None 

875 A valid column index or None that is specified by `column`. 

876 c1: int or None 

877 A valid column index or None of the column following the range specified 

878 by `column`. 

879 """ 

880 

881 def find_column_indices(ss, si, minns, maxns, c0, strict=True): 

882 if si >= len(ss): 

883 return None, None, None, None 

884 ns0 = 0 

885 for ns in range(minns, maxns+1): 

886 nsec = maxns-ns 

887 if ss[si] == '': 

888 si += 1 

889 continue 

890 for c in range(c0, len(self.header)): 

891 if nsec < len(self.header[c]) and \ 

892 ( ( strict and self.header[c][nsec] == ss[si] ) or 

893 ( not strict and ss[si] in self.header[c][nsec] ) ): 

894 ns0 = ns 

895 c0 = c 

896 si += 1 

897 if si >= len(ss): 

898 c1 = len(self.header) 

899 for c in range(c0+1, len(self.header)): 

900 if nsec < len(self.header[c]): 

901 c1 = c 

902 break 

903 return c0, c1, ns0, None 

904 elif nsec > 0: 

905 break 

906 return None, c0, ns0, si 

907 

908 if column is None: 

909 return None, None 

910 if not isinstance(column, (np.integer, int)) and column.isdigit(): 

911 column = int(column) 

912 if isinstance(column, (np.integer, int)): 

913 if column >= 0 and column < len(self.header): 

914 return column, column+1 

915 else: 

916 return None, None 

917 # find column by header: 

918 ss = column.rstrip('>').split('>') 

919 maxns = self.nsecs 

920 si0 = 0 

921 while si0 < len(ss) and ss[si0] == '': 

922 maxns -= 1 

923 si0 += 1 

924 if maxns < 0: 

925 maxns = 0 

926 c0, c1, ns, si = find_column_indices(ss, si0, 0, maxns, 0, True) 

927 if c0 is None and c1 is not None: 

928 c0, c1, ns, si = find_column_indices(ss, si, ns, maxns, c1, False) 

929 return c0, c1 

930 

931 def index(self, column): 

932 """The index of a column. 

933  

934 Parameters 

935 ---------- 

936 column: None, int, or str 

937 A specification of a column. 

938 - None: no column is specified 

939 - int: the index of the column (first column is zero), e.g. `index(2)`. 

940 - a string representing an integer is converted into the column index, 

941 e.g. `index('2')` 

942 - a string specifying a column by its header. 

943 Header names of descending hierarchy are separated by '>'. 

944 

945 Returns 

946 ------- 

947 index: int or None 

948 A valid column index or None. 

949 """ 

950 c0, c1 = self.find_col(column) 

951 return c0 

952 

953 def __contains__(self, column): 

954 """Check for existence of a column. 

955 

956 Parameters 

957 ---------- 

958 column: None, int, or str 

959 The column to be checked. 

960 See self.index() for more information on how to specify a column. 

961 

962 Returns 

963 ------- 

964 contains: bool 

965 True if `column` specifies an existing column key. 

966 """ 

967 return self.index(column) is not None 

968 

969 def keys(self): 

970 """List of unique column keys for all available columns. 

971 

972 Returns 

973 ------- 

974 keys: list of str 

975 List of unique column specifications. 

976 """ 

977 return [self.column_spec(c) for c in range(self.columns())] 

978 

979 def values(self): 

980 """List of column data corresponding to keys(). 

981 

982 Returns 

983 ------- 

984 data: list of list of values 

985 The data of the table. First index is columns! 

986 """ 

987 return self.data 

988 

989 def items(self): 

990 """Column names and corresponding data. 

991 

992 Returns 

993 ------- 

994 items: list of tuples 

995 Unique column specifications and the corresponding data. 

996 """ 

997 return [(self.column_spec(c), self.data[c]) for c in range(self.columns())] 

998 

999 def __len__(self): 

1000 """The number of columns. 

1001  

1002 Returns 

1003 ------- 

1004 columns: int 

1005 The number of columns contained in the table. 

1006 """ 

1007 return self.columns() 

1008 

1009 def __iter__(self): 

1010 """Initialize iteration over data columns. 

1011 """ 

1012 self.iter_counter = -1 

1013 return self 

1014 

1015 def __next__(self): 

1016 """Next column of data. 

1017 

1018 Returns 

1019 ------- 

1020 data: list of values 

1021 Table data of next column. 

1022 """ 

1023 self.iter_counter += 1 

1024 if self.iter_counter >= self.columns(): 

1025 raise StopIteration 

1026 else: 

1027 return self.data[self.iter_counter] 

1028 

1029 def next(self): 

1030 """Return next data columns. (python2 syntax) 

1031 

1032 See also: 

1033 --------- 

1034 `__next__()` 

1035 """ 

1036 return self.__next__() 

1037 

1038 def rows(self): 

1039 """The number of rows. 

1040  

1041 Returns 

1042 ------- 

1043 rows: int 

1044 The number of rows contained in the table. 

1045 """ 

1046 return max(map(len, self.data)) if self.data else 0 

1047 

1048 def columns(self): 

1049 """The number of columns. 

1050  

1051 Returns 

1052 ------- 

1053 columns: int 

1054 The number of columns contained in the table. 

1055 """ 

1056 return len(self.header) 

1057 

1058 def row(self, index): 

1059 """A single row of the table. 

1060 

1061 Parameters 

1062 ---------- 

1063 index: int 

1064 The index of the row to be returned. 

1065 

1066 Returns 

1067 ------- 

1068 data: TableData 

1069 A TableData object with a single row. 

1070 """ 

1071 data = TableData() 

1072 sec_indices = [-1] * self.nsecs 

1073 for c in range(self.columns()): 

1074 data.append(*self.column_head(c)) 

1075 for l in range(self.nsecs): 

1076 s, i = self.section(c, l+1) 

1077 if i != sec_indices[l]: 

1078 data.header[-1].append(s) 

1079 sec_indices[l] = i 

1080 data.data[-1] = [self.data[c][index]] 

1081 data.nsecs = self.nsecs 

1082 return data 

1083 

1084 def row_dict(self, index): 

1085 """A single row of the table. 

1086 

1087 Parameters 

1088 ---------- 

1089 index: int 

1090 The index of the row to be returned. 

1091 

1092 Returns 

1093 ------- 

1094 data: dict 

1095 A dictionary with column header as key and corresponding data value of row `index` 

1096 as value. 

1097 """ 

1098 data = {} 

1099 for c in range(self.columns()): 

1100 data[self.label(c)] = self.data[c][index] 

1101 return data 

1102 

1103 def col(self, column): 

1104 """A single column of the table. 

1105 

1106 Parameters 

1107 ---------- 

1108 column: None, int, or str 

1109 The column to be returned. 

1110 See self.index() for more information on how to specify a column. 

1111 

1112 Returns 

1113 ------- 

1114 table: TableData 

1115 A TableData object with a single column. 

1116 """ 

1117 data = TableData() 

1118 c = self.index(column) 

1119 data.append(*self.column_head(c)) 

1120 data.data = [self.data[c]] 

1121 data.nsecs = 0 

1122 return data 

1123 

1124 def __call__(self, column): 

1125 """A single column of the table as a ndarray. 

1126 

1127 Parameters 

1128 ---------- 

1129 column: None, int, or str 

1130 The column to be returned. 

1131 See self.index() for more information on how to specify a column. 

1132 

1133 Returns 

1134 ------- 

1135 data: 1-D ndarray 

1136 Content of the specified column as a ndarray. 

1137 """ 

1138 c = self.index(column) 

1139 return np.asarray(self.data[c]) 

1140 

1141 def __setupkey(self, key): 

1142 """Helper function that turns a key into row and column indices. 

1143 

1144 Returns 

1145 ------- 

1146 rows: list of int, slice, None 

1147 Indices of selected rows. 

1148 cols: list of int 

1149 Indices of selected columns. 

1150 

1151 Raises 

1152 ------ 

1153 IndexError: 

1154 If an invalid column was specified. 

1155 """ 

1156 if type(key) is not tuple: 

1157 rows = key 

1158 cols = range(self.columns()) 

1159 else: 

1160 rows = key[0] 

1161 cols = key[1] 

1162 if isinstance(cols, slice): 

1163 start = cols.start 

1164 if start is not None: 

1165 start = self.index(start) 

1166 if start is None: 

1167 raise IndexError('"%s" is not a valid column index' % cols.start) 

1168 stop = cols.stop 

1169 if stop is not None: 

1170 stop = self.index(stop) 

1171 if stop is None: 

1172 raise IndexError('"%s" is not a valid column index' % cols.stop) 

1173 cols = slice(start, stop, cols.step) 

1174 cols = range(self.columns())[cols] 

1175 else: 

1176 if not isinstance(cols, (list, tuple, np.ndarray)): 

1177 cols = [cols] 

1178 c = [self.index(inx) for inx in cols] 

1179 if None in c: 

1180 raise IndexError('"%s" is not a valid column index' % cols[c.index(None)]) 

1181 cols = c 

1182 if isinstance(rows, np.ndarray) and rows.dtype == np.dtype(bool): 

1183 rows = np.where(rows)[0] 

1184 if len(rows) == 0: 

1185 rows = None 

1186 return rows, cols 

1187 

1188 def __getitem__(self, key): 

1189 """Data elements specified by slice. 

1190 

1191 Parameters 

1192 ----------- 

1193 key: 

1194 First key specifies row, (optional) second one the column. 

1195 Columns can be specified by index or name, 

1196 see `index()` for details. 

1197 

1198 Returns 

1199 ------- 

1200 data: 

1201 - A single data value if a single row and a single column is specified. 

1202 - A ndarray of data elements if a single single column is specified. 

1203 - A TableData object for multiple columns. 

1204 - None if no row is selected (e.g. by a logical index that nowhere is True) 

1205 

1206 Raises 

1207 ------ 

1208 IndexError: 

1209 If an invalid column was specified. 

1210 """ 

1211 rows, cols = self.__setupkey(key) 

1212 if len(cols) == 1: 

1213 if rows is None: 

1214 return None 

1215 elif isinstance(rows, slice): 

1216 return np.asarray(self.data[cols[0]][rows]) 

1217 elif isinstance(rows, (list, tuple, np.ndarray)): 

1218 return np.asarray([self.data[cols[0]][r] for r in rows]) 

1219 else: 

1220 return self.data[cols[0]][rows] 

1221 else: 

1222 data = TableData() 

1223 sec_indices = [-1] * self.nsecs 

1224 for c in cols: 

1225 data.append(*self.column_head(c)) 

1226 for l in range(self.nsecs): 

1227 s, i = self.section(c, l+1) 

1228 if i != sec_indices[l]: 

1229 data.header[-1].append(s) 

1230 sec_indices[l] = i 

1231 if rows is None: 

1232 continue 

1233 if isinstance(rows, (list, tuple, np.ndarray)): 

1234 for r in rows: 

1235 data.data[-1].append(self.data[c][r]) 

1236 else: 

1237 if isinstance(self.data[c][rows], (list, tuple, np.ndarray)): 

1238 data.data[-1].extend(self.data[c][rows]) 

1239 else: 

1240 data.data[-1].append(self.data[c][rows]) 

1241 data.nsecs = self.nsecs 

1242 return data 

1243 

1244 def __setitem__(self, key, value): 

1245 """Assign values to data elements specified by slice. 

1246 

1247 Parameters 

1248 ----------- 

1249 key: 

1250 First key specifies row, (optional) second one the column. 

1251 Columns can be specified by index or name, 

1252 see `index()` for details. 

1253 value: TableData, list, ndarray, float, ... 

1254 Value(s) used to assing to the table elements as specified by `key`. 

1255 

1256 Raises 

1257 ------ 

1258 IndexError: 

1259 If an invalid column was specified. 

1260 """ 

1261 rows, cols = self.__setupkey(key) 

1262 if rows is None: 

1263 return 

1264 if isinstance(value, TableData): 

1265 if isinstance(self.data[cols[0]][rows], (list, tuple, np.ndarray)): 

1266 for k, c in enumerate(cols): 

1267 self.data[c][rows] = value.data[k] 

1268 else: 

1269 for k, c in enumerate(cols): 

1270 self.data[c][rows] = value.data[k][0] 

1271 else: 

1272 if len(cols) == 1: 

1273 if isinstance(rows, (list, tuple, np.ndarray)): 

1274 if len(rows) == 1: 

1275 self.data[cols[0]][rows[0]] = value 

1276 elif isinstance(value, (list, tuple, np.ndarray)): 

1277 for k, r in enumerate(rows): 

1278 self.data[cols[0]][r] = value[k] 

1279 else: 

1280 for r in rows: 

1281 self.data[cols[0]][r] = value 

1282 elif isinstance(value, (list, tuple, np.ndarray)): 

1283 self.data[cols[0]][rows] = value 

1284 elif isinstance(rows, (np.integer, int)): 

1285 self.data[cols[0]][rows] = value 

1286 else: 

1287 n = len(self.data[cols[0]][rows]) 

1288 if n > 1: 

1289 self.data[cols[0]][rows] = [value]*n 

1290 else: 

1291 self.data[cols[0]][rows] = value 

1292 else: 

1293 if isinstance(self.data[0][rows], (list, tuple, np.ndarray)): 

1294 for k, c in enumerate(cols): 

1295 self.data[c][rows] = value[:,k] 

1296 elif isinstance(value, (list, tuple, np.ndarray)): 

1297 for k, c in enumerate(cols): 

1298 self.data[c][rows] = value[k] 

1299 else: 

1300 for k, c in enumerate(cols): 

1301 self.data[c][rows] = value 

1302 

1303 def __delitem__(self, key): 

1304 """Delete data elements or whole columns or rows. 

1305 

1306 Parameters 

1307 ----------- 

1308 key: 

1309 First key specifies row, (optional) second one the column. 

1310 Columns can be specified by index or name, 

1311 see `index()` for details. 

1312 If all rows are selected, then the specified columns are removed from the table. 

1313 Otherwise only data values are removed. 

1314 If all columns are selected than entire rows of data values are removed. 

1315 Otherwise only data values in the specified rows are removed. 

1316 

1317 Raises 

1318 ------ 

1319 IndexError: 

1320 If an invalid column was specified. 

1321 """ 

1322 rows, cols = self.__setupkey(key) 

1323 if rows is None: 

1324 return 

1325 row_indices = np.arange(self.rows(), dtype=int)[rows] 

1326 if isinstance(row_indices, np.ndarray): 

1327 if len(row_indices) == self.rows(): 

1328 # delete whole columns: 

1329 self.remove(cols) 

1330 elif len(row_indices) > 0: 

1331 for r in reversed(sorted(row_indices)): 

1332 for c in cols: 

1333 del self.data[c][r] 

1334 self.shape = (self.rows(), self.columns()) 

1335 else: 

1336 for c in cols: 

1337 del self.data[c][row_indices] 

1338 self.shape = (self.rows(), self.columns()) 

1339 

1340 def array(self, row=None): 

1341 """The table data as a ndarray. 

1342 

1343 Parameters 

1344 ---------- 

1345 row: int or None 

1346 If specified, a 1D ndarray of that row will be returned. 

1347 

1348 Returns 

1349 ------- 

1350 data: 2D or 1D ndarray 

1351 If no row is specified, the data content of the entire table 

1352 as a 2D ndarray (rows first). 

1353 If a row is specified, a 1D ndarray of that row. 

1354 """ 

1355 if row is None: 

1356 return np.array(self.data).T 

1357 else: 

1358 return np.array([d[row] for d in self.data]) 

1359 

1360 def data_frame(self): 

1361 """The table data as a pandas DataFrame. 

1362 

1363 Returns 

1364 ------- 

1365 data: pandas.DataFrame 

1366 A pandas DataFrame of the whole table. 

1367 """ 

1368 return pd.DataFrame(self.dict()) 

1369 

1370 def dicts(self, raw_values=True, missing=default_missing_str): 

1371 """The table as a list of dictionaries. 

1372 

1373 Parameters 

1374 ---------- 

1375 raw_values: bool 

1376 If True, use raw table values as values, 

1377 else format the values and add unit string. 

1378 missing: str 

1379 String indicating non-existing data elements. 

1380 

1381 Returns 

1382 ------- 

1383 table: list of dict 

1384 For each row of the table a dictionary with header as key. 

1385 """ 

1386 table = [] 

1387 for row in range(self.rows()): 

1388 data = {} 

1389 for col in range(len(self.header)): 

1390 if raw_values: 

1391 v = self.data[col][row]; 

1392 else: 

1393 if isinstance(self.data[col][row], (float, np.floating)) and m.isnan(self.data[col][row]): 

1394 v = missing 

1395 else: 

1396 u = '' 

1397 if not self.units[col] in '1-' and self.units[col] != 'a.u.': 

1398 u = self.units[col] 

1399 v = (self.formats[col] % self.data[col][row]) + u 

1400 data[self.header[col][0]] = v 

1401 table.append(data) 

1402 return table 

1403 

1404 def dict(self): 

1405 """The table as a dictionary. 

1406 

1407 Returns 

1408 ------- 

1409 table: dict 

1410 A dictionary with keys being the column headers and 

1411 values the list of data elements of the corresponding column. 

1412 """ 

1413 table = {k: v for k, v in self.items()} 

1414 return table 

1415 

1416 def append_data(self, data, column=None): 

1417 """Append data elements to successive columns. 

1418 

1419 The current column is set behid the added columns. 

1420 

1421 Parameters 

1422 ---------- 

1423 data: float, int, str, etc. or list thereof or list of list thereof or dict or list of dict 

1424 Data values to be appended to successive columns: 

1425 - A single value is simply appened to the specified column of the table. 

1426 - A 1D-list of values is appended to successive columns of the table 

1427 starting with the specified column. 

1428 - The columns of a 2D-list of values (second index) are appended 

1429 to successive columns of the table starting with the specified column. 

1430 - Values or list of values of a dictionary are added to the 

1431 columns specified by the keys. Does not affect the current column. 

1432 - The values of dictionaries in a list are appended to the 

1433 columns specified by the keys. Does not affect the current column. 

1434 column: None, int, or str 

1435 The first column to which the data should be appended, 

1436 if `data` does not specify columns. 

1437 If None, append to the current column. 

1438 See self.index() for more information on how to specify a column. 

1439 """ 

1440 column = self.index(column) 

1441 if column is None: 

1442 column = self.setcol 

1443 if isinstance(data, (list, tuple, np.ndarray)): 

1444 if isinstance(data[0], (list, tuple, np.ndarray)): 

1445 # 2D list, rows first: 

1446 for row in data: 

1447 for i, val in enumerate(row): 

1448 self.data[column+i].append(val) 

1449 self.setcol = column + len(data[0]) 

1450 elif isinstance(data[0], dict): 

1451 # list of dictionaries: 

1452 for row in data: 

1453 for key in row: 

1454 column = self.index(k) 

1455 self.data[column].append(data[k]) 

1456 else: 

1457 # 1D list: 

1458 for val in data: 

1459 self.data[column].append(val) 

1460 column += 1 

1461 self.setcol = column 

1462 elif isinstance(data, dict): 

1463 # dictionary with values: 

1464 for key in data: 

1465 column = self.index(key) 

1466 if isinstance(data[key], (list, tuple, np.ndarray)): 

1467 self.data[column].extend(data[key]) 

1468 else: 

1469 self.data[column].append(data[key]) 

1470 else: 

1471 # single value: 

1472 self.data[column].append(data) 

1473 self.setcol = column + 1 

1474 if self.setcol >= len(self.data): 

1475 self.setcol = 0 

1476 self.shape = (self.rows(), self.columns()) 

1477 

1478 def append_data_column(self, data, column=None): 

1479 """Append data elements to a column. 

1480 

1481 The current column is incremented by one. 

1482 

1483 Parameters 

1484 ---------- 

1485 data: float, int, str, etc. or list thereof 

1486 Data values to be appended to a column. 

1487 column: None, int, or str 

1488 The column to which the data should be appended. 

1489 If None, append to the current column. 

1490 See self.index() for more information on how to specify a column. 

1491 """ 

1492 column = self.index(column) 

1493 if column is None: 

1494 column = self.setcol 

1495 if isinstance(data, (list, tuple, np.ndarray)): 

1496 self.data[column].extend(data) 

1497 column += 1 

1498 self.setcol = column 

1499 else: 

1500 self.data[column].append(data) 

1501 self.setcol = column+1 

1502 if self.setcol >= len(self.data): 

1503 self.setcol = 0 

1504 self.shape = (self.rows(), self.columns()) 

1505 

1506 def set_column(self, column): 

1507 """Set the column where to add data. 

1508 

1509 Parameters 

1510 ---------- 

1511 column: int or str 

1512 The column to which data elements should be appended. 

1513 See self.index() for more information on how to specify a column. 

1514 

1515 Raises 

1516 ------ 

1517 IndexError: 

1518 If an invalid column was specified. 

1519 """ 

1520 col = self.index(column) 

1521 if col is None: 

1522 if isinstance(column, (np.integer, int)): 

1523 column = '%d' % column 

1524 raise IndexError('column ' + column + ' not found or invalid') 

1525 self.setcol = col 

1526 return col 

1527 

1528 def fill_data(self): 

1529 """Fill up all columns with missing data to have the same number of 

1530 data elements. 

1531 """ 

1532 # maximum rows: 

1533 maxr = self.rows() 

1534 # fill up: 

1535 for c in range(len(self.data)): 

1536 while len(self.data[c]) < maxr: 

1537 self.data[c].append(np.nan) 

1538 self.setcol = 0 

1539 self.shape = (self.rows(), self.columns()) 

1540 

1541 def clear_data(self): 

1542 """Clear content of the table but keep header. 

1543 """ 

1544 for c in range(len(self.data)): 

1545 self.data[c] = [] 

1546 self.setcol = 0 

1547 self.shape = (self.rows(), self.columns()) 

1548 

1549 def sort(self, columns, reverse=False): 

1550 """Sort the table rows in place. 

1551 

1552 Parameters 

1553 ---------- 

1554 columns: int or str or list of int or str 

1555 A column specifier or a list of column specifiers of the columns 

1556 to be sorted. 

1557 reverse: boolean 

1558 If `True` sort in descending order. 

1559 

1560 Raises 

1561 ------ 

1562 IndexError: 

1563 If an invalid column was specified. 

1564 """ 

1565 # fix columns: 

1566 if not isinstance(columns, (list, tuple, np.ndarray)): 

1567 columns = [ columns ] 

1568 if not columns: 

1569 return 

1570 cols = [] 

1571 for col in columns: 

1572 c = self.index(col) 

1573 if c is None: 

1574 if isinstance(col, (np.integer, int)): 

1575 col = '%d' % col 

1576 raise IndexError('sort column ' + col + ' not found') 

1577 continue 

1578 cols.append(c) 

1579 # get sorted row indices: 

1580 row_inx = range(self.rows()) 

1581 row_inx = sorted(row_inx, key=lambda x : [float('-inf') if self.data[c][x] is np.nan \ 

1582 or self.data[c][x] != self.data[c][x] \ 

1583 else self.data[c][x] for c in cols], reverse=reverse) 

1584 # sort table according to indices: 

1585 for c in range(self.columns()): 

1586 self.data[c] = [self.data[c][r] for r in row_inx] 

1587 

1588 def statistics(self): 

1589 """Descriptive statistics of each column. 

1590 """ 

1591 ds = TableData() 

1592 if self.nsecs > 0: 

1593 ds.append_section('statistics') 

1594 for l in range(1,self.nsecs): 

1595 ds.append_section('-') 

1596 ds.append('-', '-', '%-10s') 

1597 else: 

1598 ds.append('statistics', '-', '%-10s') 

1599 ds.append_data('mean', 0) 

1600 ds.append_data('std', 0) 

1601 ds.append_data('min', 0) 

1602 ds.append_data('quartile1', 0) 

1603 ds.append_data('median', 0) 

1604 ds.append_data('quartile3', 0) 

1605 ds.append_data('max', 0) 

1606 ds.append_data('count', 0) 

1607 dc = 1 

1608 for c in range(self.columns()): 

1609 if len(self.data[c]) > 0 and isinstance(self.data[c][0], (float, int, np.floating, np.integer)): 

1610 ds.hidden.append(False) 

1611 ds.header.append(self.header[c]) 

1612 ds.units.append(self.units[c]) 

1613 # integer data still make floating point statistics: 

1614 if isinstance(self.data[c][0], (float, np.floating)): 

1615 f = self.formats[c] 

1616 i0 = f.find('.') 

1617 if i0 > 0: 

1618 p = int(f[i0+1:-1]) 

1619 if p <= 0: 

1620 f = '%.1f' 

1621 ds.formats.append(f) 

1622 else: 

1623 ds.formats.append('%.1f') 

1624 # remove nans: 

1625 data = np.asarray(self.data[c], float) 

1626 data = data[np.isfinite(data)] 

1627 # compute statistics: 

1628 ds.data.append([]) 

1629 ds.append_data(np.mean(data), dc) 

1630 ds.append_data(np.std(data), dc) 

1631 ds.append_data(np.min(data), dc) 

1632 q1, m, q3 = np.percentile(data, [25., 50., 75.]) 

1633 ds.append_data(q1, dc) 

1634 ds.append_data(m, dc) 

1635 ds.append_data(q3, dc) 

1636 ds.append_data(np.max(data), dc) 

1637 ds.append_data(len(data), dc) 

1638 dc += 1 

1639 ds.nsecs = self.nsecs 

1640 ds.shape = (ds.rows(), ds.columns()) 

1641 return ds 

1642 

1643 def key_value(self, row, col, missing=default_missing_str): 

1644 """A data element returned as a key-value pair. 

1645 

1646 Parameters 

1647 ---------- 

1648 row: int 

1649 Specifies the row from which the data element should be retrieved. 

1650 col: None, int, or str 

1651 A specification of a column. 

1652 See self.index() for more information on how to specify a column. 

1653 missing: str 

1654 String indicating non-existing data elements. 

1655 

1656 Returns 

1657 ------- 

1658 key: str 

1659 Header label of the column 

1660 value: str 

1661 A textual representation of the data element according to the format 

1662 of the column, followed by the unit of the column. 

1663 """ 

1664 col = self.index(col) 

1665 if col is None: 

1666 return '' 

1667 if isinstance(self.data[col][row], (float, np.floating)) and m.isnan(self.data[col][row]): 

1668 v = missing 

1669 else: 

1670 u = '' 

1671 if not self.units[col] in '1-' and self.units[col] != 'a.u.': 

1672 u = self.units[col] 

1673 v = (self.formats[col] % self.data[col][row]) + u 

1674 return self.header[col][0], v 

1675 

1676 def hide(self, column): 

1677 """Hide a column or a range of columns. 

1678 

1679 Hidden columns will not be printed out by the write() function. 

1680 

1681 Parameters 

1682 ---------- 

1683 column: int or str 

1684 The column to be hidden. 

1685 See self.index() for more information on how to specify a column. 

1686 """ 

1687 c0, c1 = self.find_col(column) 

1688 if c0 is not None: 

1689 for c in range(c0, c1): 

1690 self.hidden[c] = True 

1691 

1692 def hide_all(self): 

1693 """Hide all columns. 

1694 

1695 Hidden columns will not be printed out by the write() function. 

1696 """ 

1697 for c in range(len(self.hidden)): 

1698 self.hidden[c] = True 

1699 

1700 def hide_empty_columns(self, missing=default_missing_inputs): 

1701 """Hide all columns that do not contain data. 

1702 

1703 Hidden columns will not be printed out by the write() function. 

1704 

1705 Parameters 

1706 ---------- 

1707 missing: list of str 

1708 Strings indicating missing data. 

1709 """ 

1710 for c in range(len(self.data)): 

1711 # check for empty column: 

1712 isempty = True 

1713 for v in self.data[c]: 

1714 if isinstance(v, (float, np.floating)): 

1715 if not m.isnan(v): 

1716 isempty = False 

1717 break 

1718 else: 

1719 if not v in missing: 

1720 isempty = False 

1721 break 

1722 if isempty: 

1723 self.hidden[c] = True 

1724 

1725 def show(self, column): 

1726 """Show a column or a range of columns. 

1727 

1728 Undoes hiding of a column. 

1729 

1730 Parameters 

1731 ---------- 

1732 column: int or str 

1733 The column to be shown. 

1734 See self.index() for more information on how to specify a column. 

1735 """ 

1736 c0, c1 = self.find_col(column) 

1737 if c0 is not None: 

1738 for c in range(c0, c1): 

1739 self.hidden[c] = False 

1740 

1741 def write(self, fh=sys.stdout, table_format=None, delimiter=None, 

1742 unit_style=None, column_numbers=None, sections=None, 

1743 align_columns=None, shrink_width=True, 

1744 missing=default_missing_str, center_columns=False, 

1745 latex_label_command='', latex_merge_std=False): 

1746 """Write the table to a file or stream. 

1747 

1748 Parameters 

1749 ---------- 

1750 fh: filename or stream 

1751 If not a stream, the file with name `fh` is opened. 

1752 If `fh` does not have an extension, 

1753 the `table_format` is appended as an extension. 

1754 Otherwise `fh` is used as a stream for writing. 

1755 table_format: None or str 

1756 The format to be used for output. 

1757 One of 'out', 'dat', 'ascii', 'csv', 'rtai', 'md', 'tex', 'html'. 

1758 If None or 'auto' then the format is set to the extension of the filename given by `fh`. 

1759 If `fh` is a stream the format is set to 'dat'. 

1760 delimiter: str 

1761 String or character separating columns, if supported by the `table_format`. 

1762 If None or 'auto' use the default for the specified `table_format`. 

1763 unit_style: None or str 

1764 - None or 'auto': use default of the specified `table_format`. 

1765 - 'row': write an extra row to the table header specifying the units of the columns. 

1766 - 'header': add the units to the column headers. 

1767 - 'none': do not specify the units. 

1768 column_numbers: str or None 

1769 Add a row specifying the column index: 

1770 - 'index': indices are integers, first column is 0. 

1771 - 'num': indices are integers, first column is 1. 

1772 - 'aa': use 'a', 'b', 'c', ..., 'z', 'aa', 'ab', ... for indexing 

1773 - 'aa': use 'A', 'B', 'C', ..., 'Z', 'AA', 'AB', ... for indexing 

1774 - None or 'none': do not add a row with column indices 

1775 TableData.column_numbering is a list with the supported styles. 

1776 sections: None or int 

1777 Number of section levels to be printed. 

1778 If `None` or 'auto' use default of selected `table_format`. 

1779 align_columns: boolean 

1780 - `True`: set width of column formats to make them align. 

1781 - `False`: set width of column formats to 0 - no unnecessary spaces. 

1782 - None or 'auto': Use default of the selected `table_format`. 

1783 shrink_width: boolean 

1784 If `True` disregard width specified by the format strings, 

1785 such that columns can become narrower. 

1786 missing: str 

1787 Indicate missing data by this string. 

1788 center_columns: boolean 

1789 If True center all columns (markdown, html, and latex). 

1790 latex_label_command: str 

1791 LaTeX command for formatting header labels. 

1792 E.g. 'textbf' for making the header labels bold. 

1793 latex_merge_std: str 

1794 Merge header of columns with standard deviations with previous column 

1795 (LaTeX tables only). 

1796 

1797 Returns 

1798 ------- 

1799 file_name: str or None 

1800 The full name of the file into which the data were written. 

1801 

1802 Supported file formats 

1803 ---------------------- 

1804  

1805 ## `dat`: data text file 

1806 ``` plain 

1807 # info reaction  

1808 # size weight delay jitter 

1809 # m kg ms mm  

1810 2.34 123 98.7 23 

1811 56.70 3457 54.3 45 

1812 8.90 43 67.9 345 

1813 ``` 

1814 

1815 ## `ascii`: ascii-art table 

1816 ``` plain 

1817 |---------------------------------| 

1818 | info | reaction | 

1819 | size | weight | delay | jitter | 

1820 | m | kg | ms | mm | 

1821 |-------|--------|-------|--------| 

1822 | 2.34 | 123 | 98.7 | 23 | 

1823 | 56.70 | 3457 | 54.3 | 45 | 

1824 | 8.90 | 43 | 67.9 | 345 | 

1825 |---------------------------------| 

1826 ``` 

1827 

1828 ## `csv`: comma separated values 

1829 ``` plain 

1830 size/m,weight/kg,delay/ms,jitter/mm 

1831 2.34,123,98.7,23 

1832 56.70,3457,54.3,45 

1833 8.90,43,67.9,345 

1834 ``` 

1835 

1836 ## `rtai`: rtai-style table 

1837 ``` plain 

1838 RTH| info | reaction  

1839 RTH| size | weight| delay| jitter 

1840 RTH| m | kg | ms | mm  

1841 RTD| 2.34| 123| 98.7| 23 

1842 RTD| 56.70| 3457| 54.3| 45 

1843 RTD| 8.90| 43| 67.9| 345 

1844 ``` 

1845 

1846 ## `md`: markdown 

1847 ``` plain 

1848 | size/m | weight/kg | delay/ms | jitter/mm | 

1849 |------:|-------:|------:|-------:| 

1850 | 2.34 | 123 | 98.7 | 23 | 

1851 | 56.70 | 3457 | 54.3 | 45 | 

1852 | 8.90 | 43 | 67.9 | 345 | 

1853 ``` 

1854 

1855 ## `tex`: latex tabular 

1856 ``` tex 

1857 \\begin{tabular}{rrrr} 

1858 \\hline 

1859 \\multicolumn{2}{l}{info} & \\multicolumn{2}{l}{reaction} \\ 

1860 \\multicolumn{1}{l}{size} & \\multicolumn{1}{l}{weight} & \\multicolumn{1}{l}{delay} & \\multicolumn{1}{l}{jitter} \\ 

1861 \\multicolumn{1}{l}{m} & \\multicolumn{1}{l}{kg} & \\multicolumn{1}{l}{ms} & \\multicolumn{1}{l}{mm} \\ 

1862 \\hline 

1863 2.34 & 123 & 98.7 & 23 \\ 

1864 56.70 & 3457 & 54.3 & 45 \\ 

1865 8.90 & 43 & 67.9 & 345 \\ 

1866 \\hline 

1867 \\end{tabular} 

1868 ``` 

1869 

1870 ## `html`: html 

1871 ``` html 

1872 <table> 

1873 <thead> 

1874 <tr class="header"> 

1875 <th align="left" colspan="2">info</th> 

1876 <th align="left" colspan="2">reaction</th> 

1877 </tr> 

1878 <tr class="header"> 

1879 <th align="left">size</th> 

1880 <th align="left">weight</th> 

1881 <th align="left">delay</th> 

1882 <th align="left">jitter</th> 

1883 </tr> 

1884 <tr class="header"> 

1885 <th align="left">m</th> 

1886 <th align="left">kg</th> 

1887 <th align="left">ms</th> 

1888 <th align="left">mm</th> 

1889 </tr> 

1890 </thead> 

1891 <tbody> 

1892 <tr class"odd"> 

1893 <td align="right">2.34</td> 

1894 <td align="right">123</td> 

1895 <td align="right">98.7</td> 

1896 <td align="right">23</td> 

1897 </tr> 

1898 <tr class"even"> 

1899 <td align="right">56.70</td> 

1900 <td align="right">3457</td> 

1901 <td align="right">54.3</td> 

1902 <td align="right">45</td> 

1903 </tr> 

1904 <tr class"odd"> 

1905 <td align="right">8.90</td> 

1906 <td align="right">43</td> 

1907 <td align="right">67.9</td> 

1908 <td align="right">345</td> 

1909 </tr> 

1910 </tbody> 

1911 </table> 

1912 ``` 

1913 """ 

1914 # fix parameter: 

1915 if table_format == 'auto': 

1916 table_format = None 

1917 if delimiter == 'auto': 

1918 delimiter = None 

1919 if unit_style == 'auto': 

1920 unit_style = None 

1921 if column_numbers == 'none': 

1922 column_numbers = None 

1923 if sections == 'auto': 

1924 sections = None 

1925 if align_columns == 'auto': 

1926 align_columns = None 

1927 # open file: 

1928 own_file = False 

1929 file_name = None 

1930 if not hasattr(fh, 'write'): 

1931 _, ext = os.path.splitext(fh) 

1932 if table_format is None: 

1933 if len(ext) > 1 and ext[1:] in self.ext_formats: 

1934 table_format = self.ext_formats[ext[1:]] 

1935 elif not ext or not ext[1:].lower() in self.ext_formats: 

1936 fh += '.' + self.extensions[table_format] 

1937 file_name = fh 

1938 fh = open(fh, 'w') 

1939 own_file = True 

1940 if table_format is None: 

1941 table_format = 'dat' 

1942 # set style:  

1943 if table_format[0] == 'd': 

1944 align_columns = True 

1945 begin_str = '' 

1946 end_str = '' 

1947 header_start = '# ' 

1948 header_sep = ' ' 

1949 header_close = '' 

1950 header_end = '\n' 

1951 data_start = ' ' 

1952 data_sep = ' ' 

1953 data_close = '' 

1954 data_end = '\n' 

1955 top_line = False 

1956 header_line = False 

1957 bottom_line = False 

1958 if delimiter is not None: 

1959 header_sep = delimiter 

1960 data_sep = delimiter 

1961 if sections is None: 

1962 sections = 1000 

1963 elif table_format[0] == 'a': 

1964 align_columns = True 

1965 begin_str = '' 

1966 end_str = '' 

1967 header_start = '| ' 

1968 header_sep = ' | ' 

1969 header_close = '' 

1970 header_end = ' |\n' 

1971 data_start = '| ' 

1972 data_sep = ' | ' 

1973 data_close = '' 

1974 data_end = ' |\n' 

1975 top_line = True 

1976 header_line = True 

1977 bottom_line = True 

1978 if delimiter is not None: 

1979 header_sep = delimiter 

1980 data_sep = delimiter 

1981 if sections is None: 

1982 sections = 1000 

1983 elif table_format[0] == 'c': 

1984 # csv according to http://www.ietf.org/rfc/rfc4180.txt : 

1985 column_numbers=None 

1986 if unit_style is None: 

1987 unit_style = 'header' 

1988 if align_columns is None: 

1989 align_columns = False 

1990 begin_str = '' 

1991 end_str = '' 

1992 header_start='' 

1993 header_sep = ',' 

1994 header_close = '' 

1995 header_end='\n' 

1996 data_start='' 

1997 data_sep = ',' 

1998 data_close = '' 

1999 data_end='\n' 

2000 top_line = False 

2001 header_line = False 

2002 bottom_line = False 

2003 if delimiter is not None: 

2004 header_sep = delimiter 

2005 data_sep = delimiter 

2006 if sections is None: 

2007 sections = 0 

2008 elif table_format[0] == 'r': 

2009 align_columns = True 

2010 begin_str = '' 

2011 end_str = '' 

2012 header_start = 'RTH| ' 

2013 header_sep = '| ' 

2014 header_close = '' 

2015 header_end = '\n' 

2016 data_start = 'RTD| ' 

2017 data_sep = '| ' 

2018 data_close = '' 

2019 data_end = '\n' 

2020 top_line = False 

2021 header_line = False 

2022 bottom_line = False 

2023 if sections is None: 

2024 sections = 1000 

2025 elif table_format[0] == 'm': 

2026 if unit_style is None or unit_style == 'row': 

2027 unit_style = 'header' 

2028 align_columns = True 

2029 begin_str = '' 

2030 end_str = '' 

2031 header_start='| ' 

2032 header_sep = ' | ' 

2033 header_close = '' 

2034 header_end=' |\n' 

2035 data_start='| ' 

2036 data_sep = ' | ' 

2037 data_close = '' 

2038 data_end=' |\n' 

2039 top_line = False 

2040 header_line = True 

2041 bottom_line = False 

2042 if sections is None: 

2043 sections = 0 

2044 elif table_format[0] == 'h': 

2045 align_columns = False 

2046 begin_str = '<table>\n<thead>\n' 

2047 end_str = '</tbody>\n</table>\n' 

2048 if center_columns: 

2049 header_start=' <tr>\n <th align="center"' 

2050 header_sep = '</th>\n <th align="center"' 

2051 else: 

2052 header_start=' <tr>\n <th align="left"' 

2053 header_sep = '</th>\n <th align="left"' 

2054 header_close = '>' 

2055 header_end='</th>\n </tr>\n' 

2056 data_start=' <tr>\n <td' 

2057 data_sep = '</td>\n <td' 

2058 data_close = '>' 

2059 data_end='</td>\n </tr>\n' 

2060 top_line = False 

2061 header_line = False 

2062 bottom_line = False 

2063 if sections is None: 

2064 sections = 1000 

2065 elif table_format[0] == 't': 

2066 if align_columns is None: 

2067 align_columns = False 

2068 begin_str = '\\begin{tabular}' 

2069 end_str = '\\end{tabular}\n' 

2070 header_start=' ' 

2071 header_sep = ' & ' 

2072 header_close = '' 

2073 header_end=' \\\\\n' 

2074 data_start=' ' 

2075 data_sep = ' & ' 

2076 data_close = '' 

2077 data_end=' \\\\\n' 

2078 top_line = True 

2079 header_line = True 

2080 bottom_line = True 

2081 if sections is None: 

2082 sections = 1000 

2083 else: 

2084 if align_columns is None: 

2085 align_columns = True 

2086 begin_str = '' 

2087 end_str = '' 

2088 header_start = '' 

2089 header_sep = ' ' 

2090 header_close = '' 

2091 header_end = '\n' 

2092 data_start = '' 

2093 data_sep = ' ' 

2094 data_close = '' 

2095 data_end = '\n' 

2096 top_line = False 

2097 header_line = False 

2098 bottom_line = False 

2099 if sections is None: 

2100 sections = 1000 

2101 # check units: 

2102 if unit_style is None: 

2103 unit_style = 'row' 

2104 have_units = False 

2105 for u in self.units: 

2106 if u and u != '1' and u != '-': 

2107 have_units = True 

2108 break 

2109 if not have_units: 

2110 unit_style = 'none' 

2111 # find std columns: 

2112 stdev_col = np.zeros(len(self.header), dtype=bool) 

2113 for c in range(len(self.header)-1): 

2114 if self.header[c+1][0].lower() in ['sd', 'std', 's.d.', 'stdev'] and \ 

2115 not self.hidden[c+1]: 

2116 stdev_col[c] = True 

2117 # begin table: 

2118 fh.write(begin_str) 

2119 if table_format[0] == 't': 

2120 fh.write('{') 

2121 merged = False 

2122 for h, f, s in zip(self.hidden, self.formats, stdev_col): 

2123 if merged: 

2124 fh.write('l') 

2125 merged = False 

2126 continue 

2127 if h: 

2128 continue 

2129 if latex_merge_std and s: 

2130 fh.write('r@{$\\,\\pm\\,$}') 

2131 merged = True 

2132 elif center_columns: 

2133 fh.write('c') 

2134 elif f[1] == '-': 

2135 fh.write('l') 

2136 else: 

2137 fh.write('r') 

2138 fh.write('}\n') 

2139 # retrieve column formats and widths: 

2140 widths = [] 

2141 widths_pos = [] 

2142 for c, f in enumerate(self.formats): 

2143 w = 0 

2144 # position of width specification: 

2145 i0 = 1 

2146 if len(f) > 1 and f[1] == '-' : 

2147 i0 = 2 

2148 i1 = f.find('.') 

2149 if not shrink_width: 

2150 if f[i0:i1]: 

2151 w = int(f[i0:i1]) 

2152 widths_pos.append((i0, i1)) 

2153 # adapt width to header label: 

2154 hw = len(self.header[c][0]) 

2155 if unit_style == 'header' and self.units[c] and\ 

2156 self.units[c] != '1' and self.units[c] != '-': 

2157 hw += 1 + len(self.units[c]) 

2158 if w < hw: 

2159 w = hw 

2160 # adapt width to data: 

2161 if f[-1] == 's': 

2162 for v in self.data[c]: 

2163 if not isinstance(v, (float, np.floating)) and w < len(v): 

2164 w = len(v) 

2165 else: 

2166 fs = f[:i0] + str(0) + f[i1:] 

2167 for v in self.data[c]: 

2168 if isinstance(v, (float, np.floating)) and m.isnan(v): 

2169 s = missing 

2170 else: 

2171 try: 

2172 s = fs % v 

2173 except ValueError: 

2174 s = missing 

2175 if w < len(s): 

2176 w = len(s) 

2177 widths.append(w) 

2178 # adapt width to sections: 

2179 sec_indices = [0] * self.nsecs 

2180 sec_widths = [0] * self.nsecs 

2181 sec_columns = [0] * self.nsecs 

2182 for c in range(len(self.header)): 

2183 w = widths[c] 

2184 for l in range(min(self.nsecs, sections)): 

2185 if 1+l < len(self.header[c]): 

2186 if c > 0 and sec_columns[l] > 0 and \ 

2187 1+l < len(self.header[sec_indices[l]]) and \ 

2188 len(self.header[sec_indices[l]][1+l]) > sec_widths[l]: 

2189 dw = len(self.header[sec_indices[l]][1+l]) - sec_widths[l] 

2190 nc = sec_columns[l] 

2191 ddw = np.zeros(nc, dtype=int) + dw // nc 

2192 ddw[:dw % nc] += 1 

2193 wk = 0 

2194 for ck in range(sec_indices[l], c): 

2195 if not self.hidden[ck]: 

2196 widths[ck] += ddw[wk] 

2197 wk += 1 

2198 sec_widths[l] = 0 

2199 sec_indices[l] = c 

2200 if not self.hidden[c]: 

2201 if sec_widths[l] > 0: 

2202 sec_widths[l] += len(header_sep) 

2203 sec_widths[l] += w 

2204 sec_columns[l] += 1 

2205 # set width of format string: 

2206 formats = [] 

2207 for c, (f, w) in enumerate(zip(self.formats, widths)): 

2208 formats.append(f[:widths_pos[c][0]] + str(w) + f[widths_pos[c][1]:]) 

2209 # top line: 

2210 if top_line: 

2211 if table_format[0] == 't': 

2212 fh.write(' \\hline \\\\[-2ex]\n') 

2213 else: 

2214 first = True 

2215 fh.write(header_start.replace(' ', '-')) 

2216 for c in range(len(self.header)): 

2217 if self.hidden[c]: 

2218 continue 

2219 if not first: 

2220 fh.write('-'*len(header_sep)) 

2221 first = False 

2222 fh.write(header_close) 

2223 w = widths[c] 

2224 fh.write(w*'-') 

2225 fh.write(header_end.replace(' ', '-')) 

2226 # section and column headers: 

2227 nsec0 = self.nsecs-sections 

2228 if nsec0 < 0: 

2229 nsec0 = 0 

2230 for ns in range(nsec0, self.nsecs+1): 

2231 nsec = self.nsecs-ns 

2232 first = True 

2233 last = False 

2234 merged = False 

2235 fh.write(header_start) 

2236 for c in range(len(self.header)): 

2237 if nsec < len(self.header[c]): 

2238 # section width and column count: 

2239 sw = -len(header_sep) 

2240 columns = 0 

2241 if not self.hidden[c]: 

2242 sw = widths[c] 

2243 columns = 1 

2244 for k in range(c+1, len(self.header)): 

2245 if nsec < len(self.header[k]): 

2246 break 

2247 if self.hidden[k]: 

2248 continue 

2249 sw += len(header_sep) + widths[k] 

2250 columns += 1 

2251 else: 

2252 last = True 

2253 if len(header_end.strip()) == 0: 

2254 sw = 0 # last entry needs no width 

2255 if columns == 0: 

2256 continue 

2257 if not first and not merged: 

2258 fh.write(header_sep) 

2259 first = False 

2260 if table_format[0] == 'c': 

2261 sw -= len(header_sep)*(columns-1) 

2262 elif table_format[0] == 'h': 

2263 if columns>1: 

2264 fh.write(' colspan="%d"' % columns) 

2265 elif table_format[0] == 't': 

2266 if merged: 

2267 merged = False 

2268 continue 

2269 if latex_merge_std and nsec == 0 and stdev_col[c]: 

2270 merged = True 

2271 fh.write('\\multicolumn{%d}{c}{' % (columns+1)) 

2272 elif center_columns: 

2273 fh.write('\\multicolumn{%d}{c}{' % columns) 

2274 else: 

2275 fh.write('\\multicolumn{%d}{l}{' % columns) 

2276 if latex_label_command: 

2277 fh.write('\\%s{' % latex_label_command) 

2278 fh.write(header_close) 

2279 hs = self.header[c][nsec] 

2280 if nsec == 0 and unit_style == 'header': 

2281 if self.units[c] and self.units[c] != '1' and self.units[c] != '-': 

2282 hs += '/' + self.units[c] 

2283 if align_columns and not table_format[0] in 'th': 

2284 f = '%%-%ds' % sw 

2285 fh.write(f % hs) 

2286 else: 

2287 fh.write(hs) 

2288 if table_format[0] == 'c': 

2289 if not last: 

2290 fh.write(header_sep*(columns-1)) 

2291 elif table_format[0] == 't': 

2292 if latex_label_command: 

2293 fh.write('}') 

2294 fh.write('}') 

2295 fh.write(header_end) 

2296 # units: 

2297 if unit_style == 'row': 

2298 first = True 

2299 merged = False 

2300 fh.write(header_start) 

2301 for c in range(len(self.header)): 

2302 if self.hidden[c] or merged: 

2303 merged = False 

2304 continue 

2305 if not first: 

2306 fh.write(header_sep) 

2307 first = False 

2308 fh.write(header_close) 

2309 unit = self.units[c] 

2310 if not unit: 

2311 unit = '-' 

2312 if table_format[0] == 't': 

2313 if latex_merge_std and stdev_col[c]: 

2314 merged = True 

2315 fh.write('\\multicolumn{2}{c}{%s}' % latex_unit(unit)) 

2316 elif center_columns: 

2317 fh.write('\\multicolumn{1}{c}{%s}' % latex_unit(unit)) 

2318 else: 

2319 fh.write('\\multicolumn{1}{l}{%s}' % latex_unit(unit)) 

2320 else: 

2321 if align_columns and not table_format[0] in 'h': 

2322 f = '%%-%ds' % widths[c] 

2323 fh.write(f % unit) 

2324 else: 

2325 fh.write(unit) 

2326 fh.write(header_end) 

2327 # column numbers: 

2328 if column_numbers is not None: 

2329 first = True 

2330 fh.write(header_start) 

2331 for c in range(len(self.header)): 

2332 if self.hidden[c]: 

2333 continue 

2334 if not first: 

2335 fh.write(header_sep) 

2336 first = False 

2337 fh.write(header_close) 

2338 i = c 

2339 if column_numbers == 'num': 

2340 i = c+1 

2341 aa = index2aa(c, 'a') 

2342 if column_numbers == 'AA': 

2343 aa = index2aa(c, 'A') 

2344 if table_format[0] == 't': 

2345 if column_numbers == 'num' or column_numbers == 'index': 

2346 fh.write('\\multicolumn{1}{l}{%d}' % i) 

2347 else: 

2348 fh.write('\\multicolumn{1}{l}{%s}' % aa) 

2349 else: 

2350 if column_numbers == 'num' or column_numbers == 'index': 

2351 if align_columns: 

2352 f = '%%%dd' % widths[c] 

2353 fh.write(f % i) 

2354 else: 

2355 fh.write('%d' % i) 

2356 else: 

2357 if align_columns: 

2358 f = '%%-%ds' % widths[c] 

2359 fh.write(f % aa) 

2360 else: 

2361 fh.write(aa) 

2362 fh.write(header_end) 

2363 # header line: 

2364 if header_line: 

2365 if table_format[0] == 'm': 

2366 fh.write('|') 

2367 for c in range(len(self.header)): 

2368 if self.hidden[c]: 

2369 continue 

2370 w = widths[c]+2 

2371 if center_columns: 

2372 fh.write(':' + (w-2)*'-' + ':|') 

2373 elif formats[c][1] == '-': 

2374 fh.write(w*'-' + '|') 

2375 else: 

2376 fh.write((w-1)*'-' + ':|') 

2377 fh.write('\n') 

2378 elif table_format[0] == 't': 

2379 fh.write(' \\hline \\\\[-2ex]\n') 

2380 else: 

2381 first = True 

2382 fh.write(header_start.replace(' ', '-')) 

2383 for c in range(len(self.header)): 

2384 if self.hidden[c]: 

2385 continue 

2386 if not first: 

2387 fh.write(header_sep.replace(' ', '-')) 

2388 first = False 

2389 fh.write(header_close) 

2390 w = widths[c] 

2391 fh.write(w*'-') 

2392 fh.write(header_end.replace(' ', '-')) 

2393 # start table data: 

2394 if table_format[0] == 'h': 

2395 fh.write('</thead>\n<tbody>\n') 

2396 # data: 

2397 for k in range(self.rows()): 

2398 first = True 

2399 merged = False 

2400 fh.write(data_start) 

2401 for c, f in enumerate(formats): 

2402 if self.hidden[c] or merged: 

2403 merged = False 

2404 continue 

2405 if not first: 

2406 fh.write(data_sep) 

2407 first = False 

2408 if table_format[0] == 'h': 

2409 if center_columns: 

2410 fh.write(' align="center"') 

2411 elif f[1] == '-': 

2412 fh.write(' align="left"') 

2413 else: 

2414 fh.write(' align="right"') 

2415 fh.write(data_close) 

2416 if k >= len(self.data[c]) or \ 

2417 (isinstance(self.data[c][k], (float, np.floating)) and m.isnan(self.data[c][k])): 

2418 # missing data: 

2419 if table_format[0] == 't' and latex_merge_std and stdev_col[c]: 

2420 merged = True 

2421 fh.write('\\multicolumn{2}{c}{%s}' % missing) 

2422 elif align_columns: 

2423 if f[1] == '-': 

2424 fn = '%%-%ds' % widths[c] 

2425 else: 

2426 fn = '%%%ds' % widths[c] 

2427 fh.write(fn % missing) 

2428 else: 

2429 fh.write(missing) 

2430 else: 

2431 # data value: 

2432 try: 

2433 ds = f % self.data[c][k] 

2434 except ValueError: 

2435 ds = missing 

2436 if not align_columns: 

2437 ds = ds.strip() 

2438 fh.write(ds) 

2439 fh.write(data_end) 

2440 # bottom line: 

2441 if bottom_line: 

2442 if table_format[0] == 't': 

2443 fh.write(' \\hline\n') 

2444 else: 

2445 first = True 

2446 fh.write(header_start.replace(' ', '-')) 

2447 for c in range(len(self.header)): 

2448 if self.hidden[c]: 

2449 continue 

2450 if not first: 

2451 fh.write('-'*len(header_sep)) 

2452 first = False 

2453 fh.write(header_close) 

2454 w = widths[c] 

2455 fh.write(w*'-') 

2456 fh.write(header_end.replace(' ', '-')) 

2457 # end table: 

2458 fh.write(end_str) 

2459 # close file: 

2460 if own_file: 

2461 fh.close() 

2462 # return file name: 

2463 return file_name 

2464 

2465 

2466 def write_file_stream(self, basename, file_name, **kwargs): 

2467 """Write table to file or stream and return appropriate file name. 

2468 

2469 Parameters 

2470 ---------- 

2471 basename: str or stream 

2472 If str, path and basename of file. 

2473 `file_name` and an extension are appended. 

2474 If stream, write table data into this stream. 

2475 file_name: str 

2476 Name of file that is appended to a base path or `basename`. 

2477 kwargs: 

2478 Arguments passed on to `TableData.write()`. 

2479 In particular, 'table_format' is used to the set the file extension 

2480 that is appended to the returned `file_name`. 

2481 

2482 Returns 

2483 ------- 

2484 file_name: str 

2485 Path and full name of the written file in case of `basename` 

2486 being a string. Otherwise, the file name and extension that 

2487 should be appended to a base path. 

2488 """ 

2489 if hasattr(basename, 'write'): 

2490 table_format = kwargs.get('table_format', None) 

2491 if table_format is None or table_format == 'auto': 

2492 table_format = 'csv' 

2493 file_name += '.' + TableData.extensions[table_format] 

2494 self.write(basename, **kwargs) 

2495 return file_name 

2496 else: 

2497 file_name = self.write(basename + file_name, **kwargs) 

2498 return file_name 

2499 

2500 

2501 def __str__(self): 

2502 """Write table to a string. 

2503 """ 

2504 stream = StringIO() 

2505 self.write(stream, table_format='out') 

2506 return stream.getvalue() 

2507 

2508 

2509 def load(self, fh, missing=default_missing_inputs, stop=None): 

2510 """Load table from file or stream. 

2511 

2512 File type and properties are automatically inferred. 

2513 

2514 Parameters 

2515 ---------- 

2516 fh: str or stream 

2517 If not a stream, the file with name `fh` is opened for reading. 

2518 missing: str or list of str 

2519 Missing data are indicated by this string and 

2520 are translated to np.nan. 

2521 stop: str or None 

2522 If the beginning of a line matches `stop`, then stop reading the file. 

2523 

2524 Raises 

2525 ------ 

2526 FileNotFoundError: 

2527 If `fh` is a path that does not exist. 

2528 """ 

2529 

2530 def read_key_line(line, sep, table_format): 

2531 if sep is None: 

2532 cols, indices = zip(*[(m.group(0), m.start()) for m in re.finditer(r'( ?[\S]+)+(?=[ ][ ]+|\Z)', line.strip())]) 

2533 elif table_format == 'csv': 

2534 cols, indices = zip(*[(c.strip(), i) for i, c in enumerate(line.strip().split(sep)) if c.strip()]) 

2535 return cols, indices 

2536 else: 

2537 seps = r'[^'+re.escape(sep)+']+' 

2538 cols, indices = zip(*[(m.group(0), m.start()) for m in re.finditer(seps, line.strip())]) 

2539 colss = [] 

2540 indicess = [] 

2541 if table_format == 'tex': 

2542 i = 0 

2543 for c in cols: 

2544 if 'multicolumn' in c: 

2545 fields = c.split('{') 

2546 n = int(fields[1].strip().rstrip('}').rstrip()) 

2547 colss.append(fields[3].strip().rstrip('}').rstrip()) 

2548 indicess.append(i) 

2549 i += n 

2550 else: 

2551 colss.append(c.strip()) 

2552 indicess.append(i) 

2553 i += 1 

2554 else: 

2555 for k, (c, i) in enumerate(zip(cols, indices)): 

2556 if k == 0: 

2557 c = c.lstrip('|') 

2558 if k == len(cols)-1: 

2559 c = c.rstrip('|') 

2560 cs = c.strip() 

2561 colss.append(cs) 

2562 indicess.append(i) 

2563 return colss, indicess 

2564 

2565 def read_data_line(line, sep, post, precd, alld, numc, exped, 

2566 fixed, strf, missing, nans): 

2567 # read line: 

2568 cols = [] 

2569 if sep is None: 

2570 cols = [m.group(0) for m in re.finditer(r'\S+', line.strip())] 

2571 else: 

2572 if sep.isspace(): 

2573 seps = r'[^'+re.escape(sep)+']+' 

2574 cols = [m.group(0) for m in re.finditer(seps, line.strip())] 

2575 else: 

2576 cols = line.split(sep) 

2577 if len(cols) > 0 and len(cols[0]) == 0: 

2578 cols = cols[1:] 

2579 if len(cols) > 0 and len(cols[-1]) == 0: 

2580 cols = cols[:-1] 

2581 if len(cols) > 0: 

2582 cols[0] = cols[0].lstrip('|').lstrip() 

2583 cols[-1] = cols[-1].rstrip('|').rstrip() 

2584 cols = [c.strip() for c in cols if c != '|'] 

2585 # read columns: 

2586 for k, c in enumerate(cols): 

2587 try: 

2588 v = float(c) 

2589 ad = 0 

2590 ve = c.split('e') 

2591 if len(ve) <= 1: 

2592 exped[k] = False 

2593 else: 

2594 ad = len(ve[1])+1 

2595 vc = ve[0].split('.') 

2596 ad += len(vc[0]) 

2597 prec = len(vc[0].lstrip('-').lstrip('+').lstrip('0')) 

2598 if len(vc) == 2: 

2599 if numc[k] and post[k] != len(vc[1]): 

2600 fixed[k] = False 

2601 if post[k] < len(vc[1]): 

2602 post[k] = len(vc[1]) 

2603 ad += len(vc[1])+1 

2604 prec += len(vc[1].rstrip('0')) 

2605 if precd[k] < prec: 

2606 precd[k] = prec 

2607 if alld[k] < ad: 

2608 alld[k] = ad 

2609 numc[k] = True 

2610 except ValueError: 

2611 if c in missing: 

2612 v = np.nan 

2613 nans[k] = c 

2614 else: 

2615 strf[k] = True 

2616 if alld[k] < len(c): 

2617 alld[k] = len(c) 

2618 v = c 

2619 self.append_data(v, k) 

2620 

2621 # initialize: 

2622 if isinstance(missing, str): 

2623 missing = [missing] 

2624 self.data = [] 

2625 self.shape = (0, 0) 

2626 self.header = [] 

2627 self.nsecs = 0 

2628 self.units = [] 

2629 self.formats = [] 

2630 self.hidden = [] 

2631 self.setcol = 0 

2632 self.addcol = 0 

2633 # open file: 

2634 own_file = False 

2635 if not hasattr(fh, 'readline'): 

2636 fh = open(fh, 'r') 

2637 own_file = True 

2638 # read inital lines of file: 

2639 key = [] 

2640 data = [] 

2641 target = data 

2642 comment = False 

2643 table_format='dat' 

2644 for line in fh: 

2645 line = line.rstrip() 

2646 if line == stop: 

2647 break; 

2648 if line: 

2649 if r'\begin{tabular' in line: 

2650 table_format='tex' 

2651 target = key 

2652 continue 

2653 if table_format == 'tex': 

2654 if r'\end{tabular' in line: 

2655 break 

2656 if r'\hline' in line: 

2657 if key: 

2658 target = data 

2659 continue 

2660 line = line.rstrip(r'\\') 

2661 if line[0] == '#': 

2662 comment = True 

2663 table_format='dat' 

2664 target = key 

2665 line = line.lstrip('#') 

2666 elif comment: 

2667 target = data 

2668 if line[0:3] == 'RTH': 

2669 target = key 

2670 line = line[3:] 

2671 table_format='rtai' 

2672 elif line[0:3] == 'RTD': 

2673 target = data 

2674 line = line[3:] 

2675 table_format='rtai' 

2676 if (line[0:3] == '|--' or line[0:3] == '|:-') and \ 

2677 (line[-3:] == '--|' or line[-3:] == '-:|'): 

2678 if not data and not key: 

2679 table_format='ascii' 

2680 target = key 

2681 continue 

2682 elif not key: 

2683 table_format='md' 

2684 key = data 

2685 data = [] 

2686 target = data 

2687 continue 

2688 elif not data: 

2689 target = data 

2690 continue 

2691 else: 

2692 break 

2693 target.append(line) 

2694 else: 

2695 break 

2696 if len(data) > 5: 

2697 break 

2698 # find column separator of data and number of columns: 

2699 col_seps = ['|', ',', ';', ':', '\t', '&', None] 

2700 colstd = np.zeros(len(col_seps)) 

2701 colnum = np.zeros(len(col_seps), dtype=int) 

2702 for k, sep in enumerate(col_seps): 

2703 cols = [] 

2704 s = 5 if len(data) >= 8 else len(data) - 3 

2705 if s < 0 or key: 

2706 s = 0 

2707 for line in data[s:]: 

2708 cs = line.strip().split(sep) 

2709 if not cs[0]: 

2710 cs = cs[1:] 

2711 if cs and not cs[-1]: 

2712 cs = cs[:-1] 

2713 cols.append(len(cs)) 

2714 colstd[k] = np.std(cols) 

2715 colnum[k] = np.median(cols) 

2716 if np.max(colnum) < 2: 

2717 sep = None 

2718 colnum = 1 

2719 else: 

2720 ci = np.where(np.array(colnum)>1.5)[0] 

2721 ci = ci[np.argmin(colstd[ci])] 

2722 sep = col_seps[ci] 

2723 colnum = int(colnum[ci]) 

2724 # fix key: 

2725 if not key and sep is not None and sep in ',;:\t|': 

2726 table_format = 'csv' 

2727 # read key: 

2728 key_cols = [] 

2729 key_indices = [] 

2730 for line in key: 

2731 cols, indices = read_key_line(line, sep, table_format) 

2732 key_cols.append(cols) 

2733 key_indices.append(indices) 

2734 if not key_cols: 

2735 # no obviously marked table key: 

2736 key_num = 0 

2737 for line in data: 

2738 cols, indices = read_key_line(line, sep, table_format) 

2739 numbers = 0 

2740 for c in cols: 

2741 try: 

2742 v = float(c) 

2743 numbers += 1 

2744 except ValueError: 

2745 pass 

2746 if numbers == 0: 

2747 key_cols.append(cols) 

2748 key_indices.append(indices) 

2749 key_num += 1 

2750 else: 

2751 break 

2752 data = data[key_num:] 

2753 kr = len(key_cols)-1 

2754 # check for key with column indices: 

2755 if kr >= 0: 

2756 cols = key_cols[kr] 

2757 numrow = True 

2758 try: 

2759 pv = int(cols[0]) 

2760 for c in cols[1:]: 

2761 v = int(c) 

2762 if v != pv+1: 

2763 numrow = False 

2764 break 

2765 pv = v 

2766 except ValueError: 

2767 try: 

2768 pv = aa2index(cols[0]) 

2769 for c in cols[1:]: 

2770 v = aa2index(c) 

2771 if v != pv+1: 

2772 numrow = False 

2773 break 

2774 pv = v 

2775 except ValueError: 

2776 numrow = False 

2777 if numrow: 

2778 kr -= 1 

2779 # check for unit line: 

2780 units = None 

2781 if kr > 0 and len(key_cols[kr]) == len(key_cols[kr-1]): 

2782 units = key_cols[kr] 

2783 kr -= 1 

2784 # column labels: 

2785 if kr >= 0: 

2786 if units is None: 

2787 # units may be part of the label: 

2788 labels = [] 

2789 units = [] 

2790 for c in key_cols[kr]: 

2791 if c[-1] == ')': 

2792 lu = c[:-1].split('(') 

2793 if len(lu) >= 2: 

2794 labels.append(lu[0].strip()) 

2795 units.append('('.join(lu[1:]).strip()) 

2796 continue 

2797 lu = c.split('/') 

2798 if len(lu) >= 2: 

2799 labels.append(lu[0].strip()) 

2800 units.append('/'.join(lu[1:]).strip()) 

2801 else: 

2802 labels.append(c) 

2803 units.append('') 

2804 else: 

2805 labels = key_cols[kr] 

2806 indices = key_indices[kr] 

2807 # init table columns: 

2808 for k in range(colnum): 

2809 self.append(labels[k], units[k], '%g') 

2810 # read in sections: 

2811 while kr > 0: 

2812 kr -= 1 

2813 for sec_label, sec_inx in zip(key_cols[kr], key_indices[kr]): 

2814 col_inx = indices.index(sec_inx) 

2815 self.header[col_inx].append(sec_label) 

2816 if self.nsecs < len(self.header[col_inx])-1: 

2817 self.nsecs = len(self.header[col_inx])-1 

2818 # read data: 

2819 post = np.zeros(colnum) 

2820 precd = np.zeros(colnum) 

2821 alld = np.zeros(colnum) 

2822 numc = [False] * colnum 

2823 exped = [True] * colnum 

2824 fixed = [True] * colnum 

2825 strf = [False] * colnum 

2826 nans = [None] * colnum 

2827 for line in data: 

2828 read_data_line(line, sep, post, precd, alld, numc, exped, fixed, 

2829 strf, missing, nans) 

2830 # read remaining data: 

2831 for line in fh: 

2832 line = line.rstrip() 

2833 if line == stop: 

2834 break; 

2835 if table_format == 'tex': 

2836 if r'\end{tabular' in line or r'\hline' in line: 

2837 break 

2838 line = line.rstrip(r'\\') 

2839 if (line[0:3] == '|--' or line[0:3] == '|:-') and \ 

2840 (line[-3:] == '--|' or line[-3:] == '-:|'): 

2841 break 

2842 if line[0:3] == 'RTD': 

2843 line = line[3:] 

2844 read_data_line(line, sep, post, precd, alld, numc, exped, fixed, 

2845 strf, missing, nans) 

2846 # set formats: 

2847 for k in range(len(alld)): 

2848 if strf[k]: 

2849 self.set_format('%%-%ds' % alld[k], k) 

2850 # make sure all elements are strings: 

2851 for i in range(len(self.data[k])): 

2852 if self.data[k][i] is np.nan: 

2853 self.data[k][i] = nans[k] 

2854 else: 

2855 self.data[k][i] = str(self.data[k][i]) 

2856 elif exped[k]: 

2857 self.set_format('%%%d.%de' % (alld[k], post[k]), k) 

2858 elif fixed[k]: 

2859 self.set_format('%%%d.%df' % (alld[k], post[k]), k) 

2860 else: 

2861 self.set_format('%%%d.%dg' % (alld[k], precd[k]), k) 

2862 # close file: 

2863 if own_file: 

2864 fh.close() 

2865 

2866 

2867def write(fh, data, header, units=None, formats=None, 

2868 table_format=None, delimiter=None, unit_style=None, 

2869 column_numbers=None, sections=None, align_columns=None, 

2870 shrink_width=True, missing=default_missing_str, 

2871 center_columns=False, latex_label_command='', 

2872 latex_merge_std=False): 

2873 """Construct table and write to file. 

2874 

2875 Parameters 

2876 ---------- 

2877 fh: filename or stream 

2878 If not a stream, the file with name `fh` is opened. 

2879 If `fh` does not have an extension, 

2880 the `table_format` is appended as an extension. 

2881 Otherwise `fh` is used as a stream for writing. 

2882 data: 1-D or 2-D ndarray of data 

2883 The data of the table. 

2884 header: list of str 

2885 Header labels for each column. 

2886 units: list of str, optional 

2887 Unit strings for each column. 

2888 formats: str or list of str, optional 

2889 Format strings for each column. If only a single format string is 

2890 given, then all columns are initialized with this format string. 

2891 

2892 See `TableData.write()` for a description of all other parameters. 

2893 

2894 Example 

2895 ------- 

2896 ``` 

2897 write(sys.stdout, np.random.randn(4,3), ['aaa', 'bbb', 'ccc'], units=['m', 's', 'g'], formats='%.2f') 

2898 ``` 

2899 """ 

2900 td = TableData(data, header, units, formats) 

2901 td.write(fh, table_format=table_format, unit_style=unit_style, 

2902 column_numbers=column_numbers, missing=missing, 

2903 shrink_width=shrink_width, delimiter=delimiter, 

2904 align_columns=align_columns, sections=sections, 

2905 latex_label_command=latex_label_command, 

2906 latex_merge_std=latex_merge_std) 

2907 

2908 

2909def add_write_table_config(cfg, table_format=None, delimiter=None, 

2910 unit_style=None, column_numbers=None, 

2911 sections=None, align_columns=None, 

2912 shrink_width=True, missing='-', 

2913 center_columns=False, 

2914 latex_label_command='', 

2915 latex_merge_std=False): 

2916 """Add parameter specifying how to write a table to a file as a new 

2917section to a configuration. 

2918 

2919 Parameters 

2920 ---------- 

2921 cfg: ConfigFile 

2922 The configuration. 

2923 """ 

2924 

2925 cfg.add_section('File format for storing analysis results:') 

2926 cfg.add('fileFormat', table_format or 'auto', '', 'Default file format used to store analysis results.\nOne of %s.' % ', '.join(TableData.formats)) 

2927 cfg.add('fileDelimiter', delimiter or 'auto', '', 'String used to separate columns or "auto".') 

2928 cfg.add('fileUnitStyle', unit_style or 'auto', '', 'Add units as extra row ("row"), add units to header label separated by "/" ("header"), do not print out units ("none"), or "auto".') 

2929 cfg.add('fileColumnNumbers', column_numbers or 'none', '', 'Add line with column indices ("index", "num", "aa", "AA", or "none")') 

2930 cfg.add('fileSections', sections or 'auto', '', 'Maximum number of section levels or "auto"') 

2931 cfg.add('fileAlignColumns', align_columns or 'auto', '', 'If True, write all data of a column using the same width, if False write the data without any white space, or "auto".') 

2932 cfg.add('fileShrinkColumnWidth', shrink_width, '', 'Allow to make columns narrower than specified by the corresponding format strings.') 

2933 cfg.add('fileMissing', missing, '', 'String used to indicate missing data values.') 

2934 cfg.add('fileCenterColumns', center_columns, '', 'Center content of all columns instead of left align columns of strings and right align numbers (markdown, html, and latex).') 

2935 cfg.add('fileLaTeXLabelCommand', latex_label_command, '', 'LaTeX command name for formatting column labels of the table header.') 

2936 cfg.add('fileLaTeXMergeStd', latex_merge_std, '', 'Merge header of columns with standard deviations with previous column (LaTeX tables only).') 

2937 

2938 

2939def write_table_args(cfg): 

2940 """Translates a configuration to the respective parameter names for 

2941writing a table to a file. 

2942  

2943 The return value can then be passed as key-word arguments to TableData.write(). 

2944 

2945 Parameters 

2946 ---------- 

2947 cfg: ConfigFile 

2948 The configuration. 

2949 

2950 Returns 

2951 ------- 

2952 a: dict 

2953 Dictionary with names of arguments of the `TableData.write` function 

2954 and their values as supplied by `cfg`. 

2955 """ 

2956 d = cfg.map({'table_format': 'fileFormat', 

2957 'delimiter': 'fileDelimiter', 

2958 'unit_style': 'fileUnitStyle', 

2959 'column_numbers': 'fileColumnNumbers', 

2960 'sections': 'fileSections', 

2961 'align_columns': 'fileAlignColumns', 

2962 'shrink_width': 'fileShrinkColumnWidth', 

2963 'missing': 'fileMissing', 

2964 'center_columns': 'fileCenterColumns', 

2965 'latex_label_command': 'fileLaTeXLabelCommand', 

2966 'latex_merge_std': 'fileLaTeXMergeStd'}) 

2967 if 'sections' in d: 

2968 if d['sections'] != 'auto': 

2969 d['sections'] = int(d['sections']) 

2970 return d 

2971 

2972 

2973def latex_unit(unit): 

2974 """Translate unit string into SIunit LaTeX code. 

2975  

2976 Parameters 

2977 ---------- 

2978 unit: str 

2979 String enoting a unit. 

2980  

2981 Returns 

2982 ------- 

2983 unit: str 

2984 Unit string as valid LaTeX code. 

2985 """ 

2986 si_prefixes = {'y': '\\yocto', 

2987 'z': '\\zepto', 

2988 'a': '\\atto', 

2989 'f': '\\femto', 

2990 'p': '\\pico', 

2991 'n': '\\nano', 

2992 'u': '\\micro', 

2993 'm': '\\milli', 

2994 'c': '\\centi', 

2995 'd': '\\deci', 

2996 'h': '\\hecto', 

2997 'k': '\\kilo', 

2998 'M': '\\mega', 

2999 'G': '\\giga', 

3000 'T': '\\tera', 

3001 'P': '\\peta', 

3002 'E': '\\exa', 

3003 'Z': '\\zetta', 

3004 'Y': '\\yotta' } 

3005 si_units = {'m': '\\metre', 

3006 'g': '\\gram', 

3007 's': '\\second', 

3008 'A': '\\ampere', 

3009 'K': '\\kelvin', 

3010 'mol': '\\mole', 

3011 'cd': '\\candela', 

3012 'Hz': '\\hertz', 

3013 'N': '\\newton', 

3014 'Pa': '\\pascal', 

3015 'J': '\\joule', 

3016 'W': '\\watt', 

3017 'C': '\\coulomb', 

3018 'V': '\\volt', 

3019 'F': '\\farad', 

3020 'O': '\\ohm', 

3021 'S': '\\siemens', 

3022 'Wb': '\\weber', 

3023 'T': '\\tesla', 

3024 'H': '\\henry', 

3025 'C': '\\celsius', 

3026 'lm': '\\lumen', 

3027 'lx': '\\lux', 

3028 'Bq': '\\becquerel', 

3029 'Gv': '\\gray', 

3030 'Sv': '\\sievert'} 

3031 other_units = {"'": '\\arcminute', 

3032 "''": '\\arcsecond', 

3033 'a': '\\are', 

3034 'd': '\\dday', 

3035 'eV': '\\electronvolt', 

3036 'ha': '\\hectare', 

3037 'h': '\\hour', 

3038 'L': '\\liter', 

3039 'l': '\\litre', 

3040 'min': '\\minute', 

3041 'Np': '\\neper', 

3042 'rad': '\\rad', 

3043 't': '\\ton', 

3044 '%': '\\%'} 

3045 unit_powers = {'^2': '\\squared', 

3046 '^3': '\\cubed', 

3047 '/': '\\per', 

3048 '^-1': '\\power{}{-1}', 

3049 '^-2': '\\rpsquared', 

3050 '^-3': '\\rpcubed'} 

3051 if '\\' in unit: # this string is already translated! 

3052 return unit 

3053 units = '' 

3054 j = len(unit) 

3055 while j >= 0: 

3056 for k in range(-3, 0): 

3057 if j+k < 0: 

3058 continue 

3059 uss = unit[j+k:j] 

3060 if uss in unit_powers: 

3061 units = unit_powers[uss] + units 

3062 break 

3063 elif uss in other_units: 

3064 units = other_units[uss] + units 

3065 break 

3066 elif uss in si_units: 

3067 units = si_units[uss] + units 

3068 j = j+k 

3069 k = 0 

3070 if j-1 >= 0: 

3071 uss = unit[j-1:j] 

3072 if uss in si_prefixes: 

3073 units = si_prefixes[uss] + units 

3074 k = -1 

3075 break 

3076 else: 

3077 k = -1 

3078 units = unit[j+k:j] + units 

3079 j = j + k 

3080 return units 

3081 

3082 

3083def index2aa(n, a='a'): 

3084 """Convert an integer into an alphabetical representation. 

3085 

3086 The integer number is converted into 'a', 'b', 'c', ..., 'z', 

3087 'aa', 'ab', 'ac', ..., 'az', 'ba', 'bb', ... 

3088 

3089 Inspired by https://stackoverflow.com/a/37604105 

3090 

3091 Parameters 

3092 ---------- 

3093 n: int 

3094 An integer to be converted into alphabetical representation. 

3095 a: str ('a' or 'A') 

3096 Use upper or lower case characters. 

3097 

3098 Returns 

3099 ------- 

3100 ns: str 

3101 Alphabetical represtnation of an integer. 

3102 """ 

3103 d, m = divmod(n, 26) 

3104 bm = chr(ord(a)+m) 

3105 return index2aa(d-1, a) + bm if d else bm 

3106 

3107 

3108def aa2index(s): 

3109 """Convert an alphabetical representation to an index. 

3110 

3111 The alphabetical representation 'a', 'b', 'c', ..., 'z', 

3112 'aa', 'ab', 'ac', ..., 'az', 'ba', 'bb', ... 

3113 is converted to an index starting with 0. 

3114 

3115 Parameters 

3116 ---------- 

3117 s: str 

3118 Alphabetical representation of an index. 

3119 

3120 Returns 

3121 ------- 

3122 index: int 

3123 The corresponding index. 

3124 

3125 Raises 

3126 ------ 

3127 ValueError: 

3128 Invalid character in input string. 

3129 """ 

3130 index = 0 

3131 maxc = ord('z') - ord('a') + 1 

3132 for c in s.lower(): 

3133 index *= maxc 

3134 if ord(c) < ord('a') or ord(c) > ord('z'): 

3135 raise ValueError('invalid character "%s" in string.' % c) 

3136 index += ord(c) - ord('a') + 1 

3137 return index-1 

3138 

3139 

3140class IndentStream(object): 

3141 """Filter an output stream and start each newline with a number of 

3142 spaces. 

3143 """ 

3144 def __init__(self, stream, indent=4): 

3145 self.stream = stream 

3146 self.indent = indent 

3147 self.pending = True 

3148 

3149 def __getattr__(self, attr_name): 

3150 return getattr(self.stream, attr_name) 

3151 

3152 def write(self, data): 

3153 if not data: 

3154 return 

3155 if self.pending: 

3156 self.stream.write(' '*self.indent) 

3157 self.pending = False 

3158 substr = data.rstrip('\n') 

3159 rn = len(data) - len(substr) 

3160 if len(substr) > 0: 

3161 self.stream.write(substr.replace('\n', '\n'+' '*self.indent)) 

3162 if rn > 0: 

3163 self.stream.write('\n'*rn) 

3164 self.pending = True 

3165 

3166 def flush(self): 

3167 self.stream.flush() 

3168 

3169 

3170def main(): 

3171 # setup a table: 

3172 df = TableData() 

3173 df.append(["data", "partial information", "ID"], "", "%-s", list('ABCDEFGH')) 

3174 df.append("size", "m", "%6.2f", [2.34, 56.7, 8.9]) 

3175 df.append("full weight", "kg", "%.0f", 122.8) 

3176 df.append_section("complete reaction") 

3177 df.append("speed", "m/s", "%.3g", 98.7) 

3178 df.append("median jitter", "mm", "%.1f", 23) 

3179 df.append("size", "g", "%.2e", 1.234) 

3180 df.append_data(np.nan, 2) # single value 

3181 df.append_data((0.543, 45, 1.235e2)) # remaining row 

3182 df.append_data((43.21, 6789.1, 3405, 1.235e-4), 2) # next row 

3183 a = 0.5*np.arange(1, 6)*np.random.randn(5, 5) + 10.0 + np.arange(5) 

3184 df.append_data(a.T, 1) # rest of table 

3185 df[3:6,'weight'] = [11.0]*3 

3186 

3187 # write out in all formats: 

3188 for tf in TableData.formats: 

3189 print(' - `%s`: %s' % (tf, TableData.descriptions[tf])) 

3190 print(' ```') 

3191 iout = IndentStream(sys.stdout, 4+2) 

3192 df.write(iout, table_format=tf) 

3193 print(' ```') 

3194 print('') 

3195 

3196 

3197if __name__ == "__main__": 

3198 main()