diff --git a/lib/rserve.rb b/lib/rserve.rb index 295b263..28393f7 100644 --- a/lib/rserve.rb +++ b/lib/rserve.rb @@ -8,6 +8,7 @@ module Rserve require 'rserve/withnames' require 'rserve/withattributes' +require 'rserve/with2dnames' require 'rserve/protocol' diff --git a/lib/rserve/rexp.rb b/lib/rserve/rexp.rb index b28f234..00aec4f 100644 --- a/lib/rserve/rexp.rb +++ b/lib/rserve/rexp.rb @@ -401,6 +401,12 @@ def to_ruby v.names=v.attributes['names'] end + if v.attributes and v.attributes.has_name? 'dim' and v.attributes.has_name? 'dimnames' and v.attributes['dim'].size == 2 + # Maybe here we should check that dim, dimnames and the payload are consistent + v.extend Rserve::With2DNames + v.sizes = v.attributes['dim'] + v.names = v.attributes['dimnames'] + end end # Hack: change attribute row.names according to spec diff --git a/lib/rserve/with2dnames.rb b/lib/rserve/with2dnames.rb new file mode 100644 index 0000000..b6bc566 --- /dev/null +++ b/lib/rserve/with2dnames.rb @@ -0,0 +1,38 @@ +module Rserve + + module With2DNames + attr_reader :row_names, :column_names, :row_size, :column_size + + def sizes=(sizes) + raise ArgumentError, "sizes must be of size 2" unless (sizes.size == 2) + raise ArgumentError, "mismatch between provided size info and actual number of elements" unless self.size == (sizes[0] * sizes[1]) + @row_size = sizes[0] + @column_size = sizes[1] + + end + + def names=(names) + raise ArgumentError, "sizes must be of size 2" unless (names.size == 2) + raise ArgumentError, "mismatch between provided size info and actual number of elements" unless ((@row_size == names[0].size) and (@column_size == names[1].size)) + @row_names = names[0] + @column_names = names[1] + end + + def two_d_at(i, j) + index_i = (i.is_a? Integer) ? i : @row_names.index(i) + index_j = (j.is_a? Integer) ? j : @column_names.index(j) + self[index_i + index_j * row_size] + end + + def two_d_named? + @row_names and @column_names + end + + def column(j) + index_j = (j.is_a? Integer) ? j : @column_names.index(j) + end + + end + + +end \ No newline at end of file diff --git a/spec/rserve_rexp_to_ruby_spec.rb b/spec/rserve_rexp_to_ruby_spec.rb index e26bee4..a0adf5a 100644 --- a/spec/rserve_rexp_to_ruby_spec.rb +++ b/spec/rserve_rexp_to_ruby_spec.rb @@ -5,6 +5,7 @@ before do @r=Rserve::Connection.new end + after do @r.close end @@ -89,5 +90,42 @@ df.attributes['class'].should=='data.frame' end + + context "when passing a 2d object" do + + let :two_d_object do + col_names = Rserve::REXP::String.new(%w((Intercept) x1 x2 x3), nil) + row_names = Rserve::REXP::String.new(%w(1 2 3), nil) + names_list = Rserve::Rlist.new([row_names, col_names]) + names_vector = Rserve::REXP::GenericVector.new(names_list) + dim_array = [3,4] + dimensions = Rserve::REXP::Integer.new(dim_array) + attr_payload = Rserve::Rlist.new([dimensions, names_vector], %w(dim dimnames)) + attr_list = Rserve::REXP::List.new(attr_payload) + payload = [true, true, true, true, true, true, false, true, true, false, false, true] + Rserve::REXP::Logical.new(payload, attr_list) + end + + before do + @two_d_array = two_d_object.to_ruby + end + + it "should return a 2d object as an array with Rserve;;With2DNames" do + @two_d_array.should be_an Array + @two_d_array.should be_a Rserve::With2DNames + end + + it "should set the row and column labels" do + @two_d_array.row_names.should == %w(1 2 3) + @two_d_array.column_names.should == %W((Intercept) x1 x2 x3) + end + + it "should set the row and column sizes" do + @two_d_array.row_size.should == 3 + @two_d_array.column_size.should == 4 + end + + end + end end diff --git a/spec/rserve_with2dnames_spec.rb b/spec/rserve_with2dnames_spec.rb new file mode 100644 index 0000000..98fd8b4 --- /dev/null +++ b/spec/rserve_with2dnames_spec.rb @@ -0,0 +1,102 @@ +require File.expand_path(File.dirname(__FILE__)+"/spec_helper.rb") +require '../lib/rserve/rexp/string' +require '../lib/rserve/rexp/integer' +require '../lib/rserve/rexp/list' + + +describe Rserve::With2DNames do + + before do + + # when a 2d object is returned by R as an array + # the elements are listed column by column + @array = [1,5,9,2,6,10,3,7,11,4,8,12] + # corresponds to a matrix like + # 1 2 3 4 + # 5 6 7 8 + # 9 10 11 12 + @array.extend Rserve::With2DNames + end + + describe "sizes" do + + context "when passed the correct values" do + + it "should set the size values for rows and columns" do + @array.sizes = [3,4] + @array.row_size.should == 3 + @array.column_size.should == 4 + end + + end + + context "when passed the wrong values" do + + it "should throw if the object passed does not have two elements" do + expect{@array.sizes = [5]}.to raise_error ArgumentError + end + + it "should throw if the sizes passed do not match the array size" do + expect{@array.sizes = [1,5]}.to raise_error ArgumentError + end + + end + + end + + describe "names" do + + context "when passed the correct values" do + + before do + @array.sizes = [3,4] + end + + it "should set the names for rows and columns" do + @array.names = [%w(r1 r2 r3),%w(c1 c2 c3 c4)] + @array.row_names.should == %w(r1 r2 r3) + @array.column_names.should == %w(c1 c2 c3 c4) + end + + end + + context "when passed the wrong values" do + + it "should throw if the object passed does not have two elements" do + expect{@array.names = [%w(r1 r2 r3)]}.to raise_error ArgumentError + end + + it "should throw if the sizes passed do not match the array size" do + expect{@array.names = [%w(r1 r2 r3 r4),%w(c1 c2 c3 c4)]}.to raise_error ArgumentError + end + + end + + end + + describe "two_d_at" do + + before do + @array.sizes = [3,4] + @array.names = [%w(r1 r2 r3),%w(c1 c2 c3 c4)] + end + + + it "should return the correct values using integer indices" do + @array.two_d_at(0,0).should == 1 + @array.two_d_at(2,0).should == 9 + @array.two_d_at(0,3).should == 4 + @array.two_d_at(2,3).should == 12 + end + + it "should return the correct values using names" do + @array.two_d_at("r1","c2").should == 2 + @array.two_d_at("r2","c1").should == 5 + end + + + end + + + +end \ No newline at end of file