nominate 0.0.0
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/nominate.rb +17 -0
- data/lib/nominate/DW-NOMINATE.FOR +10008 -0
- data/lib/nominate/dw_nominate.rb +382 -0
- data/lib/nominate/nominate.R +34 -0
- data/lib/nominate/w_nominate.rb +51 -0
- metadata +55 -0
@@ -0,0 +1,382 @@
|
|
1
|
+
class Dwnominate
|
2
|
+
def initialize
|
3
|
+
@sessions = []
|
4
|
+
end
|
5
|
+
|
6
|
+
def add_session(session)
|
7
|
+
@sessions.push session
|
8
|
+
end
|
9
|
+
|
10
|
+
def sessions
|
11
|
+
@sessions
|
12
|
+
end
|
13
|
+
|
14
|
+
def dwnominate(prefix = 'dw_')
|
15
|
+
path = File.expand_path(File.dirname(__FILE__))
|
16
|
+
self.dw_check(path)
|
17
|
+
Dir.mkdir('nominate') unless Dir.exist?('nominate')
|
18
|
+
Dir.chdir('nominate')
|
19
|
+
self.check_ws()
|
20
|
+
self.write_dw(prefix)
|
21
|
+
system path + '/dw-nominate'
|
22
|
+
Dir.chdir('..')
|
23
|
+
end
|
24
|
+
|
25
|
+
def dw_check(path)
|
26
|
+
# compile DW-NOMINATE if needed
|
27
|
+
entries = Dir.entries(path)
|
28
|
+
if not entries.include?('dw-nominate')
|
29
|
+
puts 'You have not compiled DW-NOMINATE on this computer.'
|
30
|
+
puts 'DW-NOMINATE must be compiled to run.'
|
31
|
+
puts 'Would you like to compile DW-NOMINATE? (y/n)'
|
32
|
+
puts '(Requires sudo privileges.)'
|
33
|
+
answer = gets.chomp.downcase
|
34
|
+
if answer == 'y'
|
35
|
+
Dir.chdir(path) do
|
36
|
+
system 'sudo gfortran ' + path + '/DW-NOMINATE.FOR -w -o ' +
|
37
|
+
path + '/dw-nominate'
|
38
|
+
entries = Dir.entries(path)
|
39
|
+
if not entries.include?('dw-nominate')
|
40
|
+
puts 'Compiling failed.'
|
41
|
+
exit
|
42
|
+
else
|
43
|
+
puts ''
|
44
|
+
puts 'Code compiled successfully.'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
else
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def check_ws()
|
54
|
+
@sessions.each_with_index do |session, i|
|
55
|
+
if session.prefix == nil
|
56
|
+
session.prefix = 'session_' + (i+1).to_s + '_'
|
57
|
+
self.check_for_session(session)
|
58
|
+
else
|
59
|
+
self.check_for_session(session)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def check_for_session(session)
|
65
|
+
if not Dir.entries(Dir.pwd).include?(session.prefix + 'legislators.csv')
|
66
|
+
Dir.chdir('..')
|
67
|
+
session.wnominate(session.prefix)
|
68
|
+
Dir.chdir('nominate')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def write_dw(prefix)
|
73
|
+
# write_vote_matrix()
|
74
|
+
# write_transposed_vote_matrix()
|
75
|
+
|
76
|
+
# 1) Rollcall data file
|
77
|
+
|
78
|
+
# setting variables
|
79
|
+
leg_session = 1
|
80
|
+
legs = {}
|
81
|
+
legNum = 1
|
82
|
+
stateNum = " 1"
|
83
|
+
district = " 0"
|
84
|
+
stName = " VERMONT"
|
85
|
+
parties = {}
|
86
|
+
party = 1
|
87
|
+
votes = { "Y" => "1", "N" => "6", 'M' => '9' }
|
88
|
+
final = []
|
89
|
+
|
90
|
+
# processing
|
91
|
+
@sessions.each do |session|
|
92
|
+
lines = IO.readlines(session.prefix + 'votes.csv')
|
93
|
+
lines.each_with_index do |line, i|
|
94
|
+
data = line.gsub("||", "| |").gsub("||", "| |").gsub("|\n", "| ").chomp.split("|")
|
95
|
+
legName = data[0]
|
96
|
+
legParty = data[1]
|
97
|
+
# making legislator IDs
|
98
|
+
if legs[legName] == nil
|
99
|
+
legs[legName] = " "*(6-legNum.to_s.length) + legNum.to_s
|
100
|
+
legNum += 1
|
101
|
+
end
|
102
|
+
# making party IDs
|
103
|
+
if parties[legParty] == nil
|
104
|
+
parties[legParty] = " "*(5-party.to_s.length) + party.to_s
|
105
|
+
party += 1
|
106
|
+
end
|
107
|
+
# converting votes to a numeric string
|
108
|
+
voteNums = ' '
|
109
|
+
data[2..-1].each do |vote|
|
110
|
+
begin
|
111
|
+
voteNums += votes[vote]
|
112
|
+
rescue
|
113
|
+
puts "Unrecognized vote: " + vote + ", from " + legName + ", " +
|
114
|
+
session + " " + legParty
|
115
|
+
end
|
116
|
+
end
|
117
|
+
shortName = legName.gsub(/[^A-Za-z -]/, '')[0..9] + " "*(10-legName.gsub(/[^A-Za-z -]/, '')[0..9].length)
|
118
|
+
if shortName.delete(' ') == ''
|
119
|
+
puts 'Blank legislator name-'
|
120
|
+
puts "Line #{(i+1).to_s}, '#{session.prefix}votes.csv'"
|
121
|
+
puts 'Legislators must have non-blank names to be included.'
|
122
|
+
puts ''
|
123
|
+
next
|
124
|
+
end
|
125
|
+
final.push sprintf("%4d", leg_session) + legs[legName] + stateNum +
|
126
|
+
district + stName + parties[legParty] + " " + shortName + voteNums
|
127
|
+
end
|
128
|
+
leg_session += 1
|
129
|
+
end
|
130
|
+
File.open(prefix + "rollcall_matrix.vt3", 'w') { |f1| f1.puts final }
|
131
|
+
|
132
|
+
|
133
|
+
# 2) Transposed rollcall data file
|
134
|
+
|
135
|
+
sessionNums = []
|
136
|
+
lines = IO.readlines(prefix + "rollcall_matrix.vt3")
|
137
|
+
|
138
|
+
lines.each do |line|
|
139
|
+
if not sessionNums.include? line[0..3]
|
140
|
+
sessionNums.push line[0..3]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
final = []
|
145
|
+
|
146
|
+
sessionDatas = []
|
147
|
+
sessionNums.each do |session|
|
148
|
+
sessionData = []
|
149
|
+
lines.each do |line|
|
150
|
+
if line[0..3] == session
|
151
|
+
sessionData.push line[40..-1].chomp
|
152
|
+
end
|
153
|
+
end
|
154
|
+
sessionDatas.push sessionData
|
155
|
+
end
|
156
|
+
|
157
|
+
y = 0
|
158
|
+
sessionDatas.each do |sessionData|
|
159
|
+
x = 0
|
160
|
+
sessionData[0].each_char do |bill|
|
161
|
+
votes = ' '
|
162
|
+
sessionData.each do |legVotes|
|
163
|
+
begin
|
164
|
+
votes += legVotes[x]
|
165
|
+
rescue
|
166
|
+
puts "Session #{(y+1).to_s} , vote # #{(x+1).to_s}"
|
167
|
+
exit
|
168
|
+
end
|
169
|
+
end
|
170
|
+
final.push sessionNums[y] + " "*(5-(x+1).to_s.length) + (x+1).to_s + votes
|
171
|
+
x += 1
|
172
|
+
end
|
173
|
+
y += 1
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
File.open(prefix + "transposed_rollcall_matrix.vt3", 'w') do |f1|
|
178
|
+
final.each do |line|
|
179
|
+
f1.puts line
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
# 3) Legislator data file
|
185
|
+
|
186
|
+
final = []
|
187
|
+
final2 = []
|
188
|
+
|
189
|
+
legSession = 1
|
190
|
+
xpositives = []
|
191
|
+
ypositives = []
|
192
|
+
@sessions.each_with_index do |session, i|
|
193
|
+
flipx = false
|
194
|
+
flipy = false
|
195
|
+
|
196
|
+
# Leg file
|
197
|
+
|
198
|
+
sessionNum = " "*(4-legSession.to_s.length) + legSession.to_s
|
199
|
+
lines = IO.readlines(session.prefix + "legislators.csv")
|
200
|
+
lines.delete_at(0)
|
201
|
+
|
202
|
+
# deciding whether to flip numbers
|
203
|
+
xs = []
|
204
|
+
ys = []
|
205
|
+
if i != 0
|
206
|
+
lines.each do |line|
|
207
|
+
data = line.split("|")
|
208
|
+
name = data[0]
|
209
|
+
xs.push data[8] if xpositives.include? name
|
210
|
+
ys.push data[9] if ypositives.include? name
|
211
|
+
end
|
212
|
+
flipx = true if average(xs, i) < 0
|
213
|
+
flipy = true if average(ys, i) < 0
|
214
|
+
end
|
215
|
+
|
216
|
+
# writing the legislator file lines
|
217
|
+
lines.each_with_index do |line, i|
|
218
|
+
data = line.split("|")
|
219
|
+
name = data[0]
|
220
|
+
shortName = name.gsub(/[^A-Za-z -]/, '')[0..9] +
|
221
|
+
" "*(10-name.gsub(/[^A-Za-z -]/, '')[0..9].length)
|
222
|
+
|
223
|
+
if shortName.delete(' ') == ''
|
224
|
+
puts 'Vote is missing a name-'
|
225
|
+
puts "Line #{(i+1).to_s}, '#{session.prefix}legislators.csv'"
|
226
|
+
puts 'This vote will not be included.'
|
227
|
+
puts ''
|
228
|
+
next
|
229
|
+
end
|
230
|
+
|
231
|
+
# flipping numbers to orient each session in the same direction
|
232
|
+
xnum = data[8]
|
233
|
+
xnum = (-(xnum.to_f)).to_s if flipx
|
234
|
+
ynum = data[9]
|
235
|
+
ynum = (-(ynum.to_f)).to_s if flipy
|
236
|
+
|
237
|
+
if data[2] == 'NA' or data[3] == 'NA' or
|
238
|
+
data[4] == 'NA' or data[5] == 'NA'
|
239
|
+
numVotes = ' 0'
|
240
|
+
numErrors = ' 0'
|
241
|
+
else
|
242
|
+
voteTotal = data[2].to_i + data[3].to_i + data[4].to_i + data[5].to_i
|
243
|
+
numVotes = " "*(5-voteTotal.to_s.length) + voteTotal.to_s
|
244
|
+
errorTotal = data[3].to_i + data[4].to_i
|
245
|
+
numErrors = " "*(5-errorTotal.to_s.length) + errorTotal.to_s
|
246
|
+
end
|
247
|
+
begin
|
248
|
+
final.push sessionNum + legs[data[0]] + stateNum + district + stName +
|
249
|
+
parties[data[1]] + " " + shortName + " " + dw_format(xnum) +
|
250
|
+
dw_format(ynum) +
|
251
|
+
" 0.000 0.000 0.000 0.000 0.00000 0.00000" +
|
252
|
+
numVotes*2 + numErrors*2 + dw_format(data[6])*2
|
253
|
+
rescue
|
254
|
+
puts "Error:"
|
255
|
+
puts sessionNum
|
256
|
+
puts data[0]
|
257
|
+
puts legs[data[0]]
|
258
|
+
puts parties[data[1]]
|
259
|
+
puts format(data[8])
|
260
|
+
puts format(data[9])
|
261
|
+
puts numVotes*2
|
262
|
+
puts numErrors*2
|
263
|
+
puts format(data[6])*2
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
xpositives = []
|
268
|
+
ypositives = []
|
269
|
+
lines.each do |line|
|
270
|
+
data = line.split('|')
|
271
|
+
# keep track of legislator scores, to decide whether to flip numbers
|
272
|
+
# for the next session
|
273
|
+
xpositives.push data[0] if data[8].to_f > 0
|
274
|
+
ypositives.push data[0] if data[9].to_f > 0
|
275
|
+
end
|
276
|
+
|
277
|
+
# Bill file
|
278
|
+
|
279
|
+
sessionNum = " "*(4-legSession.to_s.length) + legSession.to_s
|
280
|
+
lines2 = IO.readlines(session.prefix + "rollcalls.csv")
|
281
|
+
lines2.delete_at(0)
|
282
|
+
lines2.each do |line|
|
283
|
+
data = line.split("|")
|
284
|
+
billNum = " "*(5-data[0].length) + data[0]
|
285
|
+
billx = data[8]
|
286
|
+
billx = (-(billx.to_f)).to_s if flipx
|
287
|
+
billy = data[10].chomp
|
288
|
+
billy = (-(billy.to_f)).to_s if flipy
|
289
|
+
|
290
|
+
final2.push sessionNum[1..3] + billNum + dw_format(data[7]) +
|
291
|
+
dw_format(data[9]) + dw_format(billx) + dw_format(billy)
|
292
|
+
end
|
293
|
+
|
294
|
+
legSession += 1
|
295
|
+
end
|
296
|
+
|
297
|
+
File.open(prefix + 'legislator_input.dat', 'w') do |f1|
|
298
|
+
final.each do |line|
|
299
|
+
f1.puts line
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
File.open(prefix + 'rollcall_input.dat', 'w') do |f1|
|
304
|
+
final2.each do |line|
|
305
|
+
f1.puts line
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
|
310
|
+
# 5) Session data file
|
311
|
+
|
312
|
+
final = []
|
313
|
+
legSession = 1
|
314
|
+
@sessions.each do |session|
|
315
|
+
sessionNum = " "*(3-legSession.to_s.length) + legSession.to_s
|
316
|
+
lines = IO.readlines(session.prefix + "rollcalls.csv")
|
317
|
+
rollcalls = " "*(5-(lines.length-1).to_s.length) + (lines.length-1).to_s
|
318
|
+
lines = IO.readlines(session.prefix + "legislators.csv")
|
319
|
+
legislators = " "*(4-(lines.length-1).to_s.length) + (lines.length-1).to_s
|
320
|
+
final.push sessionNum + rollcalls + legislators
|
321
|
+
legSession += 1
|
322
|
+
end
|
323
|
+
|
324
|
+
File.open(prefix + 'session_info.num', 'w') do |f1|
|
325
|
+
final.each do |line|
|
326
|
+
f1.puts line
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
|
331
|
+
# 6) DW-NOMSTART.DAT file
|
332
|
+
|
333
|
+
File.open('DW-NOMSTART.DAT', 'w') do |f1|
|
334
|
+
f1.puts prefix + 'rollcall_input.dat'
|
335
|
+
f1.puts prefix + 'rollcall_output.dat'
|
336
|
+
f1.puts prefix + 'legislator_input.dat'
|
337
|
+
f1.puts prefix + 'legislator_output.dat'
|
338
|
+
f1.puts prefix + 'session_info.num'
|
339
|
+
f1.puts prefix + 'rollcall_matrix.vt3'
|
340
|
+
f1.puts prefix + 'transposed_rollcall_matrix.vt3'
|
341
|
+
f1.puts 'NOMINAL DYNAMIC-WEIGHTED MULTIDIMENSIONAL UNFOLDING '
|
342
|
+
num_of_sessions = sessions.length.to_s
|
343
|
+
number_text = ' '*(5 - num_of_sessions.length) + num_of_sessions
|
344
|
+
f1.puts ' 2 1 1' + number_text + ' 2 5'
|
345
|
+
f1.puts ' 5.9539 0.3463'
|
346
|
+
end
|
347
|
+
|
348
|
+
|
349
|
+
puts ''
|
350
|
+
puts 'File formatting done.'
|
351
|
+
puts ''
|
352
|
+
puts ''
|
353
|
+
|
354
|
+
end
|
355
|
+
|
356
|
+
end
|
357
|
+
|
358
|
+
|
359
|
+
|
360
|
+
|
361
|
+
def dw_format(num)
|
362
|
+
if num[0] == '-'
|
363
|
+
return " " + num.to_f.round(3).to_s + "0"*(6-num.to_f.round(3).to_s.length)
|
364
|
+
elsif num == 'NA'
|
365
|
+
return dw_format('0')
|
366
|
+
else
|
367
|
+
return " " + num.to_f.round(3).to_s + "0"*(5-num.to_f.round(3).to_s.length)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
def average(numbers, si)
|
372
|
+
if numbers.length == 0
|
373
|
+
puts 'No legislator names matched those of the previous session.'
|
374
|
+
puts 'You will not be able to run DW-NOMINATE on these two sessions.'
|
375
|
+
puts 'Put names from both sessions in the same format to correct the issue.'
|
376
|
+
puts "Sessions #{si.to_s} and #{(si+1).to_s}."
|
377
|
+
exit
|
378
|
+
end
|
379
|
+
sum = 0
|
380
|
+
numbers.each { |n| sum = sum + n.to_f }
|
381
|
+
sum / numbers.length
|
382
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
if( require("wnominate") == FALSE ) {
|
2
|
+
print("The R 'wnominate' package is not installed.")
|
3
|
+
print("Trying to install wnominate. (Requires sudo privileges.)")
|
4
|
+
install.packages("wnominate", repos='http://cran.rstudio.com/')
|
5
|
+
if( require("wnominate") ) {
|
6
|
+
print("wnominate is now installed.")
|
7
|
+
} else {
|
8
|
+
stop("Did not install wnominate.")
|
9
|
+
}
|
10
|
+
}
|
11
|
+
|
12
|
+
# performing W-NOMINATE analysis
|
13
|
+
data <- read.csv("votes.csv", header=F, sep="|", check.names = F, stringsAsFactors=F,
|
14
|
+
quote="", row.names = NULL)
|
15
|
+
names <- data[, 1]
|
16
|
+
legData <- matrix(data[, 2], length(data[, 2]), 1)
|
17
|
+
colnames(legData) <- "party"
|
18
|
+
data <- data[, -c(1, 2)]
|
19
|
+
rc <- rollcall(data, yea = c("Y"), nay = c("N"), missing = c('r'),
|
20
|
+
notInLegis = c("M"), legis.names = names, legis.data = legData,
|
21
|
+
desc = "NA", source = "NA")
|
22
|
+
result <- wnominate(rc, polarity = c(1, 1))
|
23
|
+
|
24
|
+
write.table(result$legislators, file = "legislators.csv", sep = "|", quote = F)
|
25
|
+
write.table(result$rollcalls, file = "rollcalls.csv", sep = "|", quote = F)
|
26
|
+
write.table(result$dimensions, file = "dimensions.csv", sep = "|", quote = F)
|
27
|
+
write.table(result$eigenvalues, file = "eigenvalues.csv", sep = "|", quote = F)
|
28
|
+
write.table(result$beta, file = "beta.csv", sep = "|", quote = F)
|
29
|
+
write.table(result$weights, file = "weights.csv", sep = "|", quote = F)
|
30
|
+
write.table(result$fits, file = "fits.csv", sep = "|", quote = F)
|
31
|
+
|
32
|
+
# png('result.png', width=700, height=500, res=100)
|
33
|
+
plot(result)
|
34
|
+
# dev.off()
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class Wnominate
|
2
|
+
def initialize()
|
3
|
+
@legislators = {}
|
4
|
+
@rollcalls = []
|
5
|
+
# party is 'unknown' by default
|
6
|
+
@parties = Hash.new('unknown')
|
7
|
+
# variable for assigning each legislator a number, used for DW-NOMINATE
|
8
|
+
@x = 1
|
9
|
+
end
|
10
|
+
attr_accessor :parties
|
11
|
+
attr_accessor :prefix
|
12
|
+
def add_rollcall(rollcall_hash)
|
13
|
+
@rollcalls.push rollcall_hash
|
14
|
+
rollcall_hash.each_key do |name|
|
15
|
+
if not @legislators.has_key? name
|
16
|
+
@legislators[name] = @x
|
17
|
+
@x += 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
def wnominate(file_prefix = 'wnom_')
|
22
|
+
Dir.mkdir('nominate') unless Dir.exist?('nominate')
|
23
|
+
Dir.chdir('nominate')
|
24
|
+
@prefix = file_prefix
|
25
|
+
self.write_wnom(@legislators, @rollcalls, @parties)
|
26
|
+
path = File.expand_path(File.dirname(__FILE__))
|
27
|
+
was_good = system 'Rscript ' + path + '/nominate.R'
|
28
|
+
if was_good != true
|
29
|
+
puts ''
|
30
|
+
puts 'Something went wrong.'
|
31
|
+
puts 'If you have not installed R, please install R and try again.'
|
32
|
+
exit
|
33
|
+
else
|
34
|
+
files = ['votes.csv', 'legislators.csv', 'rollcalls.csv',
|
35
|
+
'dimensions.csv', 'eigenvalues.csv', 'beta.csv', 'weights.csv',
|
36
|
+
'fits.csv', 'Rplots.pdf']
|
37
|
+
files.each { |file| File.rename(file, file_prefix + file) }
|
38
|
+
end
|
39
|
+
Dir.chdir('..')
|
40
|
+
end
|
41
|
+
def write_wnom(legislators, rollcalls, parties)
|
42
|
+
final = []
|
43
|
+
# write output in format 'Bob Smith|party|Y|Y|N|Y|N|N|...'
|
44
|
+
legislators.each_key do |leg|
|
45
|
+
line = leg + '|' + parties[leg]
|
46
|
+
rollcalls.each { |hash| line << '|' + hash[leg] }
|
47
|
+
final.push line
|
48
|
+
end
|
49
|
+
File.open('votes.csv', 'w') { |f1| f1.puts final }
|
50
|
+
end
|
51
|
+
end
|