fat_table 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,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.