lowess 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3f8e62062c82338a4acfc42b4bab9bc873ab8535
4
+ data.tar.gz: 0b7607b81e80f5574d82f94373438496c8f84829
5
+ SHA512:
6
+ metadata.gz: 65736717ab720df61c4bd05f3d51c6beaa96710dc340c65f2170b22d7ee36f0345b7647eda3f01b9904cc7c76d8d844a126a588b21aa2b01e7a665f279882c50
7
+ data.tar.gz: 7e12d26d8d291c78cff4d80349c72294ac16c51b93adc3e19bc6049aebc5e9175461851886ce2b8bfbaf01bbead4beb1bc2dc01d7340487e901e53fe66e68b67
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ *.o
3
+ *.so
4
+ *.swp
5
+ tmp/
6
+ Makefile
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ pollster
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.3
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ If you've ever used R's [lowess()](https://svn.r-project.org/R/tags/R-3-2-4/src/library/stats/src/lowess.doc)
2
+ and you wish you could call it from Ruby, this gem's for you.
3
+
4
+ # Installation
5
+
6
+ `gem install lowess`
7
+
8
+ or in your Gemfile:
9
+
10
+ `gem 'lowess'`
11
+
12
+ # Usage
13
+
14
+ ```rb
15
+ require 'lowess'
16
+
17
+ points = [
18
+ Lowess::Point.new(1, 1.1),
19
+ Lowess::Point.new(1.3, 1.3),
20
+ Lowess::Point.new(1.7, 1.1),
21
+ Lowess::Point.new(2.1, 1.1)
22
+ ]
23
+
24
+ # returns (1, 1.1531) (1.3, 1.1854) (1.7, 1.3444) (2.1, 1.6363)
25
+ Lowess::lowess(points, f: 1.0, iter: 4)
26
+ ```
27
+
28
+ # Development
29
+
30
+ *To run tests*: `rake`
31
+
32
+ *To fiddle with C*: look in `ext/ext_lowess`
33
+
34
+ # License
35
+
36
+ [GPL2](http://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) because the
37
+ code is downloaded from R automatically.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'rake/extensiontask'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ gemspec = Gem::Specification.load('lowess.gemspec')
9
+ Rake::ExtensionTask.new('ext_lowess')
10
+
11
+ task default: [ :compile, :test ]
@@ -0,0 +1 @@
1
+ clowess.c is copied from R, which makes this whole project GPL
@@ -0,0 +1,308 @@
1
+ /*
2
+ * R : A Computer Langage for Statistical Data Analysis
3
+ * Copyright (C) 1996 Robert Gentleman and Ross Ihaka
4
+ * Copyright (C) 1999-20012 The R Core Team
5
+ *
6
+ * This program is free software; you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation; either version 2 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with this program; if not, a copy is available at
18
+ * https://www.R-project.org/Licenses/
19
+ */
20
+
21
+ #ifdef HAVE_CONFIG_H
22
+ # include <config.h>
23
+ #endif
24
+
25
+ #ifdef ENABLE_NLS
26
+ #include <libintl.h>
27
+ #define _(String) dgettext ("stats", String)
28
+ #else
29
+ #define _(String) (String)
30
+ #endif
31
+
32
+ #include <math.h>
33
+ double fmax2(double x, double y) { return (x < y) ? y : x; }
34
+ int imin2(int x, int y) { return (x < y) ? x : y; }
35
+ int imax2(int x, int y) { return (x < y) ? y : x; }
36
+ /* fmax2, imin2, imax2 */
37
+ #define R_INLINE inline /* prototypes for lowess and clowess */
38
+ typedef enum { FALSE = 0, TRUE /*, MAYBE */ } Rboolean;
39
+
40
+ static void rPsort2(double *x, int lo, int hi, int k)
41
+ {
42
+ double v, w;
43
+ int L, R, i, j;
44
+
45
+ for (L = lo, R = hi; L < R; ) {
46
+ v = x[k];
47
+ for(i = L, j = R; i <= j;) {
48
+ while (x[i] < v) i++;
49
+ while (v < x[j]) j--;
50
+ if (i <= j) { w = x[i]; x[i++] = x[j]; x[j--] = w; }
51
+ }
52
+ if (j < k) L = i;
53
+ if (k < i) R = j;
54
+ }
55
+ }
56
+
57
+ static void rPsort(double *x, int n, int k)
58
+ {
59
+ rPsort2(x, 0, n-1, k);
60
+ }
61
+ /* rPsort() */
62
+ #ifdef DEBUG_lowess
63
+ # include <R_ext/Print.h>
64
+ #endif
65
+
66
+ static R_INLINE double fsquare(double x)
67
+ {
68
+ return x * x;
69
+ }
70
+
71
+ static R_INLINE double fcube(double x)
72
+ {
73
+ return x * x * x;
74
+ }
75
+
76
+ static void lowest(double *x, double *y, int n, double *xs, double *ys,
77
+ int nleft, int nright, double *w,
78
+ Rboolean userw, double *rw, Rboolean *ok)
79
+ {
80
+ int nrt, j;
81
+ double a, b, c, h, h1, h9, r, range;
82
+
83
+ x--;
84
+ y--;
85
+ w--;
86
+ rw--;
87
+
88
+ range = x[n]-x[1];
89
+ h = fmax2(*xs-x[nleft], x[nright]-*xs);
90
+ h9 = 0.999*h;
91
+ h1 = 0.001*h;
92
+
93
+ /* sum of weights */
94
+
95
+ a = 0.;
96
+ j = nleft;
97
+ while (j <= n) {
98
+
99
+ /* compute weights */
100
+ /* (pick up all ties on right) */
101
+
102
+ w[j] = 0.;
103
+ r = fabs(x[j] - *xs);
104
+ if (r <= h9) {
105
+ if (r <= h1)
106
+ w[j] = 1.;
107
+ else
108
+ w[j] = fcube(1.-fcube(r/h));
109
+ if (userw)
110
+ w[j] *= rw[j];
111
+ a += w[j];
112
+ }
113
+ else if (x[j] > *xs)
114
+ break;
115
+ j = j+1;
116
+ }
117
+
118
+ /* rightmost pt (may be greater */
119
+ /* than nright because of ties) */
120
+
121
+ nrt = j-1;
122
+ if (a <= 0.)
123
+ *ok = FALSE;
124
+ else {
125
+ *ok = TRUE;
126
+
127
+ /* weighted least squares */
128
+ /* make sum of w[j] == 1 */
129
+
130
+ for(j=nleft ; j<=nrt ; j++)
131
+ w[j] /= a;
132
+ if (h > 0.) {
133
+ a = 0.;
134
+
135
+ /* use linear fit */
136
+ /* weighted center of x values */
137
+
138
+ for(j=nleft ; j<=nrt ; j++)
139
+ a += w[j] * x[j];
140
+ b = *xs - a;
141
+ c = 0.;
142
+ for(j=nleft ; j<=nrt ; j++)
143
+ c += w[j]*fsquare(x[j]-a);
144
+ if (sqrt(c) > 0.001*range) {
145
+ b /= c;
146
+
147
+ /* points are spread out */
148
+ /* enough to compute slope */
149
+
150
+ for(j=nleft; j <= nrt; j++)
151
+ w[j] *= (b*(x[j]-a) + 1.);
152
+ }
153
+ }
154
+ *ys = 0.;
155
+ for(j=nleft; j <= nrt; j++)
156
+ *ys += w[j] * y[j];
157
+ }
158
+ }
159
+
160
+ void clowess(double *x, double *y, int n,
161
+ double f, int nsteps, double delta,
162
+ double *ys, double *rw, double *res)
163
+ {
164
+ int i, iter, j, last, m1, m2, nleft, nright, ns;
165
+ Rboolean ok;
166
+ double alpha, c1, c9, cmad, cut, d1, d2, denom, r, sc;
167
+
168
+ if (n < 2) {
169
+ ys[0] = y[0]; return;
170
+ }
171
+
172
+ /* nleft, nright, last, etc. must all be shifted to get rid of these: */
173
+ x--;
174
+ y--;
175
+ ys--;
176
+
177
+
178
+ /* at least two, at most n points */
179
+ ns = imax2(2, imin2(n, (int)(f*n + 1e-7)));
180
+ #ifdef DEBUG_lowess
181
+ REprintf("lowess(): ns = %d\n", ns);
182
+ #endif
183
+
184
+ /* robustness iterations */
185
+
186
+ iter = 1;
187
+ while (iter <= nsteps+1) {
188
+ nleft = 1;
189
+ nright = ns;
190
+ last = 0; /* index of prev estimated point */
191
+ i = 1; /* index of current point */
192
+
193
+ for(;;) {
194
+ if (nright < n) {
195
+
196
+ /* move nleft, nright to right */
197
+ /* if radius decreases */
198
+
199
+ d1 = x[i] - x[nleft];
200
+ d2 = x[nright+1] - x[i];
201
+
202
+ /* if d1 <= d2 with */
203
+ /* x[nright+1] == x[nright], */
204
+ /* lowest fixes */
205
+
206
+ if (d1 > d2) {
207
+
208
+ /* radius will not */
209
+ /* decrease by */
210
+ /* move right */
211
+
212
+ nleft++;
213
+ nright++;
214
+ continue;
215
+ }
216
+ }
217
+
218
+ /* fitted value at x[i] */
219
+
220
+ lowest(&x[1], &y[1], n, &x[i], &ys[i],
221
+ nleft, nright, res, iter>1, rw, &ok);
222
+ if (!ok) ys[i] = y[i];
223
+
224
+ /* all weights zero */
225
+ /* copy over value (all rw==0) */
226
+
227
+ if (last < i-1) {
228
+ denom = x[i]-x[last];
229
+
230
+ /* skipped points -- interpolate */
231
+ /* non-zero - proof? */
232
+
233
+ for(j = last+1; j < i; j++) {
234
+ alpha = (x[j]-x[last])/denom;
235
+ ys[j] = alpha*ys[i] + (1.-alpha)*ys[last];
236
+ }
237
+ }
238
+
239
+ /* last point actually estimated */
240
+ last = i;
241
+
242
+ /* x coord of close points */
243
+ cut = x[last]+delta;
244
+ for (i = last+1; i <= n; i++) {
245
+ if (x[i] > cut)
246
+ break;
247
+ if (x[i] == x[last]) {
248
+ ys[i] = ys[last];
249
+ last = i;
250
+ }
251
+ }
252
+ i = imax2(last+1, i-1);
253
+ if (last >= n)
254
+ break;
255
+ }
256
+ /* residuals */
257
+ for(i = 0; i < n; i++)
258
+ res[i] = y[i+1] - ys[i+1];
259
+
260
+ /* overall scale estimate */
261
+ sc = 0.;
262
+ for(i = 0; i < n; i++) sc += fabs(res[i]);
263
+ sc /= n;
264
+
265
+ /* compute robustness weights */
266
+ /* except last time */
267
+
268
+ if (iter > nsteps)
269
+ break;
270
+ /* Note: The following code, biweight_{6 MAD|Ri|}
271
+ is also used in stl(), loess and several other places.
272
+ --> should provide API here (MM) */
273
+ for(i = 0 ; i < n ; i++)
274
+ rw[i] = fabs(res[i]);
275
+
276
+ /* Compute cmad := 6 * median(rw[], n) ---- */
277
+ /* FIXME: We need C API in R for Median ! */
278
+ m1 = n/2;
279
+ /* partial sort, for m1 & m2 */
280
+ rPsort(rw, n, m1);
281
+ if(n % 2 == 0) {
282
+ m2 = n-m1-1;
283
+ rPsort(rw, n, m2);
284
+ cmad = 3.*(rw[m1]+rw[m2]);
285
+ }
286
+ else { /* n odd */
287
+ cmad = 6.*rw[m1];
288
+ }
289
+ #ifdef DEBUG_lowess
290
+ REprintf(" cmad = %12g\n", cmad);
291
+ #endif
292
+ if(cmad < 1e-7 * sc) /* effectively zero */
293
+ break;
294
+ c9 = 0.999*cmad;
295
+ c1 = 0.001*cmad;
296
+ for(i = 0 ; i < n ; i++) {
297
+ r = fabs(res[i]);
298
+ if (r <= c1)
299
+ rw[i] = 1.;
300
+ else if (r <= c9)
301
+ rw[i] = fsquare(1.-fsquare(r/cmad));
302
+ else
303
+ rw[i] = 0.;
304
+ }
305
+ iter++;
306
+ }
307
+ }
308
+
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'open-uri'
4
+
5
+ clowess = open('https://svn.r-project.org/R/tags/R-3-2-4/src/library/stats/src/lowess.c') { |f| f.read }
6
+
7
+ rmath_stub = <<EOT
8
+ double fmax2(double x, double y) { return (x < y) ? y : x; }
9
+ int imin2(int x, int y) { return (x < y) ? x : y; }
10
+ int imax2(int x, int y) { return (x < y) ? y : x; }
11
+ EOT
12
+
13
+ boolean_stub = <<EOT
14
+ typedef enum { FALSE = 0, TRUE /*, MAYBE */ } Rboolean;
15
+ EOT
16
+
17
+ # From https://svn.r-project.org/R/tags/R-3-2-4/src/main/sort.c
18
+ rpsort_stub = <<EOT
19
+ static void rPsort2(double *x, int lo, int hi, int k)
20
+ {
21
+ double v, w;
22
+ int L, R, i, j;
23
+
24
+ for (L = lo, R = hi; L < R; ) {
25
+ v = x[k];
26
+ for(i = L, j = R; i <= j;) {
27
+ while (x[i] < v) i++;
28
+ while (v < x[j]) j--;
29
+ if (i <= j) { w = x[i]; x[i++] = x[j]; x[j--] = w; }
30
+ }
31
+ if (j < k) L = i;
32
+ if (k < i) R = j;
33
+ }
34
+ }
35
+
36
+ static void rPsort(double *x, int n, int k)
37
+ {
38
+ rPsort2(x, 0, n-1, k);
39
+ }
40
+ EOT
41
+
42
+ out = clowess
43
+ .sub(/#include <Rmath\.h>/, rmath_stub)
44
+ .sub(/#include <R_ext\/Applic\.h>/, '#define R_INLINE inline')
45
+ .sub(/#include <R_ext\/Boolean\.h>/, boolean_stub)
46
+ .sub(/#include <R_ext\/Utils\.h>/, rpsort_stub)
47
+ .sub(/static\r?\nvoid clowess/, 'void clowess')
48
+ .sub(/#include <Rinternals.*/m, '') # drop the R wrapper
49
+
50
+ open('clowess.c', 'w') { |f| f.write(out) }
@@ -0,0 +1,4 @@
1
+ require 'mkmf'
2
+
3
+ dir_config('ext_lowess')
4
+ create_makefile('ext_lowess')
@@ -0,0 +1,69 @@
1
+ #include <ruby.h>
2
+
3
+ static VALUE Lowess;
4
+ static VALUE Lowess_Point;
5
+
6
+ static ID X;
7
+ static ID Y;
8
+ static ID Point;
9
+
10
+ void clowess(double *x, double *y, int n,
11
+ double f, int nsteps, double delta,
12
+ double *ys, double *rw, double *res);
13
+
14
+ static VALUE
15
+ lowess_ext_lowess(VALUE self, VALUE in_points, VALUE in_f, VALUE in_iter, VALUE in_delta) {
16
+ const int len = RARRAY_LEN(in_points);
17
+
18
+ double* xs = ALLOC_N(double, len);
19
+ double* ys = ALLOC_N(double, len);
20
+
21
+ const double f = NUM2DBL(in_f);
22
+ const int iter = NUM2INT(in_iter);
23
+ const double delta = NUM2DBL(in_delta);
24
+
25
+ double* out_ys = ALLOC_N(double, len);
26
+ double* rw = ALLOC_N(double, len);
27
+ double* res = ALLOC_N(double, len);
28
+ VALUE* points = ALLOC_N(VALUE, len);
29
+ VALUE point_args[2];
30
+ VALUE ret = rb_ary_new_capa(len);
31
+ int i;
32
+
33
+ VALUE e;
34
+ for (i = 0; i < len; i++) {
35
+ e = rb_ary_entry(in_points, i);
36
+ xs[i] = NUM2DBL(rb_ivar_get(e, X));
37
+ ys[i] = NUM2DBL(rb_ivar_get(e, Y));
38
+ }
39
+
40
+ clowess(xs, ys, len, f, iter, delta, out_ys, rw, res);
41
+
42
+ for (i = 0; i < len; i++) {
43
+ point_args[0] = DBL2NUM(xs[i]);
44
+ point_args[1] = DBL2NUM(out_ys[i]);
45
+ points[i] = rb_class_new_instance(2, point_args, Lowess_Point);
46
+ }
47
+
48
+ rb_ary_cat(ret, points, len);
49
+
50
+ xfree(points);
51
+ xfree(res);
52
+ xfree(rw);
53
+ xfree(out_ys);
54
+ xfree(ys);
55
+ xfree(xs);
56
+
57
+ return ret;
58
+ }
59
+
60
+ void Init_ext_lowess() {
61
+ X = rb_intern("@x");
62
+ Y = rb_intern("@y");
63
+ Point = rb_intern("Point");
64
+
65
+ Lowess = rb_define_module("Lowess");
66
+ Lowess_Point = rb_const_get(Lowess, Point);
67
+
68
+ rb_define_singleton_method(Lowess, "ext_lowess", lowess_ext_lowess, 4);
69
+ }
data/lib/lowess.rb ADDED
@@ -0,0 +1,29 @@
1
+ # A LOWESS smoother
2
+ module Lowess
3
+ # Smooths the input points
4
+ #
5
+ # Arguments:
6
+ #
7
+ # * points: Array of Lowess::Point objects, in any order.
8
+ # * options: any of:
9
+ # * f: the smoother span. Larger values give more smoothness.
10
+ # * iter: the number of 'robustifying' iterations. Smaller is faster.
11
+ # * delta: minimum x distance between input points. Larger is faster.
12
+ #
13
+ # See https://stat.ethz.ch/R-manual/R-devel/library/stats/html/lowess.html
14
+ # for more complete documentation.
15
+ def self.lowess(points, options={})
16
+ raise ArgumentError.new("Must pass one or more points") if points.empty?
17
+
18
+ sorted_points = points.sort
19
+
20
+ f = (options[:f] || 2.0 / 3).to_f
21
+ iter = (options[:iter] || 3).to_i
22
+ delta = (options[:delta] || (sorted_points.last.x - sorted_points.first.x).to_f / 100).to_f || 1.0
23
+
24
+ ext_lowess(points, f, iter, delta)
25
+ end
26
+ end
27
+
28
+ require 'lowess/point'
29
+ require 'ext_lowess'
@@ -0,0 +1,22 @@
1
+ class Lowess::Point
2
+ include Comparable
3
+
4
+ attr_reader(:x, :y)
5
+
6
+ def initialize(x, y)
7
+ @x = x.to_f
8
+ @y = y.to_f
9
+ end
10
+
11
+ def <=>(p2)
12
+ if x != p2.x
13
+ x <=> p2.x
14
+ else
15
+ y <=> p2.y
16
+ end
17
+ end
18
+
19
+ def to_s
20
+ sprintf("(%0.4f, %0.4f)", @x, @y)
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module Lowess
2
+ VERSION = '0.0.1'
3
+ end
data/lowess.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ $:.push File.expand_path('../lib', __FILE__)
2
+ require 'lowess/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'lowess'
6
+ s.version = Lowess::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = [ 'Adam Hooper' ]
9
+ s.email = [ 'adam@adamhooper.com' ]
10
+ s.homepage = 'http://rubygems.org/gems/ruby-lowess'
11
+ s.summary = 'Lowess scatter plot smoothing'
12
+ s.description = 'Runs the Lowess smoothing algorithm'
13
+ s.license = 'GPL2'
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- test/*`.split("\n")
17
+
18
+ s.require_paths = [ 'lib' ]
19
+ s.extensions = [ 'ext/ext_lowess/extconf.rb' ]
20
+
21
+ s.add_development_dependency 'rake', '~> 11'
22
+ s.add_development_dependency 'rake-compiler', '~> 0.9'
23
+ end
@@ -0,0 +1,155 @@
1
+ require 'minitest/autorun'
2
+ require 'lowess'
3
+
4
+ class LowessTest < Minitest::Test
5
+ def test_typical_input
6
+ # R session:
7
+ #
8
+ # > x <- c(0, 1, 2, 3, 4)
9
+ # > y <- c(2, 3, 1, 5, 4)
10
+ # > lowess(x=x, y=y, f=1)
11
+ # $x
12
+ # [1] 0 1 2 3 4
13
+ #
14
+ # $y
15
+ # [1] 2.102895 2.549205 3.183370 3.727202 4.376544
16
+ #
17
+ out = Lowess::lowess([ p(0, 2), p(1, 3), p(2, 1), p(3, 5), p(4, 4) ], f: 1)
18
+ assert_near [ p(0, 2.102895), p(1, 2.549205), p(2, 3.183370), p(3, 3.727202), p(4, 4.376544) ], out
19
+ end
20
+
21
+ def test_zero_inputs
22
+ # R session:
23
+ #
24
+ # > x <- c()
25
+ # > y <- c()
26
+ # > lowess(x=x, y=y)
27
+ # Error in lowess(x, y) : invalid input
28
+ assert_raises ::ArgumentError do
29
+ Lowess::lowess([])
30
+ end
31
+ end
32
+
33
+ def test_one_input
34
+ # R session:
35
+ #
36
+ # > x <- c(0)
37
+ # > y <- c(1)
38
+ # > lowess(x=x, y=y)
39
+ # $x
40
+ # [1] 0
41
+ #
42
+ # $y
43
+ # [1] 1
44
+ out = Lowess::lowess([ p(0, 1) ])
45
+ assert_equal [ p(0, 1) ], out
46
+ end
47
+
48
+ def test_two_inputs
49
+ # R session:
50
+ #
51
+ # > x <- c(0, 1)
52
+ # > y <- c(0.2, 0.2)
53
+ # > lowess(x, y)
54
+ # $x
55
+ # [1] 0 1
56
+ #
57
+ # $y
58
+ # [1] 0.2 0.2
59
+ input = [ p(0, 0.2), p(1, 0.2) ]
60
+ assert_equal input, Lowess::lowess(input)
61
+ end
62
+
63
+ def test_f
64
+ # R session:
65
+ #
66
+ # > x <- c(0, 1, 2, 3, 4)
67
+ # > y <- c(2, 3, 1, 5, 4)
68
+ # > lowess(x, y, f=0.8)
69
+ # $x
70
+ # [1] 0 1 2 3 4
71
+ #
72
+ # $y
73
+ # [1] 2.314658 2.180697 2.847178 3.562879 4.498597
74
+ assert_near ps(%w(2.314658 2.180697 2.847178 3.562879 4.498597)), Lowess::lowess(ps([ 2, 3, 1, 5, 4 ]), f: 0.8)
75
+ end
76
+
77
+ def test_iter
78
+ # R session:
79
+ #
80
+ # > x <- c(0, 1, 2, 3, 4)
81
+ # > y <- c(2, 3, 1, 5, 4)
82
+ # > lowess(x, y, f=0.8, iter=1)
83
+ # $x
84
+ # [1] 0 1 2 3 4
85
+ #
86
+ # $y
87
+ # [1] 2.322932 2.167876 2.809222 3.553910 4.517957
88
+ assert_near ps(%w(2.322932 2.167876 2.809222 3.553910 4.517957)), Lowess::lowess(ps([ 2, 3, 1, 5, 4 ]), f: 0.8, iter: 1)
89
+ end
90
+
91
+ def test_delta
92
+ # R session:
93
+ #
94
+ # > x <- c(0, 1, 2, 3, 4)
95
+ # > y <- c(2, 3, 1, 5, 4)
96
+ # > lowess(x, y, f=0.9, delta=2.0)
97
+ # $x
98
+ # [1] 0 1 2 3 4
99
+ #
100
+ # $y
101
+ # [1] 2 3 4 4 4
102
+ assert_near ps(%w(2 3 4 4 4)), Lowess::lowess(ps([ 2, 3, 1, 5, 4 ]), f: 0.8, delta: 2.0)
103
+ end
104
+
105
+ def test_non_uniform_line
106
+ # R session:
107
+ #
108
+ # > x <- c(1, 1.1, 2, 2, 3)
109
+ # > y <- c(1.1, 1.2, 2.2, 1.2, 3.1)
110
+ # > lowess(x, y)
111
+ # $x
112
+ # [1] 1.0 1.1 2.0 2.0 3.0
113
+ #
114
+ # $y
115
+ # [1] 1.1 1.2 1.7 1.7 3.1
116
+ input = [ p(1, 1.1), p(1.1, 1.2), p(2, 2.2), p(2, 1.2), p(3, 3.1) ]
117
+ expect_output = [ p(1, 1.1), p(1.1, 1.2), p(2, 1.7), p(2, 1.7), p(3, 3.1) ]
118
+ assert_near expect_output, Lowess::lowess(input)
119
+ end
120
+
121
+ def test_example_from_readme
122
+ points = [
123
+ Lowess::Point.new(1, 1.1),
124
+ Lowess::Point.new(1.3, 1.3),
125
+ Lowess::Point.new(1.7, 1.1),
126
+ Lowess::Point.new(2.1, 1.7)
127
+ ]
128
+
129
+ expect = [ 1, 1.3, 1.7, 2.1 ].zip([ 1.1531, 1.1854, 1.3444, 1.6363]).map { |x, y| Lowess::Point.new(x, y) }
130
+
131
+ assert_near expect, Lowess::lowess(points, f: 1.0, iter: 4)
132
+ end
133
+
134
+ private
135
+
136
+ def assert_near(pts1, pts2)
137
+ xs1 = pts1.map { |pt| pt.x.round(4) }
138
+ xs2 = pts2.map { |pt| pt.x.round(4) }
139
+ ys1 = pts1.map { |pt| pt.y.round(4) }
140
+ ys2 = pts2.map { |pt| pt.y.round(4) }
141
+
142
+ rounded1 = xs1.zip(ys1).map { |x, y| Lowess::Point.new(x, y) }
143
+ rounded2 = xs2.zip(ys2).map { |x, y| Lowess::Point.new(x, y) }
144
+
145
+ assert_equal rounded1, rounded2
146
+ end
147
+
148
+ def ps(ys)
149
+ ys.each_with_index.map { |y, i| Lowess::Point.new(i, y) }
150
+ end
151
+
152
+ def p(x, y)
153
+ Lowess::Point.new(x, y)
154
+ end
155
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lowess
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Adam Hooper
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake-compiler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.9'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.9'
41
+ description: Runs the Lowess smoothing algorithm
42
+ email:
43
+ - adam@adamhooper.com
44
+ executables: []
45
+ extensions:
46
+ - ext/ext_lowess/extconf.rb
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - ".ruby-gemset"
51
+ - ".ruby-version"
52
+ - README.md
53
+ - Rakefile
54
+ - ext/ext_lowess/README.md
55
+ - ext/ext_lowess/clowess.c
56
+ - ext/ext_lowess/download_clowess.rb
57
+ - ext/ext_lowess/extconf.rb
58
+ - ext/ext_lowess/lowess.c
59
+ - lib/lowess.rb
60
+ - lib/lowess/point.rb
61
+ - lib/lowess/version.rb
62
+ - lowess.gemspec
63
+ - test/test_lowess.rb
64
+ homepage: http://rubygems.org/gems/ruby-lowess
65
+ licenses:
66
+ - GPL2
67
+ metadata: {}
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 2.4.8
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Lowess scatter plot smoothing
88
+ test_files:
89
+ - test/test_lowess.rb