lowess 0.0.1

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.
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