rgraph 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
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