Subscribe:

Pages

Keeping wxRuby GUI Working with Threads

A big problem with GUI applications is the unresponsive interface, which happens when the program is busy working on a complex task, leaving no time for updating the graphical interface. All graphical programs performing significant work will require at least two working threads: one thread to look after the graphical interface, redrawing the windows and buttons; and one thread to look after the task.

The following program illustrates the problem. The display is a simple menu with a 'start' item on it. Selecting 'start' will begin a thread displaying ten messages into the text control; the task is just to add up a lot of numbers. Although this works, none of the messages appears until the entire 'busy_task' method is complete.
require 'wx'

class MyFrame < Wx::Frame
def initialize
super(nil, :title => "Thread example")

set_menu_bar menubar

@text = Wx::TextCtrl.new(self, :style => Wx::TE_MULTILINE)
@tasks = 0
end

def menubar
menubar = Wx::MenuBar.new
file_menu = Wx::Menu.new
about_item = Wx::MenuItem.new(file_menu, Wx::ID_ANY, "About")
evt_menu(about_item) { @text.append_text "About\n" }
file_menu.append_item about_item
start_item = Wx::MenuItem.new(file_menu, Wx::ID_ANY, "Start")
evt_menu(start_item) { busy_task}

file_menu.append_item start_item

menubar.append(file_menu, "File")
menubar
end

def busy_task
@tasks += 1
tasks = @tasks
10.times do
@text.append_text "running #{tasks} ... #{Time.new}\n"
(1...1000_000).inject :+
end
@text.append_text "Thread #{tasks} done\n"
end
end

Wx::App.run do
MyFrame.new.show
end

Threads and Timers

We can improve on the above code by placing the work within the 'busy_task' method into a thread:

def busy_task
# start task in a separate thread
Thread.new do
@tasks += 1
tasks = @tasks
10.times do
@text.append_text "running #{tasks} ... #{Time.new}\n"
(1...1_00_000).inject :+
end
@text.append_text "Thread #{tasks} done\n"
end
end
end

This will indeed work. The GUI stays responsive, and more than one thread can be active, by selecting the 'start' menu item again. However, you may find the working thread seems to stop doing anything at times; this happens if I switch focus to another window, so the ruby application is running in the background.

The second improvement is to add a Timer. This is created in the initialize method for the frame. The Timer's role is to wake up after a short period of time, and pass processing control to the next thread. In this case, I make the Timer wake up after 10 milliseconds.

def initialize
super(nil, :title => "Thread example")

set_menu_bar menubar

# -- this timer interrupts every 100ms and gets next thread active
# -- if you don't have the timer, the GUI does not update if window
# in background
timer = Wx::Timer.new(self, Wx::ID_ANY)
evt_timer(timer.id) {Thread.pass}
timer.start(10)

@text = Wx::TextCtrl.new(self, :style => Wx::TE_MULTILINE)
@tasks = 0
end

Summary

Worker Threads and a Timer to make sure the Threads are kept active is probably the key to keeping wxRuby's GUI active under large-running tasks, although I expect to have to do some experimenting in future. For better information, Mario Steele has described the principles here and in a tutorial.

Using LibSVM from Ruby with data in files

In previous posts, I looked at RubySVM and how to set up models with data held within the memory of a running Ruby process. I have found this does not scale up to larger datasets of images, taking up to a gigabyte in storage on disk, and so I have adopted a different approach, constructing training/test files on disk and then calling the command line svm-train and svm-predict functions. Ruby then drives the whole process, automating the construction of the datasets, selection of the best models, and final evaluation. I use the UCI Letter Recognition dataset discussed earlier as an example, but the approach is fully general.

Calling System Commands

The first step in controlling external programs is calling them, and for this Ruby's backtick syntax is ideal. `ls` will call the ls command, returning the result as a string, which we can store with result = `ls`: result.split("\n") then produces an array of filenames in the current directory.

This backtick notation supports the interpolation of arbitrary Ruby code, just like in
strings. Thus, writing `ls *.#{extension}` will use the value of extension to list file of the given extension only.

Try it from irb:

peter@peter-desktop:~/Code$ irb
irb(main):001:0> `ls`
=> "instances.html\njruby.jar\ntest-instances.rb\ntrial-kmeans.rb\nweather.arff\nweka.jar\n"
irb(main):002:0> result = `ls`
=> "instances.html\njruby.jar\ntest-instances.rb\ntrial-kmeans.rb\nweather.arff\nweka.jar\n"
irb(main):003:0> result.split("\n")
=> ["instances.html", "jruby.jar", "test-instances.rb", "trial-kmeans.rb", "weather.arff", "weka.jar"]
irb(main):004:0> extension = "rb"
=> "rb"
irb(main):005:0> `ls *.#{extension}`.split("\n")
=> ["test-instances.rb", "trial-kmeans.rb"]

Constructing the Datasets

The data is read in, just like I did before, creating an array @@data containing instances of the LetterInstance datatype. I then use input parameters to partition the dataset into training, cross-validation and test datasets:

# Read in letter-recognition UCI data

LetterInstance = Struct.new(:letter, :x_box, :y_box, :width, :height,
:on_pixels, :x_bar, :y_bar, :x2bar, :y2bar,
:xybar, :x2ybar, :xy2bar, :x_ege, :xegvy,
:y_ege, :yegvx)

@@data = []

File.open("letter-recognition.data", "r") do |f|
f.each_line do |line|
@@data << LetterInstance.new(*line.strip.split(","))
end
end

# -- using given proportion for training/cross-validation sets, create three files

if ARGV.length != 2
puts "To use: ruby train-models.rb TRAINING_PROPORTION CROSS_PROPORTION"
exit
end

@@training_proportion = ARGV[0].to_f
@@cross_validation_proportion = ARGV[1].to_f

@@num_training = (@@data.length * @@training_proportion).floor
@@num_cross = (@@num_training * @@cross_validation_proportion).floor

# Select Train/Cross/Test sets
# -- note, cross-validation size is taken out of the training-set

@@data.shuffle!

TrainSet = @@data[@@num_cross...@@num_training]
CrossSet = @@data[0...@@num_cross]
TestSet = @@data[@@num_training..-1]

The three datasets must now be saved onto disk, to form the datasets to pass to the SVM. The file format for an SVM places each instance onto a single line in the file. Each instance has its class first, and then its attribute values. Every item is separated by a space. The class must be a number, and the attribute values should be in the range [0,1] or [-1,1]. The attribute-values are written by writing the index of the attribute, followed by a colon, and then the value. An example from the training file is:

17 0:0.466666666666667 1:0.6 2:0.666666666666667 3:0.533333333333333 ....

The advantage of this notation is that it supports sparse data - any attribute that has value 0 can simply be ignored.

The following code constructs the required data:

# Create SVM format of features in instance, rescaling to [0,1]
def make_data item
str = ""
(item.length-1).times do |i|
str << "#{i}:#{item[i+1].to_f/15.0} "
end
str
end

def save_file(dataset, filename)
File.open(filename, "w") do |f|
dataset.each_with_index do |item, index|
f.puts "#{item.letter.ord - 65} #{make_data(item)}"
end
end
end

save_file(TrainSet, "training-data.dat")
save_file(CrossSet, "cross-data.dat")
save_file(TestSet, "test-data.dat")

Training the Models

I create each model using svm-train, supplying it a training data set and set of parameters. As I am creating SVMs with an RBF kernel, there are two parameters that particularly interest me: cost and gamma. I will save a model file for each pair of parameters, embedding the parameter value in the name. This is all done by string interpolation in the backtick call to svm-train.

# -- train models using Cost/Gamma values and external system
Costs = [2**-5,2**-3,2**-1,1,2,8,2**5,2**8,2**10,2**13,2**15]
Gammas = [2**-15,2**-12,2**-8,2**-5,2**-3,2**-1,2,2**3,2**5,2**7,2**9]

Costs.each do |cost|
Gammas.each do |gamma|
`svm-train -s 1 -t 2 -d 3 -r 0 -e 0.001 -c #{cost} -g #{gamma} training-data.dat l-#{cost.to_f}-#{gamma.to_f}.model`
end
end

Evaluating a Model

svm-predict is the command-line tool for evaluating a model on a given dataset. It produces two sets of outputs. A file containing the actual predictions of the model for each instance in the dataset, and a printed output of the model's accuracy. This accuracy is a percentage correct. We can retrieve it using a regular expression match on the result of running svm-predict.

Our models are defined by their cost and gamma values, using the format above. Given the filename of a dataset to test on, the following function will run svm-predict and parse its output to produce the model's accuracy. The actual predictions are sent to the same file - I don't want to keep them.

# Test the model on given dataset, and parse output to get accuracy
def get_accuracy(cost, gamma, dataset)
result = `svm-predict #{dataset} l-#{cost.to_f}-#{gamma.to_f}.model test.out`
result.match(/\d+.\d+/)[0].to_f
end

Selecting a Model and Final Evaluation

Having trained models for every combination of cost and gamma, we need to find the one that does best on the cross-validation dataset, and then evaluate it on the test set. This is straightforward:

# Search for best cost/gamma pairing, based on result on cross-validation dataset
@@best_cost = Costs[0]
@@best_gamma = Gammas[0]
@@best_accuracy = 0.0
Costs.each do |cost|
Gammas.each do |gamma|
accuracy = get_accuracy(cost, gamma, "cross-data.dat")
if accuracy > @@best_accuracy
@@best_accuracy, @@best_cost, @@best_gamma = accuracy, cost, gamma
end
end
end

puts "Best cost #{@@best_cost}, Best gamma #{@@best_gamma}"
puts "Test score is: #{get_accuracy(@@best_cost, @@best_gamma, "test-data.dat")}"


Summary

Running the letter-recognition task with 2000 training examples using files, instead of in memory, took 11 minutes in total, instead of the 35 minutes my in-memory solution required. So the saving in time is considerable.

Accuracy with 2000 training examples was 82%.

I also ran the system using 72% of the data (14,400 instances) for training, 8% (1600 instances) as cross-validation, and the remaining 20% (4000 instances) as test data. I searched across 121 combinations of Cost/Gamma pairs using an RBF kernel. The best fit had a cost of 1/32, gamma of 32, and produced an accuracy of 97.5%. This took 6 hours to run.

Working with RubySVM

A classification model is a tool for mapping a sequence of inputs to a predicted output. To construct a classification model, we have to train it on some data where we have the expected output. There are many kinds of classification models; here, I build SVM models using the RubySVM library. For an explanation of SVMs and how to best use the SVM library, please see the LibSVM documentation; this post is an example of using the library from Ruby.

I use the UCI letter recognition example discussed in this earlier post, and assume the SVM library has been installed, as also discussed earlier.

In the letter recognition example, each letter has a "label" (called 'letter') and several attributes, such as 'x-box', 'width', 'height', etc. The classification problem is to compute the label from the attributes. To begin, we assume a dataset of examples which contain attributes and a label.

General Method

The pattern for constructing and using an SVM model is as follows:
  1. The data is partitioned into a training and a test set. The test set is used at the end, to evaluate the model. It is not used at all during model development or parameter selection.
  2. The training set is partitioned into a cross-validation set and an actual training set.
  3. Search through the parameter space of possible SVM models, training each model on the actual training set, and evaluating it on the cross-validation set.
  4. Choose the parameter settings which produce the best performance on the cross-validation set.
  5. Use the chosen parameter settings to evaluate the SVM model on the test set, and report this final figure.
There are many improvements needed for serious work, but that pattern is enough to get an idea of how building a model works, and, most importantly, sort out the main requirements on a Ruby program to handle all the steps.

Creating a Classification Problem

Our dataset used the following structure:
LetterInstance = Struct.new(:letter, :x_box, :y_box, :width, :height,:on_pixels, :x_bar, :y_bar, :x2bar, 
:y2bar,:xybar, :x2ybar, :xy2bar, :x_ege, :xegvy,:y_ege, :yegvx)

The field 'letter' is the label, and the rest are the attributes. Due to the way we read in and create instances of this structure, the attributes are actually strings. We store all the data into a global array, called @@data.

The data for the SVM needs to be separated into the class 'labels' and the values for the attributes. All the values need to be numeric. For the labels, we can just assign integer values to each class. For the attribute values, we need each number to be in the range [0,1] or [-1,1]. We achieve this by rescaling the actual attribute values to fit into the range. For the letter task, each attribute value is a number from 0 to 15, inclusive; dividing by 15 to form a fractional number gets each attribute value into the right range.

The following code provides two functions to collect the label and an array of attribute values for a given letter-recognition example:

@@classes = @@data.collect {|datum| datum.letter}.uniq.sort

def make_number char
@@classes.index char
end

def transform datum
data = []
1.upto(datum.members.size-1) do |i|
data << datum[i].to_f / 15.0 # all data in range [0,15]
end
data
end

We must now randomise the list (using shuffle), and collect samples for training, cross-validation, and testing. This is easy using subranges:


TrainSet = @@data[0...2000].collect {|datum| transform(datum)}
TrainLabels = @@data[0...2000].collect {|datum| make_number(datum.letter)}
CrossSet = @@data[9000...10000].collect {|datum| transform(datum)}
CrossLabels = @@data[9000...10000].collect {|datum| make_number(datum.letter)}
TestSet = @@data[10000..-1].collect {|datum| transform(datum)}
TestLabels = @@data[10000..-1].collect {|datum| make_number(datum.letter)}


Training an Individual Model


To train a model, we create an instance of SVM::Problem, which is constructed by adding each instance from the training set, in turn, giving its label and attribute values; TrainProblem is constructed here, to hold the training data. The model is trained by creating it using the Problem description and a set of Parameters. The following function constructs an SVM model using a given problem and values for the Cost and Gamma parameters; other parameter values are set to default settings. Here, I use an RBF kernel for the SVM, but others are available.


def make_problem(data, labels)
problem = SVM::Problem.new
data.each_index do |i|
problem.addExample(labels[i], data[i])
end
problem
end

def make_rbf_svm_model(problem, cost, gamma)
params = SVM::Parameter.new
params.C = cost
params.gamma = gamma
params.kernel_type = SVM::RBF
# some default values
params.svm_type = SVM::NU_SVC
params.degree = 1
params.coef0 = 0
params.eps = 0.001
# construct the model
SVM::Model.new(problem, params)
end

TrainProblem = make_problem(TrainSet, TrainLabels)


Evaluating a Model


Once we have trained a model, we can see what prediction it makes for a given instance and compare that with the true label (the one from the original dataset). The following code runs through an array of examples and counts up the number of errors made by the given model:

def test_model(model, data, labels)
errors = 0
labels.each_index do |i|
prediction, probs = model.predict_probability(data[i])
errors += 1 if labels[i] != prediction
end
errors
end

Searching for the best Cost and Gamma

The critical parameters for the Radial-Basis Function kernel are the Cost function and Gamma. We need to
do a search across a range of possible values, to locate the model which performs the best. To evaluate the model, we use a cross-validation set, which is taken from the training data, but different from the test set, which we only use at the very end, once we have selected the SVM parameters.

The following code performs a simple grid search across a range of values for Cost and Gamma, evaluating the model generated by each set of parameters against the cross-validation set. The model with the fewest errors is then selected.


Costs = [2**-5,2**-3,2**-1,1,2,8,2**5,2**8,2**10,2**13,2**15]
Gammas = [2**-15,2**-12,2**-8,2**-5,2**-3,2**-1,2,2**3,2**5,2**7,2**9]

best_model = nil
lowest_error = CrossLabels.size
Costs.each do |cost|
Gammas.each do |gamma|
svm_model = make_rbf_svm_model(TrainProblem, cost, gamma)
cross_validation_result = test_model(svm_model, CrossSet, CrossLabels)
if cross_validation_result < lowest_error
best_model = svm_model
lowest_error = cross_validation_result
end
puts "Testing: C = #{cost} G = #{gamma} -> #{cross_validation_result}"
end
end

puts "Error rate is: #{test_model(best_model, TestSet, TestLabels)*100.0/TestLabels.size}%"


Final Result

The performance of the final model is evaluated by finding the number of errors it makes against the test dataset. In this case, I get an error rate of around 16%, which is not too bad, as I only trained on 2000 of the 20000 examples.

Summary

This description has covered the very simplest
mechanics of working with RubySVM to build SVM models of data. To get good quality models and properly evaluate the results, you need to be more careful in your selection of parameters and use of cross-validation and test sets. The LibSVM documentation is a good place to start. For optimum performance, the selection of the Cost and Gamma values is critical, and needs finer scale searching than I used above.

You do have to be patient with these techniques. The grid search is not quick; it took around 35 minutes on my 2.8GHz machine to run through all the models, and this was a fairly crude search. This is not the fault of Ruby, as the computation time is required for LibSVM to build the models. Ruby manages the entire data collection, transformation and model selection process in about 80 lines of fairly straightforward code.

LibSVM and Ruby

LibSVM is the standard library for using Support Vector Machines, a powerful machine-learning tool. Installing LibSVM can be done through your usual package manager system, or through the LibSVM download page. There is Ruby-LibSVM library, available from http://rubysvm.cilibrar.com/download/. If you download the library, you will find the installation instructions do not work for Ruby 1.9. However, the fix is relatively simple. After downloading and unpacking Ruby-LibSVM, run ./configure. Before compiling, modify the main.cpp file to allow for the change between Ruby 1.8 and Ruby 1.9. I had to make three changes:
  1. remove the line #include "node.h"
  2. change RARRAY(xs)->len on line 50 to RARRAY_LEN(xs)
  3. change RARRAY(xs)->len on line 579 to RARRAY_LEN(xs)
(Thanks to Lee Hinman for the clue as to what to do.) After those changes, I could finally run 'make' and then 'sudo make install' to install the LibSVM library for Ruby. Ilya Grigorik has a nice example of using Ruby-LibSVM, which I used to check everything was working.

Displaying Multi Column Data in wxRuby

This post is about using the ListCtrl widget within wxRuby to display data in a table format, using several columns naming the fields in the data. The most straightforward way to manage the data is to construct an underlying data model, and use the virtual listctrl style to have your data displayed dynamically. This method also efficiently displays large datasets.

To demonstrate the simplest case, I will display some data from a CSV text file. For example, the
letter recognition dataset from the UCI machine learning repository consists of two files. The file ending '.data' contains the raw data. Each line contains the class and attributes of an instance from the data set. Each instance can be represented using a Ruby struct. Taking the attribute names from the file ending '.names', I build the following struct:

LetterInstance = Struct.new(:letter, :x_box, :y_box, :width, :height,
:on_pixels, :x_bar, :y_bar, :x2bar, :y2bar,
:xybar, :x2ybar, :xy2bar, :x_ege, :xegvy,
:y_ege, :yegvx)

I define the Struct with the attribute names in the order that they occur in the CSV file, as this makes reading the CSV file simple. To read in the textfile to create an array of instances of this struct:


@@data = []

File.open("letter-recognition.data", "r") do |f|
f.each_line do |line|
@@data << LetterInstance.new(*line.strip.split(","))
end
end

Displaying the Data

Building the list control is almost trivial. I use two facts about Ruby Structs to help in constructing the table automatically. First, the method 'members' can be used to find all the field names within the Struct; these are used for the column headings.

For example: @@data[0].members.join(", ")
returns: "letter, x_box, y_box, width, height, on_pixels, x_bar, y_bar, x2bar, y2bar, xybar, x2ybar, xy2bar, x_ege, xegvy, y_ege, yegvx"

Second, fields within a Struct can be accessed using array indexes. So, @@data[0][0] returns the value in the 'letter' field of the first item.

The following few lines of code create a class for our own virtual list control, which extends Wx::ListCtrl. We are building a particular kind of list control, which uses 'report format' and a 'virtual list data model'. Other kinds exist, but are not covered here. The initialize method builds the columns automatically, and sets the number of items.
The method 'on_get_item_text' is the one which makes this virtual list control so easy to use. The method is called only when an item is to be displayed. It is given the index of the item to display, and the column number. Your method just has to return the string for this item and column: as our data is stored as an array of structs, the implementation is as simple as can be.
# -- show data in a ListCtrl

require 'wx'

class LetterDataList < Wx::ListCtrl
def initialize parent
super(parent, :style => Wx::LC_REPORT | Wx::LC_VIRTUAL)
return if @@data.nil? or @@data.empty?

# Directly retrieve field names in Struct to populate table with columns
@@data[0].members.each_with_index do |name, index|
insert_column(index, name.to_s)
end

set_item_count @@data.size
end

# use array like indexing of fields in Struct to
# directly retrieve data for display
def on_get_item_text(item, column)
@@data[item][column]
end
end

class LetterDisplay < Wx::Frame
def initialize
super(nil, :title => "Letter Recognition Data")

LetterDataList.new self
end
end

Wx::App.run do
LetterDisplay.new.show
end


A screenshot of the result:


Handling Events


The list control supports a number of events. The most useful are for catching double clicks on a row or change in the highlighted row. Double clicks are known as activating the item, and single clicks as selecting the item. The event evt_list_item_activated captures a double click, and the event evt_list_item_selected captures a change in the focussed item (through a single click or use of arrow keys). Both events are easy to capture and redirect to a method to handle them. The following code will just print out the item(s) now selected by walking through the list of selected indices obtained by calling selections:

class LetterDataList < Wx::ListCtrl
def initialize parent
super(parent, :style => Wx::LC_REPORT | Wx::LC_VIRTUAL)
return if @@data.nil? or @@data.empty?

# Directly retrieve field names in Struct to populate table with columns
@@data[0].members.each_with_index do |name, index|
insert_column(index, name.to_s)
end

evt_list_item_selected(self) { selected_item }

set_item_count @@data.size
end

# use array like indexing of fields in Struct to
# directly retrieve data for display
def on_get_item_text(item, column)
@@data[item][column]
end

def selected_item
get_selections.each do |selection|
puts @@data[selection]
end
end

Using jRuby from Netbeans

Netbeans is a great development platform which supports Ruby and jRuby. In this post, I have collected together the steps I needed to write and run a jRuby script with a library file, such as Weka or my own jChrest.

First, you must download and install the Netbeans platform. ( I started by downloading the version for Java SE alone.)


Second, you must add the plugin to use Ruby programs. To do this start up Netbeans, and select the 'plugins' option on the 'Tools' menu. Under the tab for 'Available plugins', select the 'Ruby' plugin and install it. (You may not need this step, if you downloaded a version with Ruby included in the previous step.)

Third, download and install jRuby. You need to download and unpack one of the 'binary' distributions. Take note of the 'bin' directory, as you need to know this to tell Netbeans about your jRuby distribution.

Fourth, start up Netbeans and create a new ruby project.

After clicking 'next' you need to specify the Ruby platform you want to use.

The first time we do this, we must tell Netbeans about our new jRuby installation, so click on 'Manage...' and select the 'jruby' executable in the 'bin' directory you installed in step 3. Once you've done this, Netbeans will remember your jruby installation for future projects.

Now, you can create some ruby code and click on 'Run Main Project' to see the output in the 'Output window'. Two things may need changing. First, to set a project to be the 'Main' project, the one which is run when you click the green triangle, you need to right click on the name of the project and select 'Set as Main Project'. Second, you may need to change the ruby platform or version. Again, right click on the name of the project and select 'Properties'. Click on the 'Run' option on the left hand side and you can change the Ruby Platform in the choice box, and add options to the interpreter. In the example below, you can see I have made jRuby use 1.9 mode:

Accessing Java Libraries

The final step in setting up the environment is to include a Java library within the project. For example, to work with Weka, you need to access the file weka.jar. To do this within Netbeans, go to the 'Java' option in the Properties dialog box shown above. Select 'Add JAR/Folder' and add the jar file to the classpath for this project. That's all there is to it. The only change I needed to make to my scripts was to remove the line 'require "weka"'; this is taken care of by adding the jar file to the project.


Plotting Multiple Data Sources in RSRuby

In this post, I look at plotting multiple data sources on a single graph from RSRuby, and also how to use Ruby to simplify the method of constructing such graphs.

Plotting Multiple Data Sources

Consider the table below as a typical example, which contains some data on craters on different planetary bodies of the solar system. There are three columns. The first gives the parent body (planet) of the crater, the second gives the diameter of the crater, and the third gives the depth of the crater. What we would like to see is a plot with the two axes holding the quantitative information, and the labels of the points showing the planet. (Note: these are not real data.)











Planetary BodyDiameter (km)Depth (km)
Moon3355.7
Moon663.0
Moon412.5
Mars501.0
Mars801.5
Ganymede200.8
Ganymede701.0
Ganymede800.9


In a previous post, I looked at plotting a single set of points. For example, to make a simple graph of diameter vs depth for the Moon's craters, you could use the following code:
require 'rsruby'

@@r = RSRuby.instance

@@r.png("mygraph.png")
@@r.plot([335,66,41], [5.7,3.0,2.5],
:xlab => "Diameter (km)", :ylab => "Depth (km)",
:main => "Crater diameter vs. depth",
:xlim => [0, 350], :ylim => [0, 6],
:pch => 15, :col => "blue")
@@r.eval_R("dev.off()")

The points are plotted using a symbol, controlled by the number passed to :pch. The range of numbers for :pch goes from 1 to 25. http://www.phaget4.org/R/plot.html has a diagram showing the effect of the different numbers, but it is good to experiment a little for your own graph.

To add more points to the graph, with different symbols, we use the 'points' command for each group of data, before the 'eval' command:
require 'rsruby'

@@r = RSRuby.instance

@@r.png("mygraph.png")
@@r.plot([335,66,41], [5.7,3.0,2.5],
:xlab => "Diameter (km)", :ylab => "Depth (km)",
:main => "Crater diameter vs. depth",
:xlim => [0, 350], :ylim => [0, 6],
:pch => 15, :col => "blue")
@@r.points([50,80], [1.0,1.5], :pch => 19, :col => "green")
@@r.points([20,70,80], [0.8,1.0,0.9], :pch => 4, :col => "red")
@@r.eval_R("dev.off()")

The resulting graph is:


Simplifying the Code

Automation is a big benefit of doing the above kind of process from Ruby. How can we simplify the code to make it as easy as possible to construct and save a graph from a new set of data? For example, if we extend the above table with another two attributes, we might want to create all possible 6 graphs, one for each pair of attributes. To make the call as easy as possible, we need to encapsulate the graph construction process within a single function with some suitable parameters.

First, we need a structure to represent the data in a single group. Ruby provides a Struct data structure just for such purposes. We create a Struct holding the label, points and display information for the group of data.
DataItems = Struct.new(:name, :x_points, :y_points, :pch, :col)

Then, the graph construction function can process an array of these structures, adding the information to the graph. The construction process adds all the data using the 'points' method in R. For this to work, you have to provide 'xlim' and 'ylim' in the parameters so the graph is drawn to the correct size. The 'plot_graph' method works by iterating through a list of provided DataItems, and adding the points to the graph using the display attributes. As an extra, there is a line to add a legend to the graph, again by collecting information from the DataItems array:
module L
R = RSRuby.instance

def L.plot_graph(filename, data, params)
R.png(filename, :res => 100)
R.plot([], [], params)
data.each do |items|
R.points(items.x_points, items.y_points, :pch => items.pch, :col => items.col)
end
R.legend("bottomright", data.collect {|items| items.name},
:col => data.collect {|items| items.col},
:pch => data.collect {|items| items.pch})
R.eval_R("dev.off()")
end
end

Finally, this is how to plot the same graph as above:
L.plot_graph("graph.png",
[DataItems.new("Moon", [335,66,41], [5.7,3.0,2.5], 15, "blue"),
DataItems.new("Mars", [50,80], [1.0,1.5], 19, "green"),
DataItems.new("Ganymede", [20,70,80], [0.8,1.0,0.9], 4, "red")],
:xlim => [0, 350],
:ylim => [0, 6],
:xlab => "Diameter (km)",
:ylab => "Depth (km)",
:main => "Crater diameter vs. depth")


There are still parts that could be included as parameters to the function, such as the location of the legend. The advantage of an approach such as the above is not to make it into a general function, but to provide a pattern to copy and modify in a particular case. With the above, the call to create a graph is a single method call, and that can make automation of graph construction a lot easier. For example, to create a whole range of graphs from a table of data.

ILLNESS AND MEDICAL CARE

H1N1 influenza (swine flu)

H1N1 influenza, also known as "swine flu," is a newly identified virus that can spread from people who are infected to others through coughs and sneezes. When people cough or sneeze, they spread germs through the air or onto surfaces that other people may touch. H1N1 influenza is not transmitted from pigs to humans or from eating pork products. H1N1 influenza is a new virus to human populations, so people's bodies have little ability to fight H1N1 infection. To avoid spreading illness to others, people with symptoms of the flu should stay at home until any fever has been gone for at least 24 hours without the use of fever-reducing medicines.

When should I seek medical care?

Use the same judgment you would use during a typical flu season. Do not seek medical care if you are not ill or have mild symptoms for which you would not ordinarily seek medical care. If you have more severe symptoms of fever, cough, sore throat, body aches or are feeling more seriously ill, call your health care provider to discuss your symptoms and if you need to be evaluated.

If the following flu-like symptoms are mild, medical attention is not typically required.

* Runny nose or nasal stuffiness
* Low-grade fever for less than 3 days
* Mild headache
* Body aches
* Mild stomach upset

What are the symptoms of H1N1 flu (swine flu)?

The symptoms of H1N1 flu in people are similar to the symptoms of seasonal flu and include fever, cough, sore throat, body aches, headache, chills and fatigue. Some people with H1N1 flu also reported diarrhea and vomiting. In the past, severe illness (pneumonia and respiratory failure) and deaths have been reported with swine flu infection in people. Similar to seasonal flu, swine flu may make chronic medical conditions worse.

How do I get Tamiflu or Relenza?

Health care providers can prescribe Tamiflu or Relenza after examining a patient and determining that person is sick enough to need the medication. Do not try to buy Tamiflu or Relenza from companies offering the drugs online without a prescription. If your doctor prescribes Tamiflu for you, do not give your medication to anyone else, even if they have the same symptoms as you do. It can be harmful for people to take this medication if their doctor has not prescribed it.

Are there medicines to treat H1N1 flu (swine flu)?

Yes, the antiviral oseltamivir or zanamivir (brand names Tamiflu and Relenza) can treat infection with H1N1 influenza viruses. Antiviral drugs are prescription medicines (pills, liquid or an inhaler) that fight against the flu by keeping flu viruses from reproducing in your body. If you get sick, antiviral drugs can make your illness milder and make you feel better faster. They may also prevent serious flu complications. For treatment, antiviral drugs work best if started soon after getting sick (within two days of symptoms).

If you get sick with influenza

* If you get sick, Public Health - Seattle & King County strongly recommends that you stay home from work or school so you can get better and keep others from getting sick.
* Also, if you get sick with influenza, remain at home and avoid contact with others until you've had no fever for 24 hours.

When using facemasks:

* Change masks when they become moist
* Do not leave masks dangling around the neck
* Throw away used masks
* After touching or throwing away a used mask, wash hands or use alcohol sanitizer

Can I go to large gatherings, like concerts and sports events?

To date, the severity of the H1N1 flu outbreak appears to similar to a regular winter flu season. Make decisions about going to large gatherings as you would during a winter flu outbreak. If you want to do everything you can to avoid catching H1N1 flu virus, then avoid large gatherings. It is especially important not to participate in group gatherings if you are ill or have symptoms of influenza.

Public Health does not recommend the use of masks except for the following people:

* Sick people if they must be near others at home, or if they must leave the home (such as for an appointment with a health care provider).
* Caregivers of a people ill with influenza – when the caregiver leaves their home. This is to prevent spreading flu to others in case the caregiver is in the early stages of infection.

Whenever possible, do not rely on the use of facemasks or respirators alone to provide respiratory protection against novel influenza virus infection. The best way to prevent exposure to influenza is to avoid contact with ill people. Other steps include avoiding crowded setting and washing your hands frequently.

HEALTH PROTECTION TIPS

What can I do to protect myself and my family?

Take these everyday steps to protect your health:

* Cover your nose and mouth with a tissue or your sleeve when you cough or sneeze. Throw the tissue in the trash after you use it.
* Wash your hands often with soap and water, especially after you cough or sneeze. Alcohol-based hand cleaners are also effective.
* Avoid touching your eyes, nose or mouth. Germs spread this way.
* Try to avoid close contact with sick people.
* Get a H1N1 influenza vaccine. The H1N1 influenza vaccine is not available yet, but it may be available in mid-October. In King County H1N1 vaccine will be distributed using Public Health clinics, private providers and pharmacies. Individuals and families can get their H1N1 influenza vaccine at the same place they get the seasonal flu vaccine.
* Get your seasonal flu vaccine. The H1N1 influenza vaccine does not replace seasonal flu vaccine. It is important that people in high risk groups for seasonal flu get their seasonal influenza shot so that they are protected. Older individuals are at higher risk for seasonal flu and Public Health recommends they get the flu vaccine every years.
* If you don't have one yet, consider developing a family emergency plan as a precaution. This should include storing a supply of extra food, medicines, and other essential supplies. This is to avoid contact with other people as much as possible, including trips to the store. Prepare to get by for at least two weeks on what you have at home.

Should I wear a mask?

Facemasks (surgical masks) may prevent the wearer from coughing on others, and may protect the nose and mouth of the wearer from contact with other people's coughs. They do not offer complete protection because they do not fit tightly to the face, allowing very small air particles to leak in around the edge of the mask.