You will also need jruby (http://jruby.codehaus.org/); I have renamed the jruby jar to simply jruby.jar. When I wrote this, WEKA was at version 3.6.1 and jruby was at version 1.3.0.
The graphical interface for WEKA can be called up by entering 'java -jar weka.jar' on the command prompt. However, if you add weka.jar to the CLASSPATH, the classes in weka.jar become accessible as an API. The API is documented in javadoc format: point your browser at the file 'documentation.html' within the weka folder, and click on 'Package Documentation'.
Assuming weka.jar, jruby.jar, and your program are in the same folder, the calling format is:
- Write a ruby program, and store in a file, say 'wekascript.rb'
- java -jar jruby.jar wekascript.rb
Clustering Data using WEKA from jRuby
jRuby provides easy access to Java classes and methods, and WEKA is no exception. The following program builds a simple kmeans clusterer on a supplied input file, and then prints out the assigned cluster for each data instance. The 'include_class' statements are there to simplify references to classes in the API. When classifying each instance, we must watch for the exception thrown in case a classification cannot be made. Finally, notice that the filename is passed as a command-line parameter: the parameters after the name of the jruby program are packaged up into ARGV in the usual ruby style.
# Weka scripting from jruby
# Written by Peter Lane, 2009.
# Run with: java -jar jruby.jar trial-kmeans.rb FILENAME.arff
require "java"
require "weka"
include_class "java.io.FileReader"
include_class "weka.clusterers.SimpleKMeans"
include_class "weka.core.Instances"
# load data file
file = FileReader.new ARGV[0]
data = Instances.new file
# create the model
kmeans = SimpleKMeans.new
kmeans.buildClusterer data
# print out the built model
print kmeans
# Display the cluster for each instance
data.numInstances.times do |i|
cluster = "UNKNOWN"
begin
cluster = kmeans.clusterInstance(data.instance(i))
rescue java.lang.Exception
end
puts "#{data.instance(i)},#{cluster}"
end
We can see that the WEKA api makes it easy to pass in a data file. Data can be in a number of formats, including ARFF and CSV.
When run on the weather.arff example (in WEKA's 'data' folder), the output looks like the following:
kMeans
======
Number of iterations: 3
Within cluster sum of squared errors: 16.237456311387238
Missing values globally replaced with mean/mode
Cluster centroids:
Cluster#
Attribute Full Data 0 1
(14) (9) (5)
==============================================
outlook sunny sunny overcast
temperature 73.5714 75.8889 69.4
humidity 81.6429 84.1111 77.2
windy FALSE FALSE TRUE
play yes yes yes
sunny,85,85,FALSE,no,0
sunny,80,90,TRUE,no,0
overcast,83,86,FALSE,yes,0
rainy,70,96,FALSE,yes,0
rainy,68,80,FALSE,yes,0
rainy,65,70,TRUE,no,1
overcast,64,65,TRUE,yes,1
sunny,72,95,FALSE,no,0
sunny,69,70,FALSE,yes,0
rainy,75,80,FALSE,yes,0
sunny,75,70,TRUE,yes,1
overcast,72,90,TRUE,yes,1
overcast,81,75,FALSE,yes,0
rainy,71,91,TRUE,no,1
Using jRuby to Create Instances
One of the advantages of using a language like jruby to talk to WEKA is that we should have more control on how our data is constructed and passed to the machine-learning algorithms. A good start is how to construct our own set of instances, rather than reading them directly in from file. There are some quirks to WEKA's construction of a set of Instances. In particular, each attribute must be defined through an instance of the Attribute class. This class gives a string name to the attribute and, if the attribute is a nominal attribute, the class also holds a vector of the nominal values. Each instance can then be constructed and added to the growing set of Instances. The code below shows how to 'by-hand' construct a dataset which can then be passed to one of WEKA's learning algorithms.
require "java"
require "weka"
include_class "weka.clusterers.SimpleKMeans"
include_class "weka.core.Instances"
include_class "weka.core.Instance"
include_class "weka.core.Attribute"
include_class "weka.core.FastVector"
# Convert provided arguments into a FastVector
# -- use mainly in making nominal attributes
def make_vector *args
atts = FastVector.new(args.length)
args.each {|arg| atts.addElement arg}
return atts
end
# Create the five attributes
outlook = Attribute.new("outlook", make_vector("sunny", "overcast", "rainy"))
temperature = Attribute.new("temperature")
humidity = Attribute.new("humidity")
windy = Attribute.new("windy", make_vector("FALSE", "TRUE"))
play = Attribute.new("class", make_vector("yes", "no"))
# Instances class will hold the complete dataset
dataset = Instances.new(
"weather dataset",
make_vector(outlook, temperature, humidity, windy, play),
14)
# Dataset
DATA = [
["sunny", 85, 85, "FALSE", "no"],
["sunny", 80, 90, "TRUE", "no"],
["overcast", 83, 86, "FALSE", "yes"],
["rainy", 70, 96, "FALSE", "yes"],
["rainy", 68, 80, "FALSE", "yes"],
["rainy", 65, 70, "TRUE", "no"],
["overcast", 64, 65, "TRUE", "yes"],
["sunny", 72, 95, "FALSE", "no"],
["sunny", 69, 70, "FALSE", "yes"],
["rainy", 75, 80, "FALSE", "yes"],
["sunny", 75, 70, "TRUE", "yes"],
["overcast", 72, 90, "TRUE", "yes"],
["overcast", 81, 75, "FALSE", "yes"],
["rainy", 71, 91, "TRUE", "no"]
]
# Convert raw data into Instances
DATA.each do |defn|
instance = Instance.new 5
instance.setValue(outlook, defn[0])
instance.setValue(temperature, defn[1])
instance.setValue(humidity, defn[2])
instance.setValue(windy, defn[3])
instance.setValue(play, defn[4])
instance.setDataset dataset
dataset.add instance
end
puts "Created dataset:"
puts dataset # outputs ARFF format of data
puts "\nClusters from k-means"
# create the model
kmeans = SimpleKMeans.new
kmeans.buildClusterer dataset
# Display the cluster for each instance
dataset.numInstances.times do |i|
cluster = "UNKNOWN"
begin
cluster = kmeans.clusterInstance(dataset.instance(i))
rescue java.lang.Exception
end
puts "#{dataset.instance(i)}, #{cluster}"
end

