carray-dataframe 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1640 @@
1
+ require "carray"
2
+ require "carray/io/table"
3
+
4
+ module CA::TableMethods
5
+
6
+ def to_dataframe (&block)
7
+ df = CADataFrame.new(self, &block)
8
+ if @header or @note
9
+ df.instance_variable_set(:@header, @header)
10
+ df.instance_variable_set(:@note, @note)
11
+ class << df
12
+ attr_reader :note
13
+ def header (name=nil)
14
+ if name
15
+ return @header[name.to_s]
16
+ else
17
+ return @column_names
18
+ end
19
+ end
20
+ end
21
+ end
22
+ return df
23
+ end
24
+
25
+ alias to_df to_dataframe
26
+
27
+ end
28
+
29
+ class CADataFrame
30
+
31
+ #
32
+ # Constructor
33
+ #
34
+
35
+ def initialize (columns_or_table, row_index: nil, column_names: nil, &block)
36
+ case columns_or_table
37
+ when Hash
38
+ columns = columns_or_table
39
+ @column_names = columns.keys.map(&:to_s)
40
+ @columns = normalize_columns(columns)
41
+ @row_number = @columns.first[1].size
42
+ if @column_names.any?{ |key| @columns[key].size != @row_number }
43
+ raise "column sizes mismatch"
44
+ end
45
+ when CArray
46
+ table = columns_or_table
47
+ if column_names
48
+ @column_names = column_names.map(&:to_s)
49
+ else
50
+ if table.respond_to?(:column_names)
51
+ @column_names = table.column_names.map(&:to_s)
52
+ else
53
+ raise "data table (CArray) has no method 'column_names'."
54
+ end
55
+ end
56
+ @columns = table_to_columns(table)
57
+ @row_number = table.dim0
58
+ else
59
+ raise "unknown data"
60
+ end
61
+ if row_index
62
+ @row_index = row_index.to_ca.object
63
+ else
64
+ @row_index = nil
65
+ end
66
+ @__methods__ = {}
67
+ if block_given?
68
+ arrange(&block)
69
+ end
70
+ end
71
+
72
+ def __methods__
73
+ return @__methods__
74
+ end
75
+
76
+ def replace (other)
77
+ @column_names = other.column_names
78
+ @columns = other.columns
79
+ @row_index = other.row_index
80
+ @row_number = other.row_number
81
+ @__methors__ = other.__methods__
82
+ return self
83
+ end
84
+
85
+ private
86
+
87
+ def table_to_columns (table)
88
+ new_columns = {}
89
+ @column_names.each_with_index do |name, i|
90
+ new_columns[name] = table[nil,i]
91
+ end
92
+ return new_columns
93
+ end
94
+
95
+ def normalize_columns (columns)
96
+ new_columns = {}
97
+ columns.each_key do |key|
98
+ case columns[key]
99
+ when CArray
100
+ column = columns[key]
101
+ when Array
102
+ column = columns[key].to_ca
103
+ if column.rank != 1
104
+ list = columns[key].clone
105
+ column = CArray.object(list.size).convert { list.shift }
106
+ end
107
+ else
108
+ column = columns[key].to_ca
109
+ end
110
+ new_columns[key.to_s] = column
111
+ end
112
+ return new_columns
113
+ end
114
+
115
+ public
116
+
117
+ #
118
+ # Attributes
119
+ #
120
+
121
+ attr_reader :columns, :column_names, :row_index, :column_number, :row_number
122
+
123
+ def has_column?(name)
124
+ return @column_names.include?(name)
125
+ end
126
+
127
+ def column_types
128
+ return @columns_names.map{|name| @columns[name].data_type_name }
129
+ end
130
+
131
+ #
132
+ # Column, Row Access
133
+ #
134
+
135
+ def column (name_or_index)
136
+ case name_or_index
137
+ when Integer
138
+ return @columns[@column_names[name_or_index]]
139
+ when String, Symbol
140
+ return @columns[name_or_index.to_s]
141
+ end
142
+ end
143
+
144
+ alias col column
145
+
146
+ def row (idx)
147
+ if @row_index
148
+ addr = @row_index.search(idx)
149
+ return @column_names.map{|name| @columns[name][addr]}.to_ca
150
+ else
151
+ return @column_names.map{|name| @columns[name][idx]}.to_ca
152
+ end
153
+ end
154
+
155
+ def index
156
+ return CArray.int(@row_number).seq
157
+ end
158
+
159
+ def method (hash)
160
+ new_hash = {}
161
+ hash.each do |key, value|
162
+ new_hash[key.to_s] = value.to_s
163
+ end
164
+ @__methods__.update(new_hash)
165
+ end
166
+
167
+ def method_missing (name, *args)
168
+ if args.size == 0
169
+ name = name.to_s
170
+ if has_column?(name)
171
+ return @columns[name]
172
+ elsif has_column?(name.gsub(/_/,'.')) ### For R
173
+ return @columns[name.gsub(/_/,'.')]
174
+ elsif @__methods__.include?(name)
175
+ return @columns[@__methods__[name]]
176
+ end
177
+ end
178
+ raise "no method '#{name}' for CADataFrame"
179
+ end
180
+
181
+
182
+ #
183
+ # Iterators
184
+ #
185
+
186
+ def each_column (&block)
187
+ return @columns.each(&block)
188
+ end
189
+
190
+ def each_column_name (&block)
191
+ return @column_names.each(&block)
192
+ end
193
+
194
+ def each_row_index (&block)
195
+ if @row_index
196
+ @row_index.each(&block)
197
+ else
198
+ @row_number.times(&block)
199
+ end
200
+ end
201
+
202
+ def each_row (with: Array, &block)
203
+ if with == Array
204
+ @row_number.times do |i|
205
+ yield @columns.map{|n,c| c[i] }
206
+ end
207
+ elsif with == Hash
208
+ row = {}
209
+ @row_number.times do |i|
210
+ @column_names.each do |c|
211
+ row[c] = @columns[c][i]
212
+ end
213
+ yield row
214
+ end
215
+ else
216
+ raise "invalid data type for loop variable"
217
+ end
218
+ end
219
+
220
+ def each_row_with_row_index (with: Array, &block)
221
+ if with == Array
222
+ if @row_index
223
+ @row_index.each_with_index do |idx, i|
224
+ yield @columns.map{|n,c| c[i] }, idx
225
+ end
226
+ else
227
+ @row_number.times do |i|
228
+ yield @columns.map{|n,c| c[i] }, i
229
+ end
230
+ end
231
+ elsif with == Hash
232
+ row = {}
233
+ if @row_index
234
+ @row_index.each_with_index do |idx, i|
235
+ @column_names.each do |c|
236
+ row[c] = @columns[c][i]
237
+ end
238
+ yield row, @row_index[i]
239
+ end
240
+ else
241
+ @row_number.times do |idx, i|
242
+ @column_names.each do |c|
243
+ row[c] = @columns[c][i]
244
+ end
245
+ yield row, @row_index[i]
246
+ end
247
+ end
248
+ else
249
+ raise "invalid data type for loop variable"
250
+ end
251
+ end
252
+
253
+ #
254
+ # Referencing
255
+ #
256
+
257
+ def [] (*argv)
258
+ row, col = *argv
259
+ new_columns = {}
260
+ if col.is_a?(NilClass)
261
+ case row
262
+ when CADataFrame
263
+ each_column_name do |key|
264
+ if row.has_column?(key)
265
+ new_columns[key] = column(key).maskout(row.column(key))
266
+ else
267
+ new_columns[key] = column(key).to_ca
268
+ end
269
+ end
270
+ return CADataFrame.new(new_columns, row_index: row.row_index ? row.row_index : nil)
271
+ when String
272
+ return self[nil,row]
273
+ when Array
274
+ if row.all?{|s| s.is_a?(String) }
275
+ return self[nil,row]
276
+ else
277
+ @column_names.each do |key|
278
+ new_columns[key] = @columns[key][row]
279
+ end
280
+ end
281
+ return CADataFrame.new(new_columns, row_index: @row_index ? @row_index[row] : nil)
282
+ else
283
+ if row.is_a?(Integer)
284
+ row = [row]
285
+ end
286
+ @column_names.each do |key|
287
+ new_columns[key] = @columns[key][row]
288
+ end
289
+ return CADataFrame.new(new_columns, row_index: @row_index ? @row_index[row] : nil)
290
+ end
291
+ else
292
+ if row.is_a?(Integer)
293
+ row = [row]
294
+ end
295
+ case col
296
+ when String, Symbol
297
+ key = col.to_s
298
+ if has_column?(key)
299
+ return column(key)[row]
300
+ else
301
+ raise "unknow column name '#{key}'"
302
+ end
303
+ when Array
304
+ if col.all?{|s| s.is_a?(String) }
305
+ col.each do |key|
306
+ key = key.to_s
307
+ if has_column?(key)
308
+ new_columns[key] = column(key)[row]
309
+ else
310
+ raise "unknow column name '#{key}'"
311
+ end
312
+ end
313
+ else
314
+ keys = @column_names.to_ca[col].to_a
315
+ keys.each do |key|
316
+ new_columns[key] = column(key)[row]
317
+ end
318
+ end
319
+ return CADataFrame.new(new_columns, row_index: @row_index ? @row_index[row] : nil)
320
+ else
321
+ if col.is_a?(Integer)
322
+ col = [col]
323
+ end
324
+ keys = @column_names.to_ca[col].to_a
325
+ keys.each do |key|
326
+ new_columns[key] = column(key)[row]
327
+ end
328
+ return CADataFrame.new(new_columns, row_index: @row_index ? @row_index[row] : nil)
329
+ end
330
+ end
331
+ end
332
+
333
+ #
334
+ # Setting Values
335
+ #
336
+
337
+ def []= (*argv)
338
+ value = argv.pop
339
+ row, col = *argv
340
+ case col
341
+ when NilClass
342
+ case row
343
+ when CADataFrame
344
+ each_column_name do |key|
345
+ if row.has_column?(key)
346
+ column(key)[row.column(key)] = value
347
+ end
348
+ end
349
+ when String
350
+ self[nil,row] = value
351
+ else
352
+ col = @column_names.to_a
353
+ self[row,col] = value
354
+ end
355
+ when String, Symbol
356
+ key = col.to_s
357
+ if has_column?(key)
358
+ column(key)[row] = value
359
+ else
360
+ arrange {
361
+ append key, value
362
+ }
363
+ end
364
+ when Array
365
+ col.each do |key|
366
+ key = key.to_s
367
+ if has_column?(key)
368
+ column(key)[row] = value
369
+ else
370
+ raise "unknow column name '#{key}'"
371
+ end
372
+ end
373
+ else
374
+ if col.is_a?(Integer)
375
+ col = [col]
376
+ end
377
+ keys = @column_names.to_ca[col].to_a
378
+ keys.each do |key|
379
+ column(key)[row] = value
380
+ end
381
+ end
382
+ return value
383
+ end
384
+
385
+ def where (mask, value)
386
+ mask.column_names.each do |key|
387
+ if has_column?(key)
388
+ column(key)[mask.column(key).boolean.not] = value
389
+ end
390
+ end
391
+ return value
392
+ end
393
+
394
+ def fill (*names, value)
395
+ names.each do |name|
396
+ if has_column?(name)
397
+ column(name).fill(value)
398
+ end
399
+ end
400
+ return self
401
+ end
402
+
403
+ #
404
+ # Arrange
405
+ #
406
+
407
+ def arrange (&block)
408
+ return Arranger.new(self).arrange(&block)
409
+ end
410
+
411
+ def rename (name1, name2)
412
+ if idx = @column_names.index(name1.to_s)
413
+ @column_names[idx] = name2.to_s
414
+ column = @columns[name1.to_s]
415
+ @columns.delete(name1.to_s)
416
+ @columns[name2.to_s] = column
417
+ else
418
+ raise "unknown column name #{name1}"
419
+ end
420
+ end
421
+
422
+ def downcase
423
+ new_column_names = []
424
+ new_columns = {}
425
+ each_column_name do |name|
426
+ new_column_names << name.downcase
427
+ new_columns[name.downcase] = column(name)
428
+ end
429
+ @column_names = new_column_names
430
+ @columns = new_columns
431
+ return self
432
+ end
433
+
434
+ def append (name, new_column = nil, &block)
435
+ if new_column
436
+ # do nothing
437
+ elsif block
438
+ new_column = instance_exec(&block)
439
+ else
440
+ new_column = @columns.first[1].template(:object)
441
+ end
442
+ unless new_column.is_a?(CArray)
443
+ new_column = new_column.to_ca
444
+ end
445
+ if new_column.rank != 1 or new_column.size != @row_number
446
+ raise "invalid shape of appended column"
447
+ end
448
+ @column_names.push(name)
449
+ @columns[name] = new_column
450
+ return new_column
451
+ end
452
+
453
+ def lead (name, new_column = nil, &block)
454
+ if new_column
455
+ # do nothing
456
+ elsif block
457
+ new_column = instance_exec(&block)
458
+ else
459
+ new_column = @columns.first[1].template(:object)
460
+ end
461
+ unless new_column.is_a?(CArray)
462
+ new_column = new_column.to_ca
463
+ end
464
+ if new_column.rank != 1 or new_column.size != @row_number
465
+ raise "invalid shape of appended column"
466
+ end
467
+ @column_names.unshift(name)
468
+ @columns[name] = new_column
469
+ return new_column
470
+ end
471
+
472
+ def vacant_copy
473
+ new_columns = {}
474
+ each_column_name do |key|
475
+ new_columns[key] = CArray.object(0)
476
+ end
477
+ return CADataFrame.new(new_columns)
478
+ end
479
+
480
+ def merge (*args)
481
+ return CADataFrame.merge(self, *args)
482
+ end
483
+
484
+ def execute (&block)
485
+ return instance_exec(&block)
486
+ end
487
+
488
+ def calculate (label, &block)
489
+ hash = {}
490
+ each_column_name do |name|
491
+ begin
492
+ if block
493
+ hash[name] = [yield(name, column(name))]
494
+ else
495
+ hash[name] = [column(name).send(label.intern)]
496
+ end
497
+ rescue
498
+ hash[name] = [UNDEF]
499
+ end
500
+ end
501
+ return CADataFrame.new(hash, row_index: [label])
502
+ end
503
+
504
+ def resample (&block)
505
+ new_columns = {}
506
+ each_column_name do |name|
507
+ begin
508
+ new_columns[name] = yield(name, column(name))
509
+ rescue
510
+ end
511
+ end
512
+ return CADataFrame.new(new_columns)
513
+ end
514
+
515
+ def select (*names, &block)
516
+ if names.empty?
517
+ names = @column_names
518
+ end
519
+ if block
520
+ row = instance_exec(&block)
521
+ else
522
+ row = nil
523
+ end
524
+ new_columns = {}
525
+ names.map(&:to_s).each do |name|
526
+ new_columns[name] = column(name)[row]
527
+ end
528
+ return CADataFrame.new(new_columns, row_index: @row_index ? @row_index[row] : nil)
529
+ end
530
+
531
+ #
532
+ # Maintenance
533
+ #
534
+
535
+ def unmask! (value = nil)
536
+ each_column_name do |name|
537
+ column(name).unmask(value)
538
+ end
539
+ return self
540
+ end
541
+
542
+ def unmask (value = nil)
543
+ return to_df.unmask!(value)
544
+ end
545
+
546
+ def detouch!
547
+ @columns = @columns.clone
548
+ each_column_name do |name|
549
+ @columns[name] = @columns[name].to_ca
550
+ end
551
+ if @row_index
552
+ @row_index = @row_index.clone
553
+ end
554
+ return self
555
+ end
556
+
557
+ #
558
+ # Transformation
559
+ #
560
+
561
+ def eliminate_columns (*names)
562
+ if names.empty?
563
+ return self
564
+ end
565
+ names = names.map(&:to_s)
566
+ new_columns = {}
567
+ each_column_name do |name|
568
+ unless names.include?(name)
569
+ new_columns[name] = column(name)
570
+ end
571
+ end
572
+ return CADataFrame.new(new_columns, row_index: @row_index)
573
+ end
574
+
575
+ def reorder (&block)
576
+ index = instance_exec(&block)
577
+ new_columns = {}
578
+ each_column_name do |name|
579
+ new_columns[name] = column(name)[index]
580
+ end
581
+ return CADataFrame.new(new_columns, row_index: @row_index ? @row_index[index] : nil)
582
+ end
583
+
584
+ def order_by (*names, &block)
585
+ if names.empty?
586
+ if block
587
+ ret = instance_exec(&block)
588
+ case ret
589
+ when CArray
590
+ list = [ret]
591
+ when Array
592
+ list = ret
593
+ end
594
+ end
595
+ else
596
+ list = @columns.values_at(*names.map{|s| s.to_s})
597
+ end
598
+ return reorder { CA.sort_addr(*list) }
599
+ end
600
+
601
+ def reverse
602
+ new_columns = {}
603
+ each_column_name do |name|
604
+ new_columns[name] = column(name).reverse
605
+ end
606
+ return CADataFrame.new(new_columns, row_index: @row_index ? @row_index.reverse : nil)
607
+ end
608
+
609
+ def transpose (column_names: nil)
610
+ if column_names
611
+ column_names = header.map(&:to_s)
612
+ else
613
+ if @row_index
614
+ column_names = @row_index.convert(:object) {|v| v.to_s }
615
+ else
616
+ column_names = CArray.object(@row_number).seq("a",:succ)
617
+ end
618
+ end
619
+ return CADataFrame.new(ca.transpose, row_index: @column_names.to_ca, column_names: column_names)
620
+ end
621
+
622
+ def add_suffix (suf)
623
+ new_columns = {}
624
+ each_column_name do |name|
625
+ new_name = (name.to_s + suf).to_s
626
+ new_columns[new_name] = column(name)
627
+ end
628
+ return CADataFrame.new(new_columns, row_index: @row_index)
629
+ end
630
+
631
+ #
632
+ # Conversions
633
+ #
634
+
635
+ def to_df
636
+ new_columns = {}
637
+ each_column_name do |name|
638
+ new_columns[name] = column(name)
639
+ end
640
+ return CADataFrame.new(new_columns, row_index: @row_index).detouch!
641
+ end
642
+
643
+ def objectify
644
+ new_columns = {}
645
+ each_column_name do |name|
646
+ new_columns[name] = column(name).object
647
+ end
648
+ return CADataFrame.new(new_columns, row_index: @row_index)
649
+ end
650
+
651
+ def ca (*names)
652
+ if names.empty?
653
+ return CADFArray.new(@column_names, @columns)
654
+ else
655
+ return CADFArray.new(names.map(&:to_s), @columns)
656
+ end
657
+ end
658
+
659
+ def to_ca (*names)
660
+ return ca(*names).to_ca
661
+ end
662
+
663
+ def to_hash
664
+ hash = {}
665
+ @columns.each do |k,v|
666
+ hash[k] = v.to_a
667
+ end
668
+ return hash
669
+ end
670
+
671
+ def columns_to_hash (key_name, value_names)
672
+ hash = {}
673
+ unless @column_names.include?(key_name)
674
+ raise ArgumentError, "include invalid key column name #{key_name}"
675
+ end
676
+ case value_names
677
+ when String
678
+ unless @column_names.include?(value_names)
679
+ raise ArgumentError, "invalid key column name #{value_names}"
680
+ end
681
+ key_columns = @columns[key_name]
682
+ value_columns = @columns[value_names]
683
+ @row_number.times do |i|
684
+ hash[key_columns[i]] = value_columns[i]
685
+ end
686
+ when Array
687
+ unless value_names.all?{|s| @column_names.include?(s) }
688
+ raise ArgumentError, "include invalid column name in #{value_names.join(' ')}"
689
+ end
690
+ key_columns = @columns[key_name]
691
+ value_columns = @columns.values_at(*value_names)
692
+ @row_number.times do |i|
693
+ hash[key_columns[i]] = value_columns.map{|c| c[i]}
694
+ end
695
+ else
696
+ raise ArgumentError, "invalud argument"
697
+ end
698
+ return hash
699
+ end
700
+
701
+ private
702
+
703
+ def __obj_to_string__ (obj)
704
+ case obj
705
+ when Float
706
+ "%.6g" % obj
707
+ else
708
+ obj.to_s
709
+ end
710
+ end
711
+
712
+ def __strwidth__ (string)
713
+ if string.ascii_only?
714
+ return string.length
715
+ else
716
+ return string.each_char.inject(0){|s,c| s += c.bytesize > 1 ? 2 : 1 }
717
+ end
718
+ end
719
+
720
+ public
721
+
722
+ def ascii_table (rowmax = :full)
723
+ if @row_index
724
+ namelist = [" "] + @column_names
725
+ tbl = CADFArray.new(namelist, @columns.clone.update(" " => @row_index))
726
+ else
727
+ namelist = [" "] + @column_names
728
+ tbl = CADFArray.new(namelist, @columns.clone.update(" " => CArray.int(@row_number).seq))
729
+ end
730
+ if rowmax.is_a?(Integer) and @row_number > rowmax
731
+ list = tbl[0..(rowmax/2),nil].to_a
732
+ list.push namelist.map { "..." }
733
+ list.push *(tbl[-rowmax/2+1..-1,nil].to_a)
734
+ tbl = list.to_ca
735
+ end
736
+ datastr = tbl.convert {|c| __obj_to_string__(c) }.unmask("")
737
+ datamb = datastr.convert(:boolean, &:"ascii_only?").not.sum(0).ne(0)
738
+ namemb = namelist.to_ca.convert(:boolean) {|c| c.to_s.ascii_only? }.eq(0)
739
+ mb = datamb.or(namemb)
740
+ namelen = namelist.map(&:length).to_ca
741
+ datalen = datastr.convert(&:length)
742
+ if mb.max == 0
743
+ if datalen.size == 0
744
+ lengths = namelen.to_a
745
+ else
746
+ lengths = datalen.max(0).pmax(namelen).to_a
747
+ end
748
+ hrule = "-" + lengths.map {|len| "-"*len}.join("--") + "-"
749
+ header = " " +
750
+ [namelist, lengths].transpose.map{|name, len|
751
+ "#{name.to_s.ljust(len)}" }.join(" ") + " "
752
+ ary = [hrule, header, hrule]
753
+ if datalen.size > 0
754
+ datastr[:i,nil].each_with_index do |blk, i|
755
+ list = blk.flatten.to_a
756
+ ary << " " + [list, lengths].transpose.map{|value, len|
757
+ "#{value.ljust(len)}"}.join(" ") + " "
758
+ end
759
+ end
760
+ ary << hrule
761
+ return "DataFrame: rows#=#{@row_number}: \n" + ary.join("\n")
762
+ else
763
+ namewidth = namelist.to_ca.convert{|c| __strwidth__(c.to_s) }
764
+ if datalen.size == 0
765
+ maxwidth = namewidth
766
+ else
767
+ datawidth = datastr.convert{|c| __strwidth__(c.to_s) }
768
+ maxwidth = datawidth.max(0).pmax(namewidth)
769
+ end
770
+ len = maxwidth[:*,nil] - datawidth + datalen
771
+ hrule = "-" + maxwidth.map {|len| "-"*len}.join("--") + "-"
772
+ header = " " +
773
+ [namelist, maxwidth.to_a].transpose.map{|name, len|
774
+ "#{name.to_s.ljust(len-__strwidth__(name.to_s)+name.to_s.length)}" }.join(" ") + " "
775
+ ary = [hrule, header, hrule]
776
+ if datalen.size > 0
777
+ datastr[:i,nil].each_with_addr do |blk, i|
778
+ list = blk.flatten.to_a
779
+ ary << " " + list.map.with_index {|value, j|
780
+ "#{value.ljust(len[i,j])}"}.join(" ") + " "
781
+ end
782
+ end
783
+ ary << hrule
784
+ return "DataFrame: row#=#{@row_number}: \n" + ary.join("\n")
785
+ end
786
+ end
787
+
788
+
789
+ def inspect
790
+ return ascii_table(10)
791
+ end
792
+
793
+ def to_s
794
+ return ascii_table
795
+ end
796
+
797
+ def to_ary
798
+ return [to_s]
799
+ end
800
+
801
+
802
+ end
803
+
804
+ #############################################################
805
+ #
806
+ # ARRANGER
807
+ #
808
+ #############################################################
809
+
810
+
811
+ class CADataFrame
812
+
813
+ class Arranger
814
+
815
+ def initialize (dataframe)
816
+ @dataframe = dataframe
817
+ end
818
+
819
+ def arrange (&block)
820
+ instance_exec(&block)
821
+ return @dataframe
822
+ end
823
+
824
+ private
825
+
826
+ def column_names
827
+ return @dataframe.column_names
828
+ end
829
+
830
+ def row_number
831
+ return @dataframe.row_number
832
+ end
833
+
834
+ def method (hash)
835
+ @dataframe.method(hash)
836
+ end
837
+
838
+ def timeseries (name, fmt = "%Y-%m-%d %H:%M:%S")
839
+ @dataframe.columns[name.to_s] = @dataframe.columns[name.to_s].strptime(fmt)
840
+ end
841
+
842
+ def type (type, name, mask = :novalue)
843
+ @dataframe.columns[name.to_s] = @dataframe.columns[name.to_s].to_type(type)
844
+ if mask != :novalue
845
+ @dataframe.columns[name.to_s].maskout!(options[:maskout])
846
+ end
847
+ end
848
+
849
+ def eliminate (*names)
850
+ if names.empty?
851
+ return self
852
+ end
853
+ names = names.map(&:to_s)
854
+ @dataframe.column_names.each do |name|
855
+ if names.include?(name)
856
+ @dataframe.columns.delete(name)
857
+ @dataframe.column_names.delete(name)
858
+ end
859
+ end
860
+ end
861
+
862
+ def template (*args, &block)
863
+ return @dataframe.columns.first[1].template(*args, &block)
864
+ end
865
+
866
+ def double (*names)
867
+ names.flatten.map(&:to_s).each do |name|
868
+ if @dataframe.column_names.include?(name)
869
+ type(:double, name)
870
+ else
871
+ raise "Unknown column name '#{name}'"
872
+ end
873
+ end
874
+ end
875
+
876
+ def int (*names)
877
+ names.flatten.map(&:to_s).each do |name|
878
+ if @dataframe.column_names.include?(name)
879
+ type(:int, name)
880
+ else
881
+ raise "Unknown column name '#{name}'"
882
+ end
883
+ end
884
+ end
885
+
886
+ def maskout (value, *names)
887
+ names.flatten.map(&:to_s).each do |name|
888
+ @dataframe.columns[name].maskout!(value)
889
+ end
890
+ end
891
+
892
+ def unmask (value, *names)
893
+ names.flatten.map(&:to_s).each do |name|
894
+ @dataframe.columns[name].unmask(value)
895
+ end
896
+ end
897
+
898
+ def col (name)
899
+ return @dataframe.col(name)
900
+ end
901
+
902
+ def append (name, new_column)
903
+ if new_column
904
+ # do nothing
905
+ else
906
+ new_column = @dataframe.columns.first[1].template(:object)
907
+ end
908
+ unless new_column.is_a?(CArray)
909
+ new_column = new_column.to_ca
910
+ end
911
+ @dataframe.columns[name.to_s] = new_column
912
+ @dataframe.column_names.push(name.to_s)
913
+ end
914
+
915
+ def lead (name, new_column)
916
+ if new_column
917
+ # do nothing
918
+ else
919
+ new_column = @dataframe.columns.first[1].template(:object)
920
+ end
921
+ unless new_column.is_a?(CArray)
922
+ new_column = new_column.to_ca
923
+ end
924
+ @dataframe.columns[name.to_s] = new_column
925
+ @dataframe.column_names.unshift(name.to_s)
926
+ end
927
+
928
+ def rename (name1, name2)
929
+ if idx = @dataframe.column_names.index(name1.to_s)
930
+ @dataframe.column_names[idx] = name2.to_s
931
+ column = @dataframe.columns[name1.to_s]
932
+ @dataframe.columns.delete(name1.to_s)
933
+ @dataframe.columns[name2.to_s] = column
934
+ else
935
+ raise "unknown column name #{name1}"
936
+ end
937
+ end
938
+
939
+ def downcase
940
+ @dataframe.downcase
941
+ end
942
+
943
+ def classify (name, scale, opt = {})
944
+ return @dataframe.classify(name, scale, opt)
945
+ end
946
+
947
+ def map (mapper, name_or_column)
948
+ case name_or_column
949
+ when String, Symbol
950
+ name = name_or_column
951
+ column = @dataframe.columns[name.to_s]
952
+ when CArray
953
+ column = name_or_column
954
+ when Array
955
+ column = name_or_column.to_ca
956
+ else
957
+ raise "invalid argument"
958
+ end
959
+ case mapper
960
+ when Hash
961
+ return column.convert(:object) {|v| hash[v] }
962
+ when CArray
963
+ return mapper.project(column)
964
+ when Array
965
+ return mapper.to_ca.project(column)
966
+ end
967
+ end
968
+
969
+ def method_missing (name, *args)
970
+ if args.size == 0
971
+ if @dataframe.column_names.include?(name.to_s)
972
+ return @dataframe.columns[name.to_s]
973
+ elsif @dataframe.__methods__.include?(name.to_s)
974
+ return @dataframe.columns[@dataframe.__methods__[name.to_s]]
975
+ end
976
+ end
977
+ super
978
+ end
979
+
980
+ end
981
+
982
+ end
983
+
984
+ #############################################################
985
+ #
986
+ # Class methods
987
+ #
988
+ #############################################################
989
+
990
+ class CADataFrame
991
+
992
+ def self.merge (*args)
993
+ ref = args.first
994
+ new_columns = {}
995
+ args.each do |table|
996
+ table.column_names.each do |name|
997
+ new_columns[name] = table.col(name)
998
+ end
999
+ end
1000
+ return CADataFrame.new(new_columns, row_index: ref.row_index)
1001
+ end
1002
+
1003
+ def self.concat (*args)
1004
+ ref = args.first
1005
+ column_names = ref.column_names
1006
+ new_columns = {}
1007
+ column_names.each do |name|
1008
+ list = args.map{|t| t.col(name) }
1009
+ data_type = list.first.data_type
1010
+ new_columns[name] = CArray.bind(data_type, list, 0)
1011
+ end
1012
+ if args.map(&:row_index).all?
1013
+ new_row_index = CArray.join(*args.map(&:row_index))
1014
+ else
1015
+ new_row_index = nil
1016
+ end
1017
+ return CADataFrame.new(new_columns, row_index: new_row_index)
1018
+ end
1019
+
1020
+
1021
+ end
1022
+
1023
+ #############################################################
1024
+ #
1025
+ # CADFArray
1026
+ #
1027
+ #############################################################
1028
+
1029
+ class CADFArray < CAObject # :nodoc:
1030
+
1031
+ def initialize (column_names, columns)
1032
+ @column_names = column_names
1033
+ @columns = columns
1034
+ dim = [@columns[@column_names.first].size, @column_names.size]
1035
+ extend CA::TableMethods
1036
+ super(:object, dim, :read_only=>true)
1037
+ __create_mask__
1038
+ end
1039
+
1040
+ attr_reader :column_names
1041
+
1042
+ def fetch_index (idx)
1043
+ r, c = *idx
1044
+ name = @column_names[c]
1045
+ return @columns[name][r]
1046
+ end
1047
+
1048
+ def copy_data (data)
1049
+ @column_names.each_with_index do |name, i|
1050
+ data[nil,i] = @columns[name].value
1051
+ end
1052
+ end
1053
+
1054
+ def create_mask
1055
+ end
1056
+
1057
+ def mask_fetch_index (idx)
1058
+ r, c = *idx
1059
+ name = @column_names[c]
1060
+ if @columns[name].has_mask?
1061
+ return @columns[name].mask[r]
1062
+ else
1063
+ return 0
1064
+ end
1065
+ end
1066
+
1067
+ def mask_copy_data (data)
1068
+ @column_names.each_with_index do |name, i|
1069
+ if @columns[name].has_mask?
1070
+ data[nil,i] = @columns[name].mask
1071
+ end
1072
+ end
1073
+ end
1074
+
1075
+ def to_ca
1076
+ obj = super
1077
+ obj.extend CA::TableMethods
1078
+ obj.column_names = @column_names
1079
+ return obj
1080
+ end
1081
+
1082
+ end
1083
+
1084
+ #############################################################
1085
+ #
1086
+ # BASIC Comparison
1087
+ #
1088
+ #############################################################
1089
+
1090
+
1091
+ class CADataFrame
1092
+
1093
+ def -@
1094
+ return cmp(:-@)
1095
+ end
1096
+
1097
+ def < (other)
1098
+ return cmp(:<, other)
1099
+ end
1100
+
1101
+ def <= (other)
1102
+ return cmp(:<=, other)
1103
+ end
1104
+
1105
+ def > (other)
1106
+ return cmp(:>, other)
1107
+ end
1108
+
1109
+ def >= (other)
1110
+ return cmp(:>=, other)
1111
+ end
1112
+
1113
+ def is_masked
1114
+ return cmp(:is_masked)
1115
+ end
1116
+
1117
+ def is_finite
1118
+ return cmp(:is_finite)
1119
+ end
1120
+
1121
+ private
1122
+
1123
+ def cmp (method, *argv)
1124
+ return CADataFrame.new(ca.send(method,*argv), column_names: @column_names)
1125
+ end
1126
+
1127
+ end
1128
+
1129
+ #############################################################
1130
+ #
1131
+ # BASIC Manipulations
1132
+ #
1133
+ #############################################################
1134
+
1135
+ class CADataFrame
1136
+
1137
+ def matchup (keyname, reference)
1138
+ key = column(keyname.to_s)
1139
+ idx = reference.matchup(key)
1140
+ new_columns = {}
1141
+ each_column_name do |name|
1142
+ if name == keyname
1143
+ new_columns[name] = reference
1144
+ else
1145
+ new_columns[name] = column(name).project(idx)
1146
+ end
1147
+ end
1148
+ if @row_index
1149
+ new_row_index = @row_index.project(idx).unmask(nil)
1150
+ else
1151
+ new_row_index = nil
1152
+ end
1153
+ return CADataFrame.new(new_columns, row_index: new_row_index) {
1154
+ self.send(keyname)[] = reference
1155
+ }
1156
+ end
1157
+
1158
+ def join (table, on: nil)
1159
+ end
1160
+
1161
+ def histogram (name, scale = nil, options = nil)
1162
+ if scale.nil?
1163
+ return group_by(name).table{ { :count => col(name).count_valid } }
1164
+ else
1165
+ if options
1166
+ hist = CAHistogram.int(scale, options)
1167
+ else
1168
+ hist = CAHistogram.int(scale)
1169
+ end
1170
+ hist.increment(@columns[name.to_s])
1171
+ hash = {
1172
+ name.to_s => hist.midpoints[0],
1173
+ "#{name}_L".to_s => scale[0..-2],
1174
+ "#{name}_R".to_s => scale.shift(-1)[0..-2],
1175
+ :count => hist[0..-2].to_ca,
1176
+ }
1177
+ return CADataFrame.new(hash)
1178
+ end
1179
+ end
1180
+
1181
+ def classify (name, scale = nil, opt = {})
1182
+ if not scale
1183
+ column = @columns[name.to_s]
1184
+ mids = column.uniq
1185
+ mapper = {}
1186
+ mids.each_with_index do |v,i|
1187
+ mapper[v] = i
1188
+ end
1189
+ cls = columns.convert(:int32) {|v| mapper[v] }
1190
+ hash = {
1191
+ "#{name}_M" => mids,
1192
+ "#{name}_L" => mids,
1193
+ "#{name}_R" => mids,
1194
+ "#{name}_CLASS" => cls
1195
+ }
1196
+ else
1197
+ option = {
1198
+ :include_upper => false,
1199
+ :include_lowest => true,
1200
+ :offset => 0,
1201
+ }.update(opt)
1202
+ column = @columns[name.to_s]
1203
+ cls = scale.bin(column,
1204
+ option[:include_upper],
1205
+ option[:include_lowest],
1206
+ option[:offset])
1207
+ mids = ((scale + scale.shifted(-1))/2)[0..-2].to_ca
1208
+ left = scale[0..-2]
1209
+ right = scale.shift(-1)[0..-2]
1210
+ hash = {
1211
+ "#{name}_M" => mids.project(cls).to_ca,
1212
+ "#{name}_L" => left.project(cls).to_ca,
1213
+ "#{name}_R" => right.project(cls).to_ca,
1214
+ "#{name}_CLASS" => cls
1215
+ }
1216
+ end
1217
+ return CADataFrame.new(hash)
1218
+ end
1219
+
1220
+ def cross (name1, name2)
1221
+ col1 = column(name1)
1222
+ col2 = column(name2)
1223
+ var1 = col1.uniq.sort
1224
+ var2 = col2.uniq.sort
1225
+ hash = {}
1226
+ count = Hash.new {0}
1227
+ var1.each do |v1|
1228
+ var2.each do |v2|
1229
+ hash[[v1,v2]] = 0
1230
+ end
1231
+ end
1232
+ list = CArray.join([col1, col2]).to_a
1233
+ list.each do |item|
1234
+ hash[item] += 1
1235
+ end
1236
+ out = CArray.object(var1.size, var2.size) { 0 }
1237
+ var1.each_with_index do |v1, i|
1238
+ var2.each_with_index do |v2, j|
1239
+ out[i,j] = hash[[v1,v2]]
1240
+ end
1241
+ end
1242
+ return CADataFrame.new(out, row_index: var1, column_names: var2)
1243
+ end
1244
+
1245
+ end
1246
+
1247
+
1248
+ #############################################################
1249
+ #
1250
+ # GROUPING
1251
+ #
1252
+ #############################################################
1253
+
1254
+ class CADataFrame
1255
+
1256
+ def group_by (*names)
1257
+ if names.size == 1
1258
+ return CADataFrameGroup.new(self, names[0])
1259
+ else
1260
+ return CADataFrameGroupMulti.new(self, *names)
1261
+ end
1262
+ end
1263
+
1264
+ end
1265
+
1266
+ class CADataFrameGroup
1267
+
1268
+ def initialize (dataframe, name)
1269
+ @dataframe = dataframe
1270
+ case name
1271
+ when Hash
1272
+ name, list = name.first
1273
+ @column = @dataframe.col(name)
1274
+ @keys = list.to_ca
1275
+ else
1276
+ @column = @dataframe.col(name)
1277
+ @keys = @column.uniq.sort
1278
+ end
1279
+ @name = name.to_s
1280
+ @addrs = {}
1281
+ @keys.each do |k|
1282
+ @addrs[k] = @column.eq(k).where
1283
+ end
1284
+ end
1285
+
1286
+ def table (&block)
1287
+ hashpool = []
1288
+ @keys.each do |k|
1289
+ hashpool << @dataframe[@addrs[k]].execute(&block)
1290
+ end
1291
+ columns = {@name=>@keys}
1292
+ hashpool.each_with_index do |hash, i|
1293
+ hash.each do |key, value|
1294
+ columns[key] ||= []
1295
+ columns[key][i] = value
1296
+ end
1297
+ end
1298
+ return CADataFrame.new(columns)
1299
+ end
1300
+
1301
+ def calculate (label, &block)
1302
+ new_columns = {@name=>@keys}
1303
+ @dataframe.each_column do |name, clmn|
1304
+ if name == @name
1305
+ next
1306
+ end
1307
+ new_columns[name] = CArray.object(@keys.size) { UNDEF }
1308
+ @keys.each_with_index do |k, i|
1309
+ begin
1310
+ if block
1311
+ new_columns[name][i] = yield(name, clmn[@addrs[k]])
1312
+ else
1313
+ new_columns[name][i] = clmn[@addrs[k]].send(label.intern)
1314
+ end
1315
+ rescue
1316
+ end
1317
+ end
1318
+ end
1319
+ return CADataFrame.new(new_columns)
1320
+ end
1321
+
1322
+ def [] (group_value)
1323
+ if map = @addrs[group_value]
1324
+ return @dataframe[map]
1325
+ else
1326
+ return @dataframe.vacant_copy
1327
+ end
1328
+ end
1329
+
1330
+ def each
1331
+ @addrs.each do |key, map|
1332
+ yield @dataframe[map]
1333
+ end
1334
+ end
1335
+
1336
+ def each_with_index
1337
+ @addrs.each do |key, map|
1338
+ yield @dataframe[map], key
1339
+ end
1340
+ end
1341
+
1342
+ include Enumerable
1343
+
1344
+ end
1345
+
1346
+ class CADataFrameGroupMulti
1347
+
1348
+ def initialize (dataframe, *names)
1349
+ @rank = names.size
1350
+ @dataframe = dataframe
1351
+ @names = []
1352
+ @column = []
1353
+ @keys = []
1354
+ names.each_with_index do |name, i|
1355
+ case name
1356
+ when Hash
1357
+ name, list = name.first
1358
+ @column[i] = @dataframe.col(name)
1359
+ @keys[i] = list.to_ca
1360
+ else
1361
+ @column[i] = @dataframe.col(name)
1362
+ @keys[i] = @column[i].to_ca.uniq.sort
1363
+ end
1364
+ @names[i] = name
1365
+ end
1366
+ @addrs = {}
1367
+ each_with_keys do |list|
1368
+ flag = @column[0].eq(list[0])
1369
+ (1...@rank).each do |i|
1370
+ flag &= @column[i].eq(list[i])
1371
+ end
1372
+ @addrs[list] = flag.where
1373
+ end
1374
+ end
1375
+
1376
+ def each_with_keys (&block)
1377
+ @keys[0].to_a.product(*@keys[1..-1].map(&:to_a)).each(&block)
1378
+ end
1379
+
1380
+ def table (&block)
1381
+ hashpool = []
1382
+ each_with_keys do |list|
1383
+ hashpool << @dataframe[@addrs[list]].execute(&block)
1384
+ end
1385
+ columns = {}
1386
+ @names.each do |name|
1387
+ columns[name] = []
1388
+ end
1389
+ each_with_keys.with_index do |list,j|
1390
+ @names.each_with_index do |name,i|
1391
+ columns[name][j] = list[i]
1392
+ end
1393
+ end
1394
+ hashpool.each_with_index do |hash, i|
1395
+ hash.each do |key, value|
1396
+ columns[key] ||= []
1397
+ columns[key][i] = value
1398
+ end
1399
+ end
1400
+ return CADataFrame.new(columns)
1401
+ end
1402
+
1403
+ def [] (group_value)
1404
+ if map = @addrs[group_value]
1405
+ return @dataframe[map]
1406
+ else
1407
+ return @dataframe.vacant_copy
1408
+ end
1409
+ end
1410
+
1411
+ def each
1412
+ each_with_keys do |key|
1413
+ yield key, @dataframe[@addrs[key]]
1414
+ end
1415
+ end
1416
+
1417
+ end
1418
+
1419
+ #############################################################
1420
+ #
1421
+ # PIVOT TABLE
1422
+ #
1423
+ #############################################################
1424
+
1425
+ class CADataFrame
1426
+
1427
+ def pivot (name1, name2)
1428
+ return CADataFramePivot.new(self, name1, name2)
1429
+ end
1430
+
1431
+ end
1432
+
1433
+ class CADataFramePivot
1434
+
1435
+ def initialize (dataframe, name1, name2)
1436
+ @dataframe = dataframe
1437
+ case name1
1438
+ when Hash
1439
+ name1, list = name1.first
1440
+ @column1 = @dataframe.col(name1)
1441
+ @keys1 = list.to_ca
1442
+ else
1443
+ @column1 = @dataframe.col(name1)
1444
+ @keys1 = @column1.uniq.sort
1445
+ end
1446
+ case name2
1447
+ when Hash
1448
+ name2, list = name2.first
1449
+ @column2 = @dataframe.col(name2)
1450
+ @keys2 = list
1451
+ else
1452
+ @column2 = @dataframe.col(name2)
1453
+ @keys2 = @column2.uniq.sort
1454
+ end
1455
+ @addrs = {}
1456
+ @keys1.each do |k1|
1457
+ @keys2.each do |k2|
1458
+ @addrs[[k1,k2]] = (@column1.eq(k1) & @column2.eq(k2)).where
1459
+ end
1460
+ end
1461
+ end
1462
+
1463
+ def table (&block)
1464
+ columns = {}
1465
+ @keys2.each do |k2|
1466
+ columns[k2] = CArray.object(@keys1.size) { UNDEF }
1467
+ end
1468
+ @keys1.each_with_index do |k1, i|
1469
+ @keys2.each do |k2|
1470
+ columns[k2][i] = @dataframe[@addrs[[k1,k2]]].execute(&block)
1471
+ end
1472
+ end
1473
+ return CADataFrame.new(columns, row_index: @keys1)
1474
+ end
1475
+
1476
+ end
1477
+
1478
+
1479
+ #############################################################
1480
+ #
1481
+ # CArray
1482
+ #
1483
+ #############################################################
1484
+
1485
+
1486
+ class CArray
1487
+
1488
+ def value_counts
1489
+ hash = {}
1490
+ values = uniq
1491
+ values.each do |value|
1492
+ hash[value] = 0
1493
+ end
1494
+ each do |value|
1495
+ hash[value] += 1
1496
+ end
1497
+ counts = values.convert{|value| hash[value]}
1498
+ return CADataFrame.new({'value' => values, 'count' => counts})
1499
+ end
1500
+
1501
+ end
1502
+
1503
+
1504
+
1505
+ class CADataFrame
1506
+
1507
+ def to_sqlite3 (*args)
1508
+ self.ca.to_sqlite3(*args)
1509
+ end
1510
+
1511
+ def to_sql (tablename)
1512
+ if @column_names.any?{ |s| s =~ /[\. \-]/ }
1513
+ columns = {}
1514
+ each_column_name do |name|
1515
+ name2 = name.gsub(/[\. \-]/, '_')
1516
+ columns[name2] = column(name)
1517
+ end
1518
+ df = CADataFrame.new(columns)
1519
+ return df.to_sqlite3(database: ":memory:", table: tablename)
1520
+ else
1521
+ return to_sqlite3(database: ":memory:", table: tablename)
1522
+ end
1523
+ end
1524
+
1525
+ end
1526
+
1527
+ module SQLite3
1528
+
1529
+ class Database
1530
+
1531
+ def to_df (expr)
1532
+ return CADataFrame.load_sqlite3 self, expr
1533
+ end
1534
+
1535
+ end
1536
+
1537
+ end
1538
+
1539
+ ######################################
1540
+ #
1541
+ # IO methods
1542
+ #
1543
+ ######################################
1544
+
1545
+ require "spreadsheet"
1546
+
1547
+ class CArray
1548
+
1549
+ def save_excel (filename, &block)
1550
+ if self.rank >= 3
1551
+ raise "too large rank (>2) to write excel file"
1552
+ end
1553
+ book = Spreadsheet::Workbook.new
1554
+ worksheet = book.create_worksheet
1555
+ self.dim0.times do |i|
1556
+ worksheet.row(i).push *self[i,nil]
1557
+ end
1558
+ if block
1559
+ block.call(worksheet)
1560
+ end
1561
+ book.write(filename)
1562
+ end
1563
+
1564
+ def self.load_excel (filename, sheet=0)
1565
+ book = Spreadsheet.open(filename)
1566
+ sheet = book.worksheet(sheet)
1567
+ return sheet.map(&:to_a).to_ca
1568
+ end
1569
+
1570
+ end
1571
+
1572
+ class CADataFrame
1573
+
1574
+ def self.load_sqlite3 (*args)
1575
+ return CArray.load_sqlite3(*args).to_dataframe.arrange{ maskout nil, *column_names }
1576
+ end
1577
+
1578
+
1579
+ def self.load_csv (*args, &block)
1580
+ return CArray.load_csv(*args, &block).to_dataframe.arrange{ maskout nil, *column_names }
1581
+ end
1582
+
1583
+ def self.from_csv (*args, &block)
1584
+ return CArray.from_csv(*args, &block).to_dataframe.arrange{ maskout nil, *column_names }
1585
+ end
1586
+
1587
+ def to_csv (io = "", option = {}, rs: $/, sep: ",", fill: "", with_row_index: true, &block)
1588
+ if @row_index and with_row_index
1589
+ namelist = [""] + @column_names
1590
+ tbl = CADFArray.new(namelist, @columns.clone.update("" => @row_index))
1591
+ else
1592
+ tbl = ca.to_ca
1593
+ end
1594
+ return tbl.to_csv(io, option, rs: rs, sep: sep, fill: fill, &block)
1595
+ end
1596
+
1597
+ def to_daru
1598
+ require "daru"
1599
+ columns = {}
1600
+ each_column_name do |name|
1601
+ columns[name] = column(name).to_a
1602
+ end
1603
+ if @row_index
1604
+ return Daru::DataFrame.new(columns, index: @row_index.to_a, order: @column_names)
1605
+ else
1606
+ return Daru::DataFrame.new(columns, order: @column_names)
1607
+ end
1608
+ end
1609
+
1610
+ def to_xlsx (filename, sheet_name: 'Sheet1', with_row_index: false, &block)
1611
+ require "axlsx"
1612
+ xl = Axlsx::Package.new
1613
+ xl.use_shared_strings = true
1614
+ sheet = xl.workbook.add_worksheet(name: sheet_name)
1615
+ df = self.to_df.objectify.unmask("=NA()")
1616
+ if with_row_index
1617
+ sheet.add_row([""] + column_names)
1618
+ df.each_row_with_row_index(with: Array) do |list, i|
1619
+ sheet.add_row([i] + list)
1620
+ end
1621
+ else
1622
+ sheet.add_row(column_names)
1623
+ df.each_row(with: Array) do |list|
1624
+ sheet.add_row(list)
1625
+ end
1626
+ end
1627
+ if block_given?
1628
+ yield sheet
1629
+ end
1630
+ xl.serialize(filename)
1631
+ end
1632
+
1633
+ end
1634
+
1635
+
1636
+
1637
+
1638
+
1639
+
1640
+