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

1556 statements  

« prev     ^ index     » next       coverage.py v7.5.4, created at 2024-06-26 11:35 +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 whne 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): 

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) 

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): 

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 

2522 Raises 

2523 ------ 

2524 FileNotFoundError: 

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

2526 """ 

2527 

2528 def read_key_line(line, sep, table_format): 

2529 if sep is None: 

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

2531 elif table_format == 'csv': 

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

2533 return cols, indices 

2534 else: 

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

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

2537 colss = [] 

2538 indicess = [] 

2539 if table_format == 'tex': 

2540 i = 0 

2541 for c in cols: 

2542 if 'multicolumn' in c: 

2543 fields = c.split('{') 

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

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

2546 indicess.append(i) 

2547 i += n 

2548 else: 

2549 colss.append(c.strip()) 

2550 indicess.append(i) 

2551 i += 1 

2552 else: 

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

2554 if k == 0: 

2555 c = c.lstrip('|') 

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

2557 c = c.rstrip('|') 

2558 cs = c.strip() 

2559 colss.append(cs) 

2560 indicess.append(i) 

2561 return colss, indicess 

2562 

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

2564 fixed, strf, missing, nans): 

2565 # read line: 

2566 cols = [] 

2567 if sep is None: 

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

2569 else: 

2570 if sep.isspace(): 

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

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

2573 else: 

2574 cols = line.split(sep) 

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

2576 cols = cols[1:] 

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

2578 cols = cols[:-1] 

2579 if len(cols) > 0: 

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

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

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

2583 # read columns: 

2584 for k, c in enumerate(cols): 

2585 try: 

2586 v = float(c) 

2587 ad = 0 

2588 ve = c.split('e') 

2589 if len(ve) <= 1: 

2590 exped[k] = False 

2591 else: 

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

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

2594 ad += len(vc[0]) 

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

2596 if len(vc) == 2: 

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

2598 fixed[k] = False 

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

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

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

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

2603 if precd[k] < prec: 

2604 precd[k] = prec 

2605 if alld[k] < ad: 

2606 alld[k] = ad 

2607 numc[k] = True 

2608 except ValueError: 

2609 if c in missing: 

2610 v = np.nan 

2611 nans[k] = c 

2612 else: 

2613 strf[k] = True 

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

2615 alld[k] = len(c) 

2616 v = c 

2617 self.append_data(v, k) 

2618 

2619 # initialize: 

2620 if isinstance(missing, str): 

2621 missing = [missing] 

2622 self.data = [] 

2623 self.shape = (0, 0) 

2624 self.header = [] 

2625 self.nsecs = 0 

2626 self.units = [] 

2627 self.formats = [] 

2628 self.hidden = [] 

2629 self.setcol = 0 

2630 self.addcol = 0 

2631 # open file: 

2632 own_file = False 

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

2634 fh = open(fh, 'r') 

2635 own_file = True 

2636 # read inital lines of file: 

2637 key = [] 

2638 data = [] 

2639 target = data 

2640 comment = False 

2641 table_format='dat' 

2642 for line in fh: 

2643 line = line.rstrip() 

2644 if line: 

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

2646 table_format='tex' 

2647 target = key 

2648 continue 

2649 if table_format == 'tex': 

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

2651 break 

2652 if r'\hline' in line: 

2653 if key: 

2654 target = data 

2655 continue 

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

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

2658 comment = True 

2659 table_format='dat' 

2660 target = key 

2661 line = line.lstrip('#') 

2662 elif comment: 

2663 target = data 

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

2665 target = key 

2666 line = line[3:] 

2667 table_format='rtai' 

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

2669 target = data 

2670 line = line[3:] 

2671 table_format='rtai' 

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

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

2674 if not data and not key: 

2675 table_format='ascii' 

2676 target = key 

2677 continue 

2678 elif not key: 

2679 table_format='md' 

2680 key = data 

2681 data = [] 

2682 target = data 

2683 continue 

2684 elif not data: 

2685 target = data 

2686 continue 

2687 else: 

2688 break 

2689 target.append(line) 

2690 else: 

2691 break 

2692 if len(data) > 5: 

2693 break 

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

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

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

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

2698 for k, sep in enumerate(col_seps): 

2699 cols = [] 

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

2701 if s < 0 or key: 

2702 s = 0 

2703 for line in data[s:]: 

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

2705 if not cs[0]: 

2706 cs = cs[1:] 

2707 if cs and not cs[-1]: 

2708 cs = cs[:-1] 

2709 cols.append(len(cs)) 

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

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

2712 if np.max(colnum) < 2: 

2713 sep = None 

2714 colnum = 1 

2715 else: 

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

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

2718 sep = col_seps[ci] 

2719 colnum = int(colnum[ci]) 

2720 # fix key: 

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

2722 table_format = 'csv' 

2723 # read key: 

2724 key_cols = [] 

2725 key_indices = [] 

2726 for line in key: 

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

2728 key_cols.append(cols) 

2729 key_indices.append(indices) 

2730 if not key_cols: 

2731 # no obviously marked table key: 

2732 key_num = 0 

2733 for line in data: 

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

2735 numbers = 0 

2736 for c in cols: 

2737 try: 

2738 v = float(c) 

2739 numbers += 1 

2740 except ValueError: 

2741 pass 

2742 if numbers == 0: 

2743 key_cols.append(cols) 

2744 key_indices.append(indices) 

2745 key_num += 1 

2746 else: 

2747 break 

2748 data = data[key_num:] 

2749 kr = len(key_cols)-1 

2750 # check for key with column indices: 

2751 if kr >= 0: 

2752 cols = key_cols[kr] 

2753 numrow = True 

2754 try: 

2755 pv = int(cols[0]) 

2756 for c in cols[1:]: 

2757 v = int(c) 

2758 if v != pv+1: 

2759 numrow = False 

2760 break 

2761 pv = v 

2762 except ValueError: 

2763 try: 

2764 pv = aa2index(cols[0]) 

2765 for c in cols[1:]: 

2766 v = aa2index(c) 

2767 if v != pv+1: 

2768 numrow = False 

2769 break 

2770 pv = v 

2771 except ValueError: 

2772 numrow = False 

2773 if numrow: 

2774 kr -= 1 

2775 # check for unit line: 

2776 units = None 

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

2778 units = key_cols[kr] 

2779 kr -= 1 

2780 # column labels: 

2781 if kr >= 0: 

2782 if units is None: 

2783 # units may be part of the label: 

2784 labels = [] 

2785 units = [] 

2786 for c in key_cols[kr]: 

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

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

2789 if len(lu) >= 2: 

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

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

2792 continue 

2793 lu = c.split('/') 

2794 if len(lu) >= 2: 

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

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

2797 else: 

2798 labels.append(c) 

2799 units.append('') 

2800 else: 

2801 labels = key_cols[kr] 

2802 indices = key_indices[kr] 

2803 # init table columns: 

2804 for k in range(colnum): 

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

2806 # read in sections: 

2807 while kr > 0: 

2808 kr -= 1 

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

2810 col_inx = indices.index(sec_inx) 

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

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

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

2814 # read data: 

2815 post = np.zeros(colnum) 

2816 precd = np.zeros(colnum) 

2817 alld = np.zeros(colnum) 

2818 numc = [False] * colnum 

2819 exped = [True] * colnum 

2820 fixed = [True] * colnum 

2821 strf = [False] * colnum 

2822 nans = [None] * colnum 

2823 for line in data: 

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

2825 strf, missing, nans) 

2826 # read remaining data: 

2827 for line in fh: 

2828 line = line.rstrip() 

2829 if table_format == 'tex': 

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

2831 break 

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

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

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

2835 break 

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

2837 line = line[3:] 

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

2839 strf, missing, nans) 

2840 # set formats: 

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

2842 if strf[k]: 

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

2844 # make sure all elements are strings: 

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

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

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

2848 else: 

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

2850 elif exped[k]: 

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

2852 elif fixed[k]: 

2853 self.set_format('%%%d.%df' % (alld[k], post[k]), k) 

2854 else: 

2855 self.set_format('%%%d.%dg' % (alld[k], precd[k]), k) 

2856 # close file: 

2857 if own_file: 

2858 fh.close() 

2859 

2860 

2861def write(fh, data, header, units=None, formats=None, 

2862 table_format=None, delimiter=None, unit_style=None, 

2863 column_numbers=None, sections=None, align_columns=None, 

2864 shrink_width=True, missing=default_missing_str, 

2865 center_columns=False, latex_label_command='', 

2866 latex_merge_std=False): 

2867 """Construct table and write to file. 

2868 

2869 Parameters 

2870 ---------- 

2871 fh: filename or stream 

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

2873 If `fh` does not have an extension, 

2874 the `table_format` is appended as an extension. 

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

2876 data: 1-D or 2-D ndarray of data 

2877 The data of the table. 

2878 header: list of str 

2879 Header labels for each column. 

2880 units: list of str, optional 

2881 Unit strings for each column. 

2882 formats: str or list of str, optional 

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

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

2885 

2886 See `TableData.write()` for a description of all other parameters. 

2887 

2888 Example 

2889 ------- 

2890 ``` 

2891 write(sys.stdout, np.random.randn(4,3), ['aaa', 'bbb', 'ccc'], units=['m', 's', 'g'], formats='%.2f') 

2892 ``` 

2893 """ 

2894 td = TableData(data, header, units, formats) 

2895 td.write(fh, table_format=table_format, unit_style=unit_style, 

2896 column_numbers=column_numbers, missing=missing, 

2897 shrink_width=shrink_width, delimiter=delimiter, 

2898 align_columns=align_columns, sections=sections, 

2899 latex_label_command=latex_label_command, 

2900 latex_merge_std=latex_merge_std) 

2901 

2902 

2903def add_write_table_config(cfg, table_format=None, delimiter=None, 

2904 unit_style=None, column_numbers=None, 

2905 sections=None, align_columns=None, 

2906 shrink_width=True, missing='-', 

2907 center_columns=False, 

2908 latex_label_command='', 

2909 latex_merge_std=False): 

2910 """Add parameter specifying how to write a table to a file as a new 

2911section to a configuration. 

2912 

2913 Parameters 

2914 ---------- 

2915 cfg: ConfigFile 

2916 The configuration. 

2917 """ 

2918 

2919 cfg.add_section('File format for storing analysis results:') 

2920 cfg.add('fileFormat', table_format or 'auto', '', 'Default file format used to store analysis results.\nOne of %s.' % ', '.join(TableData.formats)) 

2921 cfg.add('fileDelimiter', delimiter or 'auto', '', 'String used to separate columns or "auto".') 

2922 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".') 

2923 cfg.add('fileColumnNumbers', column_numbers or 'none', '', 'Add line with column indices ("index", "num", "aa", "AA", or "none")') 

2924 cfg.add('fileSections', sections or 'auto', '', 'Maximum number of section levels or "auto"') 

2925 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".') 

2926 cfg.add('fileShrinkColumnWidth', shrink_width, '', 'Allow to make columns narrower than specified by the corresponding format strings.') 

2927 cfg.add('fileMissing', missing, '', 'String used to indicate missing data values.') 

2928 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).') 

2929 cfg.add('fileLaTeXLabelCommand', latex_label_command, '', 'LaTeX command name for formatting column labels of the table header.') 

2930 cfg.add('fileLaTeXMergeStd', latex_merge_std, '', 'Merge header of columns with standard deviations with previous column (LaTeX tables only).') 

2931 

2932 

2933def write_table_args(cfg): 

2934 """Translates a configuration to the respective parameter names for 

2935writing a table to a file. 

2936  

2937 The return value can then be passed as key-word arguments to TableData.write(). 

2938 

2939 Parameters 

2940 ---------- 

2941 cfg: ConfigFile 

2942 The configuration. 

2943 

2944 Returns 

2945 ------- 

2946 a: dict 

2947 Dictionary with names of arguments of the `TableData.write` function 

2948 and their values as supplied by `cfg`. 

2949 """ 

2950 d = cfg.map({'table_format': 'fileFormat', 

2951 'delimiter': 'fileDelimiter', 

2952 'unit_style': 'fileUnitStyle', 

2953 'column_numbers': 'fileColumnNumbers', 

2954 'sections': 'fileSections', 

2955 'align_columns': 'fileAlignColumns', 

2956 'shrink_width': 'fileShrinkColumnWidth', 

2957 'missing': 'fileMissing', 

2958 'center_columns': 'fileCenterColumns', 

2959 'latex_label_command': 'fileLaTeXLabelCommand', 

2960 'latex_merge_std': 'fileLaTeXMergeStd'}) 

2961 if 'sections' in d: 

2962 if d['sections'] != 'auto': 

2963 d['sections'] = int(d['sections']) 

2964 return d 

2965 

2966 

2967def latex_unit(unit): 

2968 """Translate unit string into SIunit LaTeX code. 

2969  

2970 Parameters 

2971 ---------- 

2972 unit: str 

2973 String enoting a unit. 

2974  

2975 Returns 

2976 ------- 

2977 unit: str 

2978 Unit string as valid LaTeX code. 

2979 """ 

2980 si_prefixes = {'y': '\\yocto', 

2981 'z': '\\zepto', 

2982 'a': '\\atto', 

2983 'f': '\\femto', 

2984 'p': '\\pico', 

2985 'n': '\\nano', 

2986 'u': '\\micro', 

2987 'm': '\\milli', 

2988 'c': '\\centi', 

2989 'd': '\\deci', 

2990 'h': '\\hecto', 

2991 'k': '\\kilo', 

2992 'M': '\\mega', 

2993 'G': '\\giga', 

2994 'T': '\\tera', 

2995 'P': '\\peta', 

2996 'E': '\\exa', 

2997 'Z': '\\zetta', 

2998 'Y': '\\yotta' } 

2999 si_units = {'m': '\\metre', 

3000 'g': '\\gram', 

3001 's': '\\second', 

3002 'A': '\\ampere', 

3003 'K': '\\kelvin', 

3004 'mol': '\\mole', 

3005 'cd': '\\candela', 

3006 'Hz': '\\hertz', 

3007 'N': '\\newton', 

3008 'Pa': '\\pascal', 

3009 'J': '\\joule', 

3010 'W': '\\watt', 

3011 'C': '\\coulomb', 

3012 'V': '\\volt', 

3013 'F': '\\farad', 

3014 'O': '\\ohm', 

3015 'S': '\\siemens', 

3016 'Wb': '\\weber', 

3017 'T': '\\tesla', 

3018 'H': '\\henry', 

3019 'C': '\\celsius', 

3020 'lm': '\\lumen', 

3021 'lx': '\\lux', 

3022 'Bq': '\\becquerel', 

3023 'Gv': '\\gray', 

3024 'Sv': '\\sievert'} 

3025 other_units = {"'": '\\arcminute', 

3026 "''": '\\arcsecond', 

3027 'a': '\\are', 

3028 'd': '\\dday', 

3029 'eV': '\\electronvolt', 

3030 'ha': '\\hectare', 

3031 'h': '\\hour', 

3032 'L': '\\liter', 

3033 'l': '\\litre', 

3034 'min': '\\minute', 

3035 'Np': '\\neper', 

3036 'rad': '\\rad', 

3037 't': '\\ton', 

3038 '%': '\\%'} 

3039 unit_powers = {'^2': '\\squared', 

3040 '^3': '\\cubed', 

3041 '/': '\\per', 

3042 '^-1': '\\power{}{-1}', 

3043 '^-2': '\\rpsquared', 

3044 '^-3': '\\rpcubed'} 

3045 if '\\' in unit: # this string is already translated! 

3046 return unit 

3047 units = '' 

3048 j = len(unit) 

3049 while j >= 0: 

3050 for k in range(-3, 0): 

3051 if j+k < 0: 

3052 continue 

3053 uss = unit[j+k:j] 

3054 if uss in unit_powers: 

3055 units = unit_powers[uss] + units 

3056 break 

3057 elif uss in other_units: 

3058 units = other_units[uss] + units 

3059 break 

3060 elif uss in si_units: 

3061 units = si_units[uss] + units 

3062 j = j+k 

3063 k = 0 

3064 if j-1 >= 0: 

3065 uss = unit[j-1:j] 

3066 if uss in si_prefixes: 

3067 units = si_prefixes[uss] + units 

3068 k = -1 

3069 break 

3070 else: 

3071 k = -1 

3072 units = unit[j+k:j] + units 

3073 j = j + k 

3074 return units 

3075 

3076 

3077def index2aa(n, a='a'): 

3078 """Convert an integer into an alphabetical representation. 

3079 

3080 The integer number is converted into 'a', 'b', 'c', ..., 'z', 

3081 'aa', 'ab', 'ac', ..., 'az', 'ba', 'bb', ... 

3082 

3083 Inspired by https://stackoverflow.com/a/37604105 

3084 

3085 Parameters 

3086 ---------- 

3087 n: int 

3088 An integer to be converted into alphabetical representation. 

3089 a: str ('a' or 'A') 

3090 Use upper or lower case characters. 

3091 

3092 Returns 

3093 ------- 

3094 ns: str 

3095 Alphabetical represtnation of an integer. 

3096 """ 

3097 d, m = divmod(n, 26) 

3098 bm = chr(ord(a)+m) 

3099 return index2aa(d-1, a) + bm if d else bm 

3100 

3101 

3102def aa2index(s): 

3103 """Convert an alphabetical representation to an index. 

3104 

3105 The alphabetical representation 'a', 'b', 'c', ..., 'z', 

3106 'aa', 'ab', 'ac', ..., 'az', 'ba', 'bb', ... 

3107 is converted to an index starting with 0. 

3108 

3109 Parameters 

3110 ---------- 

3111 s: str 

3112 Alphabetical representation of an index. 

3113 

3114 Returns 

3115 ------- 

3116 index: int 

3117 The corresponding index. 

3118 

3119 Raises 

3120 ------ 

3121 ValueError: 

3122 Invalid character in input string. 

3123 """ 

3124 index = 0 

3125 maxc = ord('z') - ord('a') + 1 

3126 for c in s.lower(): 

3127 index *= maxc 

3128 if ord(c) < ord('a') or ord(c) > ord('z'): 

3129 raise ValueError('invalid character "%s" in string.' % c) 

3130 index += ord(c) - ord('a') + 1 

3131 return index-1 

3132 

3133 

3134class IndentStream(object): 

3135 """Filter an output stream and start each newline with a number of 

3136 spaces. 

3137 """ 

3138 def __init__(self, stream, indent=4): 

3139 self.stream = stream 

3140 self.indent = indent 

3141 self.pending = True 

3142 

3143 def __getattr__(self, attr_name): 

3144 return getattr(self.stream, attr_name) 

3145 

3146 def write(self, data): 

3147 if not data: 

3148 return 

3149 if self.pending: 

3150 self.stream.write(' '*self.indent) 

3151 self.pending = False 

3152 substr = data.rstrip('\n') 

3153 rn = len(data) - len(substr) 

3154 if len(substr) > 0: 

3155 self.stream.write(substr.replace('\n', '\n'+' '*self.indent)) 

3156 if rn > 0: 

3157 self.stream.write('\n'*rn) 

3158 self.pending = True 

3159 

3160 def flush(self): 

3161 self.stream.flush() 

3162 

3163 

3164def main(): 

3165 # setup a table: 

3166 df = TableData() 

3167 df.append(["data", "partial information", "ID"], "", "%-s", list('ABCDEFGH')) 

3168 df.append("size", "m", "%6.2f", [2.34, 56.7, 8.9]) 

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

3170 df.append_section("complete reaction") 

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

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

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

3174 df.append_data(np.nan, 2) # single value 

3175 df.append_data((0.543, 45, 1.235e2)) # remaining row 

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

3177 a = 0.5*np.arange(1, 6)*np.random.randn(5, 5) + 10.0 + np.arange(5) 

3178 df.append_data(a.T, 1) # rest of table 

3179 df[3:6,'weight'] = [11.0]*3 

3180 

3181 # write out in all formats: 

3182 for tf in TableData.formats: 

3183 print(' - `%s`: %s' % (tf, TableData.descriptions[tf])) 

3184 print(' ```') 

3185 iout = IndentStream(sys.stdout, 4+2) 

3186 df.write(iout, table_format=tf) 

3187 print(' ```') 

3188 print('') 

3189 

3190 

3191if __name__ == "__main__": 

3192 main()