rust 0.7 → 0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/ruby-rust +3 -0
- data/lib/{rust-csv.rb → rust/core/csv.rb} +2 -1
- data/lib/rust/core/rust.rb +157 -0
- data/lib/rust/core/types/all.rb +4 -0
- data/lib/{rust-core.rb → rust/core/types/dataframe.rb} +17 -335
- data/lib/rust/core/types/datatype.rb +161 -0
- data/lib/rust/core/types/factor.rb +131 -0
- data/lib/rust/core/types/language.rb +166 -0
- data/lib/rust/core/types/list.rb +81 -0
- data/lib/rust/core/types/matrix.rb +132 -0
- data/lib/rust/core/types/s4class.rb +59 -0
- data/lib/rust/core/types/utils.rb +109 -0
- data/lib/rust/core.rb +7 -0
- data/lib/rust/models/all.rb +4 -0
- data/lib/rust/models/anova.rb +60 -0
- data/lib/rust/models/regression.rb +205 -0
- data/lib/rust/plots/all.rb +4 -0
- data/lib/rust/plots/basic-plots.rb +111 -0
- data/lib/{rust-plots.rb → rust/plots/core.rb} +1 -169
- data/lib/rust/plots/distribution-plots.rb +62 -0
- data/lib/rust/stats/all.rb +4 -0
- data/lib/{rust-basics.rb → rust/stats/correlation.rb} +2 -2
- data/lib/{rust-descriptive.rb → rust/stats/descriptive.rb} +24 -4
- data/lib/{rust-effsize.rb → rust/stats/effsize.rb} +7 -13
- data/lib/{rust-probabilities.rb → rust/stats/probabilities.rb} +1 -1
- data/lib/{rust-tests.rb → rust/stats/tests.rb} +84 -90
- data/lib/rust.rb +4 -9
- metadata +31 -13
- data/lib/rust-calls.rb +0 -80
@@ -0,0 +1,161 @@
|
|
1
|
+
require_relative '../rust'
|
2
|
+
|
3
|
+
module Rust
|
4
|
+
class RustDatatype
|
5
|
+
def self.pull_variable(variable, forced_interpreter = nil)
|
6
|
+
r_type = Rust._pull("as.character(typeof(#{variable}))")
|
7
|
+
r_class = Rust._pull("as.character(class(#{variable}))")
|
8
|
+
|
9
|
+
if forced_interpreter
|
10
|
+
raise ArgumentError, "Expected null or class as forced_interpreter" if forced_interpreter && !forced_interpreter.is_a?(Class)
|
11
|
+
raise ArgumentError, "Class #{forced_interpreter} can not handle type #{r_type}, class #{r_class}" unless forced_interpreter.can_pull?(r_type, r_class)
|
12
|
+
|
13
|
+
return forced_interpreter.pull_variable(variable, r_type, r_class)
|
14
|
+
end
|
15
|
+
|
16
|
+
candidates = []
|
17
|
+
ObjectSpace.each_object(Class) do |type|
|
18
|
+
if type < RustDatatype
|
19
|
+
if type.can_pull?(r_type, r_class)
|
20
|
+
candidates << type
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
if candidates.size > 0
|
26
|
+
type = candidates.max_by { |c| c.pull_priority }
|
27
|
+
|
28
|
+
puts "Using #{type} to pull #{variable}" if Rust.debug?
|
29
|
+
return type.pull_variable(variable, r_type, r_class)
|
30
|
+
else
|
31
|
+
if Rust._pull("length(#{variable})") == 0
|
32
|
+
return []
|
33
|
+
else
|
34
|
+
return Rust._pull(variable)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.pull_priority
|
40
|
+
0
|
41
|
+
end
|
42
|
+
|
43
|
+
def load_in_r_as(variable_name)
|
44
|
+
raise "Loading #{self.class} in R was not implemented"
|
45
|
+
end
|
46
|
+
|
47
|
+
def r_mirror_to(other_variable)
|
48
|
+
varname = self.mirrored_R_variable_name
|
49
|
+
|
50
|
+
Rust._eval("#{varname} = #{other_variable}")
|
51
|
+
Rust["#{varname}.hash"] = self.r_hash
|
52
|
+
|
53
|
+
return varname
|
54
|
+
end
|
55
|
+
|
56
|
+
def r_mirror
|
57
|
+
varname = self.mirrored_R_variable_name
|
58
|
+
|
59
|
+
if !Rust._pull("exists(\"#{varname}\")") || Rust._pull("#{varname}.hash") != self.r_hash
|
60
|
+
puts "Loading #{varname}" if Rust.debug?
|
61
|
+
Rust[varname] = self
|
62
|
+
Rust["#{varname}.hash"] = self.r_hash
|
63
|
+
else
|
64
|
+
puts "Using cached value for #{varname}" if Rust.debug?
|
65
|
+
end
|
66
|
+
|
67
|
+
return varname
|
68
|
+
end
|
69
|
+
|
70
|
+
def r_hash
|
71
|
+
self.hash.to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def mirrored_R_variable_name
|
76
|
+
return "rust.mirrored.#{self.object_id}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Null < RustDatatype
|
81
|
+
def self.can_pull?(type, klass)
|
82
|
+
return type == "NULL" && klass == "NULL"
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.pull_variable(variable, type, klass)
|
86
|
+
return nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class TrueClass
|
92
|
+
def to_R
|
93
|
+
"TRUE"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class FalseClass
|
98
|
+
def to_R
|
99
|
+
"FALSE"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Object
|
104
|
+
def to_R
|
105
|
+
raise TypeError, "Unsupported type for #{self.class}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class NilClass
|
110
|
+
def to_R
|
111
|
+
return "NULL"
|
112
|
+
end
|
113
|
+
|
114
|
+
def load_in_r_as(variable)
|
115
|
+
Rust._eval("#{variable} <- NULL")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class Numeric
|
120
|
+
def to_R
|
121
|
+
self.inspect
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class Float
|
126
|
+
def to_R
|
127
|
+
return self.nan? ? "NA" : super
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class Symbol
|
132
|
+
def to_R
|
133
|
+
return self.to_s.inspect
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class Array
|
138
|
+
def to_R
|
139
|
+
return "c(#{self.map { |e| e.to_R }.join(",")})"
|
140
|
+
end
|
141
|
+
|
142
|
+
def distribution
|
143
|
+
result = {}
|
144
|
+
self.each do |value|
|
145
|
+
result[value] = result[value].to_i + 1
|
146
|
+
end
|
147
|
+
return result
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class String
|
152
|
+
def to_R
|
153
|
+
return self.inspect
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class Range
|
158
|
+
def to_R
|
159
|
+
[range.min, range.max].to_R
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require_relative 'datatype'
|
2
|
+
|
3
|
+
module Rust
|
4
|
+
class Factor < RustDatatype
|
5
|
+
def self.can_pull?(type, klass)
|
6
|
+
return klass == "factor"
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.pull_variable(variable, type, klass)
|
10
|
+
levels = Rust["levels(#{variable})"]
|
11
|
+
values = Rust["as.integer(#{variable})"]
|
12
|
+
|
13
|
+
return Factor.new(values, levels)
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_in_r_as(variable_name)
|
17
|
+
Rust['tmp.levels'] = @levels.map { |v| v.to_s }
|
18
|
+
Rust['tmp.values'] = @values
|
19
|
+
|
20
|
+
Rust._eval("#{variable_name} <- factor(tmp.values, labels=tmp.levels)")
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(values, levels)
|
24
|
+
@levels = levels.map { |v| v.to_sym }
|
25
|
+
@values = values
|
26
|
+
end
|
27
|
+
|
28
|
+
def levels
|
29
|
+
@levels
|
30
|
+
end
|
31
|
+
|
32
|
+
def ==(other)
|
33
|
+
return false unless other.is_a?(Factor)
|
34
|
+
|
35
|
+
return @levels == other.levels && self.to_a == other.to_a
|
36
|
+
end
|
37
|
+
|
38
|
+
def [](i)
|
39
|
+
FactorValue.new(@values[i], @levels[@values[i] - 1])
|
40
|
+
end
|
41
|
+
|
42
|
+
def []=(i, value)
|
43
|
+
raise "The given value is outside the factor bounds" if value.is_a?(Integer) && (value < 1 || value > @levels.size)
|
44
|
+
|
45
|
+
if value.is_a?(FactorValue)
|
46
|
+
raise "Incompatible factor value, different levels used" unless @levels.include?(value.level) || @levels.index(value.level) + 1 == @value.value
|
47
|
+
value = value.value
|
48
|
+
end
|
49
|
+
|
50
|
+
if value.is_a?(String) || value.is_a?(Symbol)
|
51
|
+
value = value.to_sym
|
52
|
+
raise "Unsupported value #{value}; expected #{@levels.join(", ")}" unless @levels.include?(value)
|
53
|
+
|
54
|
+
value = @levels.index(value) + 1
|
55
|
+
end
|
56
|
+
|
57
|
+
@values[i] = value
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_a
|
61
|
+
@values.map { |v| FactorValue.new(v, @levels[v - 1]) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def method_missing(method, *args, &block)
|
65
|
+
raise NoMethodError, "Undefined method #{method} for Factor" if method.to_s.end_with?("!") || method.end_with?("=")
|
66
|
+
|
67
|
+
self.to_a.method(method).call(*args, &block)
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_s
|
71
|
+
self.to_a.to_s
|
72
|
+
end
|
73
|
+
|
74
|
+
def inspect
|
75
|
+
self.to_a.inspect
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class FactorValue
|
80
|
+
def initialize(value, level)
|
81
|
+
@value = value
|
82
|
+
@level = level
|
83
|
+
end
|
84
|
+
|
85
|
+
def value
|
86
|
+
@value
|
87
|
+
end
|
88
|
+
|
89
|
+
def level
|
90
|
+
@level
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_i
|
94
|
+
@value
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_sym
|
98
|
+
@level
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_R
|
102
|
+
self.to_i
|
103
|
+
end
|
104
|
+
|
105
|
+
def inspect
|
106
|
+
@level.inspect
|
107
|
+
end
|
108
|
+
|
109
|
+
def ==(other)
|
110
|
+
if other.is_a?(FactorValue)
|
111
|
+
@value == other.value && @level == other.level
|
112
|
+
elsif other.is_a?(Integer)
|
113
|
+
@value == other
|
114
|
+
elsif other.is_a?(Symbol)
|
115
|
+
@level == other
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def hash
|
120
|
+
@value.hash + @level.hash
|
121
|
+
end
|
122
|
+
|
123
|
+
def eql?(other)
|
124
|
+
return self == other
|
125
|
+
end
|
126
|
+
|
127
|
+
def method_missing(method, *args, &block)
|
128
|
+
@level.method(method).call(*args, &block)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require_relative 'datatype'
|
2
|
+
|
3
|
+
module Rust
|
4
|
+
class Formula < RustDatatype
|
5
|
+
def self.can_pull?(type, klass)
|
6
|
+
return klass == "formula" || (klass.is_a?(Array) && klass.include?("formula"))
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.pull_variable(variable, type, klass)
|
10
|
+
formula_elements = Rust._pull("as.character(#{variable})")
|
11
|
+
|
12
|
+
assert("The number of elements of a formula must be 2 or 3: #{formula_elements} given") { formula_elements.size > 1 && formula_elements.size < 4 }
|
13
|
+
if formula_elements.size == 2
|
14
|
+
return Formula.new(nil, formula_elements[1])
|
15
|
+
elsif formula_elements.size == 3
|
16
|
+
return Formula.new(formula_elements[2], formula_elements[1])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def load_in_r_as(variable_name)
|
21
|
+
Rust._eval("#{variable_name} <- #{self.left_part} ~ #{self.right_part}")
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :left_part
|
25
|
+
attr_reader :right_part
|
26
|
+
|
27
|
+
def initialize(left_part, right_part)
|
28
|
+
raise ArgumentError, "Expected string" if left_part && !left_part.is_a?(String)
|
29
|
+
raise ArgumentError, "Expected string" if !right_part.is_a?(String)
|
30
|
+
|
31
|
+
@left_part = left_part || ""
|
32
|
+
@right_part = right_part
|
33
|
+
end
|
34
|
+
|
35
|
+
def ==(oth)
|
36
|
+
return false unless oth.is_a?(Formula)
|
37
|
+
|
38
|
+
return @left_part == oth.left_part && @right_part == oth.right_part
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_R
|
42
|
+
return "#@left_part ~ #@right_part"
|
43
|
+
end
|
44
|
+
|
45
|
+
def inspect
|
46
|
+
return self.to_R.strip
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Call < RustDatatype
|
51
|
+
def self.can_pull?(type, klass)
|
52
|
+
return klass == "call"
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.pull_variable(variable, type, klass)
|
56
|
+
return Call.new(Rust["deparse(#{variable})"])
|
57
|
+
end
|
58
|
+
|
59
|
+
def load_in_r_as(variable_name)
|
60
|
+
Rust["call.str"] = @value
|
61
|
+
Rust._eval("#{variable_name} <- str2lang(call.str)")
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize(value)
|
65
|
+
@value = value
|
66
|
+
end
|
67
|
+
|
68
|
+
def value
|
69
|
+
@value
|
70
|
+
end
|
71
|
+
|
72
|
+
def inspect
|
73
|
+
@value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class Environment < RustDatatype
|
78
|
+
def self.can_pull?(type, klass)
|
79
|
+
return type == "environment" && klass == "environment"
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.pull_variable(variable, type, klass)
|
83
|
+
warn "Exchanging R environments is not supported!"
|
84
|
+
return Environment.new
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.load_in_r_as(variable)
|
88
|
+
warn "Exchanging R environments is not supported!"
|
89
|
+
Rust._eval("#{variable} <- environment()")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class Function
|
94
|
+
attr_reader :name
|
95
|
+
attr_reader :arguments
|
96
|
+
attr_reader :options
|
97
|
+
|
98
|
+
def initialize(name)
|
99
|
+
@function = name
|
100
|
+
@arguments = Arguments.new
|
101
|
+
@options = Options.new
|
102
|
+
end
|
103
|
+
|
104
|
+
def options=(options)
|
105
|
+
raise TypeError, "Expected Options" unless options.is_a?(Options)
|
106
|
+
|
107
|
+
@options = options
|
108
|
+
end
|
109
|
+
|
110
|
+
def arguments=(arguments)
|
111
|
+
raise TypeError, "Expected Arguments" unless options.is_a?(Arguments)
|
112
|
+
|
113
|
+
@arguments = arguments
|
114
|
+
end
|
115
|
+
|
116
|
+
def to_R
|
117
|
+
params = [@arguments.to_R, @options.to_R].select { |v| v != "" }.join(",")
|
118
|
+
return "#@function(#{params})"
|
119
|
+
end
|
120
|
+
|
121
|
+
def call
|
122
|
+
Rust._eval(self.to_R)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class SimpleFormula
|
127
|
+
def initialize(dependent, independent)
|
128
|
+
@dependent = dependent
|
129
|
+
@independent = independent
|
130
|
+
end
|
131
|
+
|
132
|
+
def to_R
|
133
|
+
return "#@dependent ~ #@independent"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class Variable
|
138
|
+
def initialize(name)
|
139
|
+
@name = name
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_R
|
143
|
+
@name
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class Arguments < Array
|
148
|
+
def to_R
|
149
|
+
return self.map { |v| v.to_R }.join(", ")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class Options < Hash
|
154
|
+
def to_R
|
155
|
+
return self.map { |k, v| "#{k}=#{v.to_R}" }.join(", ")
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.from_hash(hash)
|
159
|
+
options = Options.new
|
160
|
+
hash.each do |key, value|
|
161
|
+
options[key.to_s] = value
|
162
|
+
end
|
163
|
+
return options
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require_relative 'datatype'
|
2
|
+
|
3
|
+
module Rust
|
4
|
+
class List < RustDatatype
|
5
|
+
def self.can_pull?(type, klass)
|
6
|
+
return type == "list"
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.pull_variable(variable, type, klass)
|
10
|
+
return List.new(klass) if Rust._pull("length(#{variable})") == 0
|
11
|
+
|
12
|
+
names = [Rust["names(#{variable})"]].flatten
|
13
|
+
length = Rust["length(#{variable})"]
|
14
|
+
|
15
|
+
list = List.new(klass, names)
|
16
|
+
for i in 0...length
|
17
|
+
list[i] = Rust["#{variable}[[#{i + 1}]]"]
|
18
|
+
end
|
19
|
+
|
20
|
+
return list
|
21
|
+
end
|
22
|
+
|
23
|
+
def load_in_r_as(variable_name)
|
24
|
+
Rust._eval("#{variable_name} <- list()")
|
25
|
+
@data.each do |key, value|
|
26
|
+
Rust["#{variable_name}[[#{key + 1}]]"] = value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(klass, names = [])
|
31
|
+
@data = {}
|
32
|
+
@names = names
|
33
|
+
@klass = klass
|
34
|
+
end
|
35
|
+
|
36
|
+
def [](key)
|
37
|
+
key = get_key(key)
|
38
|
+
|
39
|
+
return @data[key]
|
40
|
+
end
|
41
|
+
alias :| :[]
|
42
|
+
|
43
|
+
def []=(key, value)
|
44
|
+
key = get_key(key)
|
45
|
+
|
46
|
+
return @data[key] = value
|
47
|
+
end
|
48
|
+
|
49
|
+
def names
|
50
|
+
@names
|
51
|
+
end
|
52
|
+
|
53
|
+
def inspect
|
54
|
+
result = ""
|
55
|
+
values_inspected = @data.map { |k, v| [k, v.inspect.split("\n").map { |l| " " + l }.join("\n")] }.to_h
|
56
|
+
max_length = [values_inspected.map { |k, v| v.split("\n").map { |line| line.length }.max.to_i }.max.to_i, 100].min
|
57
|
+
|
58
|
+
@data.keys.each do |i|
|
59
|
+
result << "-" * max_length + "\n"
|
60
|
+
result << (@names[i] || "[[#{i}]]") + "\n"
|
61
|
+
result << values_inspected[i] + "\n"
|
62
|
+
end
|
63
|
+
result << "-" * max_length
|
64
|
+
|
65
|
+
return result
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def get_key(key)
|
70
|
+
if key.is_a?(String)
|
71
|
+
new_key = @names.index(key)
|
72
|
+
raise ArgumentError, "Wrong key: #{key}" unless new_key
|
73
|
+
key = new_key
|
74
|
+
end
|
75
|
+
|
76
|
+
raise ArgumentError, "The key should be either a string or an integer" unless key.is_a?(Integer)
|
77
|
+
|
78
|
+
return key
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require_relative 'datatype'
|
2
|
+
|
3
|
+
module Rust
|
4
|
+
class Matrix < RustDatatype
|
5
|
+
def self.can_pull?(type, klass)
|
6
|
+
return klass.is_a?(Array) && klass.include?("matrix")
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.pull_variable(variable, type, klass)
|
10
|
+
if Rust._pull("length(#{variable})") == 1
|
11
|
+
core = ::Matrix[[Rust._pull("#{variable}[1]")]]
|
12
|
+
else
|
13
|
+
core = Rust._pull(variable)
|
14
|
+
end
|
15
|
+
row_names = [Rust["rownames(#{variable})"]].flatten
|
16
|
+
column_names = [Rust["colnames(#{variable})"]].flatten
|
17
|
+
|
18
|
+
row_names = nil if row_names.all? { |v| v == nil }
|
19
|
+
column_names = nil if column_names.all? { |v| v == nil }
|
20
|
+
|
21
|
+
Matrix.new(core, row_names, column_names)
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_in_r_as(variable_name)
|
25
|
+
matrix = ::Matrix[*@data]
|
26
|
+
|
27
|
+
Rust[variable_name] = matrix
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(data, row_names = nil, column_names = nil)
|
31
|
+
@data = data.clone
|
32
|
+
|
33
|
+
@row_names = row_names
|
34
|
+
@column_names = column_names
|
35
|
+
|
36
|
+
if @data.is_a?(::Matrix)
|
37
|
+
@data = @data.row_vectors.map { |v| v.to_a }
|
38
|
+
end
|
39
|
+
|
40
|
+
if self.flatten.size == 0
|
41
|
+
raise "Empty matrices are not allowed"
|
42
|
+
else
|
43
|
+
raise TypeError, "Expected array of array" unless @data.is_a?(Array) || @data[0].is_a?(Array)
|
44
|
+
raise TypeError, "Only numeric matrices are supported" unless self.flatten.all? { |e| e.is_a?(Numeric) }
|
45
|
+
raise "All the rows must have the same size" unless @data.map { |row| row.size }.uniq.size == 1
|
46
|
+
raise ArgumentError, "Expected row names #@row_names to match the number of rows in #{self.inspect}" if @row_names && @row_names.size != self.rows
|
47
|
+
raise ArgumentError, "Expected column names #@column_names to match the number of columns in #{self.inspect}" if @column_names && @column_names.size != self.cols
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def [](i, j)
|
52
|
+
i, j = indices(i, j)
|
53
|
+
|
54
|
+
return @data[i][j]
|
55
|
+
end
|
56
|
+
|
57
|
+
def rows
|
58
|
+
@data.size
|
59
|
+
end
|
60
|
+
|
61
|
+
def cols
|
62
|
+
@data[0].size
|
63
|
+
end
|
64
|
+
|
65
|
+
def flatten
|
66
|
+
return @data.flatten
|
67
|
+
end
|
68
|
+
|
69
|
+
def []=(i, j, value)
|
70
|
+
i, j = indices(i, j)
|
71
|
+
|
72
|
+
@data[i][j] = value
|
73
|
+
end
|
74
|
+
|
75
|
+
def inspect
|
76
|
+
row_names = @row_names || (0...self.rows).to_a.map { |v| v.to_s }
|
77
|
+
column_names = @column_names || (0...self.cols).to_a.map { |v| v.to_s }
|
78
|
+
|
79
|
+
separator = " | "
|
80
|
+
col_widths = column_names.map do |colname|
|
81
|
+
[
|
82
|
+
colname,
|
83
|
+
(
|
84
|
+
[colname ? colname.length : 1] +
|
85
|
+
@data.map {|r| r[column_names.index(colname)]}.map { |e| e.inspect.length }
|
86
|
+
).max
|
87
|
+
]
|
88
|
+
end.to_h
|
89
|
+
col_widths[:rowscol] = row_names.map { |rowname| rowname.length }.max + 3
|
90
|
+
|
91
|
+
result = ""
|
92
|
+
result << "-" * (col_widths.values.sum + ((col_widths.size - 1) * separator.length)) + "\n"
|
93
|
+
result << (" " * col_widths[:rowscol]) + column_names.map { |colname| (" " * (col_widths[colname] - colname.length)) + colname }.join(separator) + "\n"
|
94
|
+
result << "-" * (col_widths.values.sum + ((col_widths.size - 1) * separator.length)) + "\n"
|
95
|
+
|
96
|
+
@data.each_with_index do |row, i|
|
97
|
+
row_name = row_names[i]
|
98
|
+
row = column_names.zip(row)
|
99
|
+
|
100
|
+
index_part = "[" + (" " * (col_widths[:rowscol] - row_name.length - 3)) + "#{row_name}] "
|
101
|
+
row_part = row.map { |colname, value| (" " * (col_widths[colname] - value.inspect.length)) + value.inspect }.join(separator)
|
102
|
+
|
103
|
+
result << index_part + row_part + "\n"
|
104
|
+
end
|
105
|
+
|
106
|
+
result << "-" * (col_widths.values.sum + ((col_widths.size - 1) * separator.length))
|
107
|
+
|
108
|
+
return result
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
def indices(i, j)
|
113
|
+
if i.is_a?(String)
|
114
|
+
ri = @row_names.index(i)
|
115
|
+
raise ArgumentError, "Can not find row #{i}" unless ri
|
116
|
+
i = ri
|
117
|
+
end
|
118
|
+
|
119
|
+
if j.is_a?(String)
|
120
|
+
rj = @column_names.index(j)
|
121
|
+
raise ArgumentError, "Can not find column #{j}" unless rj
|
122
|
+
j = rj
|
123
|
+
end
|
124
|
+
|
125
|
+
raise ArgumentError, "Expected i and j to be both integers or strings" unless i.is_a?(Integer) && j.is_a?(Integer)
|
126
|
+
raise "Wrong i" unless i.between?(0, @data.size - 1)
|
127
|
+
raise "Wrong j" unless j.between?(0, @data[0].size - 1)
|
128
|
+
|
129
|
+
return [i, j]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|