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