fat_table 0.2.2

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