simpler 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +25 -11
- data/VERSION +1 -1
- data/lib/simpler.rb +94 -50
- data/lib/simpler/data_frame.rb +17 -0
- data/lib/simpler/plot.rb +7 -2
- data/lib/simpler/reply.rb +4 -5
- data/spec/simpler_spec.rb +10 -10
- metadata +3 -3
data/README.rdoc
CHANGED
@@ -12,26 +12,33 @@ to you.
|
|
12
12
|
|
13
13
|
== Examples
|
14
14
|
|
15
|
-
FYI - the API is still unstable.
|
15
|
+
FYI - the API is still a little unstable.
|
16
16
|
|
17
17
|
==== Basic execution (Raw input, raw output)
|
18
18
|
|
19
19
|
require 'simpler'
|
20
|
-
|
21
|
-
|
20
|
+
|
21
|
+
r = Simpler.new
|
22
|
+
r.eval! { "mean(c(1,2,3))" } # -> "[1] 2\n" (a Simpler::Reply object)
|
22
23
|
|
23
24
|
==== Using ruby variables (calculating correlation coefficient):
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
reply =
|
26
|
+
x = [1,2,7]
|
27
|
+
y = [3,4,8]
|
28
|
+
reply = r.eval!(x,y) {|xr,yr| "cor(#{xr},#{yr})" } # -> "[1] 0.9994238\n"
|
29
|
+
|
30
|
+
Any object that has a #to_r method can be passed in. Currently, Array and
|
31
|
+
Simpler::DataFrame are the only objects with this method defined, but one can
|
32
|
+
go really far with only these two data types.
|
28
33
|
|
29
34
|
==== Show a plot
|
30
35
|
|
31
|
-
|
36
|
+
Simpler uses <i>Rscript</i> to run R commands. So, all plots that would
|
37
|
+
normally go to X11 are saved to "Rplots.pdf". *show!* is precisly the same as
|
38
|
+
*eval!*, but it also opens "Rplots.pdf" with <i>@pdf_viewer</i>:
|
32
39
|
|
33
|
-
|
34
|
-
|
40
|
+
r.pdf_viewer = "acroread"
|
41
|
+
r.show!(x,y) {|xr,yr| "plot(#{xr}, #{yr})" }
|
35
42
|
|
36
43
|
==== Using DataFrames
|
37
44
|
|
@@ -42,9 +49,16 @@ FYI - the API is still unstable.
|
|
42
49
|
}
|
43
50
|
|
44
51
|
df = Simpler::DataFrame.new(hash)
|
45
|
-
|
52
|
+
r.show!(df) {|dfr| "plot(#{dfr})" }
|
53
|
+
|
54
|
+
One can also make a data frame from an array of structs with Simpler.from_structs
|
55
|
+
|
56
|
+
==== When Errors Arise
|
57
|
+
|
58
|
+
On an R error, raises a Simpler::RError that returns the R error message and
|
59
|
+
shows the R code submitted. (the @command queue is still cleared)
|
46
60
|
|
47
|
-
|
61
|
+
r.eval! { "plot(c(1,2,3),c(1,2))" }
|
48
62
|
|
49
63
|
== Credit
|
50
64
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
data/lib/simpler.rb
CHANGED
@@ -16,61 +16,91 @@ class Simpler
|
|
16
16
|
RPLOTS_FILE = "Rplots.pdf"
|
17
17
|
PDF_VIEWER = "evince"
|
18
18
|
|
19
|
-
#
|
19
|
+
# @param [Object] object a ruby object,
|
20
|
+
# @return [String] the variable name of the object
|
20
21
|
def self.varname(obj)
|
21
22
|
"rb#{obj.object_id}"
|
22
23
|
end
|
23
24
|
|
24
|
-
# returns it as a symbol, currently recognizes pdf, png, svg
|
25
|
-
def self.filename_to_plottype(name)
|
26
|
-
name.match(/\.([^\.]+)$/)[1].downcase.to_sym
|
27
|
-
end
|
28
|
-
|
29
25
|
attr_accessor :commands
|
30
26
|
attr_accessor :pdf_viewer
|
31
27
|
|
28
|
+
# @param [Array] commands a list of R commands to execute
|
29
|
+
# @param [Hash] opts options (currently :pdf_viewer which defaults to
|
30
|
+
# PDF_VIEWER)
|
32
31
|
def initialize(commands=[], opts={:pdf_viewer => PDF_VIEWER})
|
33
32
|
@pdf_viewer = opts[:pdf_viewer]
|
34
33
|
@commands = commands
|
35
34
|
end
|
36
35
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
36
|
+
# @example basic
|
37
|
+
# # find the p value of a t-test
|
38
|
+
# r.eval! do
|
39
|
+
# %Q{
|
40
|
+
# x <- c(1,2,3)
|
41
|
+
# y <- c(4,5,6)
|
42
|
+
# ttest = t.test(x, y)
|
43
|
+
# ttest$p.value
|
44
|
+
# }
|
45
|
+
# end # -> "[1] 0.02131164\n"
|
46
|
+
#
|
47
|
+
# @example pass in variables (roughly equivalent)
|
48
|
+
# x = [1,2,3]
|
49
|
+
# y = [4,5,6]
|
50
|
+
# r.eval!(x,y) do |xr,yr|
|
51
|
+
# %Q{
|
52
|
+
# ttest = t.test(#{xr}, #{yr})
|
53
|
+
# ttest$p.value
|
54
|
+
# }
|
55
|
+
# end # -> "[1] 0.02131164\n"
|
56
|
+
#
|
57
|
+
# (see Simpler#with)
|
58
|
+
def eval!(*objects, &block)
|
59
|
+
with(*objects, &block).run!
|
46
60
|
end
|
47
61
|
|
48
|
-
#
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
62
|
+
# (see Simpler#eval!) same as eval! but also opens "Rplots.pdf" with PDF_VIEWER
|
63
|
+
# @example plotting
|
64
|
+
# x = [1,2,3]
|
65
|
+
# y = [4,5,6]
|
66
|
+
# r.show!(x,y) do |xr,yr|
|
67
|
+
# "plot(#{xr}, #{yr})"
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# Simpler uses Rscript to run its code and Rscript writes X11 output to the
|
71
|
+
# PDF file "Rplots.pdf". Simpler#show! merely makes a system call to open up
|
72
|
+
# the pdf file for viewing. Obviously, for more interactive sessions one
|
73
|
+
# should just use R.
|
74
|
+
def show!(*objects, &block)
|
75
|
+
with(*objects, &block)
|
76
|
+
reply = run!
|
77
|
+
open_pdf_viewer(@pdf_viewer, RPLOTS_FILE)
|
55
78
|
reply
|
56
79
|
end
|
57
80
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
81
|
+
# @param [*Objects] objects list of objects
|
82
|
+
# @yield [*r_varnames] yields the R name of each variable (see Simpler.varname)
|
83
|
+
# @return [Simpler] returns self for chaining
|
84
|
+
# @example chaining
|
85
|
+
# reply = r.with(dataframe) do |df|
|
86
|
+
# %Q{ ... something with #{df} ... }
|
87
|
+
# end.with(x,y,z) do |xr,yr,zr|
|
88
|
+
# %Q{ ... something with #{xy} #{yr} #{zr} ... }
|
89
|
+
# end.eval!
|
90
|
+
# # can also .show!
|
91
|
+
def with(*objects, &block)
|
92
|
+
var_names = objects.map {|v| Simpler.varname(v) }
|
93
|
+
conversion_code = objects.map {|v| v.to_r }
|
94
|
+
@commands.push(*conversion_code)
|
95
|
+
unless block.nil?
|
96
|
+
@commands << block.call(*var_names)
|
97
|
+
end
|
98
|
+
self
|
67
99
|
end
|
68
100
|
|
69
|
-
|
70
|
-
#
|
71
|
-
|
72
|
-
def run!(string=nil)
|
73
|
-
@commands.push(string) if string
|
101
|
+
# executes all commands and clears the command array.
|
102
|
+
# @return [Simpler::Reply] a String subclass
|
103
|
+
def run!
|
74
104
|
reply = nil
|
75
105
|
error = nil
|
76
106
|
cmds_to_run = @commands.map {|v| v + "\n"}.join
|
@@ -87,24 +117,38 @@ class Simpler
|
|
87
117
|
Simpler::Reply.new(reply)
|
88
118
|
end
|
89
119
|
|
90
|
-
#
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
120
|
+
# formats strings and numbers to fit inside R code (this is for Strings and
|
121
|
+
# numbers mainly in inclusion in options)
|
122
|
+
# String ==> "\"String\""
|
123
|
+
# Numeric ==> "Numeric"
|
124
|
+
# Other objects => "#{object.to_s}"
|
125
|
+
# @param [Object] object the ruby object
|
126
|
+
# @return [String] string suitable for R code
|
127
|
+
def self.r_format(object)
|
128
|
+
case object
|
129
|
+
when String
|
130
|
+
object.inspect
|
131
|
+
when Numeric
|
132
|
+
object.to_s
|
133
|
+
else
|
134
|
+
object.to_s
|
135
|
+
end
|
97
136
|
end
|
98
137
|
|
99
|
-
def
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
138
|
+
def error_message(error_string, r_code)
|
139
|
+
error_message = ""
|
140
|
+
error_message << "\n"
|
141
|
+
error_message << "*************************** Error inside R *****************************\n"
|
142
|
+
error_message << error_string
|
143
|
+
error_message << "------------------------- [R code submitted] ---------------------------\n"
|
144
|
+
error_message << r_code
|
145
|
+
error_message << "------------------------------------------------------------------------\n"
|
146
|
+
error_message
|
105
147
|
end
|
106
148
|
|
107
|
-
|
149
|
+
def open_pdf_viewer(viewer, *files)
|
150
|
+
system "#{@pdf_viewer} #{files.join(" ")} &"
|
151
|
+
end
|
108
152
|
|
109
153
|
class RError < StandardError
|
110
154
|
end
|
data/lib/simpler/data_frame.rb
CHANGED
@@ -6,6 +6,23 @@ class Simpler
|
|
6
6
|
attr_accessor :col_names
|
7
7
|
attr_accessor :row_names
|
8
8
|
attr_accessor :hash
|
9
|
+
|
10
|
+
# takes an array of structs and returns a data frame object
|
11
|
+
def self.from_structs(array)
|
12
|
+
names = array.first.members
|
13
|
+
lengthwise_arrays = names.map { Array.new(names.size) }
|
14
|
+
array.each_with_index do |struct,m|
|
15
|
+
struct.values.each_with_index do |val,n|
|
16
|
+
lengthwise_arrays[n][m] = val
|
17
|
+
end
|
18
|
+
end
|
19
|
+
hash = {}
|
20
|
+
names.zip(lengthwise_arrays) do |name, lengthwise_array|
|
21
|
+
hash[name] = lengthwise_array
|
22
|
+
end
|
23
|
+
self.new(hash)
|
24
|
+
end
|
25
|
+
|
9
26
|
# takes a hash, where the col_name is the key and the data rows are an
|
10
27
|
# array of values. The default ordering of the hash keys will be used,
|
11
28
|
# unless overridden with col_names. The row_names can be used to specify
|
data/lib/simpler/plot.rb
CHANGED
@@ -2,9 +2,14 @@
|
|
2
2
|
class Simpler
|
3
3
|
module Plot
|
4
4
|
|
5
|
+
# returns it as a symbol, currently recognizes pdf, png, svg
|
6
|
+
def self.filename_to_plottype(name)
|
7
|
+
name.match(/\.([^\.]+)$/)[1].downcase.to_sym
|
8
|
+
end
|
9
|
+
|
5
10
|
def plot(file_w_extension, opts={}, &block)
|
6
|
-
device =
|
7
|
-
opts_as_ropts = opts.map {|k,v| "#{k}=#{r_format(v)}"}
|
11
|
+
device = Simpler::Plot.filename_to_plottype(file_w_extension)
|
12
|
+
opts_as_ropts = opts.map {|k,v| "#{k}=#{Simpler.r_format(v)}"}
|
8
13
|
string = "#{device}(#{file_w_extension.inspect}, #{opts_as_ropts.join(', ')})\n"
|
9
14
|
string << block.call << "\n"
|
10
15
|
string << "dev.off()\n"
|
data/lib/simpler/reply.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
|
2
2
|
class Simpler
|
3
3
|
class Reply < String
|
4
|
+
ONE_FLOAT_RE = /] ([\w\.-]+)/o
|
4
5
|
|
5
6
|
# removes the [1] from the line
|
6
7
|
def rm_leader
|
@@ -11,11 +12,9 @@ class Simpler
|
|
11
12
|
self.chomp.split("\n").rm_leader.split(" ")
|
12
13
|
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
def to_i
|
18
|
-
rm_leader.to_f
|
15
|
+
# returns a single float. Assumes the answer takes the form: "[1] <Float>\n" => Float
|
16
|
+
def to_sf
|
17
|
+
ONE_FLOAT_RE.match(self)[1].to_f
|
19
18
|
end
|
20
19
|
|
21
20
|
end
|
data/spec/simpler_spec.rb
CHANGED
@@ -16,7 +16,7 @@ describe "Simpler" do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
it 'can get a reply' do
|
19
|
-
reply = @r.
|
19
|
+
reply = @r.eval!{"typos = c(2,3,0,3,1,0,0,1)\nmean(typos)\nhist(typos)"}
|
20
20
|
reply.chomp.is "[1] 1.25"
|
21
21
|
end
|
22
22
|
|
@@ -26,7 +26,7 @@ describe "Simpler" do
|
|
26
26
|
"plot(c(1,2), c(2))" => %r{'x' and 'y' lengths differ},
|
27
27
|
}
|
28
28
|
code_matches.each do |code, matches|
|
29
|
-
message = lambda { @r.
|
29
|
+
message = lambda { @r.eval!{code} }.should.raise(Simpler::RError).message
|
30
30
|
message.should.match(matches)
|
31
31
|
message.should.include code
|
32
32
|
end
|
@@ -51,7 +51,7 @@ wetness 6 2 1
|
|
51
51
|
relative humidity 7 9 7
|
52
52
|
}
|
53
53
|
df = Simpler::DataFrame.new(@hash, @row_names, @col_names)
|
54
|
-
as_data_frame = Simpler.new.
|
54
|
+
as_data_frame = Simpler.new.eval!(df) {|df| df }
|
55
55
|
as_data_frame.is expected
|
56
56
|
end
|
57
57
|
|
@@ -63,7 +63,7 @@ relative humidity 7 9 7
|
|
63
63
|
4 7 9 7
|
64
64
|
}
|
65
65
|
df = Simpler::DataFrame.new(@hash, nil, @col_names)
|
66
|
-
as_data_frame = Simpler.new.
|
66
|
+
as_data_frame = Simpler.new.eval!(df) {|df| df }
|
67
67
|
as_data_frame.is expected
|
68
68
|
end
|
69
69
|
end
|
@@ -81,13 +81,13 @@ describe 'making plots' do
|
|
81
81
|
it 'has convenience wrappers for plotting to file' do
|
82
82
|
@exts.each do |ext|
|
83
83
|
file = @file + "." + ext
|
84
|
-
@r.
|
84
|
+
@r.eval!(@x, @y) do |x,y|
|
85
85
|
@r.plot(file) do
|
86
86
|
%Q{
|
87
87
|
plot(#{x},#{y}, main="#{ext} scatterplot example", col=rgb(0,100,0,50,maxColorValue=255), pch=16)
|
88
88
|
}
|
89
89
|
end
|
90
|
-
end
|
90
|
+
end
|
91
91
|
File.exist?(file).is true
|
92
92
|
IO.read(@file + ".svg").matches(/svg/) if ext == :svg
|
93
93
|
File.unlink(file)
|
@@ -116,9 +116,9 @@ describe 'showing plots' do
|
|
116
116
|
it 'can disply plots' do
|
117
117
|
ok $SYSTEM_CALL.nil?
|
118
118
|
|
119
|
-
@r.
|
119
|
+
@r.show!(@x, @y) do |x,y|
|
120
120
|
"plot(#{x}, #{y})"
|
121
|
-
end
|
121
|
+
end
|
122
122
|
|
123
123
|
# this shows that we've made a system call to visualize the data
|
124
124
|
ok !$SYSTEM_CALL.nil?
|
@@ -127,7 +127,7 @@ end
|
|
127
127
|
|
128
128
|
describe 'ancillary functions' do
|
129
129
|
it 'can get the device from the filename' do
|
130
|
-
Simpler.filename_to_plottype("bob.the.pdf").is :pdf
|
131
|
-
Simpler.filename_to_plottype("larry.the.svg").is :svg
|
130
|
+
Simpler::Plot.filename_to_plottype("bob.the.pdf").is :pdf
|
131
|
+
Simpler::Plot.filename_to_plottype("larry.the.svg").is :svg
|
132
132
|
end
|
133
133
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
|
9
|
-
version: 0.0.4
|
9
|
+
version: 0.1.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- John Prince
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-08-03 00:00:00 -06:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|