File: nest.rb

package info (click to toggle)
ruby-rubyvis 0.6.1%2Bdfsg1-2
  • links: PTS, VCS
  • area: main
  • in suites: buster, stretch
  • size: 1,808 kB
  • ctags: 679
  • sloc: ruby: 11,114; makefile: 2
file content (203 lines) | stat: -rw-r--r-- 6,547 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
module Rubyvis
  ##
  # Returns a Nest operator for the specified array. This is a
  # convenience factory method, equivalent to <tt>Nest.new(array)</tt>.
  #
  # @see Rubyvis::Nest
  # @param {array} array an array of elements to nest.
  # @returns {Nest} a nest operator for the specified array.
  ##
  def self.nest(array)
    Nest.new(array)
  end
  # :stopdoc:
  class NestedArray
    attr_accessor :key, :values
    def initialize(opts)
      @key=opts[:key]
      @values=opts[:values]
    end
    def ==(var)
      key==var.key and values==var.values
    end
  end
  # :startdoc: 
  
  # Represents a Nest operator for the specified array. Nesting
  # allows elements in an array to be grouped into a hierarchical tree
  # structure. The levels in the tree are specified by <i>key</i> functions. The
  # leaf nodes of the tree can be sorted by value, while the internal nodes can
  # be sorted by key. Finally, the tree can be returned either has a
  # multidimensional array via Nest.entries, or as a hierarchical map via
  # Nest.map. The Nest.rollup routine similarly returns a map, collapsing
  # the elements in each leaf node using a summary function.
  #
  # For example, consider the following tabular data structure of Barley
  # yields, from various sites in Minnesota during 1931-2:
  #
  #   { yield: 27.00, variety: "Manchuria", year: 1931, site: "University Farm" },
  #   { yield: 48.87, variety: "Manchuria", year: 1931, site: "Waseca" },
  #   { yield: 27.43, variety: "Manchuria", year: 1931, site: "Morris" }
  #
  # To facilitate visualization, it may be useful to nest the elements first by
  # year, and then by variety, as follows:
  #
  #     var nest = Rubyvis.nest(yields)
  #     .key(lambda {|d|  d.year})
  #     .key(lambda {|d| d.variety})
  #     .entries();
  #
  # This returns a nested array. Each element of the outer array is a key-values
  # pair, listing the values for each distinct key:
  #
  # <pre>{ key: 1931, values: [
  #   { key: "Manchuria", values: [
  #       { yield: 27.00, variety: "Manchuria", year: 1931, site: "University Farm" },
  #       { yield: 48.87, variety: "Manchuria", year: 1931, site: "Waseca" },
  #       { yield: 27.43, variety: "Manchuria", year: 1931, site: "Morris" },
  #       ...
  #     ] },
  #   { key: "Glabron", values: [
  #       { yield: 43.07, variety: "Glabron", year: 1931, site: "University Farm" },
  #       { yield: 55.20, variety: "Glabron", year: 1931, site: "Waseca" },
  #       ...
  #     ] },
  #   ] },
  # { key: 1932, values: ... }</pre>
  #
  # Further details, including sorting and rollup, is provided below on the
  # corresponding methods.
  class Nest
    attr_accessor :array, :keys, :order
    ##
    # Constructs a nest operator for the specified array. This constructor should
    # not be invoked directly; use Rubyvis.nest instead.
    
    def initialize(array)
      @array=array
      @keys=[]
      @order=nil
    end
    def key(k)
      @keys.push(k)
      return self
    end
    def sort_keys(order=nil)
      keys[keys.size-1].order = order.nil? ? Rubyvis.natural_order : order
      return self
    end
    def sort_values(order=nil)
      @order = order.nil? ? Rubyvis.natural_order : order
      return self
    end
    
    # Returns a hierarchical map of values. Each key adds one level to the
    # hierarchy. With only a single key, the returned map will have a key for each
    # distinct value of the key function; the correspond value with be an array of
    # elements with that key value. If a second key is added, this will be a nested
    # map. For example:
    #
    # <pre>Rubyvis.nest(yields)
    #     .key(function(d) d.variety)
    #     .key(function(d) d.site)
    #     .map()</pre>
    #
    # returns a map <tt>m</tt> such that <tt>m[variety][site]</tt> is an array, a subset of
    # <tt>yields</tt>, with each element having the given variety and site.
    #
    # @returns a hierarchical map of values    
    def map
      #i=0
      map={} 
      values=[]
      @array.each_with_index {|x,j|
        m=map
        (@keys.size-1).times {|i|
          k=@keys[i].call(x)
          m[k]={} if (!m[k])
          m=m[k]
        }
        k=@keys.last.call(x)
        if(!m[k])
          a=[]
          values.push(a)
          m[k]=a
        end
        m[k].push(x)
      }
      if(self.order)
        values.each_with_index {|v,vi|
          values[vi].sort!(&self.order)
        }
      end
      map
    end
    
    # Returns a hierarchical nested array. This method is similar to
    # {@link pv.entries}, but works recursively on the entire hierarchy. Rather
    # than returning a map like {@link #map}, this method returns a nested
    # array. Each element of the array has a <tt>key</tt> and <tt>values</tt>
    # field. For leaf nodes, the <tt>values</tt> array will be a subset of the
    # underlying elements array; for non-leaf nodes, the <tt>values</tt> array will
    # contain more key-values pairs.
    #
    # <p>For an example usage, see the {@link Nest} constructor.
    #
    # @returns a hierarchical nested array.
    
    def entries()
      entries_sort(entries_entries(map),0)
    end
    def entries_entries(map)
      array=[]
      map.each_pair {|k,v|
        array.push(NestedArray.new({:key=>k, :values=>(v.is_a? Array) ? v: entries_entries(v)}))
      }
      array
    end
    def entries_sort(array,i)
      o=keys[i].order
      if o
        array.sort! {|a,b| o.call(a.key, b.key)}
      end
      i+=1
      if (i<keys.size)
        array.each {|v|
          entries_sort(v, i)
        }
      end
      array
      
    end
    def rollup_rollup(map,f)
      map.each_pair {|key,value|
        if value.is_a? Array
          map[key]=f.call(value)
        else
          rollup_rollup(value,f)
        end
      }
      return map;
    end
    
    # Returns a rollup map. The behavior of this method is the same as
    # {@link #map}, except that the leaf values are replaced with the return value
    # of the specified rollup function <tt>f</tt>. For example,
    #
    # <pre>pv.nest(yields)
    #      .key(function(d) d.site)
    #      .rollup(function(v) pv.median(v, function(d) d.yield))</pre>
    #
    # first groups yield data by site, and then returns a map from site to median
    # yield for the given site.
    #
    # @see #map
    # @param {function} f a rollup function.
    # @returns a hierarchical map, with the leaf values computed by <tt>f</tt>.

    
    def rollup(f)
      rollup_rollup(self.map, f)
    end
  end
end