Commit 967532f1 authored by Dominique Merle's avatar Dominique Merle
Browse files

Initial Sprint

Works, but a bit rough on the edges.
…
And the middle, like when you run it
without any parameter nor input at all.

It'll expect you to type your CSV and
hit CTRL+D (detach).

Best make sure you give it parameters then.

Still, we import no libs but standard,
and that's good for portability.
parent dbb79ff6
Candidate A,Candidate B,Candidate C,Candidate D,Candidate E,Candidate F
REJECT,SOMEWHAT GOOD,EXCELLENT,EXCELLENT,EXCELLENT,GOOD
SOMEWHAT GOOD,SOMEWHAT GOOD,SOMEWHAT GOOD,PASSABLE,POOR,EXCELLENT
PASSABLE,VERY GOOD,PASSABLE,PASSABLE,SOMEWHAT GOOD,REJECT
SOMEWHAT GOOD,GOOD,EXCELLENT,GOOD,POOR,EXCELLENT
VERY GOOD,REJECT,EXCELLENT,SOMEWHAT GOOD,GOOD,EXCELLENT
VERY GOOD,POOR,GOOD,REJECT,VERY GOOD,EXCELLENT
VERY GOOD,EXCELLENT,GOOD,GOOD,REJECT,POOR
PASSABLE,VERY GOOD,EXCELLENT,EXCELLENT,EXCELLENT,SOMEWHAT GOOD
SOMEWHAT GOOD,POOR,PASSABLE,PASSABLE,REJECT,EXCELLENT
REJECT,PASSABLE,VERY GOOD,GOOD,POOR,PASSABLE
REJECT,POOR,VERY GOOD,PASSABLE,EXCELLENT,REJECT
PASSABLE,EXCELLENT,SOMEWHAT GOOD,VERY GOOD,POOR,SOMEWHAT GOOD
SOMEWHAT GOOD,VERY GOOD,VERY GOOD,EXCELLENT,REJECT,EXCELLENT
GOOD,VERY GOOD,POOR,SOMEWHAT GOOD,SOMEWHAT GOOD,GOOD
REJECT,VERY GOOD,SOMEWHAT GOOD,VERY GOOD,SOMEWHAT GOOD,REJECT
SOMEWHAT GOOD,GOOD,EXCELLENT,PASSABLE,EXCELLENT,PASSABLE
POOR,GOOD,VERY GOOD,EXCELLENT,REJECT,POOR
POOR,SOMEWHAT GOOD,VERY GOOD,POOR,GOOD,EXCELLENT
REJECT,VERY GOOD,POOR,REJECT,PASSABLE,GOOD
GOOD,GOOD,POOR,REJECT,GOOD,POOR
VERY GOOD,EXCELLENT,EXCELLENT,REJECT,EXCELLENT,GOOD
PASSABLE,VERY GOOD,PASSABLE,GOOD,REJECT,SOMEWHAT GOOD
VERY GOOD,REJECT,POOR,VERY GOOD,POOR,POOR
REJECT,VERY GOOD,GOOD,POOR,POOR,VERY GOOD
VERY GOOD,VERY GOOD,REJECT,SOMEWHAT GOOD,POOR,REJECT
PASSABLE,SOMEWHAT GOOD,PASSABLE,REJECT,POOR,PASSABLE
PASSABLE,POOR,POOR,VERY GOOD,EXCELLENT,GOOD
SOMEWHAT GOOD,PASSABLE,SOMEWHAT GOOD,VERY GOOD,VERY GOOD,SOMEWHAT GOOD
VERY GOOD,GOOD,VERY GOOD,PASSABLE,VERY GOOD,EXCELLENT
SOMEWHAT GOOD,EXCELLENT,REJECT,REJECT,GOOD,GOOD
VERY GOOD,EXCELLENT,REJECT,POOR,REJECT,POOR
REJECT,EXCELLENT,GOOD,GOOD,GOOD,VERY GOOD
GOOD,GOOD,SOMEWHAT GOOD,PASSABLE,GOOD,SOMEWHAT GOOD
EXCELLENT,EXCELLENT,SOMEWHAT GOOD,GOOD,REJECT,SOMEWHAT GOOD
SOMEWHAT GOOD,PASSABLE,POOR,SOMEWHAT GOOD,GOOD,POOR
SOMEWHAT GOOD,POOR,PASSABLE,SOMEWHAT GOOD,GOOD,SOMEWHAT GOOD
GOOD,REJECT,POOR,EXCELLENT,SOMEWHAT GOOD,PASSABLE
REJECT,POOR,SOMEWHAT GOOD,PASSABLE,PASSABLE,VERY GOOD
VERY GOOD,VERY GOOD,GOOD,PASSABLE,SOMEWHAT GOOD,SOMEWHAT GOOD
SOMEWHAT GOOD,EXCELLENT,GOOD,POOR,PASSABLE,POOR
REJECT,SOMEWHAT GOOD,VERY GOOD,VERY GOOD,GOOD,PASSABLE
REJECT,VERY GOOD,VERY GOOD,PASSABLE,POOR,SOMEWHAT GOOD
\ No newline at end of file
Timestamp,un Tyran Sanguinaire serait …,mon Chien préféré serait …,un Écologiste Décroissant serait …,un Capitaliste Prédateur serait …,un Humaniste Candide serait …,un Imposteur Crapuleux serait …,un Nationaliste Identitaire serait …,un Camarade Communiste serait …,une Citoyenne nommée Marianne tirée au hasard dans la population serait …
12/8/2019 12:33:54,insuffisant⋅e,excellent⋅e,excellent⋅e,à rejeter,très bien,passable,à rejeter,très bien,excellent⋅e
12/8/2019 12:58:36,à rejeter,excellent⋅e,à rejeter,à rejeter,excellent⋅e,à rejeter,bien,à rejeter,excellent⋅e
12/8/2019 13:19:19,à rejeter,insuffisant⋅e,très bien,à rejeter,insuffisant⋅e,à rejeter,à rejeter,insuffisant⋅e,très bien
12/8/2019 13:20:12,insuffisant⋅e,à rejeter,excellent⋅e,à rejeter,insuffisant⋅e,à rejeter,passable,très bien,très bien
12/8/2019 13:39:49,passable,excellent⋅e,très bien,à rejeter,passable,à rejeter,insuffisant⋅e,excellent⋅e,excellent⋅e
12/8/2019 13:49:40,à rejeter,à rejeter,passable,à rejeter,assez bien,à rejeter,à rejeter,passable,assez bien
12/8/2019 15:21:05,insuffisant⋅e,très bien,passable,passable,à rejeter,excellent⋅e,insuffisant⋅e,très bien,très bien
12/8/2019 22:17:25,passable,très bien,très bien,à rejeter,passable,insuffisant⋅e,passable,très bien,excellent⋅e
12/8/2019 23:50:04,à rejeter,à rejeter,très bien,à rejeter,assez bien,à rejeter,insuffisant⋅e,insuffisant⋅e,assez bien
12/9/2019 1:45:08,à rejeter,à rejeter,bien,à rejeter,insuffisant⋅e,à rejeter,à rejeter,insuffisant⋅e,à rejeter
12/9/2019 9:05:27,à rejeter,à rejeter,excellent⋅e,à rejeter,passable,à rejeter,à rejeter,bien,à rejeter
12/9/2019 11:36:07,à rejeter,passable,assez bien,à rejeter,très bien,passable,insuffisant⋅e,assez bien,bien
excellent⋅e
très bien
bien
assez bien
passable
insuffisant⋅e
à rejeter
\ No newline at end of file
#!/usr/bin/env python
"""
A tool to summarize majority judgment polling.
"""
__author__ = "Constituantes"
__version__ = "0.1.0"
__license__ = "MIT"
import sys
import argparse
import csv
import math
import copy
from io import StringIO
from functools import cmp_to_key
# import pandas
VERBOSITY_NONE = 0
VERBOSITY_SOME = 1
VERBOSITY_MUCH = 2
VERBOSITY_VERY = 3
def log(message):
print(message)
def find_file(some_vague_path):
return some_vague_path
def get_positions(mentions):
mentions_dict = dict()
for i, mention in enumerate(mentions):
mentions_dict[mention] = i
return mentions_dict
def get_median(tally, mentions):
low = True
count = 0
for mention in tally:
count += tally[mention]
# Lowest mention is the default
median = mentions[-1]
if 0 == count:
return median
median_index = math.floor((count + (-1 if low else 1)) / 2.0)
current = 0
for mention in reversed(mentions): # lowest to highest
mention_min = current
current += tally[mention]
mention_max = current
if mention_min <= median_index < mention_max:
median = mention
break
return median
def is_tally_empty(tally):
for mention in tally:
if 0 < tally[mention]:
return False
return True
def decrement_mention(tally, mention):
assert tally[mention] > 0
tally[mention] -= 1
def sort_two_candidates(judgments_of, tally_of, mentions, ca, cb):
toca = tally_of[ca]
tocb = tally_of[cb]
mdca = get_median(toca, mentions)
mdcb = get_median(tocb, mentions)
positions = get_positions(mentions)
if mdca == mdcb:
cotoca = copy.copy(toca)
cotocb = copy.copy(tocb)
while not is_tally_empty(cotoca) and not is_tally_empty(cotocb):
nemdca = get_median(cotoca, mentions)
nemdcb = get_median(cotocb, mentions)
if nemdca == nemdcb:
decrement_mention(cotoca, nemdca)
decrement_mention(cotocb, nemdcb)
else:
return positions[nemdca] - positions[nemdcb]
return 0
else:
return positions[mdca] - positions[mdcb]
def main(args):
log("MAJORITY JUDGMENT POLLING -- Version %s" % __version__)
# In decreasing order.
default_mentions = [
'EXCELLENT',
'VERY GOOD',
'GOOD',
'SOMEWHAT GOOD',
'PASSABLE',
'POOR',
'REJECT',
]
if not args.mentions_file:
mentions = default_mentions
else:
with open(args.mentions_file) as f:
mentions = [l.strip() for l in f.readlines() if l and l.strip()]
log("Going to use the following mentions:")
log(''+(', '.join(mentions)))
if args.input_file is not None:
with open(find_file(args.input_file)) as f:
input_csv_strings = f.readlines()
else:
input_csv_strings = sys.stdin.readlines()
if not input_csv_strings:
log("Provide an input file with --input.")
exit(1)
judgments_data = csv.reader(
StringIO("".join(input_csv_strings)),
skipinitialspace=True,
delimiter=',',
lineterminator='\n'
)
detected_mentions = list()
candidates_list = list()
everyones_judgments = dict()
skip_cols = int(args.skip_cols)
skip_rows = 0
# Set to -1 to disable header
header_on_row = skip_rows + 0
current_row = -1
for judgments in judgments_data:
current_row += 1
judgments = judgments[skip_cols:]
if current_row < skip_rows:
continue
if current_row == header_on_row:
candidates_list = judgments
continue
if not judgments:
log("Skipping empty line at row %d..." % current_row)
continue
if not candidates_list:
candidates_list = \
["Candidate %s"%(chr(i)) for i in range(len(judgments))]
for mention in judgments:
if mention not in mentions:
log("Found unknown mention `%s' at row %d." % (
mention, current_row
))
log("Use --mentions to specify a mentions file.")
exit(1)
for i in range(len(candidates_list)):
candidate = candidates_list[i]
if candidate not in everyones_judgments:
everyones_judgments[candidate] = list()
everyones_judgments[candidate].append(judgments[i])
# print(judgments)
mentions_dict = get_positions(mentions)
# Sort the mentions of each candidate.
for candidate in everyones_judgments:
s = sorted(
everyones_judgments[candidate],
key=lambda m: mentions_dict[m])
everyones_judgments[candidate] = s
judgments_tallies = dict()
for i in range(len(candidates_list)):
candidate = candidates_list[i]
if candidate not in judgments_tallies:
judgments_tallies[candidate] = dict()
for mention in mentions:
if mention not in judgments_tallies[candidate]:
judgments_tallies[candidate][mention] = 0
for mention in everyones_judgments[candidate]:
if mention not in judgments_tallies[candidate]:
# mention unknown
log("Warning: Unknown mention: %s" % mention)
judgments_tallies[candidate][mention] = 0
judgments_tallies[candidate][mention] += 1
# log("Candidates")
# log(candidates_list)
# log(judgments_tallies)
def _cmp_candidates(ca, cb):
# Here we could hook to external, replaceable classes
# to simplify usage of other algorithms.
return sort_two_candidates(
everyones_judgments,
judgments_tallies,
mentions,
ca, cb
)
sorted_candidates = sorted(
candidates_list,
key=cmp_to_key(_cmp_candidates)
)
for i, candidate in enumerate(sorted_candidates):
log("%02d. %14s %s" % (
i,
get_median(judgments_tallies[candidate], mentions),
candidate,
))
if __name__ == "__main__":
parser = argparse.ArgumentParser()
# Required positional argument
# parser.add_argument("arg", help="Required positional argument")
# Optional argument flag which defaults to False
# parser.add_argument(
# '-f',
# '--flag',
# action="store_true",
# default=False,
# help=""
# )
# Optional argument which requires a parameter (eg. -d test)
# parser.add_argument(
# "-c",
# "--config",
# action="store",
# dest="config",
# help="A configuration file in YML."
# )
# Input CSV file. Overrides stdin when provided.
parser.add_argument(
"-i",
"--input",
action="store",
dest="input_file",
help="A CSV file with the judgments."
)
parser.add_argument(
"-m",
"--mentions",
action="store",
dest="mentions_file",
help="A text file with one mention per line."
)
parser.add_argument(
"--skip-cols",
action="store",
default=0,
dest="skip_cols",
help="Amount of columns to skip on the left."
)
# Optional verbosity counter (eg. -v, -vv, -vvv, etc.)
parser.add_argument(
'-v',
'--verbose',
action='count',
default=0,
help="Verbosity (-v, -vv, etc)"
)
# Specify output of '--version'
parser.add_argument(
'--version',
action='version',
version='%(prog)s (version {version})'.format(version=__version__)
)
args = parser.parse_args()
main(args)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment