fat_table 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1965 @@
1
+ = Introduction
2
+
3
+ +FatTable+ is a gem that treats tables as a data type. It provides methods for
4
+ constructing tables from a variety of sources, building them row-by-row,
5
+ extracting rows, columns, and cells, and performing aggregate operations on
6
+ columns. It also provides as set of SQL-esque methods for manipulating table
7
+ objects: +select+ for filtering by columns or for creating new columns, +where+
8
+ for filtering by rows, +order_by+ for sorting rows, +distinct+ for eliminating
9
+ duplicate rows, +group_by+ for aggregating multiple rows into single rows and
10
+ applying column aggregate methods to ungrouped columns, a collection of +join+
11
+ methods for combining tables, and more.
12
+
13
+ Furthermore, +FatTable+ provides methods for formatting tables and producing
14
+ output that targets various output media: text, ANSI terminals, ruby data
15
+ structures, LaTeX tables, Emacs org-mode tables, and more. The formatting
16
+ methods can specify cell formatting in a way that is uniform across all the
17
+ output methods and can also decorate the output with any number of footers,
18
+ including group footers. +FatTable+ applies formatting directives to the extent
19
+ they makes sense for the output medium and treats other formatting directives as
20
+ no-ops.
21
+
22
+ +FatTable+ can be used to perform operations on data that are naturally best
23
+ conceived of as tables, which in my experience is quite often. It can also serve
24
+ as a foundation for providing reporting functions where flexibility about the
25
+ output medium can be quite useful. Finally +FatTable+ can be used within Emacs
26
+ +org-mode+ files in code blocks targeting the Ruby language. Org mode tables are
27
+ presented to a ruby code block as an array of arrays, so +FatTable+ can read
28
+ them in with its +.from_aoa+ constructor. A +FatTable+ table can output as an
29
+ array of arrays with its +.to_aoa+ output function and will be rendered in an
30
+ org-mode buffer as an org-table, ready for processing by other code blocks.
31
+
32
+ = Installation
33
+
34
+ Add this line to your application's Gemfile:
35
+
36
+ gem 'fat_table'
37
+
38
+ And then execute:
39
+
40
+ $ bundle
41
+
42
+ Or install it yourself as:
43
+
44
+ $ gem install fat_table
45
+
46
+ = Usage
47
+
48
+ == Quick Start
49
+
50
+ +FatTable+ provides table objects as a data type that can be constructed and
51
+ operated on in a number of ways. Here's a quick example to illustrate the use of
52
+ the main features of +FatTable+. See the detailed explanations further on down.
53
+
54
+ require 'fat_table'
55
+
56
+ data =
57
+ [['Date', 'Code', 'Raw', 'Shares', 'Price', 'Info', 'Ok'],
58
+ ['2013-05-29', 'S', 15_700.00, 6601.85, 24.7790, 'ENTITY3', 'F'],
59
+ ['2013-05-02', 'P', 118_186.40, 118_186.4, 11.8500, 'ENTITY1', 'T'],
60
+ ['2013-05-20', 'S', 12_000.00, 5046.00, 28.2804, 'ENTITY3', 'F'],
61
+ ['2013-05-23', 'S', 8000.00, 3364.00, 27.1083, 'ENTITY3', 'T'],
62
+ ['2013-05-23', 'S', 39_906.00, 16_780.47, 25.1749, 'ENTITY3', 'T'],
63
+ ['2013-05-20', 'S', 85_000.00, 35_742.50, 28.3224, 'ENTITY3', 'T'],
64
+ ['2013-05-02', 'P', 795_546.20, 795_546.2, 1.1850, 'ENTITY1', 'T'],
65
+ ['2013-05-29', 'S', 13_459.00, 5659.51, 24.7464, 'ENTITY3', 'T'],
66
+ ['2013-05-20', 'S', 33_302.00, 14_003.49, 28.6383, 'ENTITY3', 'T'],
67
+ ['2013-05-29', 'S', 15_900.00, 6685.95, 24.5802, 'ENTITY3', 'T'],
68
+ ['2013-05-30', 'S', 6_679.00, 2808.52, 25.0471, 'ENTITY3', 'T'],
69
+ ['2013-05-23', 'S', 23_054.00, 9694.21, 26.8015, 'ENTITY3', 'F']]
70
+
71
+ # Build the Table and then perform chained operations on it
72
+
73
+ table = FatTable.from_aoa(data) \
74
+ .where('shares > 2000') \
75
+ .order_by(:date, :code) \
76
+ .select(:date, :code, :shares,
77
+ :price, :ok, ref: '@row') \
78
+ .select(:ref, :date, :code,
79
+ :shares, :price, :ok)
80
+
81
+ # Convert the table to an ASCII text string
82
+
83
+ table.to_text do |fmt|
84
+ # Add some table footers
85
+ fmt.avg_footer(:price, :shares)
86
+ fmt.sum_footer(:shares)
87
+ # Add a group footer
88
+ fmt.gfooter('Avg', shares: :avg, price: :avg)
89
+ # Formats for all locations
90
+ fmt.format(ref: 'CB', numeric: 'R', boolean: 'CY')
91
+ # Formats for different "locations" in the table
92
+ fmt.format_for(:header, string: 'CB')
93
+ fmt.format_for(:body, code: 'C', shares: ',0.1', price: '0.4', )
94
+ fmt.format_for(:bfirst, price: '$0.4', )
95
+ fmt.format_for(:footer, shares: 'B,0.1', price: '$B0.4', )
96
+ fmt.format_for(:gfooter, shares: 'B,0.1', price: 'B0.4', )
97
+ end
98
+
99
+ +=========+============+======+=============+==========+====+
100
+ | Ref | Date | Code | Shares | Price | Ok |
101
+ +---------+------------+------+-------------+----------+----+
102
+ | 1 | 2013-05-02 | P | 118,186.4 | $11.8500 | Y |
103
+ | 2 | 2013-05-02 | P | 795,546.2 | 1.1850 | Y |
104
+ +---------+------------+------+-------------+----------+----+
105
+ | Avg | | | 456,866.3 | 6.5175 | |
106
+ +---------+------------+------+-------------+----------+----+
107
+ | 3 | 2013-05-20 | S | 5,046.0 | 28.2804 | N |
108
+ | 4 | 2013-05-20 | S | 35,742.5 | 28.3224 | Y |
109
+ | 5 | 2013-05-20 | S | 14,003.5 | 28.6383 | Y |
110
+ +---------+------------+------+-------------+----------+----+
111
+ | Avg | | | 18,264.0 | 28.4137 | |
112
+ +---------+------------+------+-------------+----------+----+
113
+ | 6 | 2013-05-23 | S | 3,364.0 | 27.1083 | Y |
114
+ | 7 | 2013-05-23 | S | 16,780.5 | 25.1749 | Y |
115
+ | 8 | 2013-05-23 | S | 9,694.2 | 26.8015 | N |
116
+ +---------+------------+------+-------------+----------+----+
117
+ | Avg | | | 9,946.2 | 26.3616 | |
118
+ +---------+------------+------+-------------+----------+----+
119
+ | 9 | 2013-05-29 | S | 6,601.9 | 24.7790 | N |
120
+ | 10 | 2013-05-29 | S | 5,659.5 | 24.7464 | Y |
121
+ | 11 | 2013-05-29 | S | 6,686.0 | 24.5802 | Y |
122
+ +---------+------------+------+-------------+----------+----+
123
+ | Avg | | | 6,315.8 | 24.7019 | |
124
+ +---------+------------+------+-------------+----------+----+
125
+ | 12 | 2013-05-30 | S | 2,808.5 | 25.0471 | Y |
126
+ +---------+------------+------+-------------+----------+----+
127
+ | Avg | | | 2,808.5 | 25.0471 | |
128
+ +---------+------------+------+-------------+----------+----+
129
+ | Average | | | 85,009.9 | $23.0428 | |
130
+ +---------+------------+------+-------------+----------+----+
131
+ | Total | | | 1,020,119.1 | | |
132
+ +=========+============+======+=============+==========+====+
133
+
134
+ == A Word About the Examples
135
+
136
+ When you install the fat_table gem, you have access to a program +ft_console+
137
+ which opens a +pry+ session with +fat_table+ loaded and the tables used in the
138
+ examples in this README defined as local variables so you can experiment with
139
+ them.
140
+
141
+ The examples in this +README+ file are executed as code blocks within the
142
+ +README.org+ file, so they typically end with a call to +.to_aoa+. That causes
143
+ the table to be inserted into the file and formatted as a table. With
144
+ +ft_console+, you should instead display your tables with +.to_text+ or
145
+ +.to_term+. These will return a string that you can print to the terminal with
146
+ +puts+.
147
+
148
+ To read in the table used in the Quick Start section above, you might do the
149
+ following:
150
+
151
+ $ ft_console
152
+ From: /home/ded/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/fat_table-0.2.1/bin/ft_console @ line 115 :
153
+
154
+ 110: [15, '2013-05-29', 'S', 15_900.00, 6685.95, 24.5802, 'YLEAC', 'T'],
155
+ 111: [16, '2013-05-30', 'S', 6_679.00, 2808.52, 25.0471, 'YLEAC', 'T']]
156
+ 112: tt = FatTable.from_aoa(AOA)
157
+ 113:
158
+ 114: binding.pry
159
+ => 115: instr <<-EOS
160
+ 116: FatTable console sets up some sample tables you can play with (see ls)
161
+ 117:
162
+ 118: For example, try 'puts tab1.to_term'
163
+ 119: EOS
164
+
165
+ [1] pry(main)> table = FatTable.from_aoa(data)
166
+ => #<FatTable::Table:0x0055b40e6cd870
167
+ @boundaries=[],
168
+ @columns=
169
+ [#<FatTable::Column:0x0055b40e6cc948
170
+ @header=:date,
171
+ @items=
172
+ [Wed, 29 May 2013,
173
+ Thu, 02 May 2013,
174
+ Mon, 20 May 2013,
175
+ Thu, 23 May 2013,
176
+ Thu, 23 May 2013,
177
+ Mon, 20 May 2013,
178
+ Thu, 02 May 2013,
179
+ Wed, 29 May 2013,
180
+ Mon, 20 May 2013,
181
+ ...
182
+ @items=["ENTITY3", "ENTITY1", "ENTITY3", "ENTITY3", "ENTITY3", "ENTITY3", "ENTITY1", "ENTITY3", "ENTITY3", "ENTITY3", "ENTITY3", "ENTITY3"],
183
+ @raw_header=:info,
184
+ @type="String">,
185
+ #<FatTable::Column:0x0055b40e6d2668 @header=:ok, @items=[false, true, false, true, true, true, true, true, true, true, true, false], @raw_header=:ok, @type="Boolean">]>
186
+ [2] pry(main)> puts table.to_text
187
+ +============+======+==========+==========+=========+=========+====+
188
+ | Date | Code | Raw | Shares | Price | Info | Ok |
189
+ +------------+------+----------+----------+---------+---------+----+
190
+ | 2013-05-29 | S | 15700.0 | 6601.85 | 24.779 | ENTITY3 | F |
191
+ | 2013-05-02 | P | 118186.4 | 118186.4 | 11.85 | ENTITY1 | T |
192
+ | 2013-05-20 | S | 12000.0 | 5046.0 | 28.2804 | ENTITY3 | F |
193
+ | 2013-05-23 | S | 8000.0 | 3364.0 | 27.1083 | ENTITY3 | T |
194
+ | 2013-05-23 | S | 39906.0 | 16780.47 | 25.1749 | ENTITY3 | T |
195
+ | 2013-05-20 | S | 85000.0 | 35742.5 | 28.3224 | ENTITY3 | T |
196
+ | 2013-05-02 | P | 795546.2 | 795546.2 | 1.185 | ENTITY1 | T |
197
+ | 2013-05-29 | S | 13459.0 | 5659.51 | 24.7464 | ENTITY3 | T |
198
+ | 2013-05-20 | S | 33302.0 | 14003.49 | 28.6383 | ENTITY3 | T |
199
+ | 2013-05-29 | S | 15900.0 | 6685.95 | 24.5802 | ENTITY3 | T |
200
+ | 2013-05-30 | S | 6679.0 | 2808.52 | 25.0471 | ENTITY3 | T |
201
+ | 2013-05-23 | S | 23054.0 | 9694.21 | 26.8015 | ENTITY3 | F |
202
+ +============+======+==========+==========+=========+=========+====+
203
+ => nil
204
+ [3] pry(main)>
205
+
206
+ And if you use +.to_term+, you can see the effect of the color formatting
207
+ directives.
208
+
209
+ == Anatomy of a Table
210
+
211
+ === Columns
212
+
213
+ +FatTable::Table+ objects consist of an array of +FatTable::Column+ objects.
214
+ Each +Column+ has a header, a type, and an array of items, all of the given type
215
+ or nil. There are only five permissible types for a +Column+:
216
+
217
+ 1. Boolean (for holding ruby +TrueClass+ and +FalseClass+ objects),
218
+ 2. DateTime (for holding ruby +DateTime+ or +Date+ objects),
219
+ 3. Numeric (for holding ruby +Integer+, +Rational+, or +BigDecimal+ objects),
220
+ 4. String (for ruby String objects), or
221
+ 5. NilClass (for the undetermined column type).
222
+
223
+ When a +Table+ is constructed from an external source, all +Columns+ start out
224
+ having a type of +NilClass+, that is, their type is as yet undetermined. When a
225
+ string or object of one of the four determined types is added to a +Column+, it
226
+ fixes the type of the column and all further items added to the +Column+ must
227
+ either be nil (indicating no value) or be capable of being coerced to the
228
+ column's type. Otherwise, +FatTable+ raises an exception.
229
+
230
+ Items of input must be either one of the permissible ruby objects or strings. If
231
+ they are strings, +FatTable+ attempts to parse them as one of the permissible
232
+ types as follows:
233
+
234
+ Boolean:: the strings, 't', 'true', 'yes', or 'y', regardless of case, are
235
+ interpreted as +TrueClass+ and the strings, 'f', 'false', 'no', or
236
+ 'n', regardless of case, are interpreted as +FalseClass+, in either
237
+ case resulting in a Boolean column. Empty strings in a column already
238
+ having a Boolean type are converted to nil.
239
+
240
+ DateTime:: strings that contain patterns of 'yyyy-mm-dd' or 'yyyy/mm/dd' will be
241
+ interpreted as a +DateTime+ or a +Date+ (if there are no sub-day time
242
+ components present). The number of digits in the month and day can be
243
+ one or two, but the year component must be four digits. Any time
244
+ components are valid if they can be properly interpreted by
245
+ +DateTime.parse+. Org mode timestamps, active or inactive, are valid
246
+ input strings for DateTime columns. Empty strings in a column already
247
+ having the DateTime type are converted to nil.
248
+
249
+ Numeric:: all commas ',', underscores, '_', and '$' dollar signs are removed
250
+ from the string and if the remaining string can be interpreted as a
251
+ +Numeric+, it will be. It is interpreted as an +Integer+ if there are
252
+ no decimal places in the remaining string, as a +Rational+ if the
253
+ string has the form '<number>:<number>' or '<number>/<number>', or as
254
+ a +BigDecimal+ if there is a decimal point in the remaining string.
255
+ Empty strings in a column already having the Numeric type are
256
+ converted to nil.
257
+
258
+ String:: if all else fails, +FatTable+ applies +#to_s+ to the input value and,
259
+ treats it as an item of type +String+. Empty strings in a column
260
+ already having the String type are kept as empty strings.
261
+
262
+ NilClass:: until the input contains a non-blank string that can be parsed as one
263
+ of the other types, it has this type, meaning that the type is still
264
+ open. A column comprised completely of blank strings or nils will
265
+ retain the +NilClass+ type.
266
+
267
+ === Headers
268
+
269
+ Headers for the columns are formed from the input. No two columns in a table can
270
+ have the same header. Headers in the input are converted to symbols by
271
+
272
+ - converting the header to a string with +#to_s+,
273
+ - converting any run of blanks to an underscore '_',
274
+ - removing any characters that are not letters, numbers, or underscores, and
275
+ - lowercasing all remaining letters
276
+
277
+ Thus, a header of 'Date' becomes +:date+, a header of 'Id Number' becomes,
278
+ +:id_number+, etc. When referring to a column in code, you must use the symbol
279
+ form of the header.
280
+
281
+ If no sensible headers can be discerned from the input, headers of the form
282
+ :col_1, :col_2, etc., are synthesized.
283
+
284
+ === Groups
285
+
286
+ The rows of a +FatTable+ table can be sub-divided into groups, either from
287
+ markers in the input or as a result of certain operations. There is only one
288
+ level of grouping, so +FatTable+ has no concept of sub-groups. Groups can be
289
+ shown on output with rules or 'hlines' that underline the last row in each
290
+ group, and you can decorate the output with group footers that summarize the
291
+ columns in each group.
292
+
293
+ == Constructing Tables
294
+
295
+ === Empty Tables
296
+
297
+ You can create an empty table with +FatTable.new+, and then add rows with the
298
+ +<<+ operator and a Hash:
299
+
300
+ tab = FatTable.new
301
+ tab << { a: 1, b: 2, c: '<2017-01-21>', d: 'f', e: '' }
302
+ tab << { a: 3.14, b: 2.17, c: '[2016-01-21 Thu]', d: 'Y', e: nil }
303
+ tab.to_aoa
304
+
305
+ After this, the table will have column headers +:a+, +:b+, +:c+, +:d+, and +:e+.
306
+ Column, +:a+ and +:b+ will have type Numeric, column +:c+ will have type
307
+ +DateTime+, and column +:d+ will have type +Boolean+. Column +:e+ will still
308
+ have an open type. Notice that dates in the input can be wrapped in brackets as
309
+ in org-mode time stamps.
310
+
311
+ === From CSV or Org Mode files or strings
312
+
313
+ Tables can also be read from +.csv+ files or files containing +org-mode+ tables.
314
+ In the case of org-mode files, +FatTable+ skips through the file until it finds
315
+ a line that look like a table, that is it begins with any number of spaces
316
+ followed by +|-+. Only the first table in an +.org+ file is read.
317
+
318
+ For both +.csv+ and +.org+ files, the first row in the tables is taken as the
319
+ header row, and the headers are converted to symbols as described above.
320
+
321
+ tab1 = FatTable.from_csv_file('~/data.csv')
322
+ tab2 = FatTable.from_org_file('~/project.org')
323
+
324
+ csv_body = <<-EOS
325
+ Ref,Date,Code,RawShares,Shares,Price,Info
326
+ 1,2006-05-02,P,5000,5000,8.6000,2006-08-09-1-I
327
+ 2,2006-05-03,P,5000,5000,8.4200,2006-08-09-1-I
328
+ 3,2006-05-04,P,5000,5000,8.4000,2006-08-09-1-I
329
+ 4,2006-05-10,P,8600,8600,8.0200,2006-08-09-1-D
330
+ 5,2006-05-12,P,10000,10000,7.2500,2006-08-09-1-D
331
+ 6,2006-05-12,P,2000,2000,6.7400,2006-08-09-1-I
332
+ EOS
333
+
334
+ tab3 = FatTable.from_csv_string(csv_body)
335
+
336
+ org_body = <<-EOS
337
+ * Smith Transactions
338
+ :PROPERTIES:
339
+ :TABLE_EXPORT_FILE: smith.csv
340
+ :END:
341
+
342
+ #+TBLNAME: smith_tab
343
+ | Ref | Date | Code | Raw | Shares | Price | Info |
344
+ |-----+------------+------+---------+--------+----------+---------|
345
+ | 29 | 2013-05-02 | P | 795,546 | 2,609 | 1.18500 | ENTITY1 |
346
+ | 30 | 2013-05-02 | P | 118,186 | 388 | 11.85000 | ENTITY1 |
347
+ | 31 | 2013-05-02 | P | 340,948 | 1,926 | 1.18500 | ENTITY2 |
348
+ | 32 | 2013-05-02 | P | 50,651 | 286 | 11.85000 | ENTITY2 |
349
+ | 33 | 2013-05-20 | S | 12,000 | 32 | 28.28040 | ENTITY3 |
350
+ | 34 | 2013-05-20 | S | 85,000 | 226 | 28.32240 | ENTITY3 |
351
+ | 35 | 2013-05-20 | S | 33,302 | 88 | 28.63830 | ENTITY3 |
352
+ | 36 | 2013-05-23 | S | 8,000 | 21 | 27.10830 | ENTITY3 |
353
+ | 37 | 2013-05-23 | S | 23,054 | 61 | 26.80150 | ENTITY3 |
354
+ | 38 | 2013-05-23 | S | 39,906 | 106 | 25.17490 | ENTITY3 |
355
+ | 39 | 2013-05-29 | S | 13,459 | 36 | 24.74640 | ENTITY3 |
356
+ | 40 | 2013-05-29 | S | 15,700 | 42 | 24.77900 | ENTITY3 |
357
+ | 41 | 2013-05-29 | S | 15,900 | 42 | 24.58020 | ENTITY3 |
358
+ | 42 | 2013-05-30 | S | 6,679 | 18 | 25.04710 | ENTITY3 |
359
+
360
+ * Another Heading
361
+ EOS
362
+
363
+ tab4 = FatTable.from_org_string(org_body)
364
+
365
+ === From Arrays of Arrays
366
+
367
+ You can also initialize a table directly from ruby data structures. You can, for
368
+ example, build a table from an array of arrays:
369
+
370
+ aoa =
371
+ [['Ref', 'Date', 'Code', 'Raw', 'Shares', 'Price', 'Info', 'Bool'],
372
+ [1, '2013-05-02', 'P', 795_546.20, 795_546.2, 1.1850, 'ENTITY1', 'T'],
373
+ [2, '2013-05-02', 'P', 118_186.40, 118_186.4, 11.8500, 'ENTITY1', 'T'],
374
+ [7, '2013-05-20', 'S', 12_000.00, 5046.00, 28.2804, 'ENTITY3', 'F'],
375
+ [8, '2013-05-20', 'S', 85_000.00, 35_742.50, 28.3224, 'ENTITY3', 'T'],
376
+ [9, '2013-05-20', 'S', 33_302.00, 14_003.49, 28.6383, 'ENTITY3', 'T'],
377
+ [10, '2013-05-23', 'S', 8000.00, 3364.00, 27.1083, 'ENTITY3', 'T'],
378
+ [11, '2013-05-23', 'S', 23_054.00, 9694.21, 26.8015, 'ENTITY3', 'F'],
379
+ [12, '2013-05-23', 'S', 39_906.00, 16_780.47, 25.1749, 'ENTITY3', 'T'],
380
+ [13, '2013-05-29', 'S', 13_459.00, 5659.51, 24.7464, 'ENTITY3', 'T'],
381
+ [14, '2013-05-29', 'S', 15_700.00, 6601.85, 24.7790, 'ENTITY3', 'F'],
382
+ [15, '2013-05-29', 'S', 15_900.00, 6685.95, 24.5802, 'ENTITY3', 'T'],
383
+ [16, '2013-05-30', 'S', 6_679.00, 2808.52, 25.0471, 'ENTITY3', 'T']]
384
+ tab = FatTable.from_aoa(aoa)
385
+
386
+ Notice that the values can either be ruby objects, such as the Integer +85_000+,
387
+ or strings that can be parsed into one of the permissible column types.
388
+
389
+ This method of building a table, +.from_aoa+, is particularly useful in dealing
390
+ with Emacs org-mode code blocks. Tables in org-mode are passed to code blocks as
391
+ arrays of arrays. Likewise, a result of a code block in the form of an array of
392
+ arrays is displayed as an org-mode table:
393
+
394
+ #+NAME: trades1
395
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | LP | QP | IPLP | IPQP |
396
+ |------+------------+------+--------+-----+------+--------+-------+--------+--------+--------|
397
+ | T001 | 2016-11-01 | P | 7.7000 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
398
+ | T002 | 2016-11-01 | P | 7.7500 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
399
+ | T003 | 2016-11-01 | P | 7.5000 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
400
+ | T004 | 2016-11-01 | S | 7.5500 | T | F | 6811 | 966 | 5845 | 0.2453 | 0.1924 |
401
+ | T005 | 2016-11-01 | S | 7.5000 | F | F | 4000 | 572 | 3428 | 0.2453 | 0.1924 |
402
+ | T006 | 2016-11-01 | S | 7.6000 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
403
+ | T007 | 2016-11-01 | S | 7.6500 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
404
+ | T008 | 2016-11-01 | P | 7.6500 | F | F | 2771 | 393 | 2378 | 0.2453 | 0.1924 |
405
+ | T009 | 2016-11-01 | P | 7.6000 | F | F | 9550 | 1363 | 8187 | 0.2453 | 0.1924 |
406
+ | T010 | 2016-11-01 | P | 7.5500 | F | T | 3175 | 451 | 2724 | 0.2453 | 0.1924 |
407
+ | T011 | 2016-11-02 | P | 7.4250 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
408
+ | T012 | 2016-11-02 | P | 7.5500 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
409
+ | T013 | 2016-11-02 | P | 7.3500 | T | T | 53100 | 7656 | 45444 | 0.2453 | 0.1924 |
410
+ | T014 | 2016-11-02 | P | 7.4500 | F | T | 5847 | 835 | 5012 | 0.2453 | 0.1924 |
411
+ | T015 | 2016-11-02 | P | 7.7500 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
412
+ | T016 | 2016-11-02 | P | 8.2500 | T | T | 100 | 14 | 86 | 0.2453 | 0.1924 |
413
+
414
+ #+HEADER: :colnames no
415
+ :#+BEGIN_SRC ruby :var tab=trades1
416
+ require 'fat_table'
417
+ tab = FatTable.from_aoa(tab).where('shares > 500')
418
+ tab.to_aoa
419
+ :#+END_SRC
420
+
421
+ #+RESULTS:
422
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
423
+ |------+------------+------+-------+-----+------+--------+------+-------+--------+--------|
424
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
425
+ | T004 | 2016-11-01 | S | 7.55 | T | F | 6811 | 966 | 5845 | 0.2453 | 0.1924 |
426
+ | T005 | 2016-11-01 | S | 7.5 | F | F | 4000 | 572 | 3428 | 0.2453 | 0.1924 |
427
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
428
+ | T008 | 2016-11-01 | P | 7.65 | F | F | 2771 | 393 | 2378 | 0.2453 | 0.1924 |
429
+ | T009 | 2016-11-01 | P | 7.6 | F | F | 9550 | 1363 | 8187 | 0.2453 | 0.1924 |
430
+ | T010 | 2016-11-01 | P | 7.55 | F | T | 3175 | 451 | 2724 | 0.2453 | 0.1924 |
431
+ | T012 | 2016-11-02 | P | 7.55 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
432
+ | T013 | 2016-11-02 | P | 7.35 | T | T | 53100 | 7656 | 45444 | 0.2453 | 0.1924 |
433
+ | T014 | 2016-11-02 | P | 7.45 | F | T | 5847 | 835 | 5012 | 0.2453 | 0.1924 |
434
+
435
+ This example illustrates several things:
436
+
437
+ 1. The named org-mode table, 'trades1', can be passed into a ruby code block
438
+ using the +:var tab=trades1+ header argument to the code block; that makes
439
+ the variable +tab+ available to the code block as an array of arrays, which
440
+ +FatTable+ then uses to initialize the table.
441
+ 2. The code block requires that you set +:colnames no+ in the header arguments.
442
+ This suppresses org-mode's own processing of the header line so that
443
+ +FatTable+ can see the headers. Failure to do this will cause an error.
444
+ 3. The table is subjected to some processing, in this case selecting those rows
445
+ where the number of shares is greater than 500. More on that later.
446
+ 4. +FatTable+ passes back to org-mode an array of arrays using the +.to_aoa+
447
+ method. In an +org-mode+ buffer, these are rendered as tables. We'll often
448
+ apply +.to_aoa+ at the end of example blocks to render the results inside
449
+ this README.org file. As we'll see below, this method can also take a block
450
+ to which formatting directives and footers can be attached.
451
+
452
+ === From Arrays of Hashes
453
+
454
+ A second ruby data structure that can be used to initialize a +FatTable+ table
455
+ is an array of ruby Hashes. Each hash represents a row of the table, and the
456
+ headers of the table are take from the keys of the hashes. Accordingly, all the
457
+ hashes should have the same keys. This same method can in fact take an array of
458
+ any objects that can be converted to a Hash with the +#to_h+ method, so you can
459
+ use an array of your own objects to initialize a table, provided that you define
460
+ a suitable +#to_h+ method for the objects' class.
461
+
462
+ aoh = [
463
+ { ref: 'T001', date: '2016-11-01', code: 'P', price: '7.7000', shares: 100 },
464
+ { ref: 'T002', date: '2016-11-01', code: 'P', price: 7.7500, shares: 200 },
465
+ { ref: 'T003', date: '2016-11-01', code: 'P', price: 7.5000, shares: 800 },
466
+ { ref: 'T004', date: '2016-11-01', code: 'S', price: 7.5500, shares: 6811 },
467
+ { ref: 'T005', date: Date.today, code: 'S', price: 7.5000, shares: 4000 },
468
+ { ref: 'T006', date: '2016-11-01', code: 'S', price: 7.6000, shares: 1000 },
469
+ { ref: 'T007', date: '2016-11-01', code: 'S', price: 7.6500, shares: 200 },
470
+ { ref: 'T008', date: '2016-11-01', code: 'P', price: 7.6500, shares: 2771 },
471
+ { ref: 'T009', date: '2016-11-01', code: 'P', price: 7.6000, shares: 9550 },
472
+ { ref: 'T010', date: '2016-11-01', code: 'P', price: 7.5500, shares: 3175 },
473
+ { ref: 'T011', date: '2016-11-02', code: 'P', price: 7.4250, shares: 100 },
474
+ { ref: 'T012', date: '2016-11-02', code: 'P', price: 7.5500, shares: 4700 },
475
+ { ref: 'T013', date: '2016-11-02', code: 'P', price: 7.3500, shares: 53100 },
476
+ { ref: 'T014', date: '2016-11-02', code: 'P', price: 7.4500, shares: 5847 },
477
+ { ref: 'T015', date: '2016-11-02', code: 'P', price: 7.7500, shares: 500 },
478
+ { ref: 'T016', date: '2016-11-02', code: 'P', price: 8.2500, shares: 100 }
479
+ ]
480
+ tab = FatTable.from_aoh(aoh)
481
+
482
+ Notice, again, that the values can either be ruby objects, such as +Date.today+,
483
+ or strings that can parsed into one of the permissible column types.
484
+
485
+ === From SQL queries
486
+
487
+ Another way to initialize a +FatTable+ table is with the results of a SQL query.
488
+ +FatTable+ uses the +dbi+ gem to query databases. You must first set the
489
+ database parameters to be used for the queries.
490
+
491
+ require 'fat_table'
492
+ FatTable.set_db(driver: 'Pg',
493
+ database: 'XXX_development',
494
+ user: 'dtd',
495
+ password: 'slflpowert',
496
+ host: 'localhost',
497
+ socket: '/tmp/.s.PGSQL.5432')
498
+ tab = FatTable.from_sql('select * from trades;')
499
+
500
+ Some of the parameters to the +.set_db+ function have defaults. The driver
501
+ defaults to 'Pg' for postgresql and the socket defaults to +/tmp/.s.PGSQL.5432+
502
+ if the host is 'localhost', which it is by default. If the host is not
503
+ 'localhost', the dsn uses a port rather than a socket and defaults to port
504
+ '5432'. While user and password default to nil, the database parameter is
505
+ required.
506
+
507
+ The +.set_db+ function need only be called once, and the database handle it
508
+ creates will be used for all subsequent +.from_sql+ calls until +.set_db+ is
509
+ called again.
510
+
511
+ === Marking Groups in Input
512
+
513
+ The +.from_aoa+ and +.from_aoh+ functions take an optional keyword parameter
514
+ +hlines:+ that, if set to +true+, causes them to mark group boundaries in the
515
+ table wherever a row Array (for +.from_aoa+) or Hash (for +.from_aoh+) is
516
+ followed by a +nil+. Each boundary means that the rows above it and after the
517
+ header or prior group boundary all belong to a group. By default +hlines+ is
518
+ false for both functions so neither expects hlines in its input.
519
+
520
+ In the case of +.from_aoa+, if +hlines:+ is set true, the input must also
521
+ include a +nil+ in the second element of the outer array to indicate that the
522
+ first row is to be used as headers. Otherwise, it will synthesize headers of
523
+ the form +:col_1+, +:col_2+, ... +:col_n+.
524
+
525
+ In org mode table text passed to +.from_org_file+ and +.from_org_string+, you
526
+ /must/ mark the header row by following it with an hrule and you /may/ mark
527
+ group boundaries with an hrule. In org mode tables, hlines are table rows
528
+ beginning with something like '+|---+'. The +.from_org_...+ functions always
529
+ recognizes hlines in the input, so it takes no +hlines:+ keyword parameter.
530
+
531
+ == Accessing Parts of Tables
532
+
533
+ === Rows
534
+
535
+ A +FatTable+ table is an Enumerable, yielding each row of the table as a Hash
536
+ keyed on the header symbols. The method +Table#rows+ returns an Array of the rows as
537
+ Hashes as well.
538
+
539
+ You can also use indexing to access a row of the table by number. Using an
540
+ integer index returns a Hash of the given row. Thus, +tab[20]+ returns the 21st
541
+ data row of the table, while +tab[0]+ returns the first row and tab[-1] returns
542
+ the last row.
543
+
544
+ === Columns
545
+
546
+ If the index provided to +[]+ is a string or a symbol, it returns an Array of
547
+ the items of the column with that header. Thus, +tab[:ref]+ returns an Array of
548
+ all the items of the table's +:ref+ column.
549
+
550
+ === Cells
551
+
552
+ The two forms of indexing can be combined to access individual cells of the
553
+ table:
554
+
555
+ tab[13] # => Hash of the 14th row
556
+ tab[:date] # => Array of all Dates in the :date column
557
+ tab[13][:date] # => The Date in the 14th row
558
+ tab[:date][13] # => The Date in the 14th row; indexes can be in either order.
559
+
560
+ === Other table attributes
561
+
562
+ tab.headers # => an Array of the headers in symbol form
563
+ tab.types # => a Hash mapping headers to column types
564
+ tab.size # => the number of rows in the table
565
+ tab.width # => the number of columns in the table
566
+ tab.empty? # => is the table empty?
567
+ tab.column?(head) # => does the table have a column with the given header?
568
+ tab.groups # => return an Array of the table's groups as Arrays of row Hashes.
569
+
570
+ == Operations on Tables
571
+
572
+ Once you have one or more tables, you will likely want to perform operations on
573
+ them. The operations provided by +FatTable+ are the subject of this section.
574
+ Before getting into the operations, though, there are a couple of issues that
575
+ cut across all or many of the operations.
576
+
577
+ First, tables are by and large immutable objects. Each operation creates a new
578
+ table without affecting the input tables. The only exception is the +degroup!+
579
+ operation, which mutated the receiver table by removing its group boundaries.
580
+
581
+ Second, because each operation returns a +FatTable::Table+ object, the
582
+ operations are chainable.
583
+
584
+ Third, +FatTable::Table+ objects can have "groups" of rows within the table.
585
+ These can be decorated with hlines and group footers on output. Some of these
586
+ operations result in marking group boundaries in the result table, others remove
587
+ group boundaries that may have existed in the input table. Operations that
588
+ either create or remove groups will be noted below.
589
+
590
+ Finally, the operations are for the most part patterned on SQL table operations,
591
+ but when expressions play a role, you write them using ruby syntax rather than
592
+ SQL.
593
+
594
+ === Example Input Table
595
+
596
+ For illustration purposes assume that the following tables are read into ruby
597
+ variables called '+tab1+' and '+tab2+. We have given the table groups, marked by
598
+ the hlines below, and some duplicate rows to illustrate the effect of certain
599
+ operations on groups and duplicates.
600
+
601
+ require 'fat_table'
602
+
603
+ tab1_str = <<-EOS
604
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | LP | QP | IPLP | IPQP |
605
+ |------+------------------+------+--------+-----+------+--------+------+-------+--------+--------|
606
+ | T001 | [2016-11-01 Tue] | P | 7.7000 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
607
+ | T002 | [2016-11-01 Tue] | P | 7.7500 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
608
+ | T003 | [2016-11-01 Tue] | P | 7.5000 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
609
+ | T003 | [2016-11-01 Tue] | P | 7.5000 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
610
+ |------+------------------+------+--------+-----+------+--------+------+-------+--------+--------|
611
+ | T004 | [2016-11-01 Tue] | S | 7.5500 | T | F | 6811 | 966 | 5845 | 0.2453 | 0.1924 |
612
+ | T005 | [2016-11-01 Tue] | S | 7.5000 | F | F | 4000 | 572 | 3428 | 0.2453 | 0.1924 |
613
+ | T006 | [2016-11-01 Tue] | S | 7.6000 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
614
+ | T006 | [2016-11-01 Tue] | S | 7.6000 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
615
+ | T007 | [2016-11-01 Tue] | S | 7.6500 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
616
+ | T008 | [2016-11-01 Tue] | P | 7.6500 | F | F | 2771 | 393 | 2378 | 0.2453 | 0.1924 |
617
+ | T009 | [2016-11-01 Tue] | P | 7.6000 | F | F | 9550 | 1363 | 8187 | 0.2453 | 0.1924 |
618
+ |------+------------------+------+--------+-----+------+--------+------+-------+--------+--------|
619
+ | T010 | [2016-11-01 Tue] | P | 7.5500 | F | T | 3175 | 451 | 2724 | 0.2453 | 0.1924 |
620
+ | T011 | [2016-11-02 Wed] | P | 7.4250 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
621
+ | T012 | [2016-11-02 Wed] | P | 7.5500 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
622
+ | T012 | [2016-11-02 Wed] | P | 7.5500 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
623
+ | T013 | [2016-11-02 Wed] | P | 7.3500 | T | T | 53100 | 7656 | 45444 | 0.2453 | 0.1924 |
624
+ |------+------------------+------+--------+-----+------+--------+------+-------+--------+--------|
625
+ | T014 | [2016-11-02 Wed] | P | 7.4500 | F | T | 5847 | 835 | 5012 | 0.2453 | 0.1924 |
626
+ | T015 | [2016-11-02 Wed] | P | 7.7500 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
627
+ | T016 | [2016-11-02 Wed] | P | 8.2500 | T | T | 100 | 14 | 86 | 0.2453 | 0.1924 |
628
+ EOS
629
+
630
+ tab2_str = <<-EOS
631
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | LP | QP | IPLP | IPQP |
632
+ |------+------------------+------+--------+-----+------+--------+-------+------+--------+--------|
633
+ | T003 | [2016-11-01 Tue] | P | 7.5000 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
634
+ | T003 | [2016-11-01 Tue] | P | 7.5000 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
635
+ | T017 | [2016-11-01 Tue] | P | 8.3 | F | T | 1801 | 1201 | 600 | 0.2453 | 0.1924 |
636
+ |------+------------------+------+--------+-----+------+--------+-------+------+--------+--------|
637
+ | T018 | [2016-11-01 Tue] | S | 7.152 | T | F | 2516 | 2400 | 116 | 0.2453 | 0.1924 |
638
+ | T018 | [2016-11-01 Tue] | S | 7.152 | T | F | 2516 | 2400 | 116 | 0.2453 | 0.1924 |
639
+ | T006 | [2016-11-01 Tue] | S | 7.6000 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
640
+ | T007 | [2016-11-01 Tue] | S | 7.6500 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
641
+ |------+------------------+------+--------+-----+------+--------+-------+------+--------+--------|
642
+ | T014 | [2016-11-02 Wed] | P | 7.4500 | F | T | 5847 | 835 | 5012 | 0.2453 | 0.1924 |
643
+ | T015 | [2016-11-02 Wed] | P | 7.7500 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
644
+ | T015 | [2016-11-02 Wed] | P | 7.7500 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
645
+ | T016 | [2016-11-02 Wed] | P | 8.2500 | T | T | 100 | 14 | 86 | 0.2453 | 0.1924 |
646
+ |------+------------------+------+--------+-----+------+--------+-------+------+--------+--------|
647
+ | T019 | [2017-01-15 Sun] | S | 8.75 | T | F | 300 | 175 | 125 | 0.2453 | 0.1924 |
648
+ | T020 | [2017-01-19 Thu] | S | 8.25 | F | T | 700 | 615 | 85 | 0.2453 | 0.1924 |
649
+ | T021 | [2017-01-23 Mon] | P | 7.16 | T | T | 12100 | 11050 | 1050 | 0.2453 | 0.1924 |
650
+ | T021 | [2017-01-23 Mon] | P | 7.16 | T | T | 12100 | 11050 | 1050 | 0.2453 | 0.1924 |
651
+ EOS
652
+
653
+ tab1 = FatTable.from_org_string(tab1_str)
654
+ tab2 = FatTable.from_org_string(tab2_str)
655
+
656
+ === Select
657
+
658
+ With the +select+ method, you can select which existing columns should appear in
659
+ the output table and create new columns in the output table that are a function
660
+ of existing and new columns.
661
+
662
+ Here we select three existing columns by simply passing header symbols in the
663
+ order we want them to appear in the output. Thus, one use of =select= is to
664
+ filter and permute the order of existing columns. The =select= method preserves
665
+ any group boundaries present in the input table.
666
+
667
+ tab1.select(:price, :ref, :shares).to_aoa
668
+
669
+ | Price | Ref | Shares |
670
+ |-------+------+--------|
671
+ | 7.7 | T001 | 100 |
672
+ | 7.75 | T002 | 200 |
673
+ | 7.5 | T003 | 800 |
674
+ | 7.5 | T003 | 800 |
675
+ |-------+------+--------|
676
+ | 7.55 | T004 | 6811 |
677
+ | 7.5 | T005 | 4000 |
678
+ | 7.6 | T006 | 1000 |
679
+ | 7.6 | T006 | 1000 |
680
+ | 7.65 | T007 | 200 |
681
+ | 7.65 | T008 | 2771 |
682
+ | 7.6 | T009 | 9550 |
683
+ |-------+------+--------|
684
+ | 7.55 | T010 | 3175 |
685
+ | 7.425 | T011 | 100 |
686
+ | 7.55 | T012 | 4700 |
687
+ | 7.55 | T012 | 4700 |
688
+ | 7.35 | T013 | 53100 |
689
+ |-------+------+--------|
690
+ | 7.45 | T014 | 5847 |
691
+ | 7.75 | T015 | 500 |
692
+ | 8.25 | T016 | 100 |
693
+
694
+ More interesting is that +select+ can take hash-like keyword arguments following
695
+ all of the symbol arguments to create new columns in the output as functions of
696
+ other columns. For each hash-like parameter, the keyword given must be a symbol,
697
+ which becomes the header for the new column, and the value must be either: (1) a
698
+ symbol representing an existing column or (2) a string representing a ruby
699
+ expression for the value of the new column.
700
+
701
+ Within the string expression, the names of existing or already-specified columns
702
+ are available as local variables, as well as the instance variables +@row+ and
703
+ +@group+. So for our example table, the string expressions for new columns have
704
+ access to local variables +ref+, +date+, +code+, +price+, +g10+, +qp10+,
705
+ +shares+, +lp+, +qp+, +iplp+, and +ipqp+ as well as the instance variables
706
+ +@row+ and +@group+. The local variables are set to the values of the cell in
707
+ their respective columns for each row in the input table and the instance
708
+ variables are set the number of the current row and group respectively.
709
+
710
+ For example, if we want to rename the :date column and compute the cost of
711
+ shares, we could do the following:
712
+
713
+ tab1.select(:ref, :price, :shares, traded_on: :date, cost: 'price * shares').to_aoa
714
+
715
+ | Ref | Price | Shares | Traded On | Cost |
716
+ |------+-------+--------+------------+----------|
717
+ | T001 | 7.7 | 100 | 2016-11-01 | 770.0 |
718
+ | T002 | 7.75 | 200 | 2016-11-01 | 1550.0 |
719
+ | T003 | 7.5 | 800 | 2016-11-01 | 6000.0 |
720
+ | T003 | 7.5 | 800 | 2016-11-01 | 6000.0 |
721
+ |------+-------+--------+------------+----------|
722
+ | T004 | 7.55 | 6811 | 2016-11-01 | 51423.05 |
723
+ | T005 | 7.5 | 4000 | 2016-11-01 | 30000.0 |
724
+ | T006 | 7.6 | 1000 | 2016-11-01 | 7600.0 |
725
+ | T006 | 7.6 | 1000 | 2016-11-01 | 7600.0 |
726
+ | T007 | 7.65 | 200 | 2016-11-01 | 1530.0 |
727
+ | T008 | 7.65 | 2771 | 2016-11-01 | 21198.15 |
728
+ | T009 | 7.6 | 9550 | 2016-11-01 | 72580.0 |
729
+ |------+-------+--------+------------+----------|
730
+ | T010 | 7.55 | 3175 | 2016-11-01 | 23971.25 |
731
+ | T011 | 7.425 | 100 | 2016-11-02 | 742.5 |
732
+ | T012 | 7.55 | 4700 | 2016-11-02 | 35485.0 |
733
+ | T012 | 7.55 | 4700 | 2016-11-02 | 35485.0 |
734
+ | T013 | 7.35 | 53100 | 2016-11-02 | 390285.0 |
735
+ |------+-------+--------+------------+----------|
736
+ | T014 | 7.45 | 5847 | 2016-11-02 | 43560.15 |
737
+ | T015 | 7.75 | 500 | 2016-11-02 | 3875.0 |
738
+ | T016 | 8.25 | 100 | 2016-11-02 | 825.0 |
739
+
740
+ The parameter '+traded_on: :date+' caused the +:date+ column of the input table
741
+ to be renamed '+:traded_on+, and the parameter +cost: 'price * shares'+ created
742
+ a new column, +:cost+, as the product of values in the +:price+ and +:shares+
743
+ columns.
744
+
745
+ The order of the columns in the result tables is the same as the order of the
746
+ parameters to the +select+ method. So, you can re-order the columns with a
747
+ second, chained call to +select+:
748
+
749
+ tab1.select(:ref, :price, :shares, traded_on: :date, cost: 'price * shares') \
750
+ .select(:ref, :traded_on, :price, :shares, :cost) \
751
+ .to_aoa
752
+
753
+ | Ref | Traded On | Price | Shares | Cost |
754
+ |------+------------+-------+--------+----------|
755
+ | T001 | 2016-11-01 | 7.7 | 100 | 770.0 |
756
+ | T002 | 2016-11-01 | 7.75 | 200 | 1550.0 |
757
+ | T003 | 2016-11-01 | 7.5 | 800 | 6000.0 |
758
+ | T003 | 2016-11-01 | 7.5 | 800 | 6000.0 |
759
+ |------+------------+-------+--------+----------|
760
+ | T004 | 2016-11-01 | 7.55 | 6811 | 51423.05 |
761
+ | T005 | 2016-11-01 | 7.5 | 4000 | 30000.0 |
762
+ | T006 | 2016-11-01 | 7.6 | 1000 | 7600.0 |
763
+ | T006 | 2016-11-01 | 7.6 | 1000 | 7600.0 |
764
+ | T007 | 2016-11-01 | 7.65 | 200 | 1530.0 |
765
+ | T008 | 2016-11-01 | 7.65 | 2771 | 21198.15 |
766
+ | T009 | 2016-11-01 | 7.6 | 9550 | 72580.0 |
767
+ |------+------------+-------+--------+----------|
768
+ | T010 | 2016-11-01 | 7.55 | 3175 | 23971.25 |
769
+ | T011 | 2016-11-02 | 7.425 | 100 | 742.5 |
770
+ | T012 | 2016-11-02 | 7.55 | 4700 | 35485.0 |
771
+ | T012 | 2016-11-02 | 7.55 | 4700 | 35485.0 |
772
+ | T013 | 2016-11-02 | 7.35 | 53100 | 390285.0 |
773
+ |------+------------+-------+--------+----------|
774
+ | T014 | 2016-11-02 | 7.45 | 5847 | 43560.15 |
775
+ | T015 | 2016-11-02 | 7.75 | 500 | 3875.0 |
776
+ | T016 | 2016-11-02 | 8.25 | 100 | 825.0 |
777
+
778
+ Notice that +select+ can take any number of arguments but all the symbol
779
+ arguments must come first followed by all the hash-like keyword arguments.
780
+
781
+ As the example illustrates, +.select+ transmits any group boundaries in its
782
+ input table to the result table.
783
+
784
+ === Where
785
+
786
+ You can filter the rows of the result table with the +.where+ method. It takes a
787
+ single string expression as an argument which is evaluated in a manner similar
788
+ to +.select+ in which the value of the cells in each column are available as
789
+ local variables and the instance variables +@row+ and +@group+ are available for
790
+ testing. The expression is evaluated for each row, and if the expression
791
+ evaluates to a truthy value, the row is included in the output, otherwise it is
792
+ not. The +.where+ method obliterates any group boundaries in the input, so the
793
+ output table has only a single group.
794
+
795
+ Here we select only those even-numbered rows where either of the two boolean
796
+ fields is true:
797
+
798
+ tab1.where('@row.even? && (g10 || qp10)') \
799
+ .to_aoa
800
+
801
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
802
+ |------+------------+------+-------+-----+------+--------+------+-------+--------+--------|
803
+ | T002 | 2016-11-01 | P | 7.75 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
804
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
805
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
806
+ | T010 | 2016-11-01 | P | 7.55 | F | T | 3175 | 451 | 2724 | 0.2453 | 0.1924 |
807
+ | T013 | 2016-11-02 | P | 7.35 | T | T | 53100 | 7656 | 45444 | 0.2453 | 0.1924 |
808
+
809
+ === Order_by
810
+
811
+ You can sort a table on any number of columns with +order_by+. The +order_by+
812
+ method takes any number of symbol arguments for the columns to sort on. If you
813
+ specify more than one column, the sort is performed on the first column, then
814
+ all columns that are equal with respect to the first column are sorted by the
815
+ second column, and so on. All columns of the input table are included in the
816
+ output.
817
+
818
+ Let's sort our table first by +:code+, then by +:date+.
819
+
820
+ tab1.order_by(:code, :date) \
821
+ .to_aoa
822
+
823
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
824
+ |------+------------+------+-------+-----+------+--------+------+-------+--------+--------|
825
+ | T001 | 2016-11-01 | P | 7.7 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
826
+ | T002 | 2016-11-01 | P | 7.75 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
827
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
828
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
829
+ | T008 | 2016-11-01 | P | 7.65 | F | F | 2771 | 393 | 2378 | 0.2453 | 0.1924 |
830
+ | T009 | 2016-11-01 | P | 7.6 | F | F | 9550 | 1363 | 8187 | 0.2453 | 0.1924 |
831
+ | T010 | 2016-11-01 | P | 7.55 | F | T | 3175 | 451 | 2724 | 0.2453 | 0.1924 |
832
+ |------+------------+------+-------+-----+------+--------+------+-------+--------+--------|
833
+ | T011 | 2016-11-02 | P | 7.425 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
834
+ | T012 | 2016-11-02 | P | 7.55 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
835
+ | T012 | 2016-11-02 | P | 7.55 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
836
+ | T013 | 2016-11-02 | P | 7.35 | T | T | 53100 | 7656 | 45444 | 0.2453 | 0.1924 |
837
+ | T014 | 2016-11-02 | P | 7.45 | F | T | 5847 | 835 | 5012 | 0.2453 | 0.1924 |
838
+ | T015 | 2016-11-02 | P | 7.75 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
839
+ | T016 | 2016-11-02 | P | 8.25 | T | T | 100 | 14 | 86 | 0.2453 | 0.1924 |
840
+ |------+------------+------+-------+-----+------+--------+------+-------+--------+--------|
841
+ | T004 | 2016-11-01 | S | 7.55 | T | F | 6811 | 966 | 5845 | 0.2453 | 0.1924 |
842
+ | T005 | 2016-11-01 | S | 7.5 | F | F | 4000 | 572 | 3428 | 0.2453 | 0.1924 |
843
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
844
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
845
+ | T007 | 2016-11-01 | S | 7.65 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
846
+
847
+ The interesting thing about +order_by+ is that, while it ignores groups in its
848
+ input, it adds group boundaries in the output table at those rows where the sort
849
+ keys change. Thus, in each group, +:code+ and +:date+ are the same, and when
850
+ either changes, +order_by+ inserts a group boundary.
851
+
852
+ === Group_by
853
+
854
+ Like +order_by+, +group_by+ takes a set of parameters of column header symbols,
855
+ the "grouping parameters", by which to sort the table into a set of groups that
856
+ are equal with respect to values in those columns. In addition, those parameters
857
+ can be followed by a series of hash-like parameters, the "aggregating
858
+ parameters", that indicate how any of the remaining, non-group columns are to be
859
+ aggregated into a single value. The output table has one row for each group for
860
+ which the grouping parameters are equal containing those columns and an
861
+ aggregate column for each of the aggregating parameters.
862
+
863
+ For example, let's summarize the +trades+ table by +:code+ and +:price+ again,
864
+ and determine total shares, average price, and other features of each group:
865
+
866
+ tab1.group_by(:code, :date, price: :avg,
867
+ shares: :sum, lp: :sum, qp: :sum,
868
+ qp10: :all?) \
869
+ .to_aoa { |f| f.format(avg_price: '0.5R') }
870
+
871
+ | Code | Date | Avg Price | Sum Shares | Sum Lp | Sum Qp | All QP10 |
872
+ |------+------------+-----------+------------+--------+--------+----------|
873
+ | P | 2016-11-01 | 7.60714 | 17396 | 2473 | 14923 | F |
874
+ | P | 2016-11-02 | 7.61786 | 69047 | 9945 | 59102 | F |
875
+ | S | 2016-11-01 | 7.58000 | 13011 | 1852 | 11159 | F |
876
+
877
+ After the grouping column parameters, +:code+ and +:date+, there are several
878
+ hash-like "aggregating" parameters where the key is the column to aggregate and
879
+ the value is a symbol for one of several aggregating methods that
880
+ +FatTable::Column+ objects understand. For example, the +:avg+ method is applied
881
+ to the :price column so that the output shows the average price in each group.
882
+ The +:shares+, +:lp+, and +:qp+ columns are summed, and the +:any?+ aggregate is
883
+ applied to one of the boolean fields, that is, it is +true+ if any of the values
884
+ in that column are +true+. The column names in the output of the aggregated
885
+ columns have the name of the aggregating method pre-pended to the column name.
886
+
887
+ Here is a list of all the aggregate methods available. If the description
888
+ restricts the aggregate to particular column types, applying it to other types
889
+ will raise an exception.
890
+
891
+ +first+:: the first non-nil item in the column,
892
+
893
+ +last+:: the last non-nil item in the column,
894
+
895
+ +rng+:: form a string of the form "#{first}..#{last}" to show the range of
896
+ values in the column,
897
+
898
+ +sum+:: for Numeric and String columns, apply '+' to all the non-nil values,
899
+
900
+ +count+:: the number of non-nil values in the column,
901
+
902
+ +min+:: for Numeric, String, and DateTime columns, return the minimum non-nil
903
+ value in the column,
904
+
905
+ +max+:: for Numeric, String, and DateTime columns, return the maximum non-nil
906
+ value in the column,
907
+
908
+ +avg+:: for Numeric and DateTime columns, return the arithmetic mean of the
909
+ non-nil values in the column; with respect to DateTime objects, each is
910
+ converted to a numeric Julian date, the average is calculated, and the
911
+ result converted back to a Date or DateTime object,
912
+
913
+ +var+:: for Numeric and DateTime columns, compute the sample variance of the
914
+ non-nil values in the column, dates are converted to numbers as for the
915
+ :avg aggregate,
916
+
917
+ +pvar+:: for Numeric and DateTime columns, compute the population variance of
918
+ the non-nil values in the column, dates are converted to numbers as for
919
+ the :avg aggregate,
920
+
921
+ +dev+:: for Numeric and DateTime columns, compute the sample standard deviation
922
+ of the non-nil values in the column, dates are converted to numbers as
923
+ for the :avg aggregate,
924
+
925
+ +pdev+:: for Numeric and DateTime columns, compute the population standard
926
+ deviation of the non-nil values in the column, dates are converted to
927
+ numbers as for the :avg aggregate,
928
+
929
+ +any?+:: for Boolean columns only, return true if any non-nil value in the
930
+ column is true,
931
+
932
+ +none?+:: for Boolean columns only, return true if no non-nil value in the
933
+ column is true,
934
+
935
+ +one?+:: for Boolean columns only, return true if exactly one non-nil value in
936
+ the column is true,
937
+
938
+ Perhaps surprisingly, the +group_by+ method ignores any groups in its input and
939
+ results in no group boundaries in the output since each group formed by the
940
+ implicit +order_by+ on the grouping columns is collapsed into a single row.
941
+
942
+ === Join
943
+
944
+ ==== Join Types
945
+
946
+ So far, all the operations have operated on a single table. +FatTable+ provides
947
+ several +join+ methods for combining two tables, each of which takes as
948
+ parameters (1) a second table and (2) except in the case of +cross_join+, zero
949
+ or more "join expressions". In the descriptions below, T1 is the table on which
950
+ the method is called, +T2+ is the table supplied as the first parameter +other+,
951
+ and +R1+ and +R2+ are rows in their respective tables being considered for
952
+ inclusion in the joined output table.
953
+
954
+ join(other, *jexps)::
955
+ Performs an "inner join" on the tables. For each row R1
956
+ of T1, the joined table has a row for each row in T2 that satisfies the join
957
+ condition with R1.
958
+
959
+ left_join(other, *jexps)::
960
+ First, an inner join is performed. Then, for each row in T1 that does not
961
+ satisfy the join condition with any row in T2, a joined row is added with null
962
+ values in columns of T2. Thus, the joined table always has at least one row
963
+ for each row in T1.
964
+
965
+ right_join(other, *jexps)::
966
+ First, an inner join is performed. Then, for each row in T2 that does not
967
+ satisfy the join condition with any row in T1, a joined row is added with null
968
+ values in columns of T1. This is the converse of a left join: the result table
969
+ will always have a row for each row in T2.
970
+
971
+ full_join(other, *jexps)::
972
+ First, an inner join is performed. Then, for each row in T1 that does not
973
+ satisfy the join condition with any row in T2, a joined row is added with null
974
+ values in columns of T2. Also, for each row of T2 that does not satisfy the
975
+ join condition with any row in T1, a joined row with null values in the
976
+ columns of T1 is added.
977
+
978
+ cross_join(other)::
979
+ For every possible combination of rows from T1 and T2 (i.e., a Cartesian
980
+ product), the joined table will contain a row consisting of all columns in T1
981
+ followed by all columns in T2. If the tables have N and M rows respectively,
982
+ the joined table will have N * M rows.
983
+
984
+ ==== Join Expressions
985
+
986
+ For each of the join types, if no join expressions are given, the tables will be
987
+ joined on columns having the same column header in both tables, and the join
988
+ condition is satisfied when all the values in those columns are equal. If the
989
+ join type is an inner join, this is a so-called "natural" join.
990
+
991
+ If the join expressions are one or more symbols, the join condition requires
992
+ that the values of both tables are equal for all columns named by the symbols. A
993
+ column that appears in both tables can be given without modification and will be
994
+ assumed to require equality on that column. If an unmodified symbol is not a
995
+ name that appears in both tables, an exception will be raised. Column names that
996
+ are unique to the first table must have a '_a' appended to the column name and
997
+ column names that are unique to the other table must have a '_b' appended to the
998
+ column name. These disambiguated column names must come in pairs, one for the
999
+ first table and one for the second, and they will imply a join condition that
1000
+ the columns must be equal on those columns. Several such symbol expressions will
1001
+ require that all such implied pairs are equal in order for the join condition to
1002
+ be met.
1003
+
1004
+ Finally, a join expression can be a string that contains an arbitrary ruby
1005
+ expression that will be evaluated for truthiness. Within the string, /all/
1006
+ column names must be disambiguated with the '_a' or '_b' modifiers whether they
1007
+ are common to both tables or not. As with +select+ and +where+ methods, the
1008
+ names of the columns in both tables (albeit disambiguated) are available as
1009
+ local variables within the expression, but the instance variables +@row+ and
1010
+ +@group+ are not.
1011
+
1012
+ ==== Join Examples
1013
+
1014
+ The following examples are taken from a the {Postgres
1015
+ tutorial}[https://www.tutorialspoint.com/postgresql/postgresql_using_joins.htm],
1016
+ with some slight modifications. The examples will use the following two tables,
1017
+ which are also available in +ft_console+:
1018
+
1019
+ require 'fat_table'
1020
+
1021
+ tab_a_str = <<-EOS
1022
+ | Id | Name | Age | Address | Salary | Join Date |
1023
+ |----+-------+-----+------------+--------+------------|
1024
+ | 1 | Paul | 32 | California | 20000 | 2001-07-13 |
1025
+ | 3 | Teddy | 23 | Norway | 20000 | 2007-12-13 |
1026
+ | 4 | Mark | 25 | Rich-Mond | 65000 | 2007-12-13 |
1027
+ | 5 | David | 27 | Texas | 85000 | 2007-12-13 |
1028
+ | 2 | Allen | 25 | Texas | | 2005-07-13 |
1029
+ | 8 | Paul | 24 | Houston | 20000 | 2005-07-13 |
1030
+ | 9 | James | 44 | Norway | 5000 | 2005-07-13 |
1031
+ | 10 | James | 45 | Texas | 5000 | |
1032
+ EOS
1033
+
1034
+ tab_b_str = <<-EOS
1035
+ | Id | Dept | Emp Id |
1036
+ |----+-------------+--------|
1037
+ | 1 | IT Billing | 1 |
1038
+ | 2 | Engineering | 2 |
1039
+ | 3 | Finance | 7 |
1040
+ EOS
1041
+
1042
+ tab_a = FatTable.from_org_string(tab_a_str)
1043
+ tab_b = FatTable.from_org_string(tab_b_str)
1044
+
1045
+ ===== Inner Joins
1046
+
1047
+ With no join expression arguments, the tables are joined when their sole common
1048
+ field, +:id+, is equal in both tables. The result is the natural join of the
1049
+ two tables.
1050
+
1051
+ tab_a.join(tab_b).to_aoa
1052
+
1053
+ | Id | Name | Age | Address | Salary | Join Date | Dept | Emp Id |
1054
+ |----+-------+-----+------------+--------+------------+-------------+--------|
1055
+ | 1 | Paul | 32 | California | 20000 | 2001-07-13 | IT Billing | 1 |
1056
+ | 3 | Teddy | 23 | Norway | 20000 | 2007-12-13 | Finance | 7 |
1057
+ | 2 | Allen | 25 | Texas | | 2005-07-13 | Engineering | 2 |
1058
+
1059
+ But the natural join joined employee IDs in the first table and department IDs
1060
+ in the second table. To correct this, we need to explicitly state the columns we
1061
+ want to join on in each table by disambiguating them with +_a+ and +_b+
1062
+ suffixes:
1063
+
1064
+ tab_a.join(tab_b, :id_a, :emp_id_b).to_aoa
1065
+
1066
+ | Id | Name | Age | Address | Salary | Join Date | Id B | Dept |
1067
+ |----+-------+-----+------------+--------+------------+------+-------------|
1068
+ | 1 | Paul | 32 | California | 20000 | 2001-07-13 | 1 | IT Billing |
1069
+ | 2 | Allen | 25 | Texas | | 2005-07-13 | 2 | Engineering |
1070
+
1071
+ Instead of using the disambiguated column names as symbols, we could also use a
1072
+ string containing a ruby expression. Within the expression, the column names
1073
+ should be treated as local variables:
1074
+
1075
+ tab_a.join(tab_b, 'id_a == emp_id_b').to_aoa
1076
+
1077
+ | Id | Name | Age | Address | Salary | Join Date | Id B | Dept | Emp Id |
1078
+ |----+-------+-----+------------+--------+------------+------+-------------+--------|
1079
+ | 1 | Paul | 32 | California | 20000 | 2001-07-13 | 1 | IT Billing | 1 |
1080
+ | 2 | Allen | 25 | Texas | | 2005-07-13 | 2 | Engineering | 2 |
1081
+
1082
+ ===== Left and Right Joins
1083
+
1084
+ In left join, all the rows of +tab_a+ are included in the output, augmented by
1085
+ the matching columns of +tab_b+ and augmented with nils where there is no match:
1086
+
1087
+ tab_a.left_join(tab_b, 'id_a == emp_id_b').to_aoa
1088
+
1089
+ | Id | Name | Age | Address | Salary | Join Date | Id B | Dept | Emp Id |
1090
+ |----+-------+-----+------------+--------+------------+------+-------------+--------|
1091
+ | 1 | Paul | 32 | California | 20000 | 2001-07-13 | 1 | IT Billing | 1 |
1092
+ | 3 | Teddy | 23 | Norway | 20000 | 2007-12-13 | | | |
1093
+ | 4 | Mark | 25 | Rich-Mond | 65000 | 2007-12-13 | | | |
1094
+ | 5 | David | 27 | Texas | 85000 | 2007-12-13 | | | |
1095
+ | 2 | Allen | 25 | Texas | | 2005-07-13 | 2 | Engineering | 2 |
1096
+ | 8 | Paul | 24 | Houston | 20000 | 2005-07-13 | | | |
1097
+ | 9 | James | 44 | Norway | 5000 | 2005-07-13 | | | |
1098
+ | 10 | James | 45 | Texas | 5000 | | | | |
1099
+
1100
+ In a right join, all the rows of +tab_b+ are included in the output, augmented
1101
+ by the matching columns of +tab_a+ and augmented with nils where there is no
1102
+ match:
1103
+
1104
+ tab_a.right_join(tab_b, 'id_a == emp_id_b').to_aoa
1105
+
1106
+ | Id | Name | Age | Address | Salary | Join Date | Id B | Dept | Emp Id |
1107
+ |----+-------+-----+------------+--------+------------+------+-------------+--------|
1108
+ | 1 | Paul | 32 | California | 20000 | 2001-07-13 | 1 | IT Billing | 1 |
1109
+ | 2 | Allen | 25 | Texas | | 2005-07-13 | 2 | Engineering | 2 |
1110
+ | | | | | | | 3 | Finance | 7 |
1111
+
1112
+ ===== Full Join
1113
+
1114
+ A full join combines the effects of a left join and a right join. All the rows
1115
+ from both tables are included in the output augmented by columns of the other
1116
+ table where the join expression is satisfied and augmented with nils otherwise.
1117
+
1118
+ tab_a.full_join(tab_b, 'id_a == emp_id_b').to_aoa
1119
+
1120
+ | Id | Name | Age | Address | Salary | Join Date | Id B | Dept | Emp Id |
1121
+ |----+-------+-----+------------+--------+------------+------+-------------+--------|
1122
+ | 1 | Paul | 32 | California | 20000 | 2001-07-13 | 1 | IT Billing | 1 |
1123
+ | 3 | Teddy | 23 | Norway | 20000 | 2007-12-13 | | | |
1124
+ | 4 | Mark | 25 | Rich-Mond | 65000 | 2007-12-13 | | | |
1125
+ | 5 | David | 27 | Texas | 85000 | 2007-12-13 | | | |
1126
+ | 2 | Allen | 25 | Texas | | 2005-07-13 | 2 | Engineering | 2 |
1127
+ | 8 | Paul | 24 | Houston | 20000 | 2005-07-13 | | | |
1128
+ | 9 | James | 44 | Norway | 5000 | 2005-07-13 | | | |
1129
+ | 10 | James | 45 | Texas | 5000 | | | | |
1130
+ | | | | | | | 3 | Finance | 7 |
1131
+
1132
+ ===== Cross Join
1133
+
1134
+ Finally, a cross join outputs every row of +tab_a+ augmented with every row of
1135
+ +tab_b+, in other words, the Cartesian product of the two tables. If +tab_a+ has
1136
+ +N+ rows and +tab_b+ has +M+ rows, the output table will have +N * M+ rows.
1137
+
1138
+ tab_a.cross_join(tab_b).to_aoa
1139
+
1140
+ | Id | Name | Age | Address | Salary | Join Date | Id B | Dept | Emp Id |
1141
+ |----+-------+-----+------------+--------+------------+------+-------------+--------|
1142
+ | 1 | Paul | 32 | California | 20000 | 2001-07-13 | 1 | IT Billing | 1 |
1143
+ | 1 | Paul | 32 | California | 20000 | 2001-07-13 | 2 | Engineering | 2 |
1144
+ | 1 | Paul | 32 | California | 20000 | 2001-07-13 | 3 | Finance | 7 |
1145
+ | 3 | Teddy | 23 | Norway | 20000 | 2007-12-13 | 1 | IT Billing | 1 |
1146
+ | 3 | Teddy | 23 | Norway | 20000 | 2007-12-13 | 2 | Engineering | 2 |
1147
+ | 3 | Teddy | 23 | Norway | 20000 | 2007-12-13 | 3 | Finance | 7 |
1148
+ | 4 | Mark | 25 | Rich-Mond | 65000 | 2007-12-13 | 1 | IT Billing | 1 |
1149
+ | 4 | Mark | 25 | Rich-Mond | 65000 | 2007-12-13 | 2 | Engineering | 2 |
1150
+ | 4 | Mark | 25 | Rich-Mond | 65000 | 2007-12-13 | 3 | Finance | 7 |
1151
+ | 5 | David | 27 | Texas | 85000 | 2007-12-13 | 1 | IT Billing | 1 |
1152
+ | 5 | David | 27 | Texas | 85000 | 2007-12-13 | 2 | Engineering | 2 |
1153
+ | 5 | David | 27 | Texas | 85000 | 2007-12-13 | 3 | Finance | 7 |
1154
+ | 2 | Allen | 25 | Texas | | 2005-07-13 | 1 | IT Billing | 1 |
1155
+ | 2 | Allen | 25 | Texas | | 2005-07-13 | 2 | Engineering | 2 |
1156
+ | 2 | Allen | 25 | Texas | | 2005-07-13 | 3 | Finance | 7 |
1157
+ | 8 | Paul | 24 | Houston | 20000 | 2005-07-13 | 1 | IT Billing | 1 |
1158
+ | 8 | Paul | 24 | Houston | 20000 | 2005-07-13 | 2 | Engineering | 2 |
1159
+ | 8 | Paul | 24 | Houston | 20000 | 2005-07-13 | 3 | Finance | 7 |
1160
+ | 9 | James | 44 | Norway | 5000 | 2005-07-13 | 1 | IT Billing | 1 |
1161
+ | 9 | James | 44 | Norway | 5000 | 2005-07-13 | 2 | Engineering | 2 |
1162
+ | 9 | James | 44 | Norway | 5000 | 2005-07-13 | 3 | Finance | 7 |
1163
+ | 10 | James | 45 | Texas | 5000 | | 1 | IT Billing | 1 |
1164
+ | 10 | James | 45 | Texas | 5000 | | 2 | Engineering | 2 |
1165
+ | 10 | James | 45 | Texas | 5000 | | 3 | Finance | 7 |
1166
+
1167
+ === Set Operations
1168
+
1169
+ +FatTable+ can perform several set operations on tables. In order for two
1170
+ tables to be used this way, they must have the same number of columns with the
1171
+ same types or an exception will be raised. We'll call two tables that qualify
1172
+ for combining with set operations "set-compatible."
1173
+
1174
+ We'll use the following two set-compatible tables in the examples. They each
1175
+ have some duplicates and some group boundaries so you can see the effect of the
1176
+ set operations on duplicates and groups.
1177
+
1178
+ tab1.to_aoa
1179
+
1180
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
1181
+ |------+------------+------+-------+-----+------+--------+------+-------+--------+--------|
1182
+ | T001 | 2016-11-01 | P | 7.7 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
1183
+ | T002 | 2016-11-01 | P | 7.75 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1184
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1185
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1186
+ |------+------------+------+-------+-----+------+--------+------+-------+--------+--------|
1187
+ | T004 | 2016-11-01 | S | 7.55 | T | F | 6811 | 966 | 5845 | 0.2453 | 0.1924 |
1188
+ | T005 | 2016-11-01 | S | 7.5 | F | F | 4000 | 572 | 3428 | 0.2453 | 0.1924 |
1189
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
1190
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
1191
+ | T007 | 2016-11-01 | S | 7.65 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1192
+ | T008 | 2016-11-01 | P | 7.65 | F | F | 2771 | 393 | 2378 | 0.2453 | 0.1924 |
1193
+ | T009 | 2016-11-01 | P | 7.6 | F | F | 9550 | 1363 | 8187 | 0.2453 | 0.1924 |
1194
+ |------+------------+------+-------+-----+------+--------+------+-------+--------+--------|
1195
+ | T010 | 2016-11-01 | P | 7.55 | F | T | 3175 | 451 | 2724 | 0.2453 | 0.1924 |
1196
+ | T011 | 2016-11-02 | P | 7.425 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
1197
+ | T012 | 2016-11-02 | P | 7.55 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
1198
+ | T012 | 2016-11-02 | P | 7.55 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
1199
+ | T013 | 2016-11-02 | P | 7.35 | T | T | 53100 | 7656 | 45444 | 0.2453 | 0.1924 |
1200
+ |------+------------+------+-------+-----+------+--------+------+-------+--------+--------|
1201
+ | T014 | 2016-11-02 | P | 7.45 | F | T | 5847 | 835 | 5012 | 0.2453 | 0.1924 |
1202
+ | T015 | 2016-11-02 | P | 7.75 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
1203
+ | T016 | 2016-11-02 | P | 8.25 | T | T | 100 | 14 | 86 | 0.2453 | 0.1924 |
1204
+
1205
+ tab2.to_aoa
1206
+
1207
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
1208
+ |------+------------+------+-------+-----+------+--------+-------+------+--------+--------|
1209
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1210
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1211
+ | T017 | 2016-11-01 | P | 8.3 | F | T | 1801 | 1201 | 600 | 0.2453 | 0.1924 |
1212
+ |------+------------+------+-------+-----+------+--------+-------+------+--------+--------|
1213
+ | T018 | 2016-11-01 | S | 7.152 | T | F | 2516 | 2400 | 116 | 0.2453 | 0.1924 |
1214
+ | T018 | 2016-11-01 | S | 7.152 | T | F | 2516 | 2400 | 116 | 0.2453 | 0.1924 |
1215
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
1216
+ | T007 | 2016-11-01 | S | 7.65 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1217
+ |------+------------+------+-------+-----+------+--------+-------+------+--------+--------|
1218
+ | T014 | 2016-11-02 | P | 7.45 | F | T | 5847 | 835 | 5012 | 0.2453 | 0.1924 |
1219
+ | T015 | 2016-11-02 | P | 7.75 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
1220
+ | T015 | 2016-11-02 | P | 7.75 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
1221
+ | T016 | 2016-11-02 | P | 8.25 | T | T | 100 | 14 | 86 | 0.2453 | 0.1924 |
1222
+ |------+------------+------+-------+-----+------+--------+-------+------+--------+--------|
1223
+ | T019 | 2017-01-15 | S | 8.75 | T | F | 300 | 175 | 125 | 0.2453 | 0.1924 |
1224
+ | T020 | 2017-01-19 | S | 8.25 | F | T | 700 | 615 | 85 | 0.2453 | 0.1924 |
1225
+ | T021 | 2017-01-23 | P | 7.16 | T | T | 12100 | 11050 | 1050 | 0.2453 | 0.1924 |
1226
+ | T021 | 2017-01-23 | P | 7.16 | T | T | 12100 | 11050 | 1050 | 0.2453 | 0.1924 |
1227
+
1228
+ ==== Unions
1229
+
1230
+ Two tables that are set-compatible can be combined with the +union+ or
1231
+ +union_all+ methods so that the rows of both tables appear in the output. In the
1232
+ output table, the headers of the receiver table are used. You can use +select+
1233
+ to change or re-order the headers if you prefer. The +union+ method eliminates
1234
+ duplicate rows in the result table, the +union_all+ method does not.
1235
+
1236
+ Any group boundaries in the input tables are destroyed by +union+ but are
1237
+ preserved by +union_all+. In addition, +union_all+ (but not +union+) adds a
1238
+ group boundary between the rows of the two input tables.
1239
+
1240
+ tab1.union(tab2).to_aoa
1241
+
1242
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
1243
+ |------+------------+------+-------+-----+------+--------+-------+-------+--------+--------|
1244
+ | T001 | 2016-11-01 | P | 7.7 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
1245
+ | T002 | 2016-11-01 | P | 7.75 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1246
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1247
+ | T004 | 2016-11-01 | S | 7.55 | T | F | 6811 | 966 | 5845 | 0.2453 | 0.1924 |
1248
+ | T005 | 2016-11-01 | S | 7.5 | F | F | 4000 | 572 | 3428 | 0.2453 | 0.1924 |
1249
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
1250
+ | T007 | 2016-11-01 | S | 7.65 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1251
+ | T008 | 2016-11-01 | P | 7.65 | F | F | 2771 | 393 | 2378 | 0.2453 | 0.1924 |
1252
+ | T009 | 2016-11-01 | P | 7.6 | F | F | 9550 | 1363 | 8187 | 0.2453 | 0.1924 |
1253
+ | T010 | 2016-11-01 | P | 7.55 | F | T | 3175 | 451 | 2724 | 0.2453 | 0.1924 |
1254
+ | T011 | 2016-11-02 | P | 7.425 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
1255
+ | T012 | 2016-11-02 | P | 7.55 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
1256
+ | T013 | 2016-11-02 | P | 7.35 | T | T | 53100 | 7656 | 45444 | 0.2453 | 0.1924 |
1257
+ | T014 | 2016-11-02 | P | 7.45 | F | T | 5847 | 835 | 5012 | 0.2453 | 0.1924 |
1258
+ | T015 | 2016-11-02 | P | 7.75 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
1259
+ | T016 | 2016-11-02 | P | 8.25 | T | T | 100 | 14 | 86 | 0.2453 | 0.1924 |
1260
+ | T017 | 2016-11-01 | P | 8.3 | F | T | 1801 | 1201 | 600 | 0.2453 | 0.1924 |
1261
+ | T018 | 2016-11-01 | S | 7.152 | T | F | 2516 | 2400 | 116 | 0.2453 | 0.1924 |
1262
+ | T019 | 2017-01-15 | S | 8.75 | T | F | 300 | 175 | 125 | 0.2453 | 0.1924 |
1263
+ | T020 | 2017-01-19 | S | 8.25 | F | T | 700 | 615 | 85 | 0.2453 | 0.1924 |
1264
+ | T021 | 2017-01-23 | P | 7.16 | T | T | 12100 | 11050 | 1050 | 0.2453 | 0.1924 |
1265
+
1266
+ tab1.union_all(tab2).to_aoa
1267
+
1268
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
1269
+ |------+------------+------+-------+-----+------+--------+-------+-------+--------+--------|
1270
+ | T001 | 2016-11-01 | P | 7.7 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
1271
+ | T002 | 2016-11-01 | P | 7.75 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1272
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1273
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1274
+ |------+------------+------+-------+-----+------+--------+-------+-------+--------+--------|
1275
+ | T004 | 2016-11-01 | S | 7.55 | T | F | 6811 | 966 | 5845 | 0.2453 | 0.1924 |
1276
+ | T005 | 2016-11-01 | S | 7.5 | F | F | 4000 | 572 | 3428 | 0.2453 | 0.1924 |
1277
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
1278
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
1279
+ | T007 | 2016-11-01 | S | 7.65 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1280
+ | T008 | 2016-11-01 | P | 7.65 | F | F | 2771 | 393 | 2378 | 0.2453 | 0.1924 |
1281
+ | T009 | 2016-11-01 | P | 7.6 | F | F | 9550 | 1363 | 8187 | 0.2453 | 0.1924 |
1282
+ |------+------------+------+-------+-----+------+--------+-------+-------+--------+--------|
1283
+ | T010 | 2016-11-01 | P | 7.55 | F | T | 3175 | 451 | 2724 | 0.2453 | 0.1924 |
1284
+ | T011 | 2016-11-02 | P | 7.425 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
1285
+ | T012 | 2016-11-02 | P | 7.55 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
1286
+ | T012 | 2016-11-02 | P | 7.55 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
1287
+ | T013 | 2016-11-02 | P | 7.35 | T | T | 53100 | 7656 | 45444 | 0.2453 | 0.1924 |
1288
+ |------+------------+------+-------+-----+------+--------+-------+-------+--------+--------|
1289
+ | T014 | 2016-11-02 | P | 7.45 | F | T | 5847 | 835 | 5012 | 0.2453 | 0.1924 |
1290
+ | T015 | 2016-11-02 | P | 7.75 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
1291
+ | T016 | 2016-11-02 | P | 8.25 | T | T | 100 | 14 | 86 | 0.2453 | 0.1924 |
1292
+ |------+------------+------+-------+-----+------+--------+-------+-------+--------+--------|
1293
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1294
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1295
+ | T017 | 2016-11-01 | P | 8.3 | F | T | 1801 | 1201 | 600 | 0.2453 | 0.1924 |
1296
+ |------+------------+------+-------+-----+------+--------+-------+-------+--------+--------|
1297
+ | T018 | 2016-11-01 | S | 7.152 | T | F | 2516 | 2400 | 116 | 0.2453 | 0.1924 |
1298
+ | T018 | 2016-11-01 | S | 7.152 | T | F | 2516 | 2400 | 116 | 0.2453 | 0.1924 |
1299
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
1300
+ | T007 | 2016-11-01 | S | 7.65 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1301
+ |------+------------+------+-------+-----+------+--------+-------+-------+--------+--------|
1302
+ | T014 | 2016-11-02 | P | 7.45 | F | T | 5847 | 835 | 5012 | 0.2453 | 0.1924 |
1303
+ | T015 | 2016-11-02 | P | 7.75 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
1304
+ | T015 | 2016-11-02 | P | 7.75 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
1305
+ | T016 | 2016-11-02 | P | 8.25 | T | T | 100 | 14 | 86 | 0.2453 | 0.1924 |
1306
+ |------+------------+------+-------+-----+------+--------+-------+-------+--------+--------|
1307
+ | T019 | 2017-01-15 | S | 8.75 | T | F | 300 | 175 | 125 | 0.2453 | 0.1924 |
1308
+ | T020 | 2017-01-19 | S | 8.25 | F | T | 700 | 615 | 85 | 0.2453 | 0.1924 |
1309
+ | T021 | 2017-01-23 | P | 7.16 | T | T | 12100 | 11050 | 1050 | 0.2453 | 0.1924 |
1310
+ | T021 | 2017-01-23 | P | 7.16 | T | T | 12100 | 11050 | 1050 | 0.2453 | 0.1924 |
1311
+
1312
+ ==== Intersections
1313
+
1314
+ The +intersect+ method returns a table having only rows common to both tables,
1315
+ eliminating any duplicate rows in the result.
1316
+
1317
+ tab1.intersect(tab2).to_aoa
1318
+
1319
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
1320
+ |------+------------+------+-------+-----+------+--------+-----+------+--------+--------|
1321
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1322
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
1323
+ | T007 | 2016-11-01 | S | 7.65 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1324
+ | T014 | 2016-11-02 | P | 7.45 | F | T | 5847 | 835 | 5012 | 0.2453 | 0.1924 |
1325
+ | T015 | 2016-11-02 | P | 7.75 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
1326
+ | T016 | 2016-11-02 | P | 8.25 | T | T | 100 | 14 | 86 | 0.2453 | 0.1924 |
1327
+
1328
+ With +intersect_all+, all the rows of the first table, including duplicates, are
1329
+ included in the result if they also occur in the second table. However,
1330
+ duplicates in the second table do not appear.
1331
+
1332
+ tab1.intersect_all(tab2).to_aoa
1333
+
1334
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
1335
+ |------+------------+------+-------+-----+------+--------+-----+------+--------+--------|
1336
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1337
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1338
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
1339
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
1340
+ | T007 | 2016-11-01 | S | 7.65 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1341
+ | T014 | 2016-11-02 | P | 7.45 | F | T | 5847 | 835 | 5012 | 0.2453 | 0.1924 |
1342
+ | T015 | 2016-11-02 | P | 7.75 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
1343
+ | T016 | 2016-11-02 | P | 8.25 | T | T | 100 | 14 | 86 | 0.2453 | 0.1924 |
1344
+
1345
+ As a result, it makes a difference which table is the receiver of the
1346
+ +intersect_all+ method call and which is the argument. In other words, order of
1347
+ operation matters.
1348
+
1349
+ tab2.intersect_all(tab1).to_aoa
1350
+
1351
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
1352
+ |------+------------+------+-------+-----+------+--------+-----+------+--------+--------|
1353
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1354
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1355
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
1356
+ | T007 | 2016-11-01 | S | 7.65 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1357
+ | T014 | 2016-11-02 | P | 7.45 | F | T | 5847 | 835 | 5012 | 0.2453 | 0.1924 |
1358
+ | T015 | 2016-11-02 | P | 7.75 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
1359
+ | T015 | 2016-11-02 | P | 7.75 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
1360
+ | T016 | 2016-11-02 | P | 8.25 | T | T | 100 | 14 | 86 | 0.2453 | 0.1924 |
1361
+
1362
+ ==== Differences with Except
1363
+
1364
+ You can use the +except+ method to delete from a table any rows that occur in
1365
+ another table, that is, compute the set difference between the tables.
1366
+
1367
+ tab1.except(tab2).to_aoa
1368
+
1369
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
1370
+ |------+------------+------+-------+-----+------+--------+------+-------+--------+--------|
1371
+ | T001 | 2016-11-01 | P | 7.7 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
1372
+ | T002 | 2016-11-01 | P | 7.75 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1373
+ | T004 | 2016-11-01 | S | 7.55 | T | F | 6811 | 966 | 5845 | 0.2453 | 0.1924 |
1374
+ | T005 | 2016-11-01 | S | 7.5 | F | F | 4000 | 572 | 3428 | 0.2453 | 0.1924 |
1375
+ | T008 | 2016-11-01 | P | 7.65 | F | F | 2771 | 393 | 2378 | 0.2453 | 0.1924 |
1376
+ | T009 | 2016-11-01 | P | 7.6 | F | F | 9550 | 1363 | 8187 | 0.2453 | 0.1924 |
1377
+ | T010 | 2016-11-01 | P | 7.55 | F | T | 3175 | 451 | 2724 | 0.2453 | 0.1924 |
1378
+ | T011 | 2016-11-02 | P | 7.425 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
1379
+ | T012 | 2016-11-02 | P | 7.55 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
1380
+ | T013 | 2016-11-02 | P | 7.35 | T | T | 53100 | 7656 | 45444 | 0.2453 | 0.1924 |
1381
+
1382
+ Like subtraction, though, the order of operands matters with set difference
1383
+ computed by +except+.
1384
+
1385
+ tab2.except(tab1).to_aoa
1386
+
1387
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
1388
+ |------+------------+------+-------+-----+------+--------+-------+------+--------+--------|
1389
+ | T017 | 2016-11-01 | P | 8.3 | F | T | 1801 | 1201 | 600 | 0.2453 | 0.1924 |
1390
+ | T018 | 2016-11-01 | S | 7.152 | T | F | 2516 | 2400 | 116 | 0.2453 | 0.1924 |
1391
+ | T019 | 2017-01-15 | S | 8.75 | T | F | 300 | 175 | 125 | 0.2453 | 0.1924 |
1392
+ | T020 | 2017-01-19 | S | 8.25 | F | T | 700 | 615 | 85 | 0.2453 | 0.1924 |
1393
+ | T021 | 2017-01-23 | P | 7.16 | T | T | 12100 | 11050 | 1050 | 0.2453 | 0.1924 |
1394
+
1395
+ As with +intersect_all+, +except_all+ includes any duplicates in the first,
1396
+ receiver table, but not those in the second, argument table.
1397
+
1398
+ tab1.except_all(tab2).to_aoa
1399
+
1400
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
1401
+ |------+------------+------+-------+-----+------+--------+------+-------+--------+--------|
1402
+ | T001 | 2016-11-01 | P | 7.7 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
1403
+ | T002 | 2016-11-01 | P | 7.75 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1404
+ | T004 | 2016-11-01 | S | 7.55 | T | F | 6811 | 966 | 5845 | 0.2453 | 0.1924 |
1405
+ | T005 | 2016-11-01 | S | 7.5 | F | F | 4000 | 572 | 3428 | 0.2453 | 0.1924 |
1406
+ | T008 | 2016-11-01 | P | 7.65 | F | F | 2771 | 393 | 2378 | 0.2453 | 0.1924 |
1407
+ | T009 | 2016-11-01 | P | 7.6 | F | F | 9550 | 1363 | 8187 | 0.2453 | 0.1924 |
1408
+ | T010 | 2016-11-01 | P | 7.55 | F | T | 3175 | 451 | 2724 | 0.2453 | 0.1924 |
1409
+ | T011 | 2016-11-02 | P | 7.425 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
1410
+ | T012 | 2016-11-02 | P | 7.55 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
1411
+ | T012 | 2016-11-02 | P | 7.55 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
1412
+ | T013 | 2016-11-02 | P | 7.35 | T | T | 53100 | 7656 | 45444 | 0.2453 | 0.1924 |
1413
+
1414
+ And, of course, the order of operands matters here as well.
1415
+
1416
+ tab2.except_all(tab1).to_aoa
1417
+
1418
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
1419
+ |------+------------+------+-------+-----+------+--------+-------+------+--------+--------|
1420
+ | T017 | 2016-11-01 | P | 8.3 | F | T | 1801 | 1201 | 600 | 0.2453 | 0.1924 |
1421
+ | T018 | 2016-11-01 | S | 7.152 | T | F | 2516 | 2400 | 116 | 0.2453 | 0.1924 |
1422
+ | T018 | 2016-11-01 | S | 7.152 | T | F | 2516 | 2400 | 116 | 0.2453 | 0.1924 |
1423
+ | T019 | 2017-01-15 | S | 8.75 | T | F | 300 | 175 | 125 | 0.2453 | 0.1924 |
1424
+ | T020 | 2017-01-19 | S | 8.25 | F | T | 700 | 615 | 85 | 0.2453 | 0.1924 |
1425
+ | T021 | 2017-01-23 | P | 7.16 | T | T | 12100 | 11050 | 1050 | 0.2453 | 0.1924 |
1426
+ | T021 | 2017-01-23 | P | 7.16 | T | T | 12100 | 11050 | 1050 | 0.2453 | 0.1924 |
1427
+
1428
+ === Uniq (aka Distinct)
1429
+
1430
+ The +uniq+ method takes no arguments and simply removes any duplicate rows from
1431
+ the input table. The +distinct+ method is an alias for +uniq+. Any groups in
1432
+ the input table are lost.
1433
+
1434
+ tab1.uniq.to_aoa
1435
+
1436
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
1437
+ |------+------------+------+-------+-----+------+--------+------+-------+--------+--------|
1438
+ | T001 | 2016-11-01 | P | 7.7 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
1439
+ | T002 | 2016-11-01 | P | 7.75 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1440
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1441
+ | T004 | 2016-11-01 | S | 7.55 | T | F | 6811 | 966 | 5845 | 0.2453 | 0.1924 |
1442
+ | T005 | 2016-11-01 | S | 7.5 | F | F | 4000 | 572 | 3428 | 0.2453 | 0.1924 |
1443
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
1444
+ | T007 | 2016-11-01 | S | 7.65 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1445
+ | T008 | 2016-11-01 | P | 7.65 | F | F | 2771 | 393 | 2378 | 0.2453 | 0.1924 |
1446
+ | T009 | 2016-11-01 | P | 7.6 | F | F | 9550 | 1363 | 8187 | 0.2453 | 0.1924 |
1447
+ | T010 | 2016-11-01 | P | 7.55 | F | T | 3175 | 451 | 2724 | 0.2453 | 0.1924 |
1448
+ | T011 | 2016-11-02 | P | 7.425 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
1449
+ | T012 | 2016-11-02 | P | 7.55 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
1450
+ | T013 | 2016-11-02 | P | 7.35 | T | T | 53100 | 7656 | 45444 | 0.2453 | 0.1924 |
1451
+ | T014 | 2016-11-02 | P | 7.45 | F | T | 5847 | 835 | 5012 | 0.2453 | 0.1924 |
1452
+ | T015 | 2016-11-02 | P | 7.75 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
1453
+ | T016 | 2016-11-02 | P | 8.25 | T | T | 100 | 14 | 86 | 0.2453 | 0.1924 |
1454
+
1455
+ === Remove groups with degroup!
1456
+
1457
+ Finally, it is sometimes helpful to remove any group boundaries from a table.
1458
+ You can do this with +.degroup!+, which is the only operation that mutates its
1459
+ receiver table by removing its groups.
1460
+
1461
+ tab1.degroup!.to_aoa
1462
+
1463
+ | Ref | Date | Code | Price | G10 | QP10 | Shares | Lp | Qp | Iplp | Ipqp |
1464
+ |------+------------+------+-------+-----+------+--------+------+-------+--------+--------|
1465
+ | T001 | 2016-11-01 | P | 7.7 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
1466
+ | T002 | 2016-11-01 | P | 7.75 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1467
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1468
+ | T003 | 2016-11-01 | P | 7.5 | F | T | 800 | 112 | 688 | 0.2453 | 0.1924 |
1469
+ | T004 | 2016-11-01 | S | 7.55 | T | F | 6811 | 966 | 5845 | 0.2453 | 0.1924 |
1470
+ | T005 | 2016-11-01 | S | 7.5 | F | F | 4000 | 572 | 3428 | 0.2453 | 0.1924 |
1471
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
1472
+ | T006 | 2016-11-01 | S | 7.6 | F | T | 1000 | 143 | 857 | 0.2453 | 0.1924 |
1473
+ | T007 | 2016-11-01 | S | 7.65 | T | F | 200 | 28 | 172 | 0.2453 | 0.1924 |
1474
+ | T008 | 2016-11-01 | P | 7.65 | F | F | 2771 | 393 | 2378 | 0.2453 | 0.1924 |
1475
+ | T009 | 2016-11-01 | P | 7.6 | F | F | 9550 | 1363 | 8187 | 0.2453 | 0.1924 |
1476
+ | T010 | 2016-11-01 | P | 7.55 | F | T | 3175 | 451 | 2724 | 0.2453 | 0.1924 |
1477
+ | T011 | 2016-11-02 | P | 7.425 | T | F | 100 | 14 | 86 | 0.2453 | 0.1924 |
1478
+ | T012 | 2016-11-02 | P | 7.55 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
1479
+ | T012 | 2016-11-02 | P | 7.55 | F | F | 4700 | 677 | 4023 | 0.2453 | 0.1924 |
1480
+ | T013 | 2016-11-02 | P | 7.35 | T | T | 53100 | 7656 | 45444 | 0.2453 | 0.1924 |
1481
+ | T014 | 2016-11-02 | P | 7.45 | F | T | 5847 | 835 | 5012 | 0.2453 | 0.1924 |
1482
+ | T015 | 2016-11-02 | P | 7.75 | F | F | 500 | 72 | 428 | 0.2453 | 0.1924 |
1483
+ | T016 | 2016-11-02 | P | 8.25 | T | T | 100 | 14 | 86 | 0.2453 | 0.1924 |
1484
+
1485
+ == Formatting Tables
1486
+
1487
+ Besides creating and operating on tables, you may want to display the resulting
1488
+ table. +FatTable+ seeks to provide a set of formatting directives that are the
1489
+ most common across many output media. It provides directives for alignment, for
1490
+ color, for adding currency symbols and grouping commas to numbers, for padding
1491
+ numbers, and for formatting dates and booleans.
1492
+
1493
+ In addition, you can add any number of footers to a table, which appear at the
1494
+ end of the table, and any number of group footers, which appear after each group
1495
+ in the table. These also can be formatted independently of the table body.
1496
+
1497
+ If the target output medium does not support a formatting directive or the
1498
+ directive simply does not make sense, it is simply ignored. For example, you
1499
+ can output an +org-mode+ table as a String, and since +org-mode+ does not
1500
+ support colors, any color directives are simply ignored. Some of the output
1501
+ targets are not strings, but ruby data structures, and for them, things such as
1502
+ alignment are simply irrelevant.
1503
+
1504
+ === Available Formatters
1505
+
1506
+ +FatTable+ supports the following output targets for its tables:
1507
+
1508
+ Text:: form the table with ACSII characters,
1509
+
1510
+ Org:: form the table with ASCII characters but in the form used by Emacs
1511
+ org-mode for constructing tables,
1512
+
1513
+ Term:: form the table with ANSI terminal codes and unicode characters, possibly
1514
+ including colored text and cell backgrounds,
1515
+
1516
+ LaTeX:: form the table as input for LaTeX's longtable environment,
1517
+
1518
+ Aoh:: output the table as a ruby data structure, building the table as an array
1519
+ of hashes, and
1520
+
1521
+ Aoa:: output the table as a ruby data structure, building the table as an array
1522
+ of array,
1523
+
1524
+ These are all implemented by classes that inherit from the FatTable::Formatter
1525
+ class by defining about a dozen methods that get called at various places during
1526
+ the construction of the output table. The idea is that more classes can be
1527
+ defined by adding additional classes.
1528
+
1529
+ === Table Locations
1530
+
1531
+ In the formatting methods, the table is divided into several "locations" for
1532
+ which separate formatting directives may be given. These locations are
1533
+ identified with the following symbols:
1534
+
1535
+ :header:: the first row of the output table containing the headers,
1536
+
1537
+ :footer:: all rows of the table's footers,
1538
+
1539
+ :gfooter:: all rows of the table's group footers,
1540
+
1541
+ :body:: all the data rows of the table, that is, those that are neither part of
1542
+ the header, footers, or gfooters,
1543
+
1544
+ :bfirst:: the first row of the table's body, and
1545
+
1546
+ :gfirst:: the first row in each group in the table's body.
1547
+
1548
+ === Formatting Directives
1549
+
1550
+ The formatting methods explained in the next section all take formatting
1551
+ directives as strings in which letters and other characters signify what
1552
+ formatting applies. For example, we may apply the formatting directive +'R,$'+
1553
+ to numbers in a certain part of the table. Each of those characters, and in
1554
+ some cases a whole substring, is a single directive. They can appear in any
1555
+ order, so +'$R,'+ and +',$R'+ are equivalent.
1556
+
1557
+ Here is a list of all the formatting directives that apply to each cell type:
1558
+
1559
+ ==== String
1560
+
1561
+ For a string element, the following instructions are valid. Note that these can
1562
+ also be applied to all the other cell types as well since they are all converted
1563
+ to a string in forming the output.
1564
+
1565
+ u:: convert the element to all lowercase,
1566
+
1567
+ U:: convert the element to all uppercase,
1568
+
1569
+ t:: title case the element, that is, upcase the initial letter in each word and
1570
+ lower case the other letters
1571
+
1572
+ B or ~B:: make the element bold, or turn off bold
1573
+
1574
+ I or ~I:: make the element italic, or turn off italic
1575
+
1576
+ R:: align the element on the right of the column
1577
+
1578
+ L:: align the element on the left of the column
1579
+
1580
+ C:: align the element in the center of the column
1581
+
1582
+ c[color]:: render the element in the given color; the color can have the form
1583
+ fgcolor, fgcolor.bgcolor, or .bgcolor, to set the foreground or
1584
+ background colors respectively, and each of those can be an ANSI or
1585
+ X11 color name in addition to the special color, 'none', which keeps
1586
+ the terminal's default color.
1587
+
1588
+ _ or ~_:: underline the element, or turn off underline
1589
+
1590
+ \* or ~*:: cause the element to blink, or turn off blink
1591
+
1592
+ For example, the directive 'tCc[red.yellow]' would title-case the element,
1593
+ center it, and color it red on a yellow background. The directives that are
1594
+ boolean have negating forms so that, for example, if bold is turned on for all
1595
+ columns of a given type, it can be countermanded in formatting directives for
1596
+ particular columns.
1597
+
1598
+ ==== Numeric
1599
+
1600
+ For a numeric element, all the instructions valid for string are available, in
1601
+ addition to the following:
1602
+
1603
+ , or ~,:: insert grouping commas, or do not insert grouping commas,
1604
+
1605
+ $ or ~$:: format the number as currency according to the locale, or not,
1606
+
1607
+ m.n:: include at least m digits before the decimal point, padding on the left
1608
+ with zeroes as needed, and round the number to the n decimal places and
1609
+ include n digits after the decimal point, padding on the right with zeroes
1610
+ as needed,
1611
+
1612
+ H:: convert the number (assumed to be in units of seconds) to HH:MM:SS.ss form.
1613
+ So a column that is the result of subtracting two :datetime forms will
1614
+ result in a :numeric expressed as seconds and can be displayed in hours,
1615
+ minutes, and seconds with this formatting instruction.
1616
+
1617
+ For example, the directive 'R5.0c[blue]' would right-align the numeric element,
1618
+ pad it on the left with zeros, and color it blue.
1619
+
1620
+ ==== DateTime
1621
+
1622
+ For a datetime, all the instructions valid for string are available, in addition
1623
+ to the following:
1624
+
1625
+ \d\[fmt\]:: apply the format to a datetime that is a whole day, that is that has
1626
+ no or zero hour, minute, and second components, where fmt is a valid
1627
+ format string for Date#strftime, otherwise, the datetime will be
1628
+ formatted as an ISO 8601 string, YYYY-MM-DD.
1629
+
1630
+ \D\[fmt\]:: apply the format to a datetime that has at least a non-zero hour
1631
+ component where fmt is a valid format string for Date#strftime,
1632
+ otherwise, the datetime will be formatted as an ISO 8601 string,
1633
+ YYYY-MM-DD.
1634
+
1635
+ For example, 'c[pink]d[%b %-d, %Y]C', would format a date element like 'Sep 22,
1636
+ 1957', center it, and color it pink.
1637
+
1638
+ ==== Boolean
1639
+
1640
+ For a boolean cell, all the instructions valid for string are available, in
1641
+ addition to the following:
1642
+
1643
+ Y:: print true as 'Y' and false as 'N',
1644
+
1645
+ T:: print true as 'T' and false as 'F',
1646
+
1647
+ X:: print true as 'X' and false as '',
1648
+
1649
+ \b\[xxx,yyy\]:: print true as the string given as xxx and false as the string
1650
+ given as yyy,
1651
+
1652
+ \c\[tcolor,fcolor\]:: color a true element with tcolor and a false element with
1653
+ fcolor. Each of the colors may be specified in the same
1654
+ manner as colors for strings described above.
1655
+
1656
+ For example, the directive 'b[Yeppers,Nope]c[green.pink,red.pink]' would render
1657
+ a true boolean as 'Yeppers' colored green on pink and render a false boolean as
1658
+ 'Nope' colored red on pink. See [[https://www.youtube.com/watch?v=oLdFFD8II8U][Yeppers]].
1659
+
1660
+ ==== NilClass
1661
+
1662
+ By default, nil elements are rendered as blank cells, but you can make them
1663
+ visible with the following, and in that case, all the formatting instructions
1664
+ valid for strings are also available:
1665
+
1666
+ \n\[niltext\]:: render a nil item with the given text.
1667
+
1668
+ For example, you might want to use 'n[-]Cc[purple]' to make nils visible as a
1669
+ centered hyphen.
1670
+
1671
+ === Footers Methods
1672
+
1673
+ You can call methods on +Formatter+ objects to add footers and group footers.
1674
+ Their signatures are:
1675
+
1676
+ footer(label, *sum_cols, **agg_cols)::
1677
+ where +label+ is a label to be placed in the first cell of the footer (unless
1678
+ that column is named as one of the +sum_cols+ or +agg_cols+, in which case the
1679
+ label is ignored), +*sum_cols+ are zero or more symbols for columns to be
1680
+ summed, and +**agg_cols+ is zero or more hash-like parameters with a column
1681
+ symbol as a key and a symbol for an aggregate method as the value. This causes
1682
+ a table-wide header to be added at the bottom of the table applying the :sum
1683
+ aggregate to the +sum_cols+ and the named aggregate method to the +agg_cols+.
1684
+ A table can have any number of footers attached, and they will appear at the
1685
+ bottom of the output table in the order they are given.
1686
+
1687
+ gfooter(label, *sum_cols, **agg_cols)::
1688
+ where the parameters have the same meaning as for the +footer+ method, but
1689
+ result in a footer for each group in the table rather than the table as a
1690
+ whole. These will appear in the output table just below each group.
1691
+
1692
+ There are also a number of convenience methods for adding common footers:
1693
+
1694
+ sum_footer(*cols)::
1695
+ Add a footer summing the given columns with the label 'Total'.
1696
+
1697
+ sum_gfooter(*cols)::
1698
+ Add a group footer summing the given columns with the label 'Group Total'.
1699
+
1700
+ avg_footer(*cols)::
1701
+ Add a footer averaging the given columns with the label 'Average'.
1702
+
1703
+ avg_gfooter(*cols)::
1704
+ Add a group footer averaging the given columns with the label 'Group Average'.
1705
+
1706
+ min_footer(*cols)::
1707
+ Add a footer showing the minimum for the given columns with the label
1708
+ 'Minimum'.
1709
+
1710
+ min_gfooter(*cols)::
1711
+ Add a group footer showing the minumum for the given columns with the label
1712
+ 'Group Minimum'.
1713
+
1714
+ max_footer(*cols)::
1715
+ Add a footer showing the maximum for the given columns with the label
1716
+ 'Maximum'.
1717
+
1718
+ max_gfooter(*cols)::
1719
+ Add a group footer showing the maximum for the given columns with the label
1720
+ 'Group Maximum'.
1721
+
1722
+ === Formatting Methods
1723
+
1724
+ You can call methods on Formatter objects to specify formatting directives for
1725
+ specific columns or types. There are two methods for doing so,
1726
+ Formatter::format_for and Formatter::format.
1727
+
1728
+ ==== Instantiating a Formatter
1729
+
1730
+ There are several ways to invoke the formatting methods on a table. First, you
1731
+ can instantiate a +XXXFormatter+ object an feed it a table. There is a Formatter
1732
+ subclass for each target output medium, for example, +AoaFormatter+ will produce
1733
+ a ruby array of arrays. You can then call the +output+ method on the
1734
+ +XXXFormatter+.
1735
+
1736
+ FatTable::AoaFormatter.new(tab_a).output
1737
+
1738
+ | Id | Name | Age | Address | Salary | Join Date |
1739
+ |----+-------+-----+------------+--------+------------|
1740
+ | 1 | Paul | 32 | California | 20000 | 2001-07-13 |
1741
+ | 3 | Teddy | 23 | Norway | 20000 | 2007-12-13 |
1742
+ | 4 | Mark | 25 | Rich-Mond | 65000 | 2007-12-13 |
1743
+ | 5 | David | 27 | Texas | 85000 | 2007-12-13 |
1744
+ | 2 | Allen | 25 | Texas | | 2005-07-13 |
1745
+ | 8 | Paul | 24 | Houston | 20000 | 2005-07-13 |
1746
+ | 9 | James | 44 | Norway | 5000 | 2005-07-13 |
1747
+ | 10 | James | 45 | Texas | 5000 | |
1748
+
1749
+ The +XXXFormatter.new+ method yields the new instance to any block given, and
1750
+ you can call methods on it to affect the formatting of the output:
1751
+
1752
+ FatTable::AoaFormatter.new(tab_a) do |f|
1753
+ f.format(numeric: '0.0,R', id: '3.0C')
1754
+ end.output
1755
+
1756
+ | Id | Name | Age | Address | Salary | Join Date |
1757
+ |-----+-------+-----+------------+--------+------------|
1758
+ | 001 | Paul | 32 | California | 20,000 | 2001-07-13 |
1759
+ | 003 | Teddy | 23 | Norway | 20,000 | 2007-12-13 |
1760
+ | 004 | Mark | 25 | Rich-Mond | 65,000 | 2007-12-13 |
1761
+ | 005 | David | 27 | Texas | 85,000 | 2007-12-13 |
1762
+ | 002 | Allen | 25 | Texas | | 2005-07-13 |
1763
+ | 008 | Paul | 24 | Houston | 20,000 | 2005-07-13 |
1764
+ | 009 | James | 44 | Norway | 5,000 | 2005-07-13 |
1765
+ | 010 | James | 45 | Texas | 5,000 | |
1766
+
1767
+ ==== +FatTable+ module-level method calls
1768
+
1769
+ The +FatTable+ module provides a set of methods of the form +to_aoa+, +to_text+,
1770
+ etc., to access a +Formatter+ without having to create an instance yourself.
1771
+ Without a block, they apply the default formatting to the table and call the
1772
+ +.output+ method automatically:
1773
+
1774
+ FatTable.to_aoa(tab_a)
1775
+
1776
+ | Id | Name | Age | Address | Salary | Join Date |
1777
+ |----+-------+-----+------------+--------+------------|
1778
+ | 1 | Paul | 32 | California | 20000 | 2001-07-13 |
1779
+ | 3 | Teddy | 23 | Norway | 20000 | 2007-12-13 |
1780
+ | 4 | Mark | 25 | Rich-Mond | 65000 | 2007-12-13 |
1781
+ | 5 | David | 27 | Texas | 85000 | 2007-12-13 |
1782
+ | 2 | Allen | 25 | Texas | | 2005-07-13 |
1783
+ | 8 | Paul | 24 | Houston | 20000 | 2005-07-13 |
1784
+ | 9 | James | 44 | Norway | 5000 | 2005-07-13 |
1785
+ | 10 | James | 45 | Texas | 5000 | |
1786
+
1787
+ With a block, these methods yield a +Formatter+ instance on which you can call
1788
+ formatting and footer methods. The +.output+ method is called on the +Formatter+
1789
+ automatically after the block:
1790
+
1791
+ FatTable.to_aoa(tab_a) do |f|
1792
+ f.format(numeric: '0.0,R', id: '3.0C')
1793
+ end
1794
+
1795
+ | Id | Name | Age | Address | Salary | Join Date |
1796
+ |-----+-------+-----+------------+--------+------------|
1797
+ | 001 | Paul | 32 | California | 20,000 | 2001-07-13 |
1798
+ | 003 | Teddy | 23 | Norway | 20,000 | 2007-12-13 |
1799
+ | 004 | Mark | 25 | Rich-Mond | 65,000 | 2007-12-13 |
1800
+ | 005 | David | 27 | Texas | 85,000 | 2007-12-13 |
1801
+ | 002 | Allen | 25 | Texas | | 2005-07-13 |
1802
+ | 008 | Paul | 24 | Houston | 20,000 | 2005-07-13 |
1803
+ | 009 | James | 44 | Norway | 5,000 | 2005-07-13 |
1804
+ | 010 | James | 45 | Texas | 5,000 | |
1805
+
1806
+ ==== Calling methods on Table objects
1807
+
1808
+ Finally, you can call methods such as +to_aoa+, +to_text+, etc., directly on a
1809
+ Table:
1810
+
1811
+ tab_a.to_aoa
1812
+
1813
+ | Id | Name | Age | Address | Salary | Join Date |
1814
+ |----+-------+-----+------------+--------+------------|
1815
+ | 1 | Paul | 32 | California | 20000 | 2001-07-13 |
1816
+ | 3 | Teddy | 23 | Norway | 20000 | 2007-12-13 |
1817
+ | 4 | Mark | 25 | Rich-Mond | 65000 | 2007-12-13 |
1818
+ | 5 | David | 27 | Texas | 85000 | 2007-12-13 |
1819
+ | 2 | Allen | 25 | Texas | | 2005-07-13 |
1820
+ | 8 | Paul | 24 | Houston | 20000 | 2005-07-13 |
1821
+ | 9 | James | 44 | Norway | 5000 | 2005-07-13 |
1822
+ | 10 | James | 45 | Texas | 5000 | |
1823
+
1824
+ And you can supply a block to them as well to specify formatting or footers:
1825
+
1826
+ tab_a.to_aoa do |f|
1827
+ f.format(numeric: '0.0,R', id: '3.0C')
1828
+ f.sum_footer(:salary, :age)
1829
+ end
1830
+
1831
+ | Id | Name | Age | Address | Salary | Join Date |
1832
+ |-------+-------+-----+------------+---------+------------|
1833
+ | 001 | Paul | 32 | California | 20,000 | 2001-07-13 |
1834
+ | 003 | Teddy | 23 | Norway | 20,000 | 2007-12-13 |
1835
+ | 004 | Mark | 25 | Rich-Mond | 65,000 | 2007-12-13 |
1836
+ | 005 | David | 27 | Texas | 85,000 | 2007-12-13 |
1837
+ | 002 | Allen | 25 | Texas | | 2005-07-13 |
1838
+ | 008 | Paul | 24 | Houston | 20,000 | 2005-07-13 |
1839
+ | 009 | James | 44 | Norway | 5,000 | 2005-07-13 |
1840
+ | 010 | James | 45 | Texas | 5,000 | |
1841
+ |-------+-------+-----+------------+---------+------------|
1842
+ | Total | | 245 | | 220,000 | |
1843
+
1844
+ === The +format+ and +format_for+ methods
1845
+
1846
+ Formatters take only two kinds of methods, those that attach footers to a
1847
+ table, which are discussed in the next section, and those that specify
1848
+ formatting for table cells, which are the subject of this section.
1849
+
1850
+ To set formatting directives for all locations in a table at once, use the
1851
+ +format+ method; to set formatting directives for a particular location in the
1852
+ table, use the +format_for+ method, giving the location as the first parameter.
1853
+
1854
+ Other than that first parameter, the two methods take the same types of
1855
+ parameters. The remaining parameters are hash-like parameters that use either a
1856
+ column name or a type as the key and a string with the formatting directives to
1857
+ apply as the value. The following example says to set the formatting for all
1858
+ locations in the table and to set all numeric fields are rounded to whole
1859
+ numbers (the '0.0' part), that are right-aligned (the 'R' part), and have
1860
+ grouping commas inserted (the ',' part). But the +:id+ column is numeric, and
1861
+ the second parameter overrides the formatting for numerics in general and calls
1862
+ for the +:id+ column to be padded to three digits with zeros on the left (the
1863
+ '3.0' part) and to be centered (the 'C' part).
1864
+
1865
+ tab_a.to_aoa do |f|
1866
+ f.format(numeric: '0.0,R', id: '3.0C')
1867
+ end
1868
+
1869
+ | Id | Name | Age | Address | Salary | Join Date |
1870
+ |-----+-------+-----+------------+--------+------------|
1871
+ | 001 | Paul | 32 | California | 20,000 | 2001-07-13 |
1872
+ | 003 | Teddy | 23 | Norway | 20,000 | 2007-12-13 |
1873
+ | 004 | Mark | 25 | Rich-Mond | 65,000 | 2007-12-13 |
1874
+ | 005 | David | 27 | Texas | 85,000 | 2007-12-13 |
1875
+ | 002 | Allen | 25 | Texas | | 2005-07-13 |
1876
+ | 008 | Paul | 24 | Houston | 20,000 | 2005-07-13 |
1877
+ | 009 | James | 44 | Norway | 5,000 | 2005-07-13 |
1878
+ | 010 | James | 45 | Texas | 5,000 | |
1879
+
1880
+ The +numeric:+ directive affected the +:age+ and +:salary+ columns and the +id:+
1881
+ directive affected only the +:id+ column. All the other cells in the table had
1882
+ the default formatting applied.
1883
+
1884
+ ==== Location priority
1885
+
1886
+ Formatting for any given cell depends on its location in the table. The
1887
+ +format_for+ method takes a location to which its formatting directive are
1888
+ restricted as the first argument. It can be one of the following:
1889
+
1890
+ +:header+:: directive apply only to the header row, that is the first row, of
1891
+ the output table,
1892
+
1893
+ +:footer+:: directives apply to all the footer rows of the output table,
1894
+ regardless of how many there are,
1895
+
1896
+ +:gfooter+:: directives apply to all group footer rows of the output tables,
1897
+ regardless of how many there are,
1898
+
1899
+ +:body+:: directives apply to all rows in the body of the table unless the row
1900
+ is the first row in the table or in a group and separate directives
1901
+ for those have been given, in which case those directives apply,
1902
+
1903
+ +:gfirst+:: directives apply to the first row in each group in the output table,
1904
+ unless the row is also the first row in the table as a whole, in
1905
+ which case the +:bfirst+ directives apply,
1906
+
1907
+ +:bfirst+:: directives apply to the first row in the table.
1908
+
1909
+ If you give directives for +:body+, they are copied to +:bfirst+ and +:gfirst+
1910
+ as well and can be overridden by directives for those locations.
1911
+
1912
+ Directives given to the +format+ method apply the directives to all locations in
1913
+ the table, but they can be overridden by more specific directives given in a
1914
+ +format_for+ directive.
1915
+
1916
+ ==== Type and Column priority
1917
+
1918
+ A directive based on type applies to all columns having that type unless
1919
+ overridden by a directive specific to a named column; a directive based on a
1920
+ column name applies only to cells in that column.
1921
+
1922
+ However, there is a twist. Since the end result of formatting is to convert all
1923
+ columns to strings, the formatting directives for the +:string+ type applies to
1924
+ all columns. Likewise, since all columns may contain nils, the +nil:+ type
1925
+ applies to nils in all columns regardless of the column's type.
1926
+
1927
+ require 'fat_table'
1928
+
1929
+ tab_a.to_text do |f|
1930
+ f.format(string: 'R', id: '3.0C', salary: 'n[N/A]')
1931
+ end
1932
+
1933
+ +=====+=======+=====+============+========+============+
1934
+ | Id | Name | Age | Address | Salary | Join Date |
1935
+ +-----+-------+-----+------------+--------+------------+
1936
+ | 001 | Paul | 32 | California | 20000 | 2001-07-13 |
1937
+ | 003 | Teddy | 23 | Norway | 20000 | 2007-12-13 |
1938
+ | 004 | Mark | 25 | Rich-Mond | 65000 | 2007-12-13 |
1939
+ | 005 | David | 27 | Texas | 85000 | 2007-12-13 |
1940
+ | 002 | Allen | 25 | Texas | N/A | 2005-07-13 |
1941
+ | 008 | Paul | 24 | Houston | 20000 | 2005-07-13 |
1942
+ | 009 | James | 44 | Norway | 5000 | 2005-07-13 |
1943
+ | 010 | James | 45 | Texas | 5000 | |
1944
+ +=====+=======+=====+============+========+============+
1945
+
1946
+ The +string: 'R'+ directive causes all the cells to be right-aligned except
1947
+ +:id+ which specifies centering for the +:id+ column only. The +n[N/A]+
1948
+ directive for nil text can be used with the numeric column, +:salary+.
1949
+
1950
+ = Development
1951
+
1952
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
1953
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
1954
+ prompt that will allow you to experiment.
1955
+
1956
+ To install this gem onto your local machine, run `bundle exec rake install`. To
1957
+ release a new version, update the version number in `version.rb`, and then run
1958
+ `bundle exec rake release`, which will create a git tag for the version, push
1959
+ git commits and tags, and push the `.gem` file to
1960
+ [rubygems.org](https://rubygems.org).
1961
+
1962
+ = Contributing
1963
+
1964
+ Bug reports and pull requests are welcome on GitHub at
1965
+ https://github.com/ddoherty03/fat_table.