scoruby 0.2.7 → 0.2.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -0
- data/.travis.yml +4 -2
- data/Gemfile.lock +4 -4
- data/README.md +9 -6
- data/bin/console +4 -3
- data/lib/scoruby/decision.rb +8 -6
- data/lib/scoruby/features.rb +5 -4
- data/lib/scoruby/models/decision_tree.rb +5 -3
- data/lib/scoruby/models/gbm.rb +10 -8
- data/lib/scoruby/models/naive_bayes/model.rb +24 -14
- data/lib/scoruby/models/naive_bayes/model_data.rb +11 -7
- data/lib/scoruby/models/random_forest.rb +26 -11
- data/lib/scoruby/models_factory.rb +2 -0
- data/lib/scoruby/node.rb +7 -8
- data/lib/scoruby/predicate_factory.rb +4 -5
- data/lib/scoruby/predicates/compound_predicate.rb +12 -7
- data/lib/scoruby/predicates/false_predicate.rb +4 -2
- data/lib/scoruby/predicates/simple_predicate.rb +12 -6
- data/lib/scoruby/predicates/simple_set_predicate.rb +6 -5
- data/lib/scoruby/predicates/true_predicate.rb +3 -1
- data/lib/scoruby/version.rb +3 -1
- data/lib/scoruby.rb +5 -3
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f78c07d033cba7c4f13531ae30e51f9c00b33d11
|
4
|
+
data.tar.gz: d487653a5f8604e669db89a8766b4e84d3e1950b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d64f07f42472a9ae54435d26d738f05381a3ae2542c4ad2fdfdf879696e0af7c6bd8df6d946fb4aafed2e316fcc00c09c75b164c6a1d56b11ea5cd2ed48b82a5
|
7
|
+
data.tar.gz: 5ffde48fb242ef57f3c06f089aecfe73b5b9b8d9038a75caae516a368619aba3f1d597ddf664783346c538f26fbe9756efc02d20036900d2d94abd0fb7219204
|
data/.rubocop.yml
ADDED
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
scoruby (0.2.
|
4
|
+
scoruby (0.2.7)
|
5
5
|
nokogiri (~> 1.7)
|
6
6
|
|
7
7
|
GEM
|
@@ -18,9 +18,9 @@ GEM
|
|
18
18
|
docile (1.1.5)
|
19
19
|
json (2.1.0)
|
20
20
|
method_source (0.8.2)
|
21
|
-
mini_portile2 (2.
|
22
|
-
nokogiri (1.8.
|
23
|
-
mini_portile2 (~> 2.
|
21
|
+
mini_portile2 (2.3.0)
|
22
|
+
nokogiri (1.8.1)
|
23
|
+
mini_portile2 (~> 2.3.0)
|
24
24
|
pry (0.10.3)
|
25
25
|
coderay (~> 1.1.0)
|
26
26
|
method_source (~> 0.8.1)
|
data/README.md
CHANGED
@@ -7,12 +7,15 @@
|
|
7
7
|
|
8
8
|
Ruby scoring API for Predictive Model Markup Language (PMML).
|
9
9
|
|
10
|
-
Currently supports Decision Tree, Random Forest Naive Bayes and Gradient Boosted Models.
|
10
|
+
Currently supports Decision Tree, Random Forest, Naive Bayes and Gradient Boosted Models.
|
11
11
|
|
12
12
|
Will be happy to implement new models by demand, or assist with any other issue.
|
13
13
|
|
14
14
|
Contact me here or at aschers@gmail.com.
|
15
15
|
|
16
|
+
[Tutorial - Deploy Machine Learning Models from R Research to Ruby Production with PMML](https://medium.com/@aschers/deploy-machine-learning-models-from-r-research-to-ruby-go-production-with-pmml-b41e79445d3d)
|
17
|
+
|
18
|
+
|
16
19
|
## Installation
|
17
20
|
|
18
21
|
Add this line to your application's Gemfile:
|
@@ -37,7 +40,7 @@ Or install it yourself as:
|
|
37
40
|
|
38
41
|
```ruby
|
39
42
|
|
40
|
-
random_forest = Scoruby.
|
43
|
+
random_forest = Scoruby.load_model 'titanic_rf.pmml'
|
41
44
|
features = {
|
42
45
|
Sex: 'male',
|
43
46
|
Parch: 0,
|
@@ -66,7 +69,7 @@ random_forest.decisions_count(features)
|
|
66
69
|
|
67
70
|
```ruby
|
68
71
|
|
69
|
-
gbm = Scoruby.
|
72
|
+
gbm = Scoruby.load_model 'gbm.pmml'
|
70
73
|
|
71
74
|
features = {
|
72
75
|
Sex: 'male',
|
@@ -87,17 +90,17 @@ gbm.score(features)
|
|
87
90
|
### Decision Tree
|
88
91
|
|
89
92
|
```ruby
|
90
|
-
decision_tree = Scoruby.
|
93
|
+
decision_tree = Scoruby.load_model 'decision_tree.pmml'
|
91
94
|
features = { f1 : v1, ... }
|
92
95
|
decision_tree.decide(features)
|
93
96
|
|
94
97
|
=> #<Decision:0x007fc232384180 @score="0", @score_distribution={"0"=>"0.999615579933873", "1"=>"0.000384420066126561"}>
|
95
98
|
```
|
96
99
|
|
97
|
-
###
|
100
|
+
### Naive Bayes
|
98
101
|
|
99
102
|
```ruby
|
100
|
-
naive_bayes = Scoruby.
|
103
|
+
naive_bayes = Scoruby.load_model 'naive_bayes.pmml'
|
101
104
|
features = { f1: v1 , ... }
|
102
105
|
naive_bayes.lvalues(features)
|
103
106
|
naive_bayes.score(features, 'l1')
|
data/bin/console
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'scoruby'
|
5
6
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +11,5 @@ require "scoruby"
|
|
10
11
|
# require "pry"
|
11
12
|
# Pry.start
|
12
13
|
|
13
|
-
require
|
14
|
+
require 'pry'
|
14
15
|
Pry.start
|
data/lib/scoruby/decision.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Scoruby
|
2
4
|
class Decision
|
3
|
-
|
4
5
|
attr_reader :score, :score_distribution
|
5
6
|
|
6
7
|
def initialize(score, score_distributions)
|
@@ -8,10 +9,11 @@ module Scoruby
|
|
8
9
|
return if score_distributions.empty?
|
9
10
|
|
10
11
|
@score_distribution = {}
|
11
|
-
score_distributions.each
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
score_distributions.each do |score_distribution|
|
13
|
+
value = score_distribution.attributes['value'].to_s
|
14
|
+
probability = score_distribution.attributes['probability'].to_s
|
15
|
+
@score_distribution[value] = probability
|
16
|
+
end
|
15
17
|
end
|
16
18
|
end
|
17
|
-
end
|
19
|
+
end
|
data/lib/scoruby/features.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Scoruby
|
2
4
|
class Features
|
3
|
-
|
4
5
|
attr_reader :formatted
|
5
6
|
|
6
7
|
def initialize(features)
|
@@ -8,11 +9,11 @@ module Scoruby
|
|
8
9
|
end
|
9
10
|
|
10
11
|
def format_booleans(features)
|
11
|
-
features.map
|
12
|
+
features.map do |k, v|
|
12
13
|
features[k] = 'f' if v == false
|
13
14
|
features[k] = 't' if v == true
|
14
|
-
|
15
|
+
end
|
15
16
|
features
|
16
17
|
end
|
17
18
|
end
|
18
|
-
end
|
19
|
+
end
|
@@ -1,9 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'scoruby/node'
|
2
4
|
|
3
5
|
module Scoruby
|
4
6
|
module Models
|
5
7
|
class DecisionTree
|
6
|
-
|
7
8
|
attr_reader :root
|
8
9
|
|
9
10
|
def initialize(tree_xml)
|
@@ -31,8 +32,9 @@ module Scoruby
|
|
31
32
|
end
|
32
33
|
|
33
34
|
def didnt_step?(curr, prev)
|
34
|
-
return false if
|
35
|
-
|
35
|
+
return false if prev.pred != curr.pred
|
36
|
+
feature = curr.children[0].pred.field
|
37
|
+
Scoruby.logger.error "Null tree: #{@id}, bad feature: #{feature}"
|
36
38
|
true
|
37
39
|
end
|
38
40
|
end
|
data/lib/scoruby/models/gbm.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'scoruby/models/decision_tree'
|
2
4
|
require 'scoruby/features'
|
3
5
|
|
@@ -8,10 +10,10 @@ module Scoruby
|
|
8
10
|
CONST_XPATH = '//Target/@rescaleConstant'
|
9
11
|
|
10
12
|
def initialize(xml)
|
11
|
-
@decision_trees = xml.xpath(GBM_FOREST_XPATH).
|
13
|
+
@decision_trees = xml.xpath(GBM_FOREST_XPATH).map do |xml_tree|
|
12
14
|
DecisionTree.new(xml_tree)
|
13
|
-
|
14
|
-
@const
|
15
|
+
end
|
16
|
+
@const = Float(xml.xpath(CONST_XPATH).to_s)
|
15
17
|
end
|
16
18
|
|
17
19
|
def tree_count
|
@@ -20,11 +22,11 @@ module Scoruby
|
|
20
22
|
|
21
23
|
def score(features)
|
22
24
|
formatted_features = Features.new(features).formatted
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
Math.exp(
|
25
|
+
scores = @decision_trees.map do |dt|
|
26
|
+
dt.decide(formatted_features).score.to_s.to_f
|
27
|
+
end
|
28
|
+
sum = scores.reduce(:+) + @const
|
29
|
+
Math.exp(sum) / (1 + Math.exp(sum))
|
28
30
|
end
|
29
31
|
end
|
30
32
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'scoruby/models/naive_bayes/model_data'
|
2
4
|
require 'forwardable'
|
3
5
|
|
@@ -6,7 +8,8 @@ module Scoruby
|
|
6
8
|
module NaiveBayes
|
7
9
|
class Model
|
8
10
|
extend Forwardable
|
9
|
-
def_delegators :@model_data, :threshold, :labels, :numerical_features,
|
11
|
+
def_delegators :@model_data, :threshold, :labels, :numerical_features,
|
12
|
+
:category_features
|
10
13
|
|
11
14
|
def initialize(xml)
|
12
15
|
@model_data = ModelData.new(xml)
|
@@ -35,28 +38,35 @@ module Scoruby
|
|
35
38
|
end
|
36
39
|
|
37
40
|
def calc_label_feature_values(features)
|
38
|
-
labels.
|
41
|
+
labels.each_key do |label|
|
39
42
|
features.each do |feature_name, feature_value|
|
40
|
-
label_value =
|
41
|
-
label_value ||=
|
43
|
+
label_value = category(feature_name, feature_value, label)
|
44
|
+
label_value ||= numerical(feature_name, feature_value, label)
|
42
45
|
labels[label][feature_name] = label_value if label_value
|
43
46
|
end
|
44
47
|
end
|
45
48
|
end
|
46
49
|
|
47
|
-
def
|
48
|
-
|
49
|
-
|
50
|
-
|
50
|
+
def category(feature_name, feature_value, label)
|
51
|
+
model_feature = category_features[feature_name]
|
52
|
+
return unless model_feature && model_feature[feature_value]
|
53
|
+
value_count = model_feature[feature_value][label].to_f
|
54
|
+
overall_count = model_feature.map { |_, value| value[label].to_f }
|
55
|
+
.reduce(0, :+)
|
51
56
|
value_count / overall_count
|
52
57
|
end
|
53
58
|
|
54
|
-
def
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
59
|
+
def numerical(feature_name, feature_value, label)
|
60
|
+
model_feature = numerical_features[feature_name]
|
61
|
+
return unless model_feature && model_feature[label]
|
62
|
+
calc_numerical(feature_value.to_f,
|
63
|
+
model_feature[label][:mean].to_f,
|
64
|
+
model_feature[label][:variance].to_f)
|
65
|
+
end
|
66
|
+
|
67
|
+
def calc_numerical(feature_value, mean, variance)
|
68
|
+
Math.exp(-(feature_value - mean)**2 / (2 * variance)) /
|
69
|
+
Math.sqrt(2 * Math::PI * variance)
|
60
70
|
end
|
61
71
|
end
|
62
72
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Scoruby
|
2
4
|
module Models
|
3
5
|
module NaiveBayes
|
@@ -14,15 +16,17 @@ module Scoruby
|
|
14
16
|
private
|
15
17
|
|
16
18
|
def fetch_threshold
|
17
|
-
@threshold = @xml.xpath('//NaiveBayesModel').attr('threshold')
|
19
|
+
@threshold = @xml.xpath('//NaiveBayesModel').attr('threshold')
|
20
|
+
.value.to_f
|
18
21
|
end
|
19
22
|
|
20
23
|
def fetch_features_data
|
21
24
|
@category_features = {}
|
22
25
|
@numerical_features = {}
|
23
26
|
@xml.xpath('//BayesInput').each do |feature|
|
24
|
-
|
25
|
-
@
|
27
|
+
field_name = feature.attr('fieldName').to_sym
|
28
|
+
@category_features[field_name] = fetch_category_feature(feature)
|
29
|
+
@numerical_features[field_name] = fetch_numerical_feature(feature)
|
26
30
|
end
|
27
31
|
end
|
28
32
|
|
@@ -33,14 +37,14 @@ module Scoruby
|
|
33
37
|
@labels[l.attr('value')] = { 'count': l.attr('count').to_f }
|
34
38
|
end
|
35
39
|
end
|
36
|
-
|
40
|
+
|
37
41
|
def fetch_numerical_feature(feature)
|
38
42
|
return unless feature.child.name == 'TargetValueStats'
|
39
43
|
features_data = {}
|
40
44
|
feature.child.children.each do |child|
|
41
45
|
features_data[child.attr('value').strip] = {
|
42
|
-
|
43
|
-
|
46
|
+
mean: child.child.attr('mean'),
|
47
|
+
variance: child.child.attr('variance')
|
44
48
|
}
|
45
49
|
end
|
46
50
|
features_data
|
@@ -65,4 +69,4 @@ module Scoruby
|
|
65
69
|
end
|
66
70
|
end
|
67
71
|
end
|
68
|
-
end
|
72
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Scoruby
|
2
4
|
module Models
|
3
5
|
class RandomForest
|
@@ -5,26 +7,39 @@ module Scoruby
|
|
5
7
|
|
6
8
|
def initialize(xml)
|
7
9
|
xml_trees = xml.xpath(RF_FOREST_XPATH)
|
8
|
-
@decision_trees = xml_trees.
|
10
|
+
@decision_trees = xml_trees.map do |xml_tree|
|
9
11
|
DecisionTree.new(xml_tree)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def predict(features)
|
16
|
+
decisions_count = decisions_count(features)
|
17
|
+
decision = decisions_count.max_by { |_, v| v }
|
18
|
+
{
|
19
|
+
label: decision[0],
|
20
|
+
score: decision[1] / decisions_count.values.reduce(0, :+).to_f
|
10
21
|
}
|
11
22
|
end
|
12
23
|
|
13
24
|
def decisions_count(features)
|
14
25
|
formatted_features = Features.new(features).formatted
|
15
|
-
decisions
|
26
|
+
decisions = traverse_trees(formatted_features)
|
27
|
+
aggregate_decisions(decisions)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def traverse_trees(formatted_features)
|
33
|
+
@decision_trees.map do |decision_tree|
|
16
34
|
decision_tree.decide(formatted_features).score
|
17
|
-
|
18
|
-
decisions.inject(Hash.new(0)) {|h, e| h[e] += 1; h}
|
35
|
+
end
|
19
36
|
end
|
20
37
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
score: decisions_count.max_by {|_, v| v}[1] / decisions_count.values.reduce(0, :+).to_f
|
26
|
-
}
|
38
|
+
def aggregate_decisions(decisions)
|
39
|
+
decisions.each_with_object(Hash.new(0)) do |score, counts|
|
40
|
+
counts[score] += 1
|
41
|
+
end
|
27
42
|
end
|
28
43
|
end
|
29
44
|
end
|
30
|
-
end
|
45
|
+
end
|
data/lib/scoruby/node.rb
CHANGED
@@ -1,19 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'scoruby/predicate_factory'
|
2
4
|
require 'scoruby/decision'
|
3
5
|
|
4
6
|
module Scoruby
|
5
7
|
class Node
|
6
|
-
|
7
8
|
attr_reader :decision, :pred, :children
|
8
9
|
|
9
10
|
def initialize(xml)
|
10
11
|
children = xml.children
|
11
|
-
|
12
|
+
distributions = children.select { |c| c.name == 'ScoreDistribution' }
|
12
13
|
@decision = Decision.new(xml.attribute('score').to_s,
|
13
|
-
|
14
|
-
|
14
|
+
distributions)
|
15
15
|
children = remove_nodes(children)
|
16
|
-
|
17
16
|
@pred = PredicateFactory.for(children[0])
|
18
17
|
@children = children_nodes(children)
|
19
18
|
end
|
@@ -26,11 +25,11 @@ module Scoruby
|
|
26
25
|
|
27
26
|
def children_nodes(children)
|
28
27
|
children.select { |c| c.name == 'Node' }
|
29
|
-
|
28
|
+
.map { |child| Node.new(child) }
|
30
29
|
end
|
31
30
|
|
32
31
|
def remove_nodes(children)
|
33
|
-
children.reject {|c| %w
|
32
|
+
children.reject { |c| %w[Extension ScoreDistribution].include? c.name }
|
34
33
|
end
|
35
34
|
end
|
36
|
-
end
|
35
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'scoruby/predicates/compound_predicate'
|
2
4
|
require 'scoruby/predicates/simple_predicate'
|
3
5
|
require 'scoruby/predicates/simple_set_predicate'
|
@@ -6,14 +8,11 @@ require 'scoruby/predicates/false_predicate'
|
|
6
8
|
|
7
9
|
module Scoruby
|
8
10
|
class PredicateFactory
|
9
|
-
|
10
11
|
def self.for(pred_xml)
|
11
|
-
return Predicates::SimplePredicate.new(pred_xml) if pred_xml.name == 'SimplePredicate'
|
12
|
-
return Predicates::SimpleSetPredicate.new(pred_xml) if pred_xml.name == 'SimpleSetPredicate'
|
13
|
-
return Predicates::CompoundPredicate.new(pred_xml) if pred_xml.name == 'CompoundPredicate'
|
14
12
|
return Predicates::TruePredicate.new if pred_xml.name == 'True'
|
15
13
|
return Predicates::FalsePredicate.new if pred_xml.name == 'False'
|
14
|
+
predicate = Object.const_get("Scoruby::Predicates::#{pred_xml.name}")
|
15
|
+
predicate.new pred_xml
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
19
|
-
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Scoruby
|
2
4
|
module Predicates
|
3
5
|
class CompoundPredicate
|
4
|
-
|
5
6
|
attr_reader :field
|
6
7
|
|
7
8
|
def initialize(pred_xml)
|
@@ -21,24 +22,28 @@ module Scoruby
|
|
21
22
|
and?(features) if @boolean_operator == 'and'
|
22
23
|
end
|
23
24
|
|
24
|
-
def
|
25
|
-
@field.any? {|f| !features.keys.include?(f)}
|
25
|
+
def missing?(features)
|
26
|
+
@field.any? { |f| !features.keys.include?(f) }
|
26
27
|
end
|
27
28
|
|
28
29
|
private
|
29
30
|
|
30
31
|
def surrogate?(features)
|
31
|
-
return @predicates[1].true?(features) if
|
32
|
+
return @predicates[1].true?(features) if first_missing?(features)
|
32
33
|
@predicates[0].true?(features)
|
33
34
|
end
|
34
35
|
|
36
|
+
def first_missing?(features)
|
37
|
+
@predicates[0].missing?(features)
|
38
|
+
end
|
39
|
+
|
35
40
|
def or?(features)
|
36
|
-
@predicates.any? {|p| p.true?(features)}
|
41
|
+
@predicates.any? { |p| p.true?(features) }
|
37
42
|
end
|
38
43
|
|
39
44
|
def and?(features)
|
40
|
-
@predicates.all? {|p| p.true?(features)}
|
45
|
+
@predicates.all? { |p| p.true?(features) }
|
41
46
|
end
|
42
47
|
end
|
43
48
|
end
|
44
|
-
end
|
49
|
+
end
|
@@ -1,12 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Scoruby
|
2
4
|
module Predicates
|
3
5
|
class SimplePredicate
|
4
|
-
|
5
6
|
GREATER_THAN = 'greaterThan'
|
6
7
|
LESS_THAN = 'lessThan'
|
7
8
|
LESS_OR_EQUAL = 'lessOrEqual'
|
8
9
|
GREATER_OR_EQUAL = 'greaterOrEqual'
|
9
|
-
MATH_OPS = [GREATER_THAN,
|
10
|
+
MATH_OPS = [GREATER_THAN,
|
11
|
+
LESS_THAN,
|
12
|
+
LESS_OR_EQUAL,
|
13
|
+
GREATER_OR_EQUAL].freeze
|
10
14
|
EQUAL = 'equal'
|
11
15
|
IS_MISSING = 'isMissing'
|
12
16
|
|
@@ -24,10 +28,10 @@ module Scoruby
|
|
24
28
|
def true?(features)
|
25
29
|
return num_true?(features) if MATH_OPS.include?(@operator)
|
26
30
|
return features[@field] == @value if @operator == EQUAL
|
27
|
-
features[field].nil? || !features.
|
31
|
+
features[field].nil? || !features.key?(field) if @operator == IS_MISSING
|
28
32
|
end
|
29
33
|
|
30
|
-
def
|
34
|
+
def missing?(features)
|
31
35
|
!features.keys.include?(@field)
|
32
36
|
end
|
33
37
|
|
@@ -35,8 +39,10 @@ module Scoruby
|
|
35
39
|
|
36
40
|
def num_true?(features)
|
37
41
|
return false unless features[@field]
|
38
|
-
|
39
|
-
|
42
|
+
compare(Float(features[@field]), Float(@value))
|
43
|
+
end
|
44
|
+
|
45
|
+
def compare(curr_value, value)
|
40
46
|
return curr_value > value if @operator == GREATER_THAN
|
41
47
|
return curr_value < value if @operator == LESS_THAN
|
42
48
|
return curr_value <= value if @operator == LESS_OR_EQUAL
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Scoruby
|
2
4
|
module Predicates
|
3
5
|
class SimpleSetPredicate
|
4
|
-
|
5
6
|
IS_IN = 'isIn'
|
6
7
|
|
7
8
|
attr_reader :field
|
@@ -17,16 +18,16 @@ module Scoruby
|
|
17
18
|
@array.include? features[@field] if @operator == IS_IN
|
18
19
|
end
|
19
20
|
|
20
|
-
def
|
21
|
+
def missing?(features)
|
21
22
|
!features.keys.include?(@field)
|
22
23
|
end
|
23
24
|
|
24
25
|
private
|
25
26
|
|
26
27
|
def single_or_quoted_words(string)
|
27
|
-
string.split(/\s(?=(?:[^"]|"[^"]*")*$)/)
|
28
|
-
|
29
|
-
|
28
|
+
string.split(/\s(?=(?:[^"]|"[^"]*")*$)/)
|
29
|
+
.reject(&:empty?)
|
30
|
+
.map { |w| w.tr('"', '') }
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
data/lib/scoruby/version.rb
CHANGED
data/lib/scoruby.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'scoruby/version'
|
2
4
|
require 'scoruby/models_factory'
|
3
5
|
require 'nokogiri'
|
@@ -9,12 +11,12 @@ module Scoruby
|
|
9
11
|
|
10
12
|
def logger
|
11
13
|
@logger ||= Logger.new($stdout).tap do |log|
|
12
|
-
log.progname =
|
14
|
+
log.progname = name
|
13
15
|
end
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
17
|
-
def self.
|
19
|
+
def self.load_model(pmml_file_name)
|
18
20
|
xml = xml_from_file_path(pmml_file_name)
|
19
21
|
ModelsFactory.factory_for(xml)
|
20
22
|
end
|
@@ -25,7 +27,7 @@ module Scoruby
|
|
25
27
|
end
|
26
28
|
|
27
29
|
def self.xml_from_string(pmml_string)
|
28
|
-
xml = Nokogiri::XML(pmml_string
|
30
|
+
xml = Nokogiri::XML(pmml_string, &:noblanks)
|
29
31
|
xml.remove_namespaces!
|
30
32
|
end
|
31
33
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scoruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Asaf Schers
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -117,6 +117,7 @@ extra_rdoc_files: []
|
|
117
117
|
files:
|
118
118
|
- ".gitignore"
|
119
119
|
- ".rspec"
|
120
|
+
- ".rubocop.yml"
|
120
121
|
- ".travis.yml"
|
121
122
|
- CODE_OF_CONDUCT.md
|
122
123
|
- Gemfile
|
@@ -167,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
167
168
|
version: '0'
|
168
169
|
requirements: []
|
169
170
|
rubyforge_project:
|
170
|
-
rubygems_version: 2.
|
171
|
+
rubygems_version: 2.6.13
|
171
172
|
signing_key:
|
172
173
|
specification_version: 4
|
173
174
|
summary: Ruby Scoring API for PMML.
|