diff --git a/.rspec b/.rspec index 8c18f1a..333d91e 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1,3 @@ --format documentation --color +--order rand diff --git a/.travis.yml b/.travis.yml index 502bde2..d2aef9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,8 @@ cache: - bundler before_install: - - gem install bundler - rvm get head - rvm use jruby-9.0.1.0 --install + - gem install bundler script: bundle exec rake spec diff --git a/README.md b/README.md index f2a5261..acb6708 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ The weka gem tries to carry over the namespaces defined in Weka and enhances som The idea behind keeping the namespaces is, that you can also use the [Weka documentation](http://weka.sourceforge.net/doc.dev/) for looking up functionality and classes. -Please refer to [the gem‘s Wiki](https://github.com/paulgoetze/weka-jruby/wiki) for +Please refer to [the gem’s Wiki](https://github.com/paulgoetze/weka-jruby/wiki) for detailed information about how to use weka with JRuby and some examplary code snippets. ## Development @@ -49,7 +49,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/paulgo For development we use the [git branching model](http://nvie.com/posts/a-successful-git-branching-model/) described by [nvie](https://github.com/nvie). -Here's how to contribute: +Here’s how to contribute: 1. Fork it ( https://github.com/paulgoetze/weka-jruby/fork ) 2. Create your feature branch (`git checkout -b feature/my-new-feature develop`) @@ -59,6 +59,9 @@ Here's how to contribute: Please try to add RSpec tests along with your new features. This will ensure that your code does not break existing functionality and that your feature is working as expected. +We use [Rubocop](https://github.com/bbatsov/rubocop) for code style recommendations. +Please make sure your contributions comply with the default config of Rubocop. + ## Acknowledgement The original ideas for wrapping Weka in JRuby come from [@arrigonialberto86](https://github.com/arrigonialberto86) and his [ruby-band](https://github.com/arrigonialberto86/ruby-band) gem. Great thanks! diff --git a/Rakefile b/Rakefile index d398b34..fc1b0a9 100644 --- a/Rakefile +++ b/Rakefile @@ -1,10 +1,10 @@ -require "bundler/gem_tasks" -require "rspec/core/rake_task" +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) -task :default => :prepare -task :install => :prepare +task default: :prepare +task install: :prepare desc 'Install weka jars & dependencies' task :prepare do @@ -15,7 +15,7 @@ task :prepare do LockJar.install('Jarfile.lock', local_repo: jars_dir) end -desc "Start an irb session with the gem loaded" +desc 'Start an irb session with the gem loaded' task :irb do sh 'irb -I ./lib -r weka' end diff --git a/bin/console b/bin/console index ba6ddc4..5b022bf 100755 --- a/bin/console +++ b/bin/console @@ -1,14 +1,14 @@ #!/usr/bin/env ruby -require "bundler/setup" -require "weka" +require 'bundler/setup' +require 'weka' # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) -# require "pry" +# require 'pry' # Pry.start -require "irb" +require 'irb' IRB.start diff --git a/lib/weka/attribute_selection/attribute_selection.rb b/lib/weka/attribute_selection/attribute_selection.rb index 573c99e..585aab7 100644 --- a/lib/weka/attribute_selection/attribute_selection.rb +++ b/lib/weka/attribute_selection/attribute_selection.rb @@ -3,9 +3,8 @@ module AttributeSelection java_import 'weka.attributeSelection.AttributeSelection' class AttributeSelection - - alias :summary :to_results_string - alias :selected_attributes_count :number_attributes_selected + alias summary to_results_string + alias selected_attributes_count number_attributes_selected end end end diff --git a/lib/weka/class_builder.rb b/lib/weka/class_builder.rb index a912a15..a842b36 100644 --- a/lib/weka/class_builder.rb +++ b/lib/weka/class_builder.rb @@ -1,14 +1,12 @@ -require 'active_support/concern' -require 'active_support/core_ext/string' -require 'active_support/core_ext/module' require 'weka/concerns' module Weka module ClassBuilder - extend ActiveSupport::Concern + def self.included(base) + base.extend(ClassMethods) + end module ClassMethods - def build_class(class_name, weka_module: nil, include_concerns: true) java_import java_class_path(class_name, weka_module) define_class(class_name, weka_module, include_concerns: include_concerns) @@ -37,7 +35,11 @@ def java_super_modules end def super_modules - toplevel_module? ? self.name : self.name.deconstantize + toplevel_module? ? name : deconstantize(name) + end + + def deconstantize(name) + name.split('::')[0...-1].join('::') end def java_including_module @@ -45,11 +47,15 @@ def java_including_module end def including_module - self.name.demodulize unless toplevel_module? + demodulize(name) unless toplevel_module? + end + + def demodulize(name) + name.split('::').last end def toplevel_module? - self.name.scan('::').count == 1 + name.scan('::').count == 1 end def define_class(class_name, weka_module, include_concerns: true) @@ -66,7 +72,7 @@ def include_serializable_for(class_name, weka_module) class_path = java_class_path(class_name, weka_module) serializable = Weka::Core::SerializationHelper.serializable?(class_path) - "include Weka::Concerns::Serializable" if serializable + 'include Weka::Concerns::Serializable' if serializable end def include_utils @@ -75,7 +81,11 @@ def include_utils end def utils_defined? - utils_super_modules.constantize.const_defined?(:Utils) + constantize(utils_super_modules).const_defined?(:Utils) + end + + def constantize(module_names) + Object.module_eval("::#{module_names}") end def utils @@ -87,10 +97,9 @@ def utils_super_modules end def downcase_first_char(string) - return if string.blank? + return if string.nil? || string.empty? string[0].downcase + string[1..-1] end end - end end diff --git a/lib/weka/classifiers/evaluation.rb b/lib/weka/classifiers/evaluation.rb index ee0529d..f8cfa2d 100644 --- a/lib/weka/classifiers/evaluation.rb +++ b/lib/weka/classifiers/evaluation.rb @@ -3,35 +3,34 @@ module Classifiers java_import 'weka.classifiers.Evaluation' class Evaluation - # Use both nomenclatures f_measure and fmeasure for consistency # due to jruby's auto method generation of 'fMeasure' to 'f_measure' and # 'weightedFMeasure' to 'weighted_fmeasure'. - alias :weighted_f_measure :weighted_fmeasure - alias :fmeasure :f_measure + alias weighted_f_measure weighted_fmeasure + alias fmeasure f_measure - alias :summary :to_summary_string - alias :class_details :to_class_details_string + alias summary to_summary_string + alias class_details to_class_details_string + alias confusion_matrix to_matrix_string - alias :instance_count :num_instances - alias :correct_count :correct - alias :incorrect_count :incorrect - alias :unclassified_count :unclassified + alias instance_count num_instances + alias correct_count correct + alias incorrect_count incorrect + alias unclassified_count unclassified - alias :correct_percentage :pct_correct - alias :incorrect_percentage :pct_incorrect - alias :unclassified_percentage :pct_unclassified + alias correct_percentage pct_correct + alias incorrect_percentage pct_incorrect + alias unclassified_percentage pct_unclassified - alias :true_negative_count :num_true_negatives - alias :false_negative_count :num_false_negatives - alias :true_positive_count :num_true_positives - alias :false_positive_count :num_false_positives - alias :average_cost :avg_cost + alias true_negative_count num_true_negatives + alias false_negative_count num_false_negatives + alias true_positive_count num_true_positives + alias false_positive_count num_false_positives + alias average_cost avg_cost - alias :cumulative_margin_distribution :to_cumulative_margin_distribution_string + alias cumulative_margin_distribution to_cumulative_margin_distribution_string end Java::WekaClassifiers::Evaluation.__persistent__ = true - end end diff --git a/lib/weka/classifiers/utils.rb b/lib/weka/classifiers/utils.rb index c0e845d..7e8096a 100644 --- a/lib/weka/classifiers/utils.rb +++ b/lib/weka/classifiers/utils.rb @@ -1,138 +1,135 @@ -require 'active_support/concern' -require 'active_support/core_ext/hash' require 'weka/classifiers/evaluation' require 'weka/core/instances' module Weka module Classifiers module Utils - extend ActiveSupport::Concern + def self.included(base) + base.class_eval do + java_import 'java.util.Random' - included do - java_import 'java.util.Random' + if instance_methods.include?(:build_classifier) + attr_reader :training_instances - if instance_methods.include?(:build_classifier) - attr_reader :training_instances + def train_with_instances(instances) + ensure_class_attribute_assigned!(instances) - def train_with_instances(instances) - ensure_class_attribute_assigned!(instances) + @training_instances = instances + build_classifier(instances) - @training_instances = instances - build_classifier(instances) + self + end - self - end - - def cross_validate(folds: 3) - ensure_trained_with_instances! + def cross_validate(folds: 3) + ensure_trained_with_instances! - evaluation = Evaluation.new(training_instances) - random = Java::JavaUtil::Random.new(1) + evaluation = Evaluation.new(training_instances) + random = Java::JavaUtil::Random.new(1) - evaluation.cross_validate_model(self, training_instances, folds.to_i, random) - evaluation - end + evaluation.cross_validate_model(self, training_instances, folds.to_i, random) + evaluation + end - def evaluate(test_instances) - ensure_trained_with_instances! - ensure_class_attribute_assigned!(test_instances) + def evaluate(test_instances) + ensure_trained_with_instances! + ensure_class_attribute_assigned!(test_instances) - evaluation = Evaluation.new(training_instances) - evaluation.evaluate_model(self, test_instances) - evaluation + evaluation = Evaluation.new(training_instances) + evaluation.evaluate_model(self, test_instances) + evaluation + end end - end - if instance_methods.include?(:classify_instance) - def classify(instance_or_values) - ensure_trained_with_instances! + if instance_methods.include?(:classify_instance) + def classify(instance_or_values) + ensure_trained_with_instances! - instance = classifiable_instance_from(instance_or_values) - index = classify_instance(instance) + instance = classifiable_instance_from(instance_or_values) + index = classify_instance(instance) - class_value_of_index(index) + class_value_of_index(index) + end end - end - if instance_methods.include?(:update_classifier) - def add_training_instance(instance) - training_instances.add(instance) - update_classifier(instance) + if instance_methods.include?(:update_classifier) + def add_training_instance(instance) + training_instances.add(instance) + update_classifier(instance) - self - end + self + end - def add_training_data(data) - values = self.training_instances.internal_values_of(data) - instance = Weka::Core::DenseInstance.new(values) - add_training_instance(instance) + def add_training_data(data) + values = training_instances.internal_values_of(data) + instance = Weka::Core::DenseInstance.new(values) + add_training_instance(instance) + end end - end - if instance_methods.include?(:distribution_for_instance) - def distribution_for(instance_or_values) - ensure_trained_with_instances! + if instance_methods.include?(:distribution_for_instance) + def distribution_for(instance_or_values) + ensure_trained_with_instances! - instance = classifiable_instance_from(instance_or_values) - distributions = distribution_for_instance(instance) + instance = classifiable_instance_from(instance_or_values) + distributions = distribution_for_instance(instance) - class_distributions_from(distributions).with_indifferent_access + class_distributions_from(distributions) + end end - end - private + private - def ensure_class_attribute_assigned!(instances) - return if instances.class_attribute_defined? + def ensure_class_attribute_assigned!(instances) + return if instances.class_attribute_defined? - error = 'Class attribute is not assigned for Instances.' - hint = 'You can assign a class attribute with #class_attribute=.' - message = "#{error} #{hint}" + error = 'Class attribute is not assigned for Instances.' + hint = 'You can assign a class attribute with #class_attribute=.' + message = "#{error} #{hint}" - raise UnassignedClassError, message - end + raise UnassignedClassError, message + end - def ensure_trained_with_instances! - return unless training_instances.nil? + def ensure_trained_with_instances! + return unless training_instances.nil? - error = 'Classifier is not trained with Instances.' - hint = 'You can set the training instances with #train_with_instances.' - message = "#{error} #{hint}" + error = 'Classifier is not trained with Instances.' + hint = 'You can set the training instances with #train_with_instances.' + message = "#{error} #{hint}" - raise UnassignedTrainingInstancesError, message - end + raise UnassignedTrainingInstancesError, message + end - def classifiable_instance_from(instance_or_values) - attributes = training_instances.attributes - instances = Weka::Core::Instances.new(attributes: attributes) + def classifiable_instance_from(instance_or_values) + attributes = training_instances.attributes + instances = Weka::Core::Instances.new(attributes: attributes) - class_attribute = training_instances.class_attribute - class_index = training_instances.class_index - instances.insert_attribute_at(class_attribute, class_index) + class_attribute = training_instances.class_attribute + class_index = training_instances.class_index + instances.insert_attribute_at(class_attribute, class_index) - instances.class_index = training_instances.class_index - instances.add_instance(instance_or_values) + instances.class_index = training_instances.class_index + instances.add_instance(instance_or_values) - instance = instances.first - instance.set_class_missing - instance - end + instance = instances.first + instance.set_class_missing + instance + end - def class_value_of_index(index) - training_instances.class_attribute.value(index) - end + def class_value_of_index(index) + training_instances.class_attribute.value(index) + end - def class_distributions_from(distributions) - class_values = training_instances.class_attribute.values + def class_distributions_from(distributions) + class_values = training_instances.class_attribute.values - distributions.each_with_index.reduce({}) do |result, (distribution, index)| - class_value = class_values[index].to_sym - result[class_value] = distribution - result + distributions.each_with_index.reduce({}) do |result, (distribution, index)| + class_value = class_values[index] + result[class_value] = distribution + result + end end end end - end end end diff --git a/lib/weka/clusterers/cluster_evaluation.rb b/lib/weka/clusterers/cluster_evaluation.rb index e4837b5..83320ff 100644 --- a/lib/weka/clusterers/cluster_evaluation.rb +++ b/lib/weka/clusterers/cluster_evaluation.rb @@ -3,12 +3,10 @@ module Clusterers java_import 'weka.clusterers.ClusterEvaluation' class ClusterEvaluation - - alias :summary :cluster_results_to_string - alias :clusters_count :num_clusters + alias summary cluster_results_to_string + alias clusters_count num_clusters end Java::WekaClusterers::ClusterEvaluation.__persistent__ = true - end end diff --git a/lib/weka/clusterers/utils.rb b/lib/weka/clusterers/utils.rb index 8f1bcb8..b08c11a 100644 --- a/lib/weka/clusterers/utils.rb +++ b/lib/weka/clusterers/utils.rb @@ -1,103 +1,100 @@ -require 'active_support/concern' require 'weka/clusterers/cluster_evaluation' require 'weka/core/instances' module Weka module Clusterers module Utils - extend ActiveSupport::Concern + def self.included(base) + base.class_eval do + java_import 'java.util.Random' - included do - java_import 'java.util.Random' + if instance_methods.include?(:build_clusterer) + attr_reader :training_instances - if instance_methods.include?(:build_clusterer) - attr_reader :training_instances + def train_with_instances(instances) + @training_instances = instances + build_clusterer(instances) - def train_with_instances(instances) - @training_instances = instances - build_clusterer(instances) + self + end - self - end + if ancestors.include?(Java::WekaClusterers::DensityBasedClusterer) + def cross_validate(folds: 3) + ensure_trained_with_instances! + + ClusterEvaluation.cross_validate_model( + self, + training_instances, + folds.to_i, + Java::JavaUtil::Random.new(1) + ) + end + end - if ancestors.include?(Java::WekaClusterers::DensityBasedClusterer) - def cross_validate(folds: 3) + def evaluate(test_instances) ensure_trained_with_instances! - ClusterEvaluation.cross_validate_model( - self, - training_instances, - folds.to_i, - Java::JavaUtil::Random.new(1) - ) + ClusterEvaluation.new.tap do |evaluation| + evaluation.clusterer = self + evaluation.evaluate_clusterer(test_instances) + end end end - def evaluate(test_instances) - ensure_trained_with_instances! + if instance_methods.include?(:cluster_instance) + def cluster(instance_or_values) + ensure_trained_with_instances! - ClusterEvaluation.new.tap do |evaluation| - evaluation.clusterer = self - evaluation.evaluate_clusterer(test_instances) + instance = clusterable_instance_from(instance_or_values) + cluster_instance(instance) end end - end - if instance_methods.include?(:cluster_instance) - def cluster(instance_or_values) - ensure_trained_with_instances! + if instance_methods.include?(:update_clusterer) + def add_training_instance(instance) + training_instances.add(instance) + update_clusterer(instance) - instance = clusterable_instance_from(instance_or_values) - cluster_instance(instance) - end - end - - if instance_methods.include?(:update_clusterer) - def add_training_instance(instance) - training_instances.add(instance) - update_clusterer(instance) - - self - end + self + end - def add_training_data(data) - values = self.training_instances.internal_values_of(data) - instance = Weka::Core::DenseInstance.new(values) - add_training_instance(instance) + def add_training_data(data) + values = training_instances.internal_values_of(data) + instance = Weka::Core::DenseInstance.new(values) + add_training_instance(instance) + end end - end - if instance_methods.include?(:distribution_for_instance) - def distribution_for(instance_or_values) - ensure_trained_with_instances! + if instance_methods.include?(:distribution_for_instance) + def distribution_for(instance_or_values) + ensure_trained_with_instances! - instance = clusterable_instance_from(instance_or_values) - distribution_for_instance(instance).to_a + instance = clusterable_instance_from(instance_or_values) + distribution_for_instance(instance).to_a + end end - end - private + private - def ensure_trained_with_instances! - return unless training_instances.nil? + def ensure_trained_with_instances! + return unless training_instances.nil? - error = 'Clusterer is not trained with Instances.' - hint = 'You can set the training instances with #train_with_instances.' - message = "#{error} #{hint}" + error = 'Clusterer is not trained with Instances.' + hint = 'You can set the training instances with #train_with_instances.' + message = "#{error} #{hint}" - raise UnassignedTrainingInstancesError, message - end + raise UnassignedTrainingInstancesError, message + end - def clusterable_instance_from(instance_or_values) - attributes = training_instances.attributes - instances = Weka::Core::Instances.new(attributes: attributes) + def clusterable_instance_from(instance_or_values) + attributes = training_instances.attributes + instances = Weka::Core::Instances.new(attributes: attributes) - instances.add_instance(instance_or_values) - instances.first + instances.add_instance(instance_or_values) + instances.first + end end end - end end end - diff --git a/lib/weka/concerns.rb b/lib/weka/concerns.rb index d5bebb8..9a9b468 100644 --- a/lib/weka/concerns.rb +++ b/lib/weka/concerns.rb @@ -1,4 +1,3 @@ -require 'active_support/concern' require 'weka/concerns/buildable' require 'weka/concerns/describable' require 'weka/concerns/optionizable' @@ -7,13 +6,11 @@ module Weka module Concerns - extend ActiveSupport::Concern - - included do - include Buildable - include Describable - include Optionizable - include Persistent + def self.included(base) + base.include Buildable + base.include Describable + base.include Optionizable + base.include Persistent end end end diff --git a/lib/weka/concerns/buildable.rb b/lib/weka/concerns/buildable.rb index 6eaa6f1..eb7bf40 100644 --- a/lib/weka/concerns/buildable.rb +++ b/lib/weka/concerns/buildable.rb @@ -1,19 +1,17 @@ -require 'active_support/concern' - module Weka module Concerns module Buildable - extend ActiveSupport::Concern + def self.included(base) + base.extend ClassMethods + end module ClassMethods - def build(&block) instance = new instance.instance_eval(&block) if block_given? instance end end - end end end diff --git a/lib/weka/concerns/describable.rb b/lib/weka/concerns/describable.rb index 58c09ac..e13d8ee 100644 --- a/lib/weka/concerns/describable.rb +++ b/lib/weka/concerns/describable.rb @@ -1,12 +1,11 @@ -require 'active_support/concern' - module Weka module Concerns module Describable - extend ActiveSupport::Concern + def self.included(base) + base.extend ClassMethods + end module ClassMethods - def description new.global_info end diff --git a/lib/weka/concerns/optionizable.rb b/lib/weka/concerns/optionizable.rb index ee7d911..1300eae 100644 --- a/lib/weka/concerns/optionizable.rb +++ b/lib/weka/concerns/optionizable.rb @@ -1,40 +1,40 @@ -require 'active_support/concern' - module Weka module Concerns module Optionizable - extend ActiveSupport::Concern + def self.included(base) + base.extend(ClassMethods) - included do - java_import "weka.core.Utils" + base.class_eval do + java_import 'weka.core.Utils' - def use_options(*single_options, **hash_options) - joined_options = join_options(single_options, hash_options) - options = Java::WekaCore::Utils.split_options(joined_options) + def use_options(*single_options, **hash_options) + joined_options = join_options(single_options, hash_options) + options = Java::WekaCore::Utils.split_options(joined_options) - set_options(options) - @options = joined_options - end + set_options(options) + @options = joined_options + end - def options - @options || self.class.default_options - end + def options + @options || self.class.default_options + end - private + private - def join_options(*single_options, **hash_options) - [ - join_single_options(*single_options), - join_hash_options(**hash_options) - ].reject(&:empty?).join(' ') - end + def join_options(*single_options, **hash_options) + [ + join_single_options(*single_options), + join_hash_options(**hash_options) + ].reject(&:empty?).join(' ') + end - def join_single_options(options) - options.map { |option| "-#{option.to_s.sub(/^-/, '')}" }.join(' ') - end + def join_single_options(options) + options.map { |option| "-#{option.to_s.sub(/^-/, '')}" }.join(' ') + end - def join_hash_options(options) - options.map { |key, value| "-#{key} #{value}" }.join(' ') + def join_hash_options(options) + options.map { |key, value| "-#{key} #{value}" }.join(' ') + end end end @@ -43,7 +43,6 @@ def default_options new.get_options.to_a.join(' ') end end - end end end diff --git a/lib/weka/concerns/persistent.rb b/lib/weka/concerns/persistent.rb index 387f3c6..367b6d8 100644 --- a/lib/weka/concerns/persistent.rb +++ b/lib/weka/concerns/persistent.rb @@ -1,16 +1,11 @@ -require 'active_support/concern' - module Weka module Concerns module Persistent - extend ActiveSupport::Concern - - included do - if self.respond_to?(:__persistent__=) - self.__persistent__ = true + def self.included(base) + base.class_eval do + self.__persistent__ = true if respond_to?(:__persistent__=) end end - end end end diff --git a/lib/weka/concerns/serializable.rb b/lib/weka/concerns/serializable.rb index 1fe11c2..9b2bf85 100644 --- a/lib/weka/concerns/serializable.rb +++ b/lib/weka/concerns/serializable.rb @@ -1,17 +1,15 @@ -require 'active_support/concern' require 'weka/core/serialization_helper' module Weka module Concerns module Serializable - extend ActiveSupport::Concern - - included do - def serialize(filename) - Weka::Core::SerializationHelper.write(filename, self) + def self.included(base) + base.class_eval do + def serialize(filename) + Weka::Core::SerializationHelper.write(filename, self) + end end end - end end -end \ No newline at end of file +end diff --git a/lib/weka/core/attribute.rb b/lib/weka/core/attribute.rb index 28b7daa..93dfaa5 100644 --- a/lib/weka/core/attribute.rb +++ b/lib/weka/core/attribute.rb @@ -1,13 +1,73 @@ +require 'weka/concerns/persistent' + module Weka module Core - java_import "weka.core.Attribute" + java_import 'weka.core.Attribute' class Attribute + include Weka::Concerns::Persistent + + TYPES = %i(numeric nominal string date).freeze + + class << self + def new_numeric(name) + new(name.to_s) + end + + def new_nominal(name, values) + new(name.to_s, Array(values).map(&:to_s)) + end + + def new_date(name, format) + new(name.to_s, format.to_s) + end + + ## + # Creates a new Attribute instance of type string. + # + # The java class defines the same constructor: + # Attribute(java.lang.String, java.util.List) + # for nominal and string attributes and handles the type internally + # based on the second argument. + # + # In Java you would write following code to create a string Attribute: + # Attribute attribute = new Attribute("name", (FastVector) null); + # + # When we use a similar approach in JRuby: + # attribute = Attribute.new('name', nil) + # then a Java::JavaLang::NullPointerException is thrown. + # + # Thus, we use refelection here and call the contructor explicitly, see + # https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#constructors + # + # The object returned from Java constructor only has class + # Java::JavaObject so we need to cast it to the proper class + # + # See also: + # https://stackoverflow.com/questions/1792495/casting-objects-in-jruby + def new_string(name) + constructor = Attribute.java_class.declared_constructor( + java.lang.String, + java.util.List + ) + + constructor.new_instance(name.to_s, nil).to_java(Attribute) + end + end def values enumerate_values.to_a end + ## + # Returns the string representation of the attribute's type. + # Overwrites the weka.core.Attribute type Java method, which returns an + # integer representation of the type based on the defined type constants. + def type + self.class.type_to_string(self) + end + + ## # The order of the if statements is important here, because a date is also # a numeric. def internal_value_of(value) @@ -15,10 +75,8 @@ def internal_value_of(value) return Float::NAN if [nil, '?'].include?(value) return parse_date(value.to_s) if date? return value.to_f if numeric? - return index_of_value(value.to_s) if nominal? + return index_of_value(value.to_s) if nominal? || string? end end - - Weka::Core::Attribute.__persistent__ = true end end diff --git a/lib/weka/core/converters.rb b/lib/weka/core/converters.rb index 137e43d..fe66f3c 100644 --- a/lib/weka/core/converters.rb +++ b/lib/weka/core/converters.rb @@ -11,6 +11,8 @@ module Converters :CSVSaver, :JSONLoader, :JSONSaver, + :C45Loader, + :C45Saver, include_concerns: false end end diff --git a/lib/weka/core/dense_instance.rb b/lib/weka/core/dense_instance.rb index d06360c..2332b23 100644 --- a/lib/weka/core/dense_instance.rb +++ b/lib/weka/core/dense_instance.rb @@ -1,13 +1,13 @@ module Weka module Core - java_import "weka.core.DenseInstance" + java_import 'weka.core.DenseInstance' class DenseInstance - java_import "java.util.Date" - java_import "java.text.SimpleDateFormat" + java_import 'java.util.Date' + java_import 'java.text.SimpleDateFormat' def initialize(data, weight: 1.0) - if data.kind_of?(Integer) + if data.is_a?(Integer) super(data) else super(weight, to_java_double(data)) @@ -38,8 +38,8 @@ def to_a end end - alias :values :to_a - alias :values_count :num_values + alias values to_a + alias values_count num_values private @@ -61,7 +61,7 @@ def value_from(value, index) format_date(value, attribute.date_format) elsif attribute.numeric? value - elsif attribute.nominal? + elsif attribute.nominal? || attribute.string? attribute.value(value) end end diff --git a/lib/weka/core/instances.rb b/lib/weka/core/instances.rb index 5ab45b0..f098c51 100644 --- a/lib/weka/core/instances.rb +++ b/lib/weka/core/instances.rb @@ -6,13 +6,13 @@ module Weka module Core - java_import "weka.core.Instances" - java_import "weka.core.FastVector" + java_import 'weka.core.Instances' + java_import 'weka.core.FastVector' class Instances include Weka::Concerns::Serializable - DEFAULT_RELATION_NAME = 'Instances' + DEFAULT_RELATION_NAME = 'Instances'.freeze class << self def from_arff(file) @@ -26,6 +26,16 @@ def from_csv(file) def from_json(file) Loader.load_json(file) end + + # Loads instances based on a given *.names file (holding the attribute + # values) or a given *.data file (holding the attribute values). + # The respective other file is loaded from the same directory. + # + # See http://www.cs.washington.edu/dm/vfml/appendixes/c45.htm for more + # information about the C4.5 file format. + def from_c45(file) + Loader.load_c45(file) + end end def initialize(relation_name: DEFAULT_RELATION_NAME, attributes: [], &block) @@ -48,13 +58,33 @@ def attribute_names end def add_attributes(&block) - self.instance_eval(&block) if block + instance_eval(&block) if block self end - alias :with_attributes :add_attributes - alias :instances_count :num_instances - alias :attributes_count :num_attributes + alias with_attributes add_attributes + alias instances_count num_instances + alias attributes_count num_attributes + alias has_string_attribute? check_for_string_attributes + + ## Check if the instances has any attribute of the given type + # @param [String, Symbol, Integer] type type of the attribute to check + # String and Symbol argument are converted to corresponding type + # defined in Weka::Core::Attribute + # + # @example Passing String + # instances.has_attribute_type?('string') + # instances.has_attribute_type?('String') + # + # @example Passing Symbol + # instances.has_attribute_type?(:String) + # + # @example Passing Integer + # instances.has_attribute_type?(Attribute::STRING) + def has_attribute_type?(type) + type = map_attribute_type(type) unless type.is_a?(Integer) + check_for_attribute_type(type) + end def each if block_given? @@ -96,26 +126,39 @@ def to_json(file) Saver.save_json(file: file, instances: self) end + # Creates a file with the istances's attribute values and a *.data file + # with the actual data. + # + # You should choose another file extension than .data (preferably + # *.names) for the file, else it will just be overwritten with the + # automatically created *.data file. + # + # See http://www.cs.washington.edu/dm/vfml/appendixes/c45.htm for more + # information about the C4.5 file format. + def to_c45(file) + Saver.save_c45(file: file, instances: self) + end + def numeric(name, class_attribute: false) - attribute = Attribute.new(name.to_s) + attribute = Attribute.new_numeric(name) add_attribute(attribute) self.class_attribute = name if class_attribute end def nominal(name, values:, class_attribute: false) - attribute = Attribute.new(name.to_s, Array(values).map(&:to_s)) + attribute = Attribute.new_nominal(name, values) add_attribute(attribute) self.class_attribute = name if class_attribute end def string(name, class_attribute: false) - attribute = Attribute.new(name.to_s, []) + attribute = Attribute.new_string(name) add_attribute(attribute) self.class_attribute = name if class_attribute end def date(name, format: 'yyyy-MM-dd HH:mm', class_attribute: false) - attribute = Attribute.new(name.to_s, format) + attribute = Attribute.new_date(name, format) add_attribute(attribute) self.class_attribute = name if class_attribute end @@ -129,10 +172,10 @@ def class_attribute=(name) end end - alias :add_numeric_attribute :numeric - alias :add_string_attribute :string - alias :add_nominal_attribute :nominal - alias :add_date_attribute :date + alias add_numeric_attribute numeric + alias add_string_attribute string + alias add_nominal_attribute nominal + alias add_date_attribute date def class_attribute classAttribute if class_attribute_defined? @@ -187,7 +230,7 @@ def ensure_attribute_defined!(name) return if attribute_names.include?(name.to_s) error = "\"#{name}\" is not defined." - hint = "Only defined attributes can be used as class attribute!" + hint = 'Only defined attributes can be used as class attribute!' message = "#{error} #{hint}" raise ArgumentError, message @@ -198,14 +241,31 @@ def attribute_with_name(name) end def instance_from(instance_or_values, weight:) - if instance_or_values.kind_of?(Java::WekaCore::Instance) + if instance_or_values.is_a?(Java::WekaCore::Instance) instance_or_values.weight = weight instance_or_values else data = internal_values_of(instance_or_values) + + # string attribute has unlimited range of possible values. + # Check the return index, if it is -1 then add the value to + # the attribute before creating the instance + data.map!.with_index do |value, index| + if value == -1 && attribute(index).string? + attribute(index).add_string_value(instance_or_values[index].to_s) + else + value + end + end + DenseInstance.new(data, weight: weight) end end + + def map_attribute_type(type) + return -1 unless Attribute::TYPES.include?(type.downcase.to_sym) + Attribute.const_get(type.upcase) + end end Java::WekaCore::Instances.__persistent__ = true diff --git a/lib/weka/core/loader.rb b/lib/weka/core/loader.rb index f9d6579..dc9f237 100644 --- a/lib/weka/core/loader.rb +++ b/lib/weka/core/loader.rb @@ -18,6 +18,16 @@ def load_json(file) load_with(Converters::JSONLoader, file: file) end + # Takes either a *.names or a *.data file and loads the respective other + # file from the same directory automatically. + # Returns a Weka::Core::Instances object. + # + # See http://www.cs.washington.edu/dm/vfml/appendixes/c45.htm for more + # information about the C4.5 file format. + def load_c45(file) + load_with(Converters::C45Loader, file: file) + end + private def load_with(loader_class, file:) @@ -27,6 +37,5 @@ def load_with(loader_class, file:) end end end - end end diff --git a/lib/weka/core/saver.rb b/lib/weka/core/saver.rb index d4c4696..d79a1ba 100644 --- a/lib/weka/core/saver.rb +++ b/lib/weka/core/saver.rb @@ -18,6 +18,27 @@ def save_json(file:, instances:) save_with(Converters::JSONSaver, file: file, instances: instances) end + # Saves the given `instances` into a file with the given name and a + # *.data file in the same directory. + # The file with the given file name includes the instances's attribute + # values, the *.data file holds the actual data. + # + # Example: + # + # Weka::Core::Saver.save_c45( + # file: './path/to/example.names', + # instances: instances + # ) + # + # creates an example.names file and an example.data file in the + # ./path/to/ directory. + # + # See: http://www.cs.washington.edu/dm/vfml/appendixes/c45.htm for more + # information about the C4.5 file format. + def save_c45(file:, instances:) + save_with(Converters::C45Saver, file: file, instances: instances) + end + private def save_with(saver_class, file:, instances:) @@ -29,6 +50,5 @@ def save_with(saver_class, file:, instances:) end end end - end end diff --git a/lib/weka/core/serialization_helper.rb b/lib/weka/core/serialization_helper.rb index 8fb6f5f..91e6ccd 100644 --- a/lib/weka/core/serialization_helper.rb +++ b/lib/weka/core/serialization_helper.rb @@ -3,10 +3,9 @@ module Core java_import 'weka.core.SerializationHelper' class SerializationHelper - class << self - alias :deserialize :read - alias :serialize :write + alias deserialize read + alias serialize write end end end diff --git a/lib/weka/filters/filter.rb b/lib/weka/filters/filter.rb index 250924e..488b8d6 100644 --- a/lib/weka/filters/filter.rb +++ b/lib/weka/filters/filter.rb @@ -4,6 +4,5 @@ module Filters class Filter end - end end diff --git a/lib/weka/filters/supervised/attribute.rb b/lib/weka/filters/supervised/attribute.rb index 4608504..5560dbd 100644 --- a/lib/weka/filters/supervised/attribute.rb +++ b/lib/weka/filters/supervised/attribute.rb @@ -16,10 +16,9 @@ module Attribute :PartitionMembership class AttributeSelection - alias :use_evaluator :set_evaluator - alias :use_search :set_search + alias use_evaluator set_evaluator + alias use_search set_search end - end end end diff --git a/lib/weka/filters/utils.rb b/lib/weka/filters/utils.rb index a01474a..70f5955 100644 --- a/lib/weka/filters/utils.rb +++ b/lib/weka/filters/utils.rb @@ -1,17 +1,14 @@ -require 'active_support/concern' - module Weka module Filters module Utils - extend ActiveSupport::Concern - - included do - def filter(instances) - set_input_format(instances) - Filter.use_filter(instances, self) + def self.included(base) + base.class_eval do + def filter(instances) + set_input_format(instances) + Filter.use_filter(instances, self) + end end end - end end end diff --git a/lib/weka/jars.rb b/lib/weka/jars.rb index 12e557c..0d90f55 100644 --- a/lib/weka/jars.rb +++ b/lib/weka/jars.rb @@ -1,19 +1,16 @@ -require 'active_support/concern' - module Weka module Jars - extend ActiveSupport::Concern - - included do - require 'lock_jar' + def self.included(base) + base.class_eval do + require 'lock_jar' - lib_path = File.expand_path('../../', File.dirname(__FILE__)) - lockfile = File.join(lib_path, 'Jarfile.lock') - jars_dir = File.join(lib_path, 'jars') + lib_path = File.expand_path('../../', File.dirname(__FILE__)) + lockfile = File.join(lib_path, 'Jarfile.lock') + jars_dir = File.join(lib_path, 'jars') - LockJar.install(lockfile, local_repo: jars_dir) - LockJar.load(lockfile, local_repo: jars_dir) + LockJar.install(lockfile, local_repo: jars_dir) + LockJar.load(lockfile, local_repo: jars_dir) + end end - end end diff --git a/lib/weka/version.rb b/lib/weka/version.rb index 159eb8a..af7625d 100644 --- a/lib/weka/version.rb +++ b/lib/weka/version.rb @@ -1,3 +1,3 @@ module Weka - VERSION = "0.3.0" + VERSION = '0.4.0'.freeze end diff --git a/spec/attribute_selection/attribute_selection_spec.rb b/spec/attribute_selection/attribute_selection_spec.rb index 014b730..79ff0ed 100644 --- a/spec/attribute_selection/attribute_selection_spec.rb +++ b/spec/attribute_selection/attribute_selection_spec.rb @@ -1,13 +1,12 @@ require 'spec_helper' describe Weka::AttributeSelection::AttributeSelection do - describe 'aliases:' do { to_results_string: :summary, number_attributes_selected: :selected_attributes_count }.each do |method, alias_method| - it "should define the alias ##{alias_method} for ##{method}" do + it "defines the alias ##{alias_method} for ##{method}" do expect(subject.method(method)).to eq subject.method(alias_method) end end diff --git a/spec/attribute_selection/evaluator_spec.rb b/spec/attribute_selection/evaluator_spec.rb index 9c74a69..4412ca1 100644 --- a/spec/attribute_selection/evaluator_spec.rb +++ b/spec/attribute_selection/evaluator_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::AttributeSelection::Evaluator do - subject { described_class } it_behaves_like 'class builder' @@ -16,19 +15,19 @@ :SymmetricalUncertAttribute => :SymmetricalUncertAttributeEval, :WrapperSubset => :WrapperSubsetEval }.each do |class_name, super_class_name| - it "should define a class #{class_name}" do + it "defines a class #{class_name}" do expect(subject.const_defined?(class_name)).to be true end - it "should inherit class #{class_name} from #{super_class_name}" do - evaluator_class = "#{subject}::#{class_name}".constantize - super_class = "#{subject}::#{super_class_name}".constantize + it "inherits class #{class_name} from #{super_class_name}" do + evaluator_class = Object.module_eval("#{subject}::#{class_name}") + super_class = Object.module_eval("#{subject}::#{super_class_name}") expect(evaluator_class.new).to be_kind_of super_class end end - it "should define a class PrincipalComponents" do - expect(subject.const_defined?(:PrincipalComponents)).to be true - end + it 'defines a class PrincipalComponents' do + expect(subject.const_defined?(:PrincipalComponents)).to be true + end end diff --git a/spec/attribute_selection/search_spec.rb b/spec/attribute_selection/search_spec.rb index 5d32fae..008816a 100644 --- a/spec/attribute_selection/search_spec.rb +++ b/spec/attribute_selection/search_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::AttributeSelection::Search do - subject { described_class } it_behaves_like 'class builder' @@ -11,7 +10,7 @@ :Ranker, :BestFirst ].each do |class_name| - it "should define a class #{class_name}" do + it "defines a class #{class_name}" do expect(subject.const_defined?(class_name)).to be true end end diff --git a/spec/attribute_selection_spec.rb b/spec/attribute_selection_spec.rb index b9d1fc0..6f3d2c6 100644 --- a/spec/attribute_selection_spec.rb +++ b/spec/attribute_selection_spec.rb @@ -1,8 +1,7 @@ require 'spec_helper' describe Weka::AttributeSelection do - - it "should define a class AttributeSelection" do + it 'defines a class AttributeSelection' do expect(described_class.const_defined?(:AttributeSelection)).to be true end end diff --git a/spec/class_builder_spec.rb b/spec/class_builder_spec.rb index 8b28f74..833d814 100644 --- a/spec/class_builder_spec.rb +++ b/spec/class_builder_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::ClassBuilder do - subject do module Some module Weka @@ -14,18 +13,18 @@ module Module end end - let(:class_name){ :SomeClass } + let(:class_name) { :SomeClass } before { allow(subject).to receive(:java_import).and_return('') } [:build_class, :build_classes].each do |method| - it "should define .#{method} if included" do + it "defines .#{method} if included" do expect(subject).to respond_to method end end describe '.build_class' do - it 'should run java_import with the right resolved class path' do + it 'runs java_import with the right resolved class path' do class_path = "some.weka.customCamelCased.module.#{class_name}" expect(subject).to receive(:java_import).once.with(class_path) @@ -37,7 +36,7 @@ module Module describe 'built class including Describable functionality' do Weka::Concerns::Describable::ClassMethods.instance_methods.each do |method| - it "should respond to .#{method}" do + it "responds to .#{method}" do expect(built_class).to respond_to method end end @@ -45,7 +44,7 @@ module Module describe 'built class including Buildable functionality' do Weka::Concerns::Buildable::ClassMethods.instance_methods.each do |method| - it "should respond to .#{method}" do + it "responds to .#{method}" do expect(built_class).to respond_to method end end @@ -54,12 +53,12 @@ module Module describe 'built class including Optionizable functionality' do let(:built_class_instance) { built_class.new } - it 'should respond to .default_options' do + it 'responds to .default_options' do expect(built_class).to respond_to :default_options end [:use_options, :options].each do |method| - it "should respond to ##{method}" do + it "responds to ##{method}" do expect(built_class_instance).to respond_to method end end @@ -78,7 +77,7 @@ def shared_method end end - it 'should include them in the defined class' do + it 'includes them in the defined class' do built_class = subject.build_class(class_name) expect(built_class.public_method_defined?(:shared_method)).to be true end @@ -103,7 +102,7 @@ module Module .and_return(subject.module_eval("class #{class_name}; end")) end - it 'should not include extra methods in the defined class' do + it 'does not include extra methods in the defined class' do built_class = subject.build_class(class_name) expect(built_class.public_method_defined?(:shared_method)).to be false end @@ -112,7 +111,7 @@ module Module context 'with a defined Weka module' do let(:weka_module) { 'explicitly.defined.module' } - it 'should import the given classes from the defined module' do + it 'imports the given classes from the defined module' do class_path = "#{weka_module}.#{class_name}" expect(subject).to receive(:java_import).once.with(class_path) @@ -123,8 +122,8 @@ module Module describe '.build_classes' do context 'without a given weka_module' do - it 'should run .build_class for each of the given classes' do - class_names = %i{ SomeClass SomeOtherClass } + it 'runs .build_class for each of the given classes' do + class_names = %i(SomeClass SomeOtherClass) expect(subject).to receive(:build_class).exactly(class_names.count).times subject.build_classes(*class_names) @@ -132,8 +131,8 @@ module Module end context 'with a given weka_module' do - it 'should run .build_class for each of the given classes' do - class_names = %i{ SomeClass SomeOtherClass } + it 'runs .build_class for each of the given classes' do + class_names = %i(SomeClass SomeOtherClass) expect(subject).to receive(:build_class).exactly(class_names.count).times subject.build_classes(*class_names, weka_module: 'weka.module') diff --git a/spec/classifiers/bayes_spec.rb b/spec/classifiers/bayes_spec.rb index 5489f06..1279424 100644 --- a/spec/classifiers/bayes_spec.rb +++ b/spec/classifiers/bayes_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Classifiers::Bayes do - it_behaves_like 'class builder' [ @@ -12,7 +11,7 @@ :NaiveBayesMultinomialUpdateable, :NaiveBayesUpdateable ].each do |class_name| - it "should define a class #{class_name}" do + it "defines a class #{class_name}" do expect(described_class.const_defined?(class_name)).to be true end end diff --git a/spec/classifiers/evaluation_spec.rb b/spec/classifiers/evaluation_spec.rb index a54cfed..601b053 100644 --- a/spec/classifiers/evaluation_spec.rb +++ b/spec/classifiers/evaluation_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Classifiers::Evaluation do - subject do instances = load_instances('weather.arff') instances.class_attribute = :play @@ -29,8 +28,9 @@ unclassified_percentage: :pct_unclassified, weighted_f_measure: :weighted_fmeasure, cumulative_margin_distribution: :toCumulativeMarginDistributionString, + confusion_matrix: :to_matrix_string }.each do |alias_method, method| - it "should define the alias ##{alias_method} for ##{method}" do + it "defines the alias ##{alias_method} for ##{method}" do expect(subject.method(method)).to eq subject.method(alias_method) end end diff --git a/spec/classifiers/functions_spec.rb b/spec/classifiers/functions_spec.rb index 28cc3bc..234f17a 100644 --- a/spec/classifiers/functions_spec.rb +++ b/spec/classifiers/functions_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Classifiers::Functions do - it_behaves_like 'class builder' [ @@ -17,7 +16,7 @@ :SMOreg, :VotedPerceptron ].each do |class_name| - it "should define a class #{class_name}" do + it "defines a class #{class_name}" do expect(described_class.const_defined?(class_name)).to be true end end diff --git a/spec/classifiers/lazy_spec.rb b/spec/classifiers/lazy_spec.rb index b10fc5d..c2b7383 100644 --- a/spec/classifiers/lazy_spec.rb +++ b/spec/classifiers/lazy_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Classifiers::Lazy do - it_behaves_like 'class builder' [ @@ -9,7 +8,7 @@ :KStar, :LWL ].each do |class_name| - it "should defines a class #{class_name}" do + it "definess a class #{class_name}" do expect(described_class.const_defined?(class_name)).to be true end end diff --git a/spec/classifiers/meta_spec.rb b/spec/classifiers/meta_spec.rb index 00b8bf3..4c09994 100644 --- a/spec/classifiers/meta_spec.rb +++ b/spec/classifiers/meta_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Classifiers::Meta do - it_behaves_like 'class builder' [ @@ -25,7 +24,7 @@ :Stacking, :Vote ].each do |class_name| - it "should define a class #{class_name}" do + it "defines a class #{class_name}" do expect(described_class.const_defined?(class_name)).to be true end end diff --git a/spec/classifiers/rules_spec.rb b/spec/classifiers/rules_spec.rb index 1c9e92d..038b2a8 100644 --- a/spec/classifiers/rules_spec.rb +++ b/spec/classifiers/rules_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Classifiers::Rules do - it_behaves_like 'class builder' [ @@ -12,7 +11,7 @@ :PART, :ZeroR ].each do |class_name| - it "should define a class #{class_name}" do + it "defines a class #{class_name}" do expect(described_class.const_defined?(class_name)).to be true end end diff --git a/spec/classifiers/trees_spec.rb b/spec/classifiers/trees_spec.rb index 3020eab..9b0b006 100644 --- a/spec/classifiers/trees_spec.rb +++ b/spec/classifiers/trees_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Classifiers::Trees do - it_behaves_like 'class builder' [ @@ -14,7 +13,7 @@ :RandomTree, :REPTree ].each do |class_name| - it "should define a class #{class_name}" do + it "defines a class #{class_name}" do expect(described_class.const_defined?(class_name)).to be true end end diff --git a/spec/classifiers/utils_spec.rb b/spec/classifiers/utils_spec.rb index 067a2fe..ec8d896 100644 --- a/spec/classifiers/utils_spec.rb +++ b/spec/classifiers/utils_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Classifiers::Utils do - let(:including_class) do Class.new do def build_classifier(instances) @@ -36,24 +35,24 @@ def distribution_for_instance it { is_expected.to respond_to :classify } describe '#train_with_instances' do - it 'should call Java‘s #build_classifier' do + it 'calls Java’s #build_classifier' do expect(subject).to receive(:build_classifier).once.with(instances) subject.train_with_instances(instances) end - it 'should set the training_instances' do + it 'sets the training_instances' do subject = including_class.new expect(subject.training_instances).to be_nil subject.train_with_instances(instances) expect(subject.training_instances).to eq instances end - it 'should return itself' do + it 'returns itself' do expect(subject.train_with_instances(instances)).to be subject end context 'without an assigned class attribute on instances' do - it 'should raise an UnassignedClassError' do + it 'raises an UnassignedClassError' do instances = load_instances('weather.arff') expect { including_class.new.train_with_instances(instances) } @@ -70,19 +69,19 @@ def distribution_for_instance subject.train_with_instances(instances) end - it 'should call Java‘s #update_classifier' do + it 'calls Java’s #update_classifier' do expect(subject).to receive(:update_classifier).once.with(instance) subject.add_training_instance(instance) end - it 'should add the instance to training_instances' do + it 'adds the instance to training_instances' do expect { subject.add_training_instance(instance) } .to change { subject.training_instances.count } .by(1) end - it 'should return itself' do - expect(subject.add_training_instance(instance)).to be_kind_of subject.class + it 'returns itself' do + expect(subject.add_training_instance(instance)).to be_a subject.class end end @@ -94,7 +93,7 @@ def distribution_for_instance subject.train_with_instances(instances) end - it 'should call #add_training_instance' do + it 'calls #add_training_instance' do expect(subject) .to receive(:add_training_instance).once .with(an_instance_of(Weka::Core::DenseInstance)) @@ -102,7 +101,7 @@ def distribution_for_instance subject.add_training_data(values) end - it 'should return itself' do + it 'returns itself' do expect(subject.add_training_data(values)).to be_kind_of subject.class end end @@ -116,19 +115,19 @@ def distribution_for_instance .to receive(:cross_validate_model) end - it 'should return a Weka::Classifiers::Evaluation' do + it 'returns a Weka::Classifiers::Evaluation' do return_value = subject.cross_validate expect(return_value).to be_kind_of Weka::Classifiers::Evaluation end - it 'should run Java‘s #cross_validate_model on an Evaluation' do + it 'runs Java’s #cross_validate_model on an Evaluation' do expect_any_instance_of(Weka::Classifiers::Evaluation) .to receive(:cross_validate_model).once subject.cross_validate end - it 'should use 3 folds and the training instances as default test instances' do + it 'uses 3 folds and the training instances as default test instances' do expect_any_instance_of(Weka::Classifiers::Evaluation) .to receive(:cross_validate_model).once .with( @@ -144,7 +143,7 @@ def distribution_for_instance context 'with given folds' do let(:folds) { default_folds + 1 } - it 'should use the given number of folds' do + it 'uses the given number of folds' do expect_any_instance_of(Weka::Classifiers::Evaluation) .to receive(:cross_validate_model).once .with( @@ -157,7 +156,7 @@ def distribution_for_instance subject.cross_validate(folds: folds) end - it 'should use the folds as an integer value' do + it 'uses the folds as an integer value' do expect_any_instance_of(Weka::Classifiers::Evaluation) .to receive(:cross_validate_model).once .with( @@ -174,7 +173,7 @@ def distribution_for_instance context 'without training instances' do before { allow(subject).to receive(:training_instances).and_return(nil) } - it 'should raise an UnassignedTrainingInstancesError' do + it 'raises an UnassignedTrainingInstancesError' do expect { subject.cross_validate } .to raise_error Weka::UnassignedTrainingInstancesError end @@ -187,12 +186,12 @@ def distribution_for_instance allow_any_instance_of(Weka::Classifiers::Evaluation).to receive(:evaluate_model) end - it 'should return a Weka::Classifiers::Evaluation' do + it 'returns a Weka::Classifiers::Evaluation' do return_value = subject.evaluate(instances) expect(return_value).to be_kind_of Weka::Classifiers::Evaluation end - it 'should run Java‘s #evaluate_model on an Evaluation' do + it 'runs Java’s #evaluate_model on an Evaluation' do expect_any_instance_of(Weka::Classifiers::Evaluation) .to receive(:evaluate_model).once .with(subject, instances) @@ -201,7 +200,7 @@ def distribution_for_instance end context 'without an assigned class attribute on test instances' do - it 'should raise an UnassignedClassError' do + it 'raises an UnassignedClassError' do instances = load_instances('weather.arff') expect { subject.evaluate(instances) } @@ -212,7 +211,7 @@ def distribution_for_instance context 'without training instances' do before { allow(subject).to receive(:training_instances).and_return(nil) } - it 'should raise an UnassignedTrainingInstancesError' do + it 'raises an UnassignedTrainingInstancesError' do expect { subject.evaluate(instances) } .to raise_error Weka::UnassignedTrainingInstancesError end @@ -230,7 +229,7 @@ def distribution_for_instance end context 'with a given instance' do - it 'should call Java‘s #classify_instance' do + it 'calls Java’s #classify_instance' do expect(subject) .to receive(:classify_instance).once .with(an_instance_of(instance.class)) @@ -238,13 +237,13 @@ def distribution_for_instance subject.classify(instance) end - it 'should return the predicted class value of the instance' do + it 'returns the predicted class value of the instance' do expect(subject.classify(instance)).to eq class_value end end context 'with a given array of values' do - it 'should call Java‘s #classify_instance' do + it 'calls Java’s #classify_instance' do expect(subject) .to receive(:classify_instance).once .with(an_instance_of(Weka::Core::DenseInstance)) @@ -252,7 +251,7 @@ def distribution_for_instance subject.classify(values) end - it 'should return the predicted class value of the instance' do + it 'returns the predicted class value of the instance' do expect(subject.classify(values)).to eq class_value end end @@ -260,7 +259,7 @@ def distribution_for_instance context 'without training instances' do before { allow(subject).to receive(:training_instances).and_return(nil) } - it 'should raise an UnassignedTrainingInstancesError' do + it 'raises an UnassignedTrainingInstancesError' do expect { subject.classify(instance) } .to raise_error Weka::UnassignedTrainingInstancesError end @@ -274,11 +273,13 @@ def distribution_for_instance let(:class_distributions) { { 'yes' => distributions[0], 'no' => distributions[1] } } before do - allow(subject).to receive(:distribution_for_instance).and_return(distributions) + allow(subject) + .to receive(:distribution_for_instance) + .and_return(distributions) end context 'with a given instance' do - it 'should call Java‘s #distribution_for_instance' do + it 'calls Java’s #distribution_for_instance' do expect(subject) .to receive(:distribution_for_instance).once .with(an_instance_of(instance.class)) @@ -286,13 +287,13 @@ def distribution_for_instance subject.distribution_for(instance) end - it 'should return the predicted class distributions of the instance' do + it 'returns the predicted class distributions of the instance' do expect(subject.distribution_for(instance)).to eq class_distributions end end context 'with a given array of values' do - it 'should call Java‘s #distribution_for_instance' do + it 'calls Java’s #distribution_for_instance' do expect(subject) .to receive(:distribution_for_instance).once .with(an_instance_of(Weka::Core::DenseInstance)) @@ -300,7 +301,7 @@ def distribution_for_instance subject.distribution_for(values) end - it 'should return the predicted class distributions of the instance' do + it 'returns the predicted class distributions of the instance' do expect(subject.distribution_for(values)).to eq class_distributions end end @@ -308,11 +309,10 @@ def distribution_for_instance context 'without training instances' do before { allow(subject).to receive(:training_instances).and_return(nil) } - it 'should raise an UnassignedTrainingInstancesError' do + it 'raises an UnassignedTrainingInstancesError' do expect { subject.distribution_for(instance) } .to raise_error Weka::UnassignedTrainingInstancesError end end end - end diff --git a/spec/clusterers/cluster_evaluation_spec.rb b/spec/clusterers/cluster_evaluation_spec.rb index 06280c8..0b856e3 100644 --- a/spec/clusterers/cluster_evaluation_spec.rb +++ b/spec/clusterers/cluster_evaluation_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Clusterers::ClusterEvaluation do - it { is_expected.to be_kind_of(Java::WekaClusterers::ClusterEvaluation) } describe 'aliases:' do @@ -9,10 +8,9 @@ summary: :cluster_results_to_string, clusters_count: :num_clusters }.each do |alias_method, method| - it "should define the alias ##{alias_method} for ##{method}" do + it "defines the alias ##{alias_method} for ##{method}" do expect(subject.method(method)).to eq subject.method(alias_method) end end end - end diff --git a/spec/clusterers/utils_spec.rb b/spec/clusterers/utils_spec.rb index 0e07b91..36e9a96 100644 --- a/spec/clusterers/utils_spec.rb +++ b/spec/clusterers/utils_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Clusterers::Utils do - let(:including_class) do Class.new do def build_clusterer(instances) @@ -29,19 +28,19 @@ def distribution_for_instance it { is_expected.to respond_to :evaluate } describe '#train_with_instances' do - it 'should call Java‘s #build_classifier' do + it 'calls Java’s #build_classifier' do expect(subject).to receive(:build_clusterer).once.with(instances) subject.train_with_instances(instances) end - it 'should set the training_instances' do + it 'sets the training_instances' do subject = including_class.new expect(subject.training_instances).to be_nil subject.train_with_instances(instances) expect(subject.training_instances).to eq instances end - it 'should return itself' do + it 'returns itself' do expect(subject.train_with_instances(instances)).to be subject end end @@ -54,23 +53,22 @@ def distribution_for_instance subject.train_with_instances(instances) end - it 'should call Java‘s #update_classifier' do + it 'calls Java’s #update_classifier' do expect(subject).to receive(:update_clusterer).once.with(instance) subject.add_training_instance(instance) end - it 'should add the instance to training_instances' do + it 'adds the instance to training_instances' do expect { subject.add_training_instance(instance) } .to change { subject.training_instances.count } .by(1) end - it 'should return itself' do - expect(subject.add_training_instance(instance)).to be_kind_of subject.class + it 'returns itself' do + expect(subject.add_training_instance(instance)).to be_a subject.class end end - describe '#add_training_data' do let(:values) { [:sunny, 85, 85, :FALSE, :no] } @@ -79,7 +77,7 @@ def distribution_for_instance subject.train_with_instances(instances) end - it 'should call #add_training_instance' do + it 'calls #add_training_instance' do expect(subject) .to receive(:add_training_instance).once .with(an_instance_of(Weka::Core::DenseInstance)) @@ -87,8 +85,8 @@ def distribution_for_instance subject.add_training_data(values) end - it 'should return itself' do - expect(subject.add_training_data(values)).to be_kind_of subject.class + it 'returns itself' do + expect(subject.add_training_data(values)).to be_a subject.class end end @@ -130,17 +128,19 @@ def build_clusterer(instances) it { is_expected.to respond_to :cross_validate } - it 'should return a Weka::Clusterers::ClusterEvaluation' do + it 'returns a Weka::Clusterers::ClusterEvaluation' do return_value = subject.cross_validate expect(return_value).to eq cross_validation_result end - it 'should run Java‘s #cross_validate_model on a ClusterEvaluation' do - expect(Weka::Clusterers::ClusterEvaluation).to receive(:cross_validate_model).once + it 'runs Java’s #cross_validate_model on a ClusterEvaluation' do + expect(Weka::Clusterers::ClusterEvaluation) + .to receive(:cross_validate_model).once + subject.cross_validate end - it 'should use 3 folds and the training instances as default test instances' do + it 'uses 3 folds and the training instances as default test instances' do expect(Weka::Clusterers::ClusterEvaluation) .to receive(:cross_validate_model).once .with( @@ -156,7 +156,7 @@ def build_clusterer(instances) context 'with given folds' do let(:folds) { default_folds + 1 } - it 'should use the given number of folds' do + it 'uses the given number of folds' do expect(Weka::Clusterers::ClusterEvaluation) .to receive(:cross_validate_model).once .with( @@ -169,7 +169,7 @@ def build_clusterer(instances) subject.cross_validate(folds: folds) end - it 'should use the folds as an integer value' do + it 'uses the folds as an integer value' do expect(Weka::Clusterers::ClusterEvaluation) .to receive(:cross_validate_model).once .with( @@ -184,9 +184,11 @@ def build_clusterer(instances) end context 'without training instances' do - before { allow(subject).to receive(:training_instances).and_return(nil) } + before do + allow(subject).to receive(:training_instances).and_return(nil) + end - it 'should raise an UnassignedTrainingInstancesError' do + it 'raises an UnassignedTrainingInstancesError' do expect { subject.cross_validate } .to raise_error Weka::UnassignedTrainingInstancesError end @@ -197,15 +199,16 @@ def build_clusterer(instances) describe '#evaluate' do before do allow(subject).to receive(:training_instances).and_return(instances) - allow_any_instance_of(Weka::Clusterers::ClusterEvaluation).to receive(:evaluate_clusterer) + allow_any_instance_of(Weka::Clusterers::ClusterEvaluation) + .to receive(:evaluate_clusterer) end - it 'should return a Weka::Clusterers::ClusterEvaluation' do + it 'returns a Weka::Clusterers::ClusterEvaluation' do return_value = subject.evaluate(instances) expect(return_value).to be_kind_of Weka::Clusterers::ClusterEvaluation end - it 'should run Java‘s #evaluate_model on an Evaluation' do + it 'runs Java’s #evaluate_model on an Evaluation' do expect_any_instance_of(Weka::Clusterers::ClusterEvaluation) .to receive(:evaluate_clusterer).once .with(instances) @@ -216,7 +219,7 @@ def build_clusterer(instances) context 'without training instances' do before { allow(subject).to receive(:training_instances).and_return(nil) } - it 'should raise an UnassignedTrainingInstancesError' do + it 'raises an UnassignedTrainingInstancesError' do expect { subject.evaluate(instances) } .to raise_error Weka::UnassignedTrainingInstancesError end @@ -224,16 +227,16 @@ def build_clusterer(instances) end describe '#cluster' do - let(:instance) { instances.first } - let(:values) { [:overcast, 83, 86, :FALSE, :yes] } - let(:cluster) { 1 } + let(:instance) { instances.first } + let(:values) { [:overcast, 83, 86, :FALSE, :yes] } + let(:cluster) { 1 } before do allow(subject).to receive(:cluster_instance).and_return(cluster) end context 'with a given instance' do - it 'should call Java‘s #cluster_instance' do + it 'calls Java’s #cluster_instance' do expect(subject) .to receive(:cluster_instance).once .with(an_instance_of(instance.class)) @@ -241,13 +244,13 @@ def build_clusterer(instances) subject.cluster(instance) end - it 'should return the predicted class value of the instance' do + it 'returns the predicted class value of the instance' do expect(subject.cluster(instance)).to eq cluster end end context 'with a given array of values' do - it 'should call Java‘s #cluster_instance' do + it 'calls Java’s #cluster_instance' do expect(subject) .to receive(:cluster_instance).once .with(an_instance_of(Weka::Core::DenseInstance)) @@ -255,7 +258,7 @@ def build_clusterer(instances) subject.cluster(values) end - it 'should return the predicted class value of the instance' do + it 'returns the predicted class value of the instance' do expect(subject.cluster(values)).to eq cluster end end @@ -263,7 +266,7 @@ def build_clusterer(instances) context 'without training instances' do before { allow(subject).to receive(:training_instances).and_return(nil) } - it 'should raise an UnassignedTrainingInstancesError' do + it 'raises an UnassignedTrainingInstancesError' do expect { subject.cluster(instance) } .to raise_error Weka::UnassignedTrainingInstancesError end @@ -271,16 +274,18 @@ def build_clusterer(instances) end describe '#distribution_for' do - let(:instance) { instances.first } - let(:values) { [:overcast, 83, 86, :FALSE, :yes] } - let(:distributions) { [0.543684388757196, 0.4563156112428039] } + let(:instance) { instances.first } + let(:values) { [:overcast, 83, 86, :FALSE, :yes] } + let(:distributions) { [0.543684388757196, 0.4563156112428039] } before do - allow(subject).to receive(:distribution_for_instance).and_return(distributions) + allow(subject) + .to receive(:distribution_for_instance) + .and_return(distributions) end context 'with a given instance' do - it 'should call Java‘s #distribution_for_instance' do + it 'calls Java’s #distribution_for_instance' do expect(subject) .to receive(:distribution_for_instance).once .with(an_instance_of(instance.class)) @@ -288,13 +293,13 @@ def build_clusterer(instances) subject.distribution_for(instance) end - it 'should return the predicted cluster distributions of the instance' do + it 'returns the predicted cluster distributions of the instance' do expect(subject.distribution_for(instance)).to eq distributions end end context 'with a given array of values' do - it 'should call Java‘s #distribution_for_instance' do + it 'calls Java’s #distribution_for_instance' do expect(subject) .to receive(:distribution_for_instance).once .with(an_instance_of(Weka::Core::DenseInstance)) @@ -302,7 +307,7 @@ def build_clusterer(instances) subject.distribution_for(values) end - it 'should return the predicted cluster distributions of the instance' do + it 'returns the predicted cluster distributions of the instance' do expect(subject.distribution_for(values)).to eq distributions end end @@ -310,11 +315,10 @@ def build_clusterer(instances) context 'without training instances' do before { allow(subject).to receive(:training_instances).and_return(nil) } - it 'should raise an UnassignedTrainingInstancesError' do + it 'raises an UnassignedTrainingInstancesError' do expect { subject.distribution_for(instance) } .to raise_error Weka::UnassignedTrainingInstancesError end end end - end diff --git a/spec/clusterers_spec.rb b/spec/clusterers_spec.rb index f44d79c..5a29afc 100644 --- a/spec/clusterers_spec.rb +++ b/spec/clusterers_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Clusterers do - it_behaves_like 'class builder' [ @@ -12,7 +11,7 @@ :HierarchicalClusterer, :SimpleKMeans ].each do |class_name| - it "should define a class #{class_name}" do + it "defines a class #{class_name}" do expect(described_class.const_defined?(class_name)).to be true end end diff --git a/spec/concerns/buildable_spec.rb b/spec/concerns/buildable_spec.rb index adf97a1..74b1c06 100644 --- a/spec/concerns/buildable_spec.rb +++ b/spec/concerns/buildable_spec.rb @@ -1,12 +1,11 @@ require 'spec_helper' describe Weka::Concerns::Buildable do - subject do Class.new { include Weka::Concerns::Buildable } end - it 'should respond to .build' do + it 'responds to .build' do expect(subject).to respond_to :build end @@ -16,19 +15,19 @@ allow_any_instance_of(subject).to receive(:a_public_method) end - it 'should evaluate the given block on a new class instance' do + it 'evaluates the given block on a new class instance' do expect_any_instance_of(subject).to receive(:a_public_method).once subject.build { a_public_method } end - it "should return an instance of the including class" do + it 'returns an instance of the including class' do instance = subject.build { a_public_method } expect(instance).to be_kind_of subject end end context 'called without a block' do - it 'should return an instance of the including class' do + it 'returns an instance of the including class' do expect(subject.build).to be_kind_of subject end end diff --git a/spec/concerns/describable_spec.rb b/spec/concerns/describable_spec.rb index 5f0d837..ece06c8 100644 --- a/spec/concerns/describable_spec.rb +++ b/spec/concerns/describable_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Concerns::Describable do - subject do Class.new { include Weka::Concerns::Describable } end @@ -14,7 +13,7 @@ allow_any_instance_of(subject).to receive(:global_info).and_return('') end - it 'should call Weka’s #global_info on a new instance' do + it 'calls Weka’s #global_info on a new instance' do expect_any_instance_of(subject).to receive(:global_info).once subject.description end @@ -25,13 +24,13 @@ allow_any_instance_of(subject).to receive(:list_options).and_return([]) end - it 'should call Weka’s #list_options on a new instance' do + it 'calls Weka’s #list_options on a new instance' do expect_any_instance_of(subject).to receive(:list_options).once subject.options end - it 'should return a string' do - expect(subject.options).to be_kind_of String + it 'returns a string' do + expect(subject.options).to be_a String end end end diff --git a/spec/concerns/optionizable_spec.rb b/spec/concerns/optionizable_spec.rb index 7554dcf..7db99d1 100644 --- a/spec/concerns/optionizable_spec.rb +++ b/spec/concerns/optionizable_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Concerns::Optionizable do - subject do Class.new { include Weka::Concerns::Optionizable }.new end @@ -9,7 +8,7 @@ it { is_expected.to respond_to :use_options } it { is_expected.to respond_to :options } - it 'should respond to .default_options' do + it 'responds to .default_options' do expect(subject.class).to respond_to :default_options end @@ -22,7 +21,7 @@ end context 'when called with hash options' do - it 'should set the given options' do + it 'sets the given options' do expect(Java::WekaCore::Utils) .to receive(:split_options).once .with('-I 100 -K 0') @@ -33,7 +32,7 @@ end context 'when called with single options' do - it 'should set the given options' do + it 'sets the given options' do expect(Java::WekaCore::Utils) .to receive(:split_options).once .with('-O -B') @@ -44,7 +43,7 @@ end context 'when called with single options & hash options' do - it 'should set the given options' do + it 'sets the given options' do expect(Java::WekaCore::Utils) .to receive(:split_options).once .with('-O -I 100') @@ -55,7 +54,7 @@ end context 'when called with a string' do - it 'should set the given options' do + it 'sets the given options' do expect(Java::WekaCore::Utils) .to receive(:split_options).once .with('-O -I 100') @@ -72,7 +71,7 @@ context 'if both single options & hash option are defined' do before { subject.use_options(:O, :B, I: 100) } - it 'should return the defined options' do + it 'returns the defined options' do expect(subject.options).to eq '-O -B -I 100' end end @@ -80,7 +79,7 @@ context 'if only single options are defined' do before { subject.use_options(:O, :B) } - it 'should not include an empty hash' do + it 'does not include an empty hash' do expect(subject.options).to eq '-O -B' end end @@ -94,7 +93,7 @@ .and_return(default_options) end - it 'should return the default options' do + it 'returns the default options' do expect(subject.options).to eq default_options end end @@ -104,15 +103,15 @@ before do allow_any_instance_of(subject.class) .to receive(:get_options) - .and_return(['-C', 'last', '-Z', '-P', '10', '-M', '-B', '0.1']) + .and_return(%w(-C last -Z -P 10 -M -B 0.1)) end - it 'should receive Java‘s #get_options' do + it 'receives Java’s #get_options' do expect_any_instance_of(subject.class).to receive(:get_options).once subject.class.default_options end - it 'should return a string with the default options' do + it 'returns a string with the default options' do options = '-C last -Z -P 10 -M -B 0.1' expect(subject.class.default_options).to eq options end diff --git a/spec/concerns/persistent_spec.rb b/spec/concerns/persistent_spec.rb index 94a3b08..0799dae 100644 --- a/spec/concerns/persistent_spec.rb +++ b/spec/concerns/persistent_spec.rb @@ -1,11 +1,10 @@ require 'spec_helper' describe Weka::Concerns::Persistent do - describe 'if included' do subject { Class.new } - it 'should set __persistent__ to true' do + it 'sets __persistent__ to true' do expect(subject).to receive(:__persistent__=).with(true).once subject.include(Weka::Concerns::Persistent) end diff --git a/spec/concerns/serializable_spec.rb b/spec/concerns/serializable_spec.rb index d8bb8ad..75c6602 100644 --- a/spec/concerns/serializable_spec.rb +++ b/spec/concerns/serializable_spec.rb @@ -1,19 +1,18 @@ require 'spec_helper' describe Weka::Concerns::Serializable do - subject do Class.new { include Weka::Concerns::Serializable } end let(:filename) { 'file.model' } - it 'should respond to #serialize' do + it 'responds to #serialize' do expect(subject.new).to respond_to :serialize end describe '#serialize' do - it 'should call Weka::Core::SerializationHelper.write' do + it 'calls Weka::Core::SerializationHelper.write' do expect(Weka::Core::SerializationHelper) .to receive(:write) .with(filename, subject) diff --git a/spec/core/attribute_spec.rb b/spec/core/attribute_spec.rb index fe60c0f..53ac527 100644 --- a/spec/core/attribute_spec.rb +++ b/spec/core/attribute_spec.rb @@ -1,100 +1,236 @@ require 'spec_helper' describe Weka::Core::Attribute do + let(:values) { %w(true false) } + let(:name) { 'name' } + let(:format) { 'yyyy-MM-dd HH:mm' } - let(:values) { ['yes', 'no'] } - subject { Weka::Core::Attribute.new('class', values) } + subject { Weka::Core::Attribute.new(name, values) } it { is_expected.to respond_to :values } it { is_expected.to respond_to :internal_value_of } + describe '.new_numeric' do + subject { Weka::Core::Attribute.new_numeric(name) } + + it 'returns a numeric Attribute' do + expect(subject.numeric?).to be true + end + + it 'returns an Attribute with the given name' do + expect(subject.name).to eq name + end + end + + describe '.new_nominal' do + subject { Weka::Core::Attribute.new_nominal(name, values) } + + it 'returns a nominal Attribute' do + expect(subject.nominal?).to be true + end + + it 'returns an Attribute with the given name' do + expect(subject.name).to eq name + end + end + + describe '.new_date' do + subject { Weka::Core::Attribute.new_date(name, format) } + + it 'returns a date Attribute' do + expect(subject.date?).to be true + end + + it 'returns an Attribute with the given name' do + expect(subject.name).to eq name + end + end + + describe '.new_string' do + subject { Weka::Core::Attribute.new_string(name) } + + it 'returns a string Attribute' do + expect(subject.string?).to be true + end + + it 'returns an Attribute with the given name' do + expect(subject.name).to eq name + end + end + describe '#values' do - it 'should return an array of the values' do + it 'returns an array of the values' do expect(subject.values).to eq values end end describe '#internal_value_of' do - context 'a numeric attribute' do - let(:attribute) { Weka::Core::Attribute.new('numeric attribute') } + context 'for a numeric attribute' do + subject { Weka::Core::Attribute.new_numeric(name) } - it 'should return the value as a float' do - expect(attribute.internal_value_of(3.5)).to eq 3.5 + it 'returns the value as a float' do + expect(subject.internal_value_of(3.5)).to eq 3.5 end - it 'should return the value as a float if given as string' do - expect(attribute.internal_value_of('3.5')).to eq 3.5 + it 'returns the value as a float if given as string' do + expect(subject.internal_value_of('3.5')).to eq 3.5 end - it 'should return NaN if the given value is Float::NAN' do - expect(attribute.internal_value_of(Float::NAN)).to be Float::NAN + it 'returns NaN if the given value is Float::NAN' do + expect(subject.internal_value_of(Float::NAN)).to be Float::NAN end - it 'should return NaN if the given value is nil' do - expect(attribute.internal_value_of(nil)).to be Float::NAN + it 'returns NaN if the given value is nil' do + expect(subject.internal_value_of(nil)).to be Float::NAN end - it 'should return NaN if the given value is "?"' do - expect(attribute.internal_value_of("?")).to be Float::NAN + it 'returns NaN if the given value is "?"' do + expect(subject.internal_value_of('?')).to be Float::NAN end end - context 'a nominal attribute' do - let(:attribute) { Weka::Core::Attribute.new('class', ['true', 'false']) } + context 'for a nominal attribute' do + subject { Weka::Core::Attribute.new_nominal(name, values) } - it 'should return the correct internal index' do - expect(attribute.internal_value_of('true')).to eq 0 - expect(attribute.internal_value_of('false')).to eq 1 + it 'returns the correct internal index' do + expect(subject.internal_value_of('true')).to eq 0 + expect(subject.internal_value_of('false')).to eq 1 end - it 'should return the correct internal index as given as a non-String' do - expect(attribute.internal_value_of(true)).to eq 0 - expect(attribute.internal_value_of(false)).to eq 1 + it 'returns the correct internal index as given as a non-String' do + expect(subject.internal_value_of(true)).to eq 0 + expect(subject.internal_value_of(false)).to eq 1 - expect(attribute.internal_value_of(:true)).to eq 0 - expect(attribute.internal_value_of(:false)).to eq 1 + expect(subject.internal_value_of(:true)).to eq 0 + expect(subject.internal_value_of(:false)).to eq 1 end - it 'should return NaN if the given value is Float::NAN' do - expect(attribute.internal_value_of(Float::NAN)).to be Float::NAN + it 'returns NaN if the given value is Float::NAN' do + expect(subject.internal_value_of(Float::NAN)).to be Float::NAN end - it 'should return NaN if the given value is nil' do - expect(attribute.internal_value_of(nil)).to be Float::NAN + it 'returns NaN if the given value is nil' do + expect(subject.internal_value_of(nil)).to be Float::NAN end - it 'should return NaN if the given value is "?"' do - expect(attribute.internal_value_of("?")).to be Float::NAN + it 'returns NaN if the given value is "?"' do + expect(subject.internal_value_of('?')).to be Float::NAN end end - context 'a data attribute' do - let(:attribute) { Weka::Core::Attribute.new('date', 'yyyy-MM-dd HH:mm') } + context 'for a data attribute' do let(:datetime) { '2015-12-24 11:11' } - let(:unix_timestamp) { 1450955460000.0 } + let(:unix_timestamp) { 1_450_955_460_000.0 } + + subject { Weka::Core::Attribute.new_date(name, format) } before do - allow(attribute) + allow(subject) .to receive(:parse_date) .with(datetime) .and_return(unix_timestamp) end - it 'should return the right date timestamp value' do - expect(attribute.internal_value_of(datetime)).to eq unix_timestamp + it 'returns the right date timestamp value' do + expect(subject.internal_value_of(datetime)).to eq unix_timestamp + end + + it 'returns NaN if the given value is Float::NAN' do + expect(subject.internal_value_of(Float::NAN)).to be Float::NAN + end + + it 'returns NaN if the given value is nil' do + expect(subject.internal_value_of(nil)).to be Float::NAN + end + + it 'returns NaN if the given value is "?"' do + expect(subject.internal_value_of('?')).to be Float::NAN + end + end + + context 'for a string attribute' do + let(:string_values) { %w(first_string second_string) } + let(:phantom_string_value) { 'i_do_not_exist' } + + subject do + attribute = Weka::Core::Attribute.new_string(name) + string_values.each { |value| attribute.add_string_value(value) } + attribute + end + + it 'returns the correct internal index' do + expect(subject.internal_value_of(string_values[0])).to eq 0 + expect(subject.internal_value_of(string_values[1])).to eq 1 + end + + it 'returns -1 as internal index for non-existent string values' do + expect(subject.internal_value_of(phantom_string_value)).to eq(-1) + end + + it 'returns the correct internal index as given as a non-String' do + expect(subject.internal_value_of(:first_string)).to eq 0 + expect(subject.internal_value_of(:second_string)).to eq 1 + end + + it 'returns -1 as internal index for non-existent non-String values' do + expect(subject.internal_value_of(:phantom_string_value)).to eq(-1) + end + + it 'returns NaN if the given value is Float::NAN' do + expect(subject.internal_value_of(Float::NAN)).to be Float::NAN + end + + it 'returns NaN if the given value is nil' do + expect(subject.internal_value_of(nil)).to be Float::NAN + end + + it 'returns NaN if the given value is "?"' do + expect(subject.internal_value_of('?')).to be Float::NAN + end + end + end + + describe '#type' do + context 'for a numeric attribute' do + subject { Weka::Core::Attribute.new_numeric(name) } + + it 'returns "numeric"' do + expect(subject.type).to eq 'numeric' end + end + + context 'for a nominal attribute' do + subject { Weka::Core::Attribute.new_nominal(name, values) } - it 'should return NaN if the given value is Float::NAN' do - expect(attribute.internal_value_of(Float::NAN)).to be Float::NAN + it 'returns "nominal"' do + expect(subject.type).to eq 'nominal' end + end + + context 'for a string attribute' do + subject { Weka::Core::Attribute.new_string(name) } - it 'should return NaN if the given value is nil' do - expect(attribute.internal_value_of(nil)).to be Float::NAN + it 'returns "string"' do + expect(subject.type).to eq 'string' + end + end + + context 'for a data attribute' do + let(:datetime) { '2015-12-24 11:11' } + let(:unix_timestamp) { 1_450_955_460_000.0 } + + subject { Weka::Core::Attribute.new_date(name, format) } + + before do + allow(subject) + .to receive(:parse_date) + .with(datetime) + .and_return(unix_timestamp) end - it 'should return NaN if the given value is "?"' do - expect(attribute.internal_value_of("?")).to be Float::NAN + it 'returns "date"' do + expect(subject.type).to eq 'date' end end end -end \ No newline at end of file +end diff --git a/spec/core/converters_spec.rb b/spec/core/converters_spec.rb index 53427b2..df7f1f8 100644 --- a/spec/core/converters_spec.rb +++ b/spec/core/converters_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Core::Converters do - it_behaves_like 'class builder' [ @@ -12,7 +11,7 @@ :JSONLoader, :JSONSaver ].each do |class_name| - it "should define a class #{class_name}" do + it "defines a class #{class_name}" do expect(described_class.const_defined?(class_name)).to be true end end diff --git a/spec/core/dense_instance_spec.rb b/spec/core/dense_instance_spec.rb index d381abc..995194d 100644 --- a/spec/core/dense_instance_spec.rb +++ b/spec/core/dense_instance_spec.rb @@ -1,11 +1,10 @@ require 'spec_helper' describe Weka::Core::DenseInstance do - subject do instances = load_instances('weather.arff') instances.add_date_attribute('recorded_at') - instances.add_instance(['rainy',50, 50,'TRUE','no','2015-12-24 11:11']) + instances.add_instance(['rainy', 50, 50, 'TRUE', 'no', '2015-12-24 11:11']) instances.instances.last end @@ -19,7 +18,7 @@ values: :to_a, values_count: :num_values }.each do |method, alias_method| - it "should define the alias ##{alias_method} for ##{method}" do + it "defines the alias ##{alias_method} for ##{method}" do expect(subject.method(method)).to eq subject.method(alias_method) end end @@ -27,19 +26,19 @@ describe 'instantiation' do describe 'with an Integer value' do - it 'should create a instance with only missing values' do + it 'creates an instance with only missing values' do values = Weka::Core::DenseInstance.new(2).values expect(values).to eq ['?', '?'] end end describe 'with an array' do - it 'should create an instance with the given values' do + it 'creates an instance with the given values' do values = Weka::Core::DenseInstance.new([1, 2, 3]).values expect(values).to eq [1, 2, 3] end - it 'should handle "?" values or nil values' do + it 'handles "?" values or nil values' do values = Weka::Core::DenseInstance.new([1, '?', nil, 4]).values expect(values).to eq [1, '?', '?', 4] end @@ -47,9 +46,9 @@ end describe '#to_a' do - let(:values) { ['rainy',50.0, 50.0,'TRUE','no','2015-12-24 11:11'] } + let(:values) { ['rainy', 50.0, 50.0, 'TRUE', 'no', '2015-12-24 11:11'] } - it 'should return an Array with the values of the instance' do + it 'returns an Array with the values of the instance' do expect(subject.to_a).to eq values end @@ -62,19 +61,42 @@ instances.instances.last end - it 'should return an Array with the values of the instance' do + it 'returns an Array with the values of the instance' do + expect(subject.to_a).to eq values + end + end + + context 'with string attribute' do + let(:values) do + ['overcast', + 'Wind increasing. A few clouds from time to time. High 32F. Winds WNW at 20 to 30 mph.', + 15.0, + 40.0, + 'TRUE', + 'no' + ] + end + + subject do + instances = load_instances('weather.string.arff') + instances.add_instance(values) + instances.class_attribute = :play + instances.instances.last + end + + it 'returns an Array with the values of the instance' do expect(subject.to_a).to eq values end end end describe '#attributes' do - it 'should return an Array of Attributes' do + it 'returns an Array of Attributes' do attributes = subject.attributes expect(attributes).to be_an Array all_kind_of_attribute = attributes.reduce(true) do |result, attribute| - result &&= attribute.kind_of?(Java::WekaCore::Attribute) + result && attribute.is_a?(Java::WekaCore::Attribute) end expect(all_kind_of_attribute).to be true @@ -85,7 +107,7 @@ before { @result = nil } describe '#each_attribute' do - it 'should run a block on each attribute' do + it 'runs a block on each attribute' do subject.each_attribute do |attribute| @result = attribute.name unless @result end @@ -94,7 +116,7 @@ end context 'without a given block' do - it 'should return a WekaEnumerator' do + it 'returns a WekaEnumerator' do expect(subject.each_attribute) .to be_kind_of(Java::WekaCore::WekaEnumeration) end @@ -102,7 +124,7 @@ end describe '#each_attribute_with_index' do - it 'should run a block on each attribute' do + it 'runs a block on each attribute' do subject.each_attribute_with_index do |attribute, index| @result = "#{attribute.name}, #{index}" if index == 0 end @@ -111,12 +133,11 @@ end context 'without a given block' do - it 'should return a WekaEnumerator' do + it 'returns a WekaEnumerator' do expect(subject.each_attribute_with_index) .to be_kind_of(Java::WekaCore::WekaEnumeration) end end end end - -end \ No newline at end of file +end diff --git a/spec/core/instances_spec.rb b/spec/core/instances_spec.rb index bbcdb96..35e4e0b 100644 --- a/spec/core/instances_spec.rb +++ b/spec/core/instances_spec.rb @@ -2,7 +2,6 @@ require 'fileutils' describe Weka::Core::Instances do - subject { load_instances('weather.arff') } it { is_expected.to respond_to :each } @@ -35,18 +34,19 @@ it { is_expected.to respond_to :serialize } describe 'aliases:' do - let (:instances) { described_class.new } + let(:instances) { described_class.new } { - numeric: :add_numeric_attribute, - string: :add_string_attribute, - nominal: :add_nominal_attribute, - date: :add_date_attribute, - add_attributes: :with_attributes, - instances_count: :num_instances, - attributes_count: :num_attributes + numeric: :add_numeric_attribute, + string: :add_string_attribute, + nominal: :add_nominal_attribute, + date: :add_date_attribute, + add_attributes: :with_attributes, + instances_count: :num_instances, + attributes_count: :num_attributes, + has_string_attribute?: :check_for_string_attributes }.each do |method, alias_method| - it "should define the alias ##{alias_method} for ##{method}" do + it "defines the alias ##{alias_method} for ##{method}" do expect(instances.method(method)).to eq instances.method(alias_method) end end @@ -59,7 +59,7 @@ end describe ".from_#{type}" do - it "should call the Weka::Core::Loader#load_#{type}" do + it "calls Weka::Core::Loader#load_#{type}" do expect(Weka::Core::Loader) .to receive(:"load_#{type}").once .with("test.#{type}") @@ -68,6 +68,20 @@ end end end + + describe '.from_c45' do + let(:file) { 'example.data' } + + before { allow(Weka::Core::Loader).to receive(:load_c45).and_return('') } + + it 'calls Weka::Core::Loader#load_c45' do + expect(Weka::Core::Loader) + .to receive(:load_c45).once + .with(file) + + described_class.send(:from_c45, file) + end + end end describe 'saver' do @@ -77,7 +91,7 @@ end describe "#to_#{type}" do - it "should call the Weka::Core::Saver#save_#{type}" do + it "calls the Weka::Core::Saver.save_#{type}" do expect(Weka::Core::Saver) .to receive(:"save_#{type}").once .with(file: "test.#{type}", instances: subject) @@ -86,15 +100,31 @@ end end end + + describe '#to_c45' do + let(:file) { 'test.names' } + + before do + allow(Weka::Core::Saver).to receive(:save_c45).and_return('') + end + + it 'calls the Weka::Core::Saver.save_c45' do + expect(Weka::Core::Saver) + .to receive(:save_c45).once + .with(file: file, instances: subject) + + subject.to_c45(file) + end + end end describe '#instances' do - it 'should return an Array of DenseInstance objects' do + it 'returns an Array of DenseInstance objects' do objects = subject.instances expect(objects).to be_an Array all_kind_of_instance = objects.reduce(true) do |result, object| - result &&= object.kind_of?(Java::WekaCore::DenseInstance) + result && object.is_a?(Java::WekaCore::DenseInstance) end expect(all_kind_of_instance).to be true @@ -102,12 +132,12 @@ end describe '#attributes' do - it 'should return an Array of Attribute objects' do + it 'returns an Array of Attribute objects' do objects = subject.attributes expect(objects).to be_an Array all_kind_of_attribute = objects.reduce(true) do |result, object| - result &&= object.kind_of?(Java::WekaCore::Attribute) + result && object.is_a?(Java::WekaCore::Attribute) end expect(all_kind_of_attribute).to be true @@ -115,8 +145,8 @@ end describe '#attribute_names' do - it 'should return an Array of the attribute names' do - names = %w{ outlook temperature humidity windy play } + it 'returns an Array of the attribute names' do + names = %w(outlook temperature humidity windy play) expect(subject.attribute_names).to eq names end end @@ -132,29 +162,36 @@ end context 'with the class_attribute option' do - it 'should define the attribute as class attribute' do + it 'defines the attribute as class attribute' do instances.numeric(name, class_attribute: true) expect(instances.class_attribute.name).to eq name end end end - xdescribe '#string' do + describe '#string' do it 'can be used to add a string attribute' do instances.string(name) expect(instances.attributes.first).to be_string end + + context 'with the class_attribute option' do + it 'defines the attribute as class attribute' do + instances.string(name, class_attribute: true) + expect(instances.class_attribute.name).to eq name + end + end end describe '#nominal' do it 'can be used to add a nominal attribute' do - instances.nominal(name, values: ['yes', 'no']) + instances.nominal(name, values: %w(yes no)) expect(instances.attributes.first).to be_nominal end context 'with the class_attribute option' do - it 'should define the attribute as class attribute' do - instances.nominal(name, values: ['yes', 'no'], class_attribute: true) + it 'defines the attribute as class attribute' do + instances.nominal(name, values: %w(yes no), class_attribute: true) expect(instances.class_attribute.name).to eq name end end @@ -167,7 +204,7 @@ end context 'with the class_attribute option' do - it 'should define the attribute as class attribute' do + it 'defines the attribute as class attribute' do instances.date(name, class_attribute: true) expect(instances.class_attribute.name).to eq name end @@ -182,7 +219,7 @@ end end - xdescribe '#string' do + describe '#string' do it 'can be used to add a string attribute' do instances.string(:attribute_name) expect(instances.attributes.first).to be_string @@ -195,14 +232,14 @@ expect(instances.attributes.first).to be_nominal end - it 'should convert a single option into an Array' do + it 'converts a single option into an Array' do instances.nominal(:attribute_name, values: 'yes') expect(instances.attributes.first.values).to eq ['yes'] end - it 'should convert the options into strings' do + it 'converts the options into strings' do instances.nominal(:attribute_name, values: [true, false]) - expect(instances.attributes.first.values).to eq ['true', 'false'] + expect(instances.attributes.first.values).to eq %w(true false) end end @@ -221,7 +258,7 @@ expect(subject.class_attribute.name).to eq 'play' end - it 'should reset the class attribute if it assigns nil' do + it 'resets the class attribute if it assigns nil' do subject.class_attribute = :play expect { subject.class_attribute = nil } @@ -230,7 +267,7 @@ .to(nil) end - it 'should raise an ArgumentError if the given attribute is not defined' do + it 'raises an ArgumentError if the given attribute is not defined' do expect { subject.class_attribute = :not_existing_attribute } .to raise_error(ArgumentError) end @@ -240,19 +277,18 @@ context 'if class attribute is set' do before { subject.class_attribute = :play } - it 'should return the Attribute' do + it 'returns the Attribute' do expect(subject.class_attribute).to be_kind_of Weka::Core::Attribute expect(subject.class_attribute.name).to eq 'play' end end context 'if class attribute is not set' do - it 'should not raise a Java::WekaCore::UnassignedClassException' do - expect { subject.class_attribute } - .not_to raise_error Java::WekaCore::UnassignedClassException + it 'does not raise a Java::WekaCore::UnassignedClassException' do + expect { subject.class_attribute }.not_to raise_error end - it 'should return nil' do + it 'returns nil' do expect(subject.class_attribute).to be_nil end end @@ -262,13 +298,13 @@ context 'if class_attribute is set' do before { subject.class_attribute = :outlook } - it 'should return true' do + it 'returns true' do expect(subject.class_attribute_defined?).to be true end end context 'if class_attribute is not set' do - it 'should return false' do + it 'returns false' do expect(subject.class_attribute_defined?).to be false end end @@ -277,49 +313,49 @@ describe '#reset_class_attribute' do before { subject.class_attribute = :play } - it 'should reset the class attribute' do + it 'resets the class attribute' do expect { subject.reset_class_attribute } .to change { subject.class_attribute }.to(nil) end end describe '#add_attributes' do - it 'should add the numbers of attributes given in the block' do + it 'adds the numbers of attributes given in the block' do instances = Weka::Core::Instances.new expect { instances.add_attributes do numeric 'attribute' - nominal 'class', values: ['YES', 'NO'] + nominal 'class', values: %w(YES NO) end }.to change { instances.attributes.count }.from(0).to(2) end - it 'should add the types of attributes given in the block' do + it 'adds the types of attributes given in the block' do instances = Weka::Core::Instances.new instances.add_attributes do numeric 'attribute' - nominal 'class', values: ['YES', 'NO'] + nominal 'class', values: %w(YES NO) end - expect(instances.attributes.map(&:name)).to eq ['attribute', 'class'] + expect(instances.attributes.map(&:name)).to eq %w(attribute class) end end describe '#initialize' do - it 'should take a relation_name as argument' do - name = 'name' + it 'takes a relation_name as argument' do + name = 'name' instances = Weka::Core::Instances.new(relation_name: name) expect(instances.relation_name).to eq name end - it 'should have a default relation_name of "Instances"' do + it 'has a default relation_name of "Instances"' do expect(Weka::Core::Instances.new.relation_name).to eq 'Instances' end - it 'should take attributes as argument' do + it 'takes attributes as argument' do attributes = subject.attributes instances = Weka::Core::Instances.new(attributes: attributes) @@ -331,7 +367,7 @@ before { @result = nil } describe '#each' do - it 'should run a block on each instance' do + it 'runs a block on each instance' do subject.each do |instance| @result = instance.value(0) unless @result end @@ -340,7 +376,7 @@ end context 'without a given block' do - it 'should return a WekaEnumerator' do + it 'returns a WekaEnumerator' do expect(subject.each) .to be_kind_of(Java::WekaCore::WekaEnumeration) end @@ -348,7 +384,7 @@ end describe '#each_with_index' do - it 'should run a block on each instance' do + it 'runs a block on each instance' do subject.each_with_index do |instance, index| @result = "#{instance.value(0)}, #{index}" if index == 0 end @@ -357,7 +393,7 @@ end context 'without a given block' do - it 'should return a WekaEnumerator' do + it 'returns a WekaEnumerator' do expect(subject.each_with_index) .to be_kind_of(Java::WekaCore::WekaEnumeration) end @@ -365,7 +401,7 @@ end describe '#each_attribute' do - it 'should run a block on each attribute' do + it 'runs a block on each attribute' do subject.each_attribute do |attribute| @result = attribute.name unless @result end @@ -374,7 +410,7 @@ end context 'without a given block' do - it 'should return a WekaEnumerator' do + it 'returns a WekaEnumerator' do expect(subject.each_attribute) .to be_kind_of(Java::WekaCore::WekaEnumeration) end @@ -382,7 +418,7 @@ end describe '#each_attribute_with_index' do - it 'should run a block on each attribute' do + it 'runs a block on each attribute' do subject.each_attribute_with_index do |attribute, index| @result = "#{attribute.name}, #{index}" if index == 0 end @@ -391,7 +427,7 @@ end context 'without a given block' do - it 'should return a WekaEnumerator' do + it 'returns a WekaEnumerator' do expect(subject.each_attribute_with_index) .to be_kind_of(Java::WekaCore::WekaEnumeration) end @@ -400,27 +436,27 @@ end describe '#add_instance' do - it 'should add an instance from given values to the Instances object' do + it 'adds an instance from given values to the Instances object' do data = [:sunny, 70, 80, 'TRUE', :yes] subject.add_instance(data) expect(subject.instances.last.to_s).to eq data.join(',') end - it 'should add a given instance to the Instances object' do + it 'adds a given instance to the Instances object' do data = subject.first subject.add_instance(data) expect(subject.instances.last.to_s).to eq data.to_s end - it 'should add a given instance with only missing values' do + it 'adds a given instance with only missing values' do data = Weka::Core::DenseInstance.new(subject.size) subject.add_instance(data) expect(subject.instances.last.to_s).to eq data.to_s end - it 'should add a given instance with partly missing values' do + it 'adds a given instance with partly missing values' do data = [:sunny, 70, nil, '?', Float::NAN] subject.add_instance(data) @@ -433,20 +469,20 @@ [[:sunny, 70, 80, :TRUE, :yes], [:overcast, 80, 85, :FALSE, :yes]] end - it 'should add the data to the Instances object' do + it 'adds the data to the Instances object' do expect { subject.add_instances(data) } .to change { subject.instances_count } .by(data.count) end - it 'should call #add_instance internally' do + it 'calls #add_instance internally' do expect(subject).to receive(:add_instance).exactly(data.count).times subject.add_instances(data) end end describe '#internal_values_of' do - it 'should return the internal values of the given values' do + it 'returns the internal values of the given values' do values = [:sunny, 85, 85, :FALSE, :no] internal_values = [0, 85.0, 85.0, 1, 1] @@ -458,7 +494,7 @@ let(:filter) { double('filter') } before { allow(filter).to receive(:filter).and_return(subject) } - it 'should call the given filter‘s #filter method' do + it 'calls the given filter’s #filter method' do expect(filter).to receive(:filter).once.with(subject) subject.apply_filter(filter) end @@ -468,7 +504,7 @@ let(:filter) { double('filter') } before { allow(filter).to receive(:filter).and_return(subject) } - it 'should call the given filters‘ #filter methods' do + it 'calls the given filters’s #filter methods' do expect(filter).to receive(:filter).twice.with(subject) subject.apply_filters(filter, filter) end @@ -484,7 +520,7 @@ let(:instances_c) { Weka::Core::Instances.new(attributes: [attribute_c]) } context 'when merging one instances object' do - it 'should call .merge_instance of Weka::Core::Instances' do + it 'calls .merge_instance of Weka::Core::Instances' do expect(Weka::Core::Instances) .to receive(:merge_instances) .with(instances_a, instances_b) @@ -492,21 +528,24 @@ instances_a.merge(instances_b) end - it 'should return the result of .merge_instance' do + it 'returns the result of .merge_instance' do merged = double('instances') - allow(Weka::Core::Instances).to receive(:merge_instances).and_return(merged) + + allow(Weka::Core::Instances) + .to receive(:merge_instances) + .and_return(merged) expect(instances_a.merge(instances_b)).to eq merged end end context 'when merging multiple instances' do - it 'should call .merge_instances mutliple times' do + it 'calls .merge_instances mutliple times' do expect(Weka::Core::Instances).to receive(:merge_instances).twice instances_a.merge(instances_b, instances_c) end - it 'should return the merged instances' do + it 'returns the merged instances' do merged = instances_a.merge(instances_b, instances_c) merged_attributes = [attribute_a, attribute_b, attribute_c] @@ -515,4 +554,101 @@ end end + describe '#has_string_attribute?' do + context 'if no string attribute exists' do + it 'returns false' do + expect(subject.has_string_attribute?).to be false + end + end + + context 'if dataset has string attribute' do + subject { load_instances('weather.string.arff') } + + it 'returns true' do + expect(subject.has_string_attribute?).to be true + end + end + end + + describe '#has_attribute_type?' do + subject { load_instances('weather.string.arff') } + let(:type) { 'nominal' } + + it 'calls the underlying Java method .check_for_attribute_type' do + expect(subject) + .to receive(:check_for_attribute_type) + .with(subject.send(:map_attribute_type, type)) + + subject.has_attribute_type?(type) + end + + context 'when given String argument' do + Weka::Core::Attribute::TYPES.map(&:to_s).each do |type| + if type == 'date' + it 'returns false if the attribute type does not exist' do + expect(subject.has_attribute_type?(type)).to be false + end + else + it 'returns true if the attribute type exists' do + expect(subject.has_attribute_type?(type)).to be true + end + end + end + + it 'handles attribute type in uppercase' do + expect(subject.has_attribute_type?('STRING')).to be true + end + + it 'returns false for undefined attribute type' do + expect(subject.has_attribute_type?('I_DO_NOT_EXIST')).to be false + end + end + + context 'when given Symbol argument' do + Weka::Core::Attribute::TYPES.each do |type| + if type == :date + it 'returns false if the attribute type does not exist' do + expect(subject.has_attribute_type?(type)).to be false + end + else + it 'returns true if the attribute type exists' do + expect(subject.has_attribute_type?(type)).to be true + end + end + end + + it 'handles attribute type in uppercase' do + expect(subject.has_attribute_type?(:STRING)).to be true + end + + it 'returns false for undefined attribute type' do + expect(subject.has_attribute_type?(:I_DO_NOT_EXIST)).to be false + end + end + + context 'when given Integer argument' do + attribute_types = [ + Weka::Core::Attribute::NUMERIC, + Weka::Core::Attribute::NOMINAL, + Weka::Core::Attribute::STRING, + Weka::Core::Attribute::DATE + ] + + attribute_types.each do |type| + if type == Weka::Core::Attribute::DATE + it 'returns false if the attribute type does not exist' do + expect(subject.has_attribute_type?(type)).to be false + end + else + it 'returns true if the attribute type exists' do + expect(subject.has_attribute_type?(type)).to be true + end + end + end + + it 'returns false for undefined attribute type' do + expect(subject.has_attribute_type?(1000)).to be false + end + end + end end diff --git a/spec/core/loader_spec.rb b/spec/core/loader_spec.rb index a6ffd64..5c34914 100644 --- a/spec/core/loader_spec.rb +++ b/spec/core/loader_spec.rb @@ -1,8 +1,7 @@ require 'spec_helper' describe Weka::Core::Loader do - - CLASS_METHODS = %i{ load_arff load_csv load_json } + CLASS_METHODS = %i(load_arff load_csv load_json load_c45).freeze CLASS_METHODS.each do |method| it "responds to .#{method}" do @@ -14,13 +13,34 @@ method = "load_#{type}" describe "##{method}" do - let(:file) { File.expand_path("../../support/resources/weather.#{type}", __FILE__) } + let(:file) do + File.expand_path("../../support/resources/weather.#{type}", __FILE__) + end it "returns an Instances object for a given #{type.upcase} file" do instances = described_class.send(method, file) - expect(instances).to be_kind_of Weka::Core::Instances + expect(instances).to be_a Weka::Core::Instances end end end + describe '#load_c45' do + let(:names_file) do + File.expand_path('../../support/resources/weather.names', __FILE__) + end + + let(:data_file) do + File.expand_path('../../support/resources/weather.data', __FILE__) + end + + it 'returns an Instances object for a given *.names file' do + instances = described_class.load_c45(names_file) + expect(instances).to be_a Weka::Core::Instances + end + + it 'returns an Instances object for a given *.data file' do + instances = described_class.load_c45(data_file) + expect(instances).to be_a Weka::Core::Instances + end + end end diff --git a/spec/core/saver_spec.rb b/spec/core/saver_spec.rb index 3fac735..2378f15 100644 --- a/spec/core/saver_spec.rb +++ b/spec/core/saver_spec.rb @@ -1,13 +1,12 @@ require 'spec_helper' describe Weka::Core::Saver do - let(:instances) { load_instances('weather.arff') } before(:all) { @tmp_dir = File.expand_path('../../tmp/', __FILE__) } after(:all) { FileUtils.remove_dir(@tmp_dir, true) } - CLASS_METHODS = %i{ save_arff save_csv save_json } + CLASS_METHODS = %i(save_arff save_csv save_json).freeze CLASS_METHODS.each do |method| it "responds to .#{method}" do @@ -29,4 +28,26 @@ end end + describe '#save_c45' do + let(:file) { "#{@tmp_dir}/test" } + let(:names_file) { "#{file}.names" } + let(:data_file) { "#{file}.data" } + + before { instances.class_attribute = :play } + + after do + FileUtils.rm_f(names_file) + FileUtils.rm_f(data_file) + end + + it 'creates a *.names file and a *.data file' do + expect(File.exist?(names_file)).to be false + expect(File.exist?(data_file)).to be false + + described_class.save_c45(file: names_file, instances: instances) + + expect(File.exist?(names_file)).to be true + expect(File.exist?(data_file)).to be true + end + end end diff --git a/spec/core/serialization_helper_spec.rb b/spec/core/serialization_helper_spec.rb index dcbaddc..6fbe724 100644 --- a/spec/core/serialization_helper_spec.rb +++ b/spec/core/serialization_helper_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Core::SerializationHelper do - it { is_expected.to be_kind_of Java::WekaCore::SerializationHelper } describe 'aliases:' do @@ -9,7 +8,7 @@ write: :serialize, read: :deserialize }.each do |method, alias_method| - it "should define the alias .#{alias_method} for .#{method}" do + it "defines the alias .#{alias_method} for .#{method}" do expect(Weka::Core::SerializationHelper.public_class_method(method)) .to eq Weka::Core::SerializationHelper.public_class_method(alias_method) end diff --git a/spec/filters/supervised/attribute_selection_spec.rb b/spec/filters/supervised/attribute_selection_spec.rb index 48c75c1..52137fc 100644 --- a/spec/filters/supervised/attribute_selection_spec.rb +++ b/spec/filters/supervised/attribute_selection_spec.rb @@ -1,13 +1,12 @@ require 'spec_helper' describe Weka::Filters::Supervised::Attribute::AttributeSelection do - describe 'aliases:' do { set_evaluator: :use_evaluator, set_search: :use_search }.each do |method, alias_method| - it "should define the alias ##{alias_method} for ##{method}" do + it "defines the alias ##{alias_method} for ##{method}" do expect(subject.method(method)).to eq subject.method(alias_method) end end diff --git a/spec/filters/supervised/attribute_spec.rb b/spec/filters/supervised/attribute_spec.rb index d00caa9..6bfffaf 100644 --- a/spec/filters/supervised/attribute_spec.rb +++ b/spec/filters/supervised/attribute_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Filters::Supervised::Attribute do - it_behaves_like 'class builder' [ @@ -14,7 +13,7 @@ :NominalToBinary, :PartitionMembership ].each do |class_name| - it "should define a class #{class_name}" do + it "defines a class #{class_name}" do expect(described_class.const_defined?(class_name)).to be true end end diff --git a/spec/filters/supervised/instance_spec.rb b/spec/filters/supervised/instance_spec.rb index d686a1e..20618ef 100644 --- a/spec/filters/supervised/instance_spec.rb +++ b/spec/filters/supervised/instance_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Filters::Supervised::Instance do - it_behaves_like 'class builder' [ @@ -10,7 +9,7 @@ :SpreadSubsample, :StratifiedRemoveFolds ].each do |class_name| - it "should define a class #{class_name}" do + it "defines a class #{class_name}" do expect(described_class.const_defined?(class_name)).to be true end end diff --git a/spec/filters/unsupervised/attribute_spec.rb b/spec/filters/unsupervised/attribute_spec.rb index 8c57a6e..e8344b0 100644 --- a/spec/filters/unsupervised/attribute_spec.rb +++ b/spec/filters/unsupervised/attribute_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Filters::Unsupervised::Attribute do - it_behaves_like 'class builder' [ @@ -61,7 +60,7 @@ :TimeSeriesTranslate, :Transpose ].each do |class_name| - it "should define a class #{class_name}" do + it "defines a class #{class_name}" do expect(described_class.const_defined?(class_name)).to be true end end diff --git a/spec/filters/unsupervised/instance_spec.rb b/spec/filters/unsupervised/instance_spec.rb index d83d06a..6777f28 100644 --- a/spec/filters/unsupervised/instance_spec.rb +++ b/spec/filters/unsupervised/instance_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Filters::Unsupervised::Instance do - it_behaves_like 'class builder' [ @@ -19,7 +18,7 @@ :SparseToNonSparse, :SubsetByExpression ].each do |class_name| - it "should define a class #{class_name}" do + it "defines a class #{class_name}" do expect(described_class.const_defined?(class_name)).to be true end end diff --git a/spec/filters/utils_spec.rb b/spec/filters/utils_spec.rb index fc22172..42db8fe 100644 --- a/spec/filters/utils_spec.rb +++ b/spec/filters/utils_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Weka::Filters::Utils do - subject do Class.new { include Weka::Filters::Utils }.new end @@ -16,12 +15,12 @@ allow(Weka::Filters::Filter).to receive(:use_filter).and_return(nil) end - it 'should set the filter input format' do + it 'sets the filter input format' do expect(subject).to receive(:set_input_format).with(instances) subject.filter(instances) end - it 'should apply the including filter' do + it 'applies the including filter' do expect(Weka::Filters::Filter) .to receive(:use_filter) .with(instances, subject) diff --git a/spec/support/instances_helpers.rb b/spec/support/instances_helpers.rb index 59374d6..77a15f4 100644 --- a/spec/support/instances_helpers.rb +++ b/spec/support/instances_helpers.rb @@ -1,7 +1,8 @@ module InstancesHelpers - def load_instances(file_name) - file = File.expand_path("./../resources/#{file_name}", __FILE__) - Weka::Core::Loader.send("load_#{File.extname(file_name)[1..-1]}", file) + file = File.expand_path("./../resources/#{file_name}", __FILE__) + file_extension = File.extname(file_name)[1..-1] + + Weka::Core::Loader.send("load_#{file_extension}", file) end end diff --git a/spec/support/resources/weather.data b/spec/support/resources/weather.data new file mode 100644 index 0000000..8e004e6 --- /dev/null +++ b/spec/support/resources/weather.data @@ -0,0 +1,14 @@ +sunny,85.0,85.0,FALSE,no +sunny,80.0,90.0,TRUE,no +overcast,83.0,86.0,FALSE,yes +rainy,70.0,96.0,FALSE,yes +rainy,68.0,80.0,FALSE,yes +rainy,65.0,70.0,TRUE,no +overcast,64.0,65.0,TRUE,yes +sunny,72.0,95.0,FALSE,no +sunny,69.0,70.0,FALSE,yes +rainy,75.0,80.0,FALSE,yes +sunny,75.0,70.0,TRUE,yes +overcast,72.0,90.0,TRUE,yes +overcast,81.0,75.0,FALSE,yes +rainy,71.0,91.0,TRUE,no diff --git a/spec/support/resources/weather.names b/spec/support/resources/weather.names new file mode 100644 index 0000000..bef3a43 --- /dev/null +++ b/spec/support/resources/weather.names @@ -0,0 +1,5 @@ +yes,no. +outlook: sunny,overcast,rainy. +temperature: continuous. +humidity: continuous. +windy: TRUE,FALSE. diff --git a/spec/support/resources/weather.string.arff b/spec/support/resources/weather.string.arff new file mode 100644 index 0000000..1dac96a --- /dev/null +++ b/spec/support/resources/weather.string.arff @@ -0,0 +1,25 @@ +@relation weather + +@attribute outlook {sunny, overcast, rainy} +@attribute description string +@attribute temperature real +@attribute humidity real +@attribute windy {TRUE, FALSE} +@attribute play {yes, no} + +@data +sunny,'Sunny. High near 40F. Winds WNW at 15 to 25 mph.',85,85,FALSE,no +sunny,'Sunny skies. High 39F. Winds WNW at 10 to 15 mph.',80,90,FALSE,no +overcast,'Partly cloudy. Low 27F. Winds NW at 5 to 10 mph.',83,86,FALSE,yes +rainy,'Clear skies. Low 27F. Winds WNW at 10 to 15 mph.',70,96,FALSE,yes +rainy,'Cloudy and damp with rain in the morning...then becoming partly cloudy. High 58F. Winds SW at 10 to 15 mph. Chance of rain 100%.',68,80,FALSE,yes +rainy,'Cloudy early with showers for the afternoon hours. High near 45F. Winds NW at 5 to 10 mph. Chance of rain 50%.',65,70,FALSE,no +overcast,'Cloudy. High 41F. Winds SE at 5 to 10 mph.',64,65,TRUE,yes +sunny,'Sunny skies. High 39F. Winds WNW at 10 to 15 mph.',72,95,FALSE,no +sunny,'Except for a few afternoon clouds, mainly sunny. High 34F. Winds WNW at 5 to 10 mph.',69,70,FALSE,yes +rainy,'Rain showers early mixing with snow showers later in the day. Temps nearly steady in the upper 30s. Winds WNW at 5 to 10 mph. Chance of precip 40%. Snow accumulations less than one inch.',75,80,FALSE,yes +overcast,'Partly cloudy skies in the morning will give way to cloudy skies during the afternoon. High 46F. Winds NW at 25 to 30 mph.',75,70,TRUE,yes +overcast,'Partly cloudy. Low 27F. Winds NW at 5 to 10 mph.',72,90,TRUE,yes +overcast,'Considerable clouds early. Some decrease in clouds late. Low 34F. Winds light and variable',81,75,FALSE,yes +rainy,'Overcast with rain showers at times. Low 36F. Winds light and variable. Chance of rain 60%.',71,91,TRUE,no + diff --git a/spec/support/shared_examples/class_builder.rb b/spec/support/shared_examples/class_builder.rb index f485596..6b9f57a 100644 --- a/spec/support/shared_examples/class_builder.rb +++ b/spec/support/shared_examples/class_builder.rb @@ -1,5 +1,4 @@ shared_examples 'class builder' do - subject { described_class } it { is_expected.to respond_to :build_class } diff --git a/weka.gemspec b/weka.gemspec index 334453a..a7eba1e 100644 --- a/weka.gemspec +++ b/weka.gemspec @@ -9,8 +9,8 @@ Gem::Specification.new do |spec| spec.authors = ['Paul Götze'] spec.email = ['paul.christoph.goetze@gmail.com'] - spec.summary = %q{Machine Learning & Data Mining with JRuby.} - spec.description = %q{A JRuby wrapper for the Weka library (http://www.cs.waikato.ac.nz/ml/weka/)} + spec.summary = 'Machine Learning & Data Mining with JRuby.' + spec.description = 'A JRuby wrapper for the Weka library (http://www.cs.waikato.ac.nz/ml/weka/)' spec.homepage = 'https://github.com/paulgoetze/weka-jruby' spec.license = 'MIT' @@ -23,7 +23,6 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] spec.add_runtime_dependency 'lock_jar', '~> 0.13' - spec.add_runtime_dependency 'activesupport', '~> 4.0' spec.add_development_dependency 'bundler', '~> 1.6' spec.add_development_dependency 'rake', '~> 10.0'