rgraph 0.0.9 → 0.0.10

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/lib/rgraph/graph.rb CHANGED
@@ -6,24 +6,32 @@ require_relative '../../lib/rgraph/node'
6
6
  class Graph
7
7
  attr_accessor :nodes, :links, :infinity
8
8
 
9
+ def self.new_from_string(string)
10
+ csv = CSV.new(string, headers: true)
11
+ new(csv)
12
+ end
13
+
9
14
  def initialize(csv)
10
15
  @nodes = []
11
16
  @links = []
12
17
  @distance = nil
13
18
  @infinity = 100_000
14
- raise Exception.new("the file must be a .csv") unless File.extname(csv) == ".csv"
15
19
 
16
- CSV.foreach(csv, headers: true) do |row|
20
+ csv = CSV.new(File.open(csv), headers: true) if csv.is_a?(String)
21
+
22
+ csv.each do |row|
17
23
  #last because CSV#delete returns [column,value]
18
24
  source_id = row.delete('source').last
19
25
  target_id = row.delete('target').last
26
+ type = row.delete('type').last || 'undirected'
27
+ weight = row.delete('weight').last || 1
20
28
 
21
29
  source = get_node_by_id(source_id) || Node.new(id: source_id)
22
30
  target = get_node_by_id(target_id) || Node.new(id: target_id)
23
31
 
24
32
  maybe_add_to_nodes(source, target)
25
33
 
26
- @links << Link.new(source: source, target: target, weight: row['weight'], year: row['year'])
34
+ @links << Link.new(source: source, target: target, weight: weight, type: type)
27
35
  end
28
36
  end
29
37
 
@@ -84,6 +92,13 @@ class Graph
84
92
  @distance
85
93
  end
86
94
 
95
+ def mdistance
96
+ distances unless @distance
97
+
98
+ fdistance = @distance.flatten.select{|d| d != @infinity}
99
+ fdistance.inject(:+) / fdistance.count.to_f
100
+ end
101
+
87
102
  def shortest_paths
88
103
  tmp = []
89
104
 
@@ -127,7 +142,7 @@ class Graph
127
142
 
128
143
  def betweenness(args = {})
129
144
  cumulative = args[:cumulative] || false
130
-
145
+
131
146
  #If we ask cumulative, normalized must be true
132
147
  normalized = args[:normalized] || cumulative || false
133
148
 
@@ -153,8 +168,50 @@ class Graph
153
168
  (distances.flatten - [@infinity]).max
154
169
  end
155
170
 
171
+ def single_clustering(node)
172
+ possible = possible_connections(node)
173
+ return 0 if possible == 0
174
+
175
+ existent = node.neighbours.combination(2).select{ |t| t[0].neighbours.include?(t[1]) }.count
176
+ existent / possible.to_f
177
+ end
178
+
179
+ def clustering
180
+ nodes.map{ |node| single_clustering(node) }.inject(:+) / nodes.size.to_f
181
+ end
182
+
183
+ def page_rank(d = 0.85, e = 0.01)
184
+ n = @nodes.count
185
+
186
+ #Initial values
187
+ @page_rank = Array.new(n, 1 / n.to_f)
188
+
189
+ while true
190
+ return @page_rank if @page_rank.inject(:+) - step_page_rank(d, e).inject(:+) < e
191
+ end
192
+ end
193
+
156
194
  private
157
195
 
196
+ def step_page_rank(d = 0.85, e = 0.01)
197
+ n = @nodes.count
198
+ tmp = Array.new(n, 0)
199
+
200
+ @nodes.each_with_index do |node,i|
201
+ #How much 'node' will give to its neighbours
202
+ to_neighbours = @page_rank[i] / node.neighbours.count
203
+
204
+ #Give 'to_neighbours' to each neighbour
205
+ node.neighbours.each do |ne|
206
+ j = @nodes.index(ne)
207
+ tmp[j] += to_neighbours
208
+ end
209
+ end
210
+
211
+ #Calculates the final value
212
+ @page_rank = tmp.map{|t| ((1 - d) / n) + t * d}
213
+ end
214
+
158
215
  def get_node_by_id(node_id)
159
216
  @nodes.select{|n| n.id == node_id}.first
160
217
  end
@@ -164,4 +221,9 @@ class Graph
164
221
  @nodes << node unless get_node_by_id(node.id)
165
222
  end
166
223
  end
224
+
225
+ def possible_connections(node)
226
+ total = node.neighbours.count
227
+ total * (total - 1) / 2
228
+ end
167
229
  end
data/lib/rgraph/link.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  class Link
2
- attr_accessor :source, :target
2
+ attr_accessor :source, :target, :type
3
3
 
4
4
  def initialize(arg)
5
5
  @args = arg
6
6
  @source = @args.delete(:source)
7
7
  @target = @args.delete(:target)
8
+ @type = @args.delete(:type) || 'undirected'
8
9
 
9
10
  raise Exception.new("source cant be nil") unless @source
10
11
  raise Exception.new("target cant be nil") unless @target
@@ -14,7 +15,7 @@ class Link
14
15
  @args[:weight] ||= 1
15
16
 
16
17
  @source.neighbours << @target
17
- @target.neighbours << @source
18
+ @target.neighbours << @source unless @type == 'directed'
18
19
  end
19
20
 
20
21
  def method_missing(name, *args)
@@ -1,3 +1,3 @@
1
1
  module Rgraph
2
- VERSION = '0.0.9'
2
+ VERSION = '0.0.10'
3
3
  end
@@ -39,8 +39,17 @@ describe Graph do
39
39
  expect(Graph.new('spec/fixtures/2005.csv').nodes.size).to eq(445)
40
40
  end
41
41
 
42
- it "creates a Graph with a not csv file" do
43
- expect { Graph.new('spec/fixtures/2005') }.to raise_exception("the file must be a .csv")
42
+ it "creates a graph with string as input" do
43
+ graph = Graph.new_from_string("source,target\n1,2")
44
+ expect(graph.nodes.count).to eq(2)
45
+ expect(graph.links.count).to eq(1)
46
+ end
47
+
48
+ it "understands directed graph" do
49
+ graph = Graph.new_from_string("source,target,type\n1,2,directed")
50
+ expect(graph.nodes.first.neighbours.count).to eq(1)
51
+ expect(graph.nodes.last.neighbours.count).to eq(0)
52
+ expect(graph.links.count).to eq(1)
44
53
  end
45
54
  end
46
55
 
@@ -76,7 +85,7 @@ describe Graph do
76
85
  [k,k,k,1,k,0]])
77
86
  end
78
87
 
79
- it "checks distances" do
88
+ it "calculates distances" do
80
89
  expect(subject.distances).to eq(
81
90
  [[0,1,2,2,1,3],
82
91
  [1,0,1,2,1,3],
@@ -86,6 +95,17 @@ describe Graph do
86
95
  [3,3,2,1,2,0]])
87
96
  end
88
97
 
98
+ it "calculates the mean distance" do
99
+ mdistance = subject.distances.flatten
100
+ mdistance = mdistance.inject(:+) / mdistance.count.to_f
101
+ expect(subject.mdistance).to eq(mdistance)
102
+ end
103
+
104
+ it "calculates the mean distance ignoring 'infinity'" do
105
+ graph = Graph.new('spec/fixtures/two_links_with_hole.csv')
106
+ expect(graph.mdistance).to eq(10.0/13.0)
107
+ end
108
+
89
109
  it "understands holes on the graph" do
90
110
  graph = Graph.new('spec/fixtures/two_links_with_hole.csv')
91
111
  k = graph.infinity
@@ -149,5 +169,47 @@ describe Graph do
149
169
  it "calculates cumulative betweenness" do
150
170
  expect(subject.betweenness(cumulative: true)).to eq([[6, 0.0], [4, 0.5], [1, 1.0]])
151
171
  end
172
+ it "calculates clustering of a single node" do
173
+ nodes = subject.nodes.sort{|a,b| a.id <=> b.id}
174
+
175
+ expect(subject.single_clustering(nodes[0])).to eq(1.0/1.0)
176
+ expect(subject.single_clustering(nodes[1])).to eq(1.0/3.0)
177
+ expect(subject.single_clustering(nodes[2])).to eq(0.0/1.0)
178
+ expect(subject.single_clustering(nodes[3])).to eq(0.0/3.0)
179
+ expect(subject.single_clustering(nodes[4])).to eq(1.0/3.0)
180
+ expect(subject.single_clustering(nodes[5])).to eq(0.0/1.0)
181
+ end
182
+ it "calculates clustering of all nodes" do
183
+ expect(subject.clustering).to eq((1.0/1.0 + 2.0/3.0) / 6.0)
184
+ end
185
+ end
186
+ describe "PageRank" do
187
+ it "calculates with two nodes connected" do
188
+ pr = Graph.new_from_string("source,target,type\n1,2,directed").page_rank
189
+ expect(pr.first).to be < pr.last
190
+ end
191
+ it "calculates with three nodes within a line" do
192
+ pr = Graph.new_from_string("source,target,type\n1,2,directed\n2,3,directed").page_rank
193
+ expect(pr[0]).to be < pr[1]
194
+ expect(pr[1]).to be < pr[2]
195
+ end
196
+ it "calculates with two nodes pointing to the same" do
197
+ pr = Graph.new_from_string("source,target,type\n1,2,directed\n3,2,directed").page_rank
198
+ expect(pr[0]).to be < pr[1]
199
+ expect(pr[2]).to be < pr[1]
200
+ expect(pr[0]).to eq(pr[2])
201
+ end
202
+
203
+ it "calculates with a cycle" do
204
+ pr = Graph.new_from_string("source,target,type\n1,2,directed\n2,3,directed\n3,1,directed").page_rank
205
+ expect(pr[0]).to eq(pr[1])
206
+ expect(pr[1]).to eq(pr[2])
207
+ expect(pr.inject(:+)).to eq(1)
208
+ end
209
+ it "calculates with two subgraphs" do
210
+ pr = Graph.new_from_string("source,target,type\n1,2,directed\n3,4,directed\n3,1,directed").page_rank
211
+ expect(pr[0]).to be < pr[1]
212
+ expect(pr[2]).to be < pr[3]
213
+ end
152
214
  end
153
215
  end
@@ -4,7 +4,7 @@ require_relative '../../lib/rgraph/node'
4
4
 
5
5
  describe Link do
6
6
  describe "creates a link without a weight" do
7
- subject { Link.new(source: Node.new(id: 00001), target: Node.new(id: 00002), years: [2011, 2012]) }
7
+ subject { Link.new(source: Node.new(id: 1), target: Node.new(id: 2), years: [2011, 2012]) }
8
8
  its(:source) { should be_kind_of Node }
9
9
  its(:target) { should be_kind_of Node }
10
10
  its(:weight) { should == 1 }
@@ -15,31 +15,38 @@ describe Link do
15
15
  expect(subject.target.neighbours).to eq([subject.source])
16
16
  end
17
17
 
18
+ it "checks link's direction" do
19
+ link = Link.new(source: Node.new(id: 1), target: Node.new(id: 2), type: 'directed')
20
+ expect(link.source.neighbours.count).to eq(1)
21
+ expect(link.target.neighbours.count).to eq(0)
22
+ end
23
+
18
24
  end
19
25
 
20
26
  describe "creates a link passing a weight" do
21
27
  subject { Link.new(source: Node.new(id: 00001), target: Node.new(id: 00002), weight: 4, years: [2011, 2012]) }
22
28
  its(:weight) { should == 4 }
23
29
 
24
- it "checks the creation of neighbours" do
25
- expect(subject.source.neighbours).to eq([subject.target])
26
- expect(subject.target.neighbours).to eq([subject.source])
27
- end
28
30
  end
29
31
 
30
- it "creates a link without source" do
32
+ it "doesn't create a link without source" do
31
33
  expect { Link.new(target: Node.new(id: 00001)) }.to raise_exception("source cant be nil")
32
34
  end
33
35
 
34
- it "creates a link without target" do
36
+ it "doesn't create a link without target" do
35
37
  expect { Link.new(source: Node.new(id: 00001)) }.to raise_exception("target cant be nil")
36
38
  end
37
39
 
38
- it "creates a link with wrong source type" do
40
+ it "doesn't create a link with wrong source type" do
39
41
  expect { Link.new(source: "Lucas", target: Node.new(id: 1)) }.to raise_exception("source must be of type Node")
40
42
  end
41
43
 
42
- it "creates a link with wrong target type" do
44
+ it "doesn't create a link with wrong target type" do
43
45
  expect { Link.new(source: Node.new(id: 1), target: "Lucas") }.to raise_exception("target must be of type Node")
44
46
  end
47
+
48
+ it "sets default type as 'undirected'" do
49
+ expect(Link.new(source: Node.new(id: 1), target: Node.new(id: 2)).type).to eq('undirected')
50
+ end
51
+
45
52
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rgraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.0.10
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-08-30 00:00:00.000000000 Z
13
+ date: 2013-09-12 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler