From 9fc0c06f6a6ccf1ce0348521914684e2751417da Mon Sep 17 00:00:00 2001 From: Lancasterwu Date: Fri, 8 Mar 2019 15:58:53 -0500 Subject: [PATCH 01/39] added the genetic_algorithm from maria's fork --- genetic_algorithm.py | 422 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 422 insertions(+) create mode 100644 genetic_algorithm.py diff --git a/genetic_algorithm.py b/genetic_algorithm.py new file mode 100644 index 00000000..21440a31 --- /dev/null +++ b/genetic_algorithm.py @@ -0,0 +1,422 @@ +"""Generate grouping based on student skills and preferences.""" +from typing import List +import random +from colors import bold +import numpy as np +import workbook +import math + + +class Student: + """Represent student.""" + def __init__(self, email: str, skills: List[int], preferences: List[str]): + self.email = email + self.skills = skills + self.preferences = preferences + + def __str__(self): + student_str = self.email + "\n" + student_str += "\tPreferences: " + str(self.preferences) + "\n" + student_str += "\tSkills: " + str(self.skills) + return student_str + + def __repr__(self): + return self.email + + def __eq__(self, other): + if isinstance(self, other.__class__): + return self.email == other.email + return NotImplemented + + def __hash__(self): + return hash(self.email) + + +class Individual: + """Represent individual.""" + def __init__(self, grouping: List[List[Student]], fitness): + self.grouping = grouping + self.fitness = fitness + + def __str__(self): + grouping_str = "" + for number, group in enumerate(self.grouping): + grouping_str += "Group {}".format(number) + "\n" + for student in group: + grouping_str += str(student) + "\n" + return bold("Grouping\n") + grouping_str + bold("Fitness\n") + str(self.fitness) + + +class Fitness: + """Represent fitness. Variables range from 0 to 1.""" + def __init__(self, preference, balance, fairness): + self.preference = preference + self.balance = balance + self.fairness = fairness + # FIXME: Can give weights to each variable + self.value = 0.5 * preference + 3.0 * balance + 1.5 * fairness + + def __gt__(self, other): + return self.value > other.value + + def __str__(self): + string = "Preference: " + str(self.preference) + "\n" + string += "Balance: " + str(self.balance) + "\n" + string += "Fairness: " + str(self.fairness) + "\n" + string += "Value: " + str(self.value) + "\n" + return string + + +best_grouping = list() +best_fitness = Fitness(0, 0, 0) + + +def create(): + students_to_group = workbook.STUDENTS[:] + random.shuffle(students_to_group) + + grouping = list() + + for _ in range(workbook.GROUPING_SIZE): + grouping.append(list()) + + for index, student in enumerate(students_to_group): + grouping[index % workbook.GROUPING_SIZE].append(student) + + if len(grouping) < 1: + print("CREATED TOO SMALL GROUPING") + + # print("CREATED GROUPNG: " + str(grouping)) + return grouping + +"""population_size: int, mutation_rate: float, crossover_rate: float, fitness, mutations, create""" +def evolve(population_size, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations): + + global best_grouping + global best_fitness + + print("in evolve") + population = [create() for _ in range(population_size)] + population = list(map(lambda grouping: Individual(grouping, calculate_fitness(grouping)), population)) + + gen = 0 + while gen < 200: + # spawn next generation + # print("Start of gen {}".format(gen)) + gen += 1 + population = spawn(population, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations) + population = list(map(lambda grouping: Individual(grouping, calculate_fitness(grouping)), population)) + + dupl = False + for ind in population: + seen = set() + for group in ind.grouping: + for student in group: + if student in seen: + print("MAIN SCAN DUPLICATE") + print(ind) + dupl = True + seen.add(student) + if dupl: + exit() + + avg = 0 + for ind in population: + avg += ind.fitness.value + avg /= population_size + print("AVG Fitness of gen {} is {}".format(gen, avg)) + + print("Best grouping: " + str(best_grouping)) + print_grouping(best_grouping) + print("Best fitness: " + str(best_fitness)) + + +def crossover(individual_one, individual_two): + + grouping_one = individual_one.grouping[:] + grouping_two = individual_two.grouping[:] + + # print("crossing {} with {}".format(grouping_one, grouping_two)) + + group_count = len(grouping_one) + offspring = list() + + # add groups that appear in both groupings + for one in grouping_one: + for two in grouping_two: + if set(one) == set(two): + dupl = False + for group in offspring: + for student in one: + if student in group: + dupl = True + break + if dupl: + break + if not dupl: + offspring.append(one) + + # print("added equals: {}".format(offspring)) + + # step through groupings one and two, adding a whole group when possible to offspring. alternate between groupings after every successful addition + on_one = True + index_one = 0 + index_two = 0 + while index_one < len(grouping_one) or index_two < len(grouping_two): + if on_one and index_one < len(grouping_one): + dupl = False + for student in grouping_one[index_one]: + for group in offspring: + if student in group: + dupl = True + break + if dupl: + break + if not dupl: + offspring.append(grouping_one[index_one]) + # print("appending {} from one".format(grouping_one[index_one])) + del grouping_one[index_one] + on_one = False + else: + index_one += 1 + elif on_one: + on_one = False + elif not on_one and index_two < len(grouping_two): + dupl = False + for student in grouping_two[index_two]: + for group in offspring: + if student in group: + dupl = True + break + if dupl: + break + if not dupl: + offspring.append(grouping_two[index_two]) + # print("appending {} from two".format(grouping_two[index_two])) + del grouping_two[index_two] + on_one = True + else: + index_two += 1 + elif not on_one: + on_one = True + + # print("Finished appending all possible, list: {}".format(offspring)) + + num_groups_so_far = len(offspring) + num_groups_left = group_count - num_groups_so_far + + # remove students alread grouped again + students_to_group = workbook.STUDENTS[:] + for group in offspring: + for student in group: + students_to_group.remove(student) + + # print("Remaining: {}".format(students_to_group)) + + # initialize groups for remainder groups + remaining = list() + + for _ in range(num_groups_left): + remaining.append(list()) + + for index, student in enumerate(students_to_group): + remaining[index % num_groups_left].append(student) + + for group in remaining: + offspring.append(group) + + if len(offspring) != group_count: + print("CROSSED OVER GROUPING NOT SAME SIZE") + + return offspring + + +def mutate(mutations, grouping: List[List[Student]]): + """Mutate a grouping with a randomly chosen mutation.""" + return random.choice(mutations)(grouping) + + +def spawn(prev_population: List[Individual], mutation_rate: float, elitism_rate: float, create_rate: float, crossover_rate: float, mutations): + count = len(prev_population) + + next_population = list() + + elite_count = math.floor(count * elitism_rate) + create_count = math.floor(count * create_rate) + crossover_count = count - elite_count - create_count + + for _ in range(elite_count): + toap = select(prev_population).grouping + # print("appending {}".format(toap)) + seen = set() + dupl = False + for group in toap: + for student in group: + if student in seen: + print("DUPLICATE") + print(toap) + dupl = True + seen.add(student) + if dupl: + print("GOT DUPLICATE FROM ELITE") + exit() + next_population.append(toap) + + for _ in range(create_count): + toap = create() + # print("appending {}".format(toap)) + seen = set() + dupl = False + for group in toap: + for student in group: + if student in seen: + print("DUPLICATE") + print(toap) + dupl = True + seen.add(student) + if dupl: + print("GOT DUPLICATE FROM CREATE") + exit() + next_population.append(toap) + + for _ in range(crossover_count): + parent_one = select(prev_population) + parent_two = select(prev_population) + toap = crossover(parent_one, parent_two) + # print("appending {}".format(toap)) + seen = set() + dupl = False + for group in toap: + for student in group: + if student in seen: + print("DUPLICATE") + print(toap) + dupl = True + seen.add(student) + if dupl: + print("GOT DUPLICATE FROM CROSSOVER") + exit() + next_population.append(toap) + + for i, ind in enumerate(next_population): + r = random.random() + if r < mutation_rate: + next_population[i] = mutate(mutations, ind) + seen = set() + dupl = False + for group in next_population[i]: + for student in group: + if student in seen: + print("DUPLICATE") + print(next_population[i]) + dupl = True + seen.add(student) + if dupl: + print("GOT DUPLICATE FROM MUTATE") + exit() + return next_population + + +def select(population: List[Individual]): + """Select random individuals from population and find most fit tournament-style.""" + SELECT_NUMBER = 8 #math.floor(len(population) / 3) + selected = random.sample(population, SELECT_NUMBER) + while len(selected) > 1: + individual_one = selected.pop(0) + individual_two = selected.pop(0) + if (individual_one.fitness > individual_two.fitness): + selected.append(individual_one) + else: + selected.append(individual_two) + return selected[0] + + +def calculate_fitness(grouping: List[List[Student]]): + + global best_grouping + global best_fitness + + # STUDENT PREFERENCES + preferences_count = 0 + for group in grouping: + for student in group: + preferences_count += len(student.preferences) + + preferences_respected = 0 + for group in grouping: + for student in group: + for other in group: + if other.email in student.preferences: + preferences_respected += 1 + + preferences_value = preferences_respected / preferences_count # 0 to 1 + + # SKILL BALANCE, measured by the coefficient of variation of skills across group + # Reference: http://www.statisticshowto.com/probability-and-statistics/how-to-find-a-coefficient-of-variation/ + # e.g. group = [[1, 2, 3, 4, 5], + # [1, 2, 3, 4, 5]] + # group_skills = [1, 2, 3, 4, 5] + # group_skill_avg = 3 + # group_skill_std = 1.41 + # group_skills_coef = 1.41 / 3 = 0.47 + + if len(grouping) < 1: + print("GROUPING IS TOO SMALL") + print(grouping) + for group in grouping: + if len(group) < 1: + print("GROUP IS TOO SMALL") + print(grouping) + + + skills_by_group = [] + for _ in range(len(grouping)): + skills_by_group += [[0] * len(grouping[0][0].skills)] # assumes there is at least one student in one group + + for group_index, group in enumerate(grouping): + skills_within_group = [0] * len(group[0].skills) + for student in group: + for skill_index, skill in enumerate(student.skills): + skills_within_group[skill_index] += skill + for skill_total in skills_within_group: + skill_total = skill_total / len(group) # get average + skills_by_group[group_index] = skills_within_group + + skills_coef_by_group = list() + for skills in skills_by_group: + # print("skills: " + str(skills)) + # print("mean of skills: " + str(np.mean(skills))) + # print("stdev of skills: " + str(np.std(skills))) + skills_coef_by_group.append(np.std(skills) / np.mean(skills)) + + balance_value = 1 - np.mean(skills_coef_by_group) + + # SKILL FAIRNESS, measured by the coefficient of variation of skills across grouping + # e.g. group_1_skills = [1, 2, 3, 4, 5] + # group_2_skills = [5, 4, 3, 2, 1] + # grouping_skills_avg = [3, 3, 3, 3, 3] + # grouping_skills_std = [2, 1, 0, 1, 2] + # grouping_coef = [(2/3), (1/3), (0/3), (1/3), (2/3)] + # grouping_coef_avg = 0.396 + + # transpose the list of lists + skills_by_group_transposed = list(map(list, zip(*skills_by_group))) + skills_coef_by_grouping = list() + for skills in skills_by_group_transposed: + skills_coef_by_grouping.append(np.std(skills) / np.mean(skills)) + + fairness_value = 1 - np.mean(skills_coef_by_grouping) + + current_fitness = Fitness(preferences_value, balance_value, fairness_value) + if current_fitness > best_fitness: + best_fitness = current_fitness + best_grouping = grouping + + return current_fitness + + +def print_grouping(grouping): + for index, group in enumerate(grouping): + print("Group " + str(index) + "\n") + for student in group: + print(student) From 3512c2903496db7ba638f761529b74e3ac8d00d0 Mon Sep 17 00:00:00 2001 From: Lancasterwu Date: Fri, 8 Mar 2019 15:59:09 -0500 Subject: [PATCH 02/39] moved the location for generic_algorithm --- genetic_algorithm.py => gatorgrouper/utils/genetic_algorithm.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename genetic_algorithm.py => gatorgrouper/utils/genetic_algorithm.py (100%) diff --git a/genetic_algorithm.py b/gatorgrouper/utils/genetic_algorithm.py similarity index 100% rename from genetic_algorithm.py rename to gatorgrouper/utils/genetic_algorithm.py From 8a583c7822460b7f034d2a150c4e11b7e18d431a Mon Sep 17 00:00:00 2001 From: Lancasterwu Date: Fri, 8 Mar 2019 16:01:22 -0500 Subject: [PATCH 03/39] update the package settings --- Pipfile | 1 + Pipfile.lock | 43 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/Pipfile b/Pipfile index eb4850ea..0030b7a6 100644 --- a/Pipfile +++ b/Pipfile @@ -26,6 +26,7 @@ py = "*" django = "*" networkx = "*" social-auth-app-django = "*" +numpy = "*" [pipenv] allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock index a13e057c..d1f457d7 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d4f696cbbdc67c85fea23809779f21382d424dc4ce54d23bf2bc21a6c1830133" + "sha256": "30bf3950c4c83dcd9260a9e02d375a7dc3f09fcf06aca10ccb44cf113a819abf" }, "pipfile-spec": 6, "requires": {}, @@ -65,6 +65,35 @@ "index": "pypi", "version": "==2.2" }, + "numpy": { + "hashes": [ + "sha256:1980f8d84548d74921685f68096911585fee393975f53797614b34d4f409b6da", + "sha256:22752cd809272671b273bb86df0f505f505a12368a3a5fc0aa811c7ece4dfd5c", + "sha256:23cc40313036cffd5d1873ef3ce2e949bdee0646c5d6f375bf7ee4f368db2511", + "sha256:2b0b118ff547fecabc247a2668f48f48b3b1f7d63676ebc5be7352a5fd9e85a5", + "sha256:3a0bd1edf64f6a911427b608a894111f9fcdb25284f724016f34a84c9a3a6ea9", + "sha256:3f25f6c7b0d000017e5ac55977a3999b0b1a74491eacb3c1aa716f0e01f6dcd1", + "sha256:4061c79ac2230594a7419151028e808239450e676c39e58302ad296232e3c2e8", + "sha256:560ceaa24f971ab37dede7ba030fc5d8fa173305d94365f814d9523ffd5d5916", + "sha256:62be044cd58da2a947b7e7b2252a10b42920df9520fc3d39f5c4c70d5460b8ba", + "sha256:6c692e3879dde0b67a9dc78f9bfb6f61c666b4562fd8619632d7043fb5b691b0", + "sha256:6f65e37b5a331df950ef6ff03bd4136b3c0bbcf44d4b8e99135d68a537711b5a", + "sha256:7a78cc4ddb253a55971115f8320a7ce28fd23a065fc33166d601f51760eecfa9", + "sha256:80a41edf64a3626e729a62df7dd278474fc1726836552b67a8c6396fd7e86760", + "sha256:893f4d75255f25a7b8516feb5766c6b63c54780323b9bd4bc51cdd7efc943c73", + "sha256:972ea92f9c1b54cc1c1a3d8508e326c0114aaf0f34996772a30f3f52b73b942f", + "sha256:9f1d4865436f794accdabadc57a8395bd3faa755449b4f65b88b7df65ae05f89", + "sha256:9f4cd7832b35e736b739be03b55875706c8c3e5fe334a06210f1a61e5c2c8ca5", + "sha256:adab43bf657488300d3aeeb8030d7f024fcc86e3a9b8848741ea2ea903e56610", + "sha256:bd2834d496ba9b1bdda3a6cf3de4dc0d4a0e7be306335940402ec95132ad063d", + "sha256:d20c0360940f30003a23c0adae2fe50a0a04f3e48dc05c298493b51fd6280197", + "sha256:d3b3ed87061d2314ff3659bb73896e622252da52558f2380f12c421fbdee3d89", + "sha256:dc235bf29a406dfda5790d01b998a1c01d7d37f449128c0b1b7d1c89a84fae8b", + "sha256:fb3c83554f39f48f3fa3123b9c24aecf681b1c289f9334f8215c1d3c8e2f6e5b" + ], + "index": "pypi", + "version": "==1.16.2" + }, "oauthlib": { "hashes": [ "sha256:0ce32c5d989a1827e3f1148f98b9085ed2370fc939bf524c9c851d8714797298", @@ -172,10 +201,10 @@ }, "astroid": { "hashes": [ - "sha256:39183aecc01d6f74eb54edc6739992e842f3bf3068bdfaaba30b5a1742f44091", - "sha256:62bd1d8d2ace3e812b3a22eb3c4b7856536d8cc0cdb37466f6a53cf881dbad1f" + "sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4", + "sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4" ], - "version": "==2.2.4" + "version": "==2.2.5" }, "atomicwrites": { "hashes": [ @@ -347,10 +376,10 @@ }, "isort": { "hashes": [ - "sha256:89041186651a9a6159683098f337eed0994d9d94e006f891c6e8cbeb8e65f1c7", - "sha256:ba51a651505242b0b37ad94b281e1154301e221a40c623e62334ed863fc1c98c" + "sha256:38a74a5ccf3a15a7a99f975071164f48d4d10eed4154879009c18e6e8933e5aa", + "sha256:abbb2684aa234d5eb8a67ef36d4aa62ea080d46c2eba36ad09e2990ae52e4305" ], - "version": "==4.3.12" + "version": "==4.3.13" }, "lazy-object-proxy": { "hashes": [ From 41615bdcd940b05e89c1ec49bd41a53207255c4d Mon Sep 17 00:00:00 2001 From: Lancasterwu Date: Fri, 8 Mar 2019 16:10:44 -0500 Subject: [PATCH 04/39] added reference and license --- LICENSE | 26 +++++++++++++++++++++++++ gatorgrouper/utils/genetic_algorithm.py | 4 +++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index f288702d..1ea7743b 100644 --- a/LICENSE +++ b/LICENSE @@ -672,3 +672,29 @@ may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . + +Notice that `group_genetic.py` is from +https://github.com/yeeunmariakim/gatorgrouper/blob/master/genetic_algorithm.py +under MIT license: + +MIT License + +Copyright (c) 2019 Maria Kim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/gatorgrouper/utils/genetic_algorithm.py b/gatorgrouper/utils/genetic_algorithm.py index 21440a31..4b343cd2 100644 --- a/gatorgrouper/utils/genetic_algorithm.py +++ b/gatorgrouper/utils/genetic_algorithm.py @@ -1,4 +1,6 @@ -"""Generate grouping based on student skills and preferences.""" +"""Generate grouping based on student skills and preferences. +This code is under MIT license from +https://github.com/yeeunmariakim/gatorgrouper/blob/master/genetic_algorithm.py""" from typing import List import random from colors import bold From 1da25277a5e7a0a858922d380764bba2f85c6b37 Mon Sep 17 00:00:00 2001 From: Lancasterwu Date: Fri, 8 Mar 2019 16:12:07 -0500 Subject: [PATCH 05/39] used black and chanegd the file name --- gatorgrouper/utils/{genetic_algorithm.py => group_genetic.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename gatorgrouper/utils/{genetic_algorithm.py => group_genetic.py} (100%) diff --git a/gatorgrouper/utils/genetic_algorithm.py b/gatorgrouper/utils/group_genetic.py similarity index 100% rename from gatorgrouper/utils/genetic_algorithm.py rename to gatorgrouper/utils/group_genetic.py From f8cbae192bf5e5bd1d007750262d1f68ca67c2ad Mon Sep 17 00:00:00 2001 From: Lancasterwu Date: Fri, 8 Mar 2019 23:49:54 -0500 Subject: [PATCH 06/39] reformated group_genetic --- gatorgrouper/utils/group_genetic.py | 51 ++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index 4b343cd2..174da469 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -11,6 +11,7 @@ class Student: """Represent student.""" + def __init__(self, email: str, skills: List[int], preferences: List[str]): self.email = email self.skills = skills @@ -36,6 +37,7 @@ def __hash__(self): class Individual: """Represent individual.""" + def __init__(self, grouping: List[List[Student]], fitness): self.grouping = grouping self.fitness = fitness @@ -51,6 +53,7 @@ def __str__(self): class Fitness: """Represent fitness. Variables range from 0 to 1.""" + def __init__(self, preference, balance, fairness): self.preference = preference self.balance = balance @@ -91,23 +94,45 @@ def create(): # print("CREATED GROUPNG: " + str(grouping)) return grouping + """population_size: int, mutation_rate: float, crossover_rate: float, fitness, mutations, create""" -def evolve(population_size, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations): + + +def evolve( + population_size, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations +): global best_grouping global best_fitness print("in evolve") population = [create() for _ in range(population_size)] - population = list(map(lambda grouping: Individual(grouping, calculate_fitness(grouping)), population)) + population = list( + map( + lambda grouping: Individual(grouping, calculate_fitness(grouping)), + population, + ) + ) gen = 0 while gen < 200: # spawn next generation # print("Start of gen {}".format(gen)) gen += 1 - population = spawn(population, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations) - population = list(map(lambda grouping: Individual(grouping, calculate_fitness(grouping)), population)) + population = spawn( + population, + mutation_rate, + elitism_rate, + create_rate, + crossover_rate, + mutations, + ) + population = list( + map( + lambda grouping: Individual(grouping, calculate_fitness(grouping)), + population, + ) + ) dupl = False for ind in population: @@ -238,7 +263,14 @@ def mutate(mutations, grouping: List[List[Student]]): return random.choice(mutations)(grouping) -def spawn(prev_population: List[Individual], mutation_rate: float, elitism_rate: float, create_rate: float, crossover_rate: float, mutations): +def spawn( + prev_population: List[Individual], + mutation_rate: float, + elitism_rate: float, + create_rate: float, + crossover_rate: float, + mutations, +): count = len(prev_population) next_population = list() @@ -321,12 +353,12 @@ def spawn(prev_population: List[Individual], mutation_rate: float, elitism_rate: def select(population: List[Individual]): """Select random individuals from population and find most fit tournament-style.""" - SELECT_NUMBER = 8 #math.floor(len(population) / 3) + SELECT_NUMBER = 8 # math.floor(len(population) / 3) selected = random.sample(population, SELECT_NUMBER) while len(selected) > 1: individual_one = selected.pop(0) individual_two = selected.pop(0) - if (individual_one.fitness > individual_two.fitness): + if individual_one.fitness > individual_two.fitness: selected.append(individual_one) else: selected.append(individual_two) @@ -370,10 +402,11 @@ def calculate_fitness(grouping: List[List[Student]]): print("GROUP IS TOO SMALL") print(grouping) - skills_by_group = [] for _ in range(len(grouping)): - skills_by_group += [[0] * len(grouping[0][0].skills)] # assumes there is at least one student in one group + skills_by_group += [ + [0] * len(grouping[0][0].skills) + ] # assumes there is at least one student in one group for group_index, group in enumerate(grouping): skills_within_group = [0] * len(group[0].skills) From 7c6e59cc560ff0bbd56969b3c434ea1ed842fc8b Mon Sep 17 00:00:00 2001 From: Spencer Huang Date: Mon, 11 Mar 2019 11:42:49 -0400 Subject: [PATCH 07/39] Fix import statements --- gatorgrouper/utils/group_genetic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index 174da469..27a007df 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -3,10 +3,10 @@ https://github.com/yeeunmariakim/gatorgrouper/blob/master/genetic_algorithm.py""" from typing import List import random -from colors import bold +import math import numpy as np +from colors import bold import workbook -import math class Student: From 812aa03ab098541a4d4adf5d145b8d73275a8bb9 Mon Sep 17 00:00:00 2001 From: Spencer Huang Date: Mon, 11 Mar 2019 11:43:26 -0400 Subject: [PATCH 08/39] Edit the "FIXME" statement to pass pylint --- gatorgrouper/utils/group_genetic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index 27a007df..3fc25ca4 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -58,7 +58,7 @@ def __init__(self, preference, balance, fairness): self.preference = preference self.balance = balance self.fairness = fairness - # FIXME: Can give weights to each variable + # Need to do: Can give weights to each variable self.value = 0.5 * preference + 3.0 * balance + 1.5 * fairness def __gt__(self, other): From 6d23f3aa9b22720b236a3a05a4e4394d996c90e1 Mon Sep 17 00:00:00 2001 From: Spencer Huang Date: Mon, 11 Mar 2019 11:44:19 -0400 Subject: [PATCH 09/39] Add docstrings --- gatorgrouper/utils/group_genetic.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index 3fc25ca4..52df24e5 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -77,6 +77,7 @@ def __str__(self): def create(): + """Create the groups of student""" students_to_group = workbook.STUDENTS[:] random.shuffle(students_to_group) @@ -102,6 +103,9 @@ def evolve( population_size, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations ): + """population_size: int, mutation_rate: float, crossover_rate: float, fitness, + mutations, create""" + # pylint: disable=global-statement global best_grouping global best_fitness @@ -159,6 +163,7 @@ def evolve( def crossover(individual_one, individual_two): + """Add smaller groupings of students to the larger groups""" grouping_one = individual_one.grouping[:] grouping_two = individual_two.grouping[:] @@ -366,7 +371,7 @@ def select(population: List[Individual]): def calculate_fitness(grouping: List[List[Student]]): - + """Calculate compatibility between students""" global best_grouping global best_fitness @@ -451,6 +456,7 @@ def calculate_fitness(grouping: List[List[Student]]): def print_grouping(grouping): + """Print out the groups""" for index, group in enumerate(grouping): print("Group " + str(index) + "\n") for student in group: From a044a358be7e29ea5c87f7f3d68634e9e8cdd582 Mon Sep 17 00:00:00 2001 From: Spencer Huang Date: Mon, 11 Mar 2019 11:44:47 -0400 Subject: [PATCH 10/39] Fix len pylint error --- gatorgrouper/utils/group_genetic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index 52df24e5..9fdb4e2d 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -89,7 +89,7 @@ def create(): for index, student in enumerate(students_to_group): grouping[index % workbook.GROUPING_SIZE].append(student) - if len(grouping) < 1: + if grouping < 1: print("CREATED TOO SMALL GROUPING") # print("CREATED GROUPNG: " + str(grouping)) @@ -399,11 +399,11 @@ def calculate_fitness(grouping: List[List[Student]]): # group_skill_std = 1.41 # group_skills_coef = 1.41 / 3 = 0.47 - if len(grouping) < 1: + if grouping < 1: print("GROUPING IS TOO SMALL") print(grouping) for group in grouping: - if len(group) < 1: + if group < 1: print("GROUP IS TOO SMALL") print(grouping) From 03b97b46f13e6403e14a88a60c16df8ce9e07f12 Mon Sep 17 00:00:00 2001 From: Spencer Huang Date: Mon, 11 Mar 2019 11:45:37 -0400 Subject: [PATCH 11/39] Fix some indentation errors --- gatorgrouper/utils/group_genetic.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index 9fdb4e2d..2efa479c 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -96,13 +96,9 @@ def create(): return grouping -"""population_size: int, mutation_rate: float, crossover_rate: float, fitness, mutations, create""" - - def evolve( - population_size, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations + population_size, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations ): - """population_size: int, mutation_rate: float, crossover_rate: float, fitness, mutations, create""" # pylint: disable=global-statement @@ -269,12 +265,12 @@ def mutate(mutations, grouping: List[List[Student]]): def spawn( - prev_population: List[Individual], - mutation_rate: float, - elitism_rate: float, - create_rate: float, - crossover_rate: float, - mutations, + prev_population: List[Individual], + mutation_rate: float, + elitism_rate: float, + create_rate: float, + crossover_rate: float, + mutations, ): count = len(prev_population) @@ -372,6 +368,7 @@ def select(population: List[Individual]): def calculate_fitness(grouping: List[List[Student]]): """Calculate compatibility between students""" + # pylint: disable=global-statement global best_grouping global best_fitness From 5ea108eddd81495078ace0313c6931249112564f Mon Sep 17 00:00:00 2001 From: Spencer Huang Date: Mon, 11 Mar 2019 11:46:04 -0400 Subject: [PATCH 12/39] Fix line lengths --- gatorgrouper/utils/group_genetic.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index 2efa479c..09def39a 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -186,7 +186,8 @@ def crossover(individual_one, individual_two): # print("added equals: {}".format(offspring)) - # step through groupings one and two, adding a whole group when possible to offspring. alternate between groupings after every successful addition + # step through groupings one and two, adding a whole group when possible to offspring. + # Alternate between groupings after every successful addition on_one = True index_one = 0 index_two = 0 @@ -272,6 +273,7 @@ def spawn( crossover_rate: float, mutations, ): + """Spawn new population""" count = len(prev_population) next_population = list() @@ -388,7 +390,7 @@ def calculate_fitness(grouping: List[List[Student]]): preferences_value = preferences_respected / preferences_count # 0 to 1 # SKILL BALANCE, measured by the coefficient of variation of skills across group - # Reference: http://www.statisticshowto.com/probability-and-statistics/how-to-find-a-coefficient-of-variation/ + # Reference: https://bit.ly/2Hh1LXI # e.g. group = [[1, 2, 3, 4, 5], # [1, 2, 3, 4, 5]] # group_skills = [1, 2, 3, 4, 5] From ea84095a383d364a29a6b70ff5baf00a3943ff7a Mon Sep 17 00:00:00 2001 From: enpuyou Date: Mon, 11 Mar 2019 14:09:47 -0400 Subject: [PATCH 13/39] ran black to reformat --- gatorgrouper/utils/group_genetic.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index 09def39a..e1111866 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -97,7 +97,7 @@ def create(): def evolve( - population_size, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations + population_size, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations ): """population_size: int, mutation_rate: float, crossover_rate: float, fitness, mutations, create""" @@ -266,12 +266,12 @@ def mutate(mutations, grouping: List[List[Student]]): def spawn( - prev_population: List[Individual], - mutation_rate: float, - elitism_rate: float, - create_rate: float, - crossover_rate: float, - mutations, + prev_population: List[Individual], + mutation_rate: float, + elitism_rate: float, + create_rate: float, + crossover_rate: float, + mutations, ): """Spawn new population""" count = len(prev_population) From 9bc3f9a846309c0572fb395eb9edace02d46ac1d Mon Sep 17 00:00:00 2001 From: enpuyou Date: Mon, 11 Mar 2019 14:11:29 -0400 Subject: [PATCH 14/39] fixed indentation --- gatorgrouper/utils/group_genetic.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index e1111866..d2129592 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -266,12 +266,12 @@ def mutate(mutations, grouping: List[List[Student]]): def spawn( - prev_population: List[Individual], - mutation_rate: float, - elitism_rate: float, - create_rate: float, - crossover_rate: float, - mutations, + prev_population: List[Individual], + mutation_rate: float, + elitism_rate: float, + create_rate: float, + crossover_rate: float, + mutations, ): """Spawn new population""" count = len(prev_population) From e77ad52d988b849c667fcb5615827455c5a53605 Mon Sep 17 00:00:00 2001 From: enpuyou Date: Mon, 11 Mar 2019 14:14:38 -0400 Subject: [PATCH 15/39] disable too many local variables --- gatorgrouper/utils/group_genetic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index d2129592..38c68dc3 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -369,6 +369,7 @@ def select(population: List[Individual]): def calculate_fitness(grouping: List[List[Student]]): + # pylint: disable=R0914 """Calculate compatibility between students""" # pylint: disable=global-statement global best_grouping From b38b1ba2a3933bfd8d809f6adf01a8175b53bfa1 Mon Sep 17 00:00:00 2001 From: enpuyou Date: Mon, 11 Mar 2019 14:15:23 -0400 Subject: [PATCH 16/39] Changed indentation --- gatorgrouper/utils/group_genetic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index 38c68dc3..0e816389 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -97,7 +97,7 @@ def create(): def evolve( - population_size, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations + population_size, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations ): """population_size: int, mutation_rate: float, crossover_rate: float, fitness, mutations, create""" From 3adfe8090e9169411d5a601e686e90a94c8060d9 Mon Sep 17 00:00:00 2001 From: enpuyou Date: Mon, 11 Mar 2019 14:19:01 -0400 Subject: [PATCH 17/39] ran black --- gatorgrouper/utils/group_genetic.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index 0e816389..e314cfce 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -97,7 +97,7 @@ def create(): def evolve( - population_size, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations + population_size, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations ): """population_size: int, mutation_rate: float, crossover_rate: float, fitness, mutations, create""" @@ -266,12 +266,12 @@ def mutate(mutations, grouping: List[List[Student]]): def spawn( - prev_population: List[Individual], - mutation_rate: float, - elitism_rate: float, - create_rate: float, - crossover_rate: float, - mutations, + prev_population: List[Individual], + mutation_rate: float, + elitism_rate: float, + create_rate: float, + crossover_rate: float, + mutations, ): """Spawn new population""" count = len(prev_population) From 164c650b3ab9e91405bf1dd5ca34cbf976c8bbb4 Mon Sep 17 00:00:00 2001 From: enpuyou Date: Mon, 11 Mar 2019 14:20:50 -0400 Subject: [PATCH 18/39] Fixed pylint error --- gatorgrouper/utils/group_genetic.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index e314cfce..eb9b792c 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -97,6 +97,8 @@ def create(): def evolve( + # pylint: disable = C0330 + # Black would reformat the code in the way that does not pass pylint population_size, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations ): """population_size: int, mutation_rate: float, crossover_rate: float, fitness, @@ -266,6 +268,8 @@ def mutate(mutations, grouping: List[List[Student]]): def spawn( + # pylint: disable = C0330 + # Black would reformat the code in the way that does not pass pylint prev_population: List[Individual], mutation_rate: float, elitism_rate: float, From 7b3ff3687c80b0f1f989cde782563ca2a1c677b4 Mon Sep 17 00:00:00 2001 From: enpuyou Date: Mon, 11 Mar 2019 14:27:26 -0400 Subject: [PATCH 19/39] Added ansicolors for the import --- Pipfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Pipfile b/Pipfile index 0030b7a6..3fdc185c 100644 --- a/Pipfile +++ b/Pipfile @@ -27,6 +27,7 @@ django = "*" networkx = "*" social-auth-app-django = "*" numpy = "*" +ansicolors = "*" [pipenv] allow_prereleases = true From ab34d4bb93286f8b7df60c561b84a10e56c98551 Mon Sep 17 00:00:00 2001 From: enpuyou Date: Mon, 11 Mar 2019 14:35:38 -0400 Subject: [PATCH 20/39] updated pipfile.lock --- Pipfile.lock | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index d1f457d7..fd1abc63 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "30bf3950c4c83dcd9260a9e02d375a7dc3f09fcf06aca10ccb44cf113a819abf" + "sha256": "8dce5a8ef967fb9d1e62abdb43d0b03a54adae7cac208c2f7b44317a3b22252c" }, "pipfile-spec": 6, "requires": {}, @@ -14,12 +14,20 @@ ] }, "default": { + "ansicolors": { + "hashes": [ + "sha256:00d2dde5a675579325902536738dd27e4fac1fd68f773fe36c21044eb559e187", + "sha256:99f94f5e3348a0bcd43c82e5fc4414013ccc19d70bd939ad71e0133ce9c372e0" + ], + "index": "pypi", + "version": "==1.1.8" + }, "certifi": { "hashes": [ - "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", - "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" + "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", + "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" ], - "version": "==2018.11.29" + "version": "==2019.3.9" }, "chardet": { "hashes": [ @@ -237,10 +245,10 @@ }, "certifi": { "hashes": [ - "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", - "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" + "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", + "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" ], - "version": "==2018.11.29" + "version": "==2019.3.9" }, "chardet": { "hashes": [ @@ -360,12 +368,12 @@ }, "hypothesis": { "hashes": [ - "sha256:24981f561fdce632699c14cba907b3f78624d40dab61d612000b6299c61d35d4", - "sha256:2f2f8d798fdddf676c0ff266265c580a65e42b14a57c1f9852ac768ec6ae0af1", - "sha256:a24bca99f9e8dc39527cadbdda7bca299906da0c8717e6b69773fa22b847ce4c" + "sha256:5d73252c5dddba45875162f15e473024d154d61af11dafacc3507edefb63c9dc", + "sha256:6e679d1dba80b623517948f3d83425e3c96b393e0e7909e4b597cc4f143a8465", + "sha256:b6f3d36307ab0ae9739fb80520eb303d8fa7e2b5963cb1979d463f065ef1a87b" ], "index": "pypi", - "version": "==4.8.0" + "version": "==4.10.0" }, "idna": { "hashes": [ @@ -376,10 +384,10 @@ }, "isort": { "hashes": [ - "sha256:38a74a5ccf3a15a7a99f975071164f48d4d10eed4154879009c18e6e8933e5aa", - "sha256:abbb2684aa234d5eb8a67ef36d4aa62ea080d46c2eba36ad09e2990ae52e4305" + "sha256:18c796c2cd35eb1a1d3f012a214a542790a1aed95e29768bdcb9f2197eccbd0b", + "sha256:96151fca2c6e736503981896495d344781b60d18bfda78dc11b290c6125ebdb6" ], - "version": "==4.3.13" + "version": "==4.3.15" }, "lazy-object-proxy": { "hashes": [ From fbba629331a510a4317eb40e4cb64fe68dd4619c Mon Sep 17 00:00:00 2001 From: enpuyou Date: Mon, 11 Mar 2019 14:40:43 -0400 Subject: [PATCH 21/39] Added in workbook file from Maria's branch. the license is updated as well --- LICENSE | 4 +- gatorgrouper/utils/workbook.py | 93 ++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 gatorgrouper/utils/workbook.py diff --git a/LICENSE b/LICENSE index 1ea7743b..886f2062 100644 --- a/LICENSE +++ b/LICENSE @@ -673,8 +673,8 @@ the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . -Notice that `group_genetic.py` is from -https://github.com/yeeunmariakim/gatorgrouper/blob/master/genetic_algorithm.py +Notice that `group_genetic.py` and `workbook.py` is from +https://github.com/yeeunmariakim/gatorgrouper/blob/master under MIT license: MIT License diff --git a/gatorgrouper/utils/workbook.py b/gatorgrouper/utils/workbook.py new file mode 100644 index 00000000..b366ffe1 --- /dev/null +++ b/gatorgrouper/utils/workbook.py @@ -0,0 +1,93 @@ +"""Integrate GatorGrouper with Google Sheets. +This code is under MIT license from +https://github.com/yeeunmariakim/gatorgrouper/blob/master/workbook.py""" + +import csv +import math +import logging +import gspread +import pandas as pd +from oauth2client.service_account import ServiceAccountCredentials + +from genetic_algorithm import Student +import config + + +EMAIL_COL = None +PREFERENCES_COL = None +SKILLS_COLS = set() + +STUDENTS = None +GROUPING_SIZE = None + +def get(group_size): + """Retrieve data from Google Sheets and write to a CSV file.""" + + global EMAIL_COL + global PREFERENCES_COL + global SKILLS_COLS + + logging.info( + "Authenticating to Google Sheets...") + + scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive'] + creds = ServiceAccountCredentials.from_json_keyfile_name( + 'client_secret.json', scope) + client = gspread.authorize(creds) + + logging.info("Opening spreadsheet...") + sheet = client.open(config.WORKBOOK).sheet1 + + logging.info("Extracting data from spreadsheet...") + records = sheet.get_all_records() + + formatted_records = list() + for entry in records: + formatted_entry = list() + for index, (question, response) in enumerate(entry.items()): + if question == 'Email Address': + EMAIL_COL = index - 1 # subtracting one because timestamp column not collected + formatted_entry.append(response) + elif "prefer" in question: + PREFERENCES_COL = index - 1 + formatted_entry.append(response) + elif "skill" in question: + SKILLS_COLS.add(index - 1) + formatted_entry.append(response) + formatted_records.append(formatted_entry) + + logging.debug("Writing formatted records to " + config.WORKBOOK_CSV + "...") + with open(config.WORKBOOK_CSV, 'w') as output: + writer = csv.writer(output, quoting=csv.QUOTE_ALL) + for item in formatted_records: + writer.writerow(item) + + global STUDENTS + global GROUPING_SIZE + + # EMAIL_COL = 0 + # PREFERENCES_COL = 1 + # SKILLS_COLS = [2, 3, 4, 5, 6] + + DATA = pd.read_csv(config.WORKBOOK_CSV, header=None) + + EMAILS = DATA.iloc[:, EMAIL_COL] + + STUDENTS = list() + for current_row, email in enumerate(EMAILS): + skills = list() + for skill_col in SKILLS_COLS: + skills.append(DATA.iat[current_row, skill_col]) + preferences_str = DATA.iat[current_row, PREFERENCES_COL] + + if isinstance(preferences_str, float) and math.isnan(preferences_str): + preferences = [] + else: + preferences = preferences_str.replace(" ", "").split(",") + + STUDENTS.append(Student(email, skills, preferences)) + + # for student in STUDENTS: + # print(str(student) + "\n") + + GROUPING_SIZE = math.floor(len(STUDENTS) / group_size) From 69fbd83357f0b12a39e6441bc4acd397af9cf711 Mon Sep 17 00:00:00 2001 From: enpuyou Date: Mon, 11 Mar 2019 14:41:17 -0400 Subject: [PATCH 22/39] Ran black to reformat --- gatorgrouper/utils/group_genetic.py | 7 ++++++- gatorgrouper/utils/workbook.py | 20 +++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index eb9b792c..96461212 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -99,7 +99,12 @@ def create(): def evolve( # pylint: disable = C0330 # Black would reformat the code in the way that does not pass pylint - population_size, mutation_rate, elitism_rate, create_rate, crossover_rate, mutations + population_size, + mutation_rate, + elitism_rate, + create_rate, + crossover_rate, + mutations, ): """population_size: int, mutation_rate: float, crossover_rate: float, fitness, mutations, create""" diff --git a/gatorgrouper/utils/workbook.py b/gatorgrouper/utils/workbook.py index b366ffe1..dc431b43 100644 --- a/gatorgrouper/utils/workbook.py +++ b/gatorgrouper/utils/workbook.py @@ -20,6 +20,7 @@ STUDENTS = None GROUPING_SIZE = None + def get(group_size): """Retrieve data from Google Sheets and write to a CSV file.""" @@ -27,12 +28,15 @@ def get(group_size): global PREFERENCES_COL global SKILLS_COLS - logging.info( - "Authenticating to Google Sheets...") + logging.info("Authenticating to Google Sheets...") - scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive'] + scope = [ + "https://spreadsheets.google.com/feeds", + "https://www.googleapis.com/auth/drive", + ] creds = ServiceAccountCredentials.from_json_keyfile_name( - 'client_secret.json', scope) + "client_secret.json", scope + ) client = gspread.authorize(creds) logging.info("Opening spreadsheet...") @@ -45,8 +49,10 @@ def get(group_size): for entry in records: formatted_entry = list() for index, (question, response) in enumerate(entry.items()): - if question == 'Email Address': - EMAIL_COL = index - 1 # subtracting one because timestamp column not collected + if question == "Email Address": + EMAIL_COL = ( + index - 1 + ) # subtracting one because timestamp column not collected formatted_entry.append(response) elif "prefer" in question: PREFERENCES_COL = index - 1 @@ -57,7 +63,7 @@ def get(group_size): formatted_records.append(formatted_entry) logging.debug("Writing formatted records to " + config.WORKBOOK_CSV + "...") - with open(config.WORKBOOK_CSV, 'w') as output: + with open(config.WORKBOOK_CSV, "w") as output: writer = csv.writer(output, quoting=csv.QUOTE_ALL) for item in formatted_records: writer.writerow(item) From 99d60c12400e835b4762a84964ce99350f4396c6 Mon Sep 17 00:00:00 2001 From: enpuyou Date: Mon, 11 Mar 2019 14:43:38 -0400 Subject: [PATCH 23/39] disable global --- gatorgrouper/utils/workbook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gatorgrouper/utils/workbook.py b/gatorgrouper/utils/workbook.py index dc431b43..437fc476 100644 --- a/gatorgrouper/utils/workbook.py +++ b/gatorgrouper/utils/workbook.py @@ -23,7 +23,7 @@ def get(group_size): """Retrieve data from Google Sheets and write to a CSV file.""" - + # pylint: disable=global-statement global EMAIL_COL global PREFERENCES_COL global SKILLS_COLS From e900a528c01cc2ac3e913db86b49e8fe1502aec1 Mon Sep 17 00:00:00 2001 From: Spencer Huang Date: Mon, 11 Mar 2019 15:01:33 -0400 Subject: [PATCH 24/39] Add pandas a dev package for workbook --- Pipfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Pipfile b/Pipfile index 3fdc185c..85d3b187 100644 --- a/Pipfile +++ b/Pipfile @@ -19,6 +19,7 @@ pytest-django = "*" mixer = "*" pylint = "*" hypothesis = "*" +pandas = "*" [packages] pathlib = "*" From 03aaadd2848cf9655a405c0da3961b53167976fb Mon Sep 17 00:00:00 2001 From: Spencer Huang Date: Mon, 11 Mar 2019 15:03:46 -0400 Subject: [PATCH 25/39] Add in the constants for workbook --- gatorgrouper/utils/constants.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gatorgrouper/utils/constants.py b/gatorgrouper/utils/constants.py index adacbf8b..2035b1c9 100644 --- a/gatorgrouper/utils/constants.py +++ b/gatorgrouper/utils/constants.py @@ -14,3 +14,9 @@ # recognizer EMPTY_STRING = "" ERROR = "error:" + +# Define configuration variables + +DEFAULT_GROUP_SIZE = 2 +WORKBOOK = "GatorGrouper (Responses)" +WORKBOOK_CSV = WORKBOOK.replace(" ", "_") + ".csv" From 7d2cc7608067db93728557b6304e6f0b719df8a0 Mon Sep 17 00:00:00 2001 From: Spencer Huang Date: Mon, 11 Mar 2019 15:04:07 -0400 Subject: [PATCH 26/39] Fix import statements in workbook --- gatorgrouper/utils/workbook.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gatorgrouper/utils/workbook.py b/gatorgrouper/utils/workbook.py index 437fc476..3c43bd4f 100644 --- a/gatorgrouper/utils/workbook.py +++ b/gatorgrouper/utils/workbook.py @@ -6,11 +6,11 @@ import math import logging import gspread -import pandas as pd from oauth2client.service_account import ServiceAccountCredentials +import pandas as pd -from genetic_algorithm import Student -import config +from group_genetic import Student +import constants EMAIL_COL = None From c244d710d0b5ff9d10b056892da354fd04044621 Mon Sep 17 00:00:00 2001 From: Spencer Huang Date: Mon, 11 Mar 2019 15:04:33 -0400 Subject: [PATCH 27/39] Change config to constants --- gatorgrouper/utils/workbook.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gatorgrouper/utils/workbook.py b/gatorgrouper/utils/workbook.py index 3c43bd4f..280c9c4a 100644 --- a/gatorgrouper/utils/workbook.py +++ b/gatorgrouper/utils/workbook.py @@ -40,7 +40,7 @@ def get(group_size): client = gspread.authorize(creds) logging.info("Opening spreadsheet...") - sheet = client.open(config.WORKBOOK).sheet1 + sheet = client.open(constants.WORKBOOK).sheet1 logging.info("Extracting data from spreadsheet...") records = sheet.get_all_records() @@ -75,7 +75,7 @@ def get(group_size): # PREFERENCES_COL = 1 # SKILLS_COLS = [2, 3, 4, 5, 6] - DATA = pd.read_csv(config.WORKBOOK_CSV, header=None) + DATA = pd.read_csv(constants.WORKBOOK_CSV, header=None) EMAILS = DATA.iloc[:, EMAIL_COL] From 7ee9429a290333327fe86d3e414bf4c85f039edd Mon Sep 17 00:00:00 2001 From: Spencer Huang Date: Mon, 11 Mar 2019 15:04:47 -0400 Subject: [PATCH 28/39] Supress a linting error --- gatorgrouper/utils/workbook.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gatorgrouper/utils/workbook.py b/gatorgrouper/utils/workbook.py index 280c9c4a..923167f7 100644 --- a/gatorgrouper/utils/workbook.py +++ b/gatorgrouper/utils/workbook.py @@ -62,8 +62,9 @@ def get(group_size): formatted_entry.append(response) formatted_records.append(formatted_entry) - logging.debug("Writing formatted records to " + config.WORKBOOK_CSV + "...") - with open(config.WORKBOOK_CSV, "w") as output: + # pylint: disable=W1201 + logging.debug("Writing formatted records to " + constants.WORKBOOK_CSV + "...") + with open(constants.WORKBOOK_CSV, "w") as output: writer = csv.writer(output, quoting=csv.QUOTE_ALL) for item in formatted_records: writer.writerow(item) From 9a93a4107c28c8dcd87d293cc2966220731052c8 Mon Sep 17 00:00:00 2001 From: Jonathan Reibel Date: Tue, 12 Mar 2019 15:39:53 -0400 Subject: [PATCH 29/39] Removed zip call, increased pylint rating --- gatorgrouper/utils/group_genetic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index 96461212..346ea5b9 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -449,7 +449,7 @@ def calculate_fitness(grouping: List[List[Student]]): # grouping_coef_avg = 0.396 # transpose the list of lists - skills_by_group_transposed = list(map(list, zip(*skills_by_group))) + skills_by_group_transposed = list(map(list, skills_by_group)) skills_coef_by_grouping = list() for skills in skills_by_group_transposed: skills_coef_by_grouping.append(np.std(skills) / np.mean(skills)) From 567f57f7d3b3e8e2a79315b2e46d380460dae44b Mon Sep 17 00:00:00 2001 From: Jonathan Reibel Date: Tue, 12 Mar 2019 15:47:15 -0400 Subject: [PATCH 30/39] installed numpy and fixed nested blocks, increased pylint rating. --- Pipfile.lock | 76 ++++++++++++++++++++++++++--- gatorgrouper/utils/group_genetic.py | 16 +++--- 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index fd1abc63..95d16aa5 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "8dce5a8ef967fb9d1e62abdb43d0b03a54adae7cac208c2f7b44317a3b22252c" + "sha256": "329fd5537ca63edddea5e72f2c7b3195cd14daac528fa7dcfe47e445cc85211a" }, "pipfile-spec": 6, "requires": {}, @@ -186,10 +186,10 @@ }, "sqlparse": { "hashes": [ - "sha256:ce028444cfab83be538752a2ffdb56bc417b7784ff35bb9a3062413717807dec", - "sha256:d9cf190f51cbb26da0412247dfe4fb5f4098edb73db84e02f9fc21fdca31fed4" + "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", + "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" ], - "version": "==0.2.4" + "version": "==0.3.0" }, "urllib3": { "hashes": [ @@ -475,6 +475,35 @@ "index": "pypi", "version": "==0.3.1" }, + "numpy": { + "hashes": [ + "sha256:1980f8d84548d74921685f68096911585fee393975f53797614b34d4f409b6da", + "sha256:22752cd809272671b273bb86df0f505f505a12368a3a5fc0aa811c7ece4dfd5c", + "sha256:23cc40313036cffd5d1873ef3ce2e949bdee0646c5d6f375bf7ee4f368db2511", + "sha256:2b0b118ff547fecabc247a2668f48f48b3b1f7d63676ebc5be7352a5fd9e85a5", + "sha256:3a0bd1edf64f6a911427b608a894111f9fcdb25284f724016f34a84c9a3a6ea9", + "sha256:3f25f6c7b0d000017e5ac55977a3999b0b1a74491eacb3c1aa716f0e01f6dcd1", + "sha256:4061c79ac2230594a7419151028e808239450e676c39e58302ad296232e3c2e8", + "sha256:560ceaa24f971ab37dede7ba030fc5d8fa173305d94365f814d9523ffd5d5916", + "sha256:62be044cd58da2a947b7e7b2252a10b42920df9520fc3d39f5c4c70d5460b8ba", + "sha256:6c692e3879dde0b67a9dc78f9bfb6f61c666b4562fd8619632d7043fb5b691b0", + "sha256:6f65e37b5a331df950ef6ff03bd4136b3c0bbcf44d4b8e99135d68a537711b5a", + "sha256:7a78cc4ddb253a55971115f8320a7ce28fd23a065fc33166d601f51760eecfa9", + "sha256:80a41edf64a3626e729a62df7dd278474fc1726836552b67a8c6396fd7e86760", + "sha256:893f4d75255f25a7b8516feb5766c6b63c54780323b9bd4bc51cdd7efc943c73", + "sha256:972ea92f9c1b54cc1c1a3d8508e326c0114aaf0f34996772a30f3f52b73b942f", + "sha256:9f1d4865436f794accdabadc57a8395bd3faa755449b4f65b88b7df65ae05f89", + "sha256:9f4cd7832b35e736b739be03b55875706c8c3e5fe334a06210f1a61e5c2c8ca5", + "sha256:adab43bf657488300d3aeeb8030d7f024fcc86e3a9b8848741ea2ea903e56610", + "sha256:bd2834d496ba9b1bdda3a6cf3de4dc0d4a0e7be306335940402ec95132ad063d", + "sha256:d20c0360940f30003a23c0adae2fe50a0a04f3e48dc05c298493b51fd6280197", + "sha256:d3b3ed87061d2314ff3659bb73896e622252da52558f2380f12c421fbdee3d89", + "sha256:dc235bf29a406dfda5790d01b998a1c01d7d37f449128c0b1b7d1c89a84fae8b", + "sha256:fb3c83554f39f48f3fa3123b9c24aecf681b1c289f9334f8215c1d3c8e2f6e5b" + ], + "index": "pypi", + "version": "==1.16.2" + }, "oauth2client": { "hashes": [ "sha256:b8a81cc5d60e2d364f0b1b98f958dbd472887acaf1a5b05e21c28c31a2d6d3ac", @@ -490,6 +519,32 @@ ], "version": "==19.0" }, + "pandas": { + "hashes": [ + "sha256:02c830f951f3dc8c3164e2639a8961881390f7492f71a7835c2330f54539ad57", + "sha256:179015834c72a577486337394493cc2969feee9a04a2ea09f50c724e4b52ab42", + "sha256:3894960d43c64cfea5142ac783b101362f5008ee92e962392156a3f8d1558995", + "sha256:435821cb2501eabbcee7e83614bd710940dc0cf28b5afbc4bdb816c31cec71af", + "sha256:8294dea9aa1811f93558702856e3b68dd1dfd7e9dbc8e0865918a07ee0f21c2c", + "sha256:844e745ab27a9a01c86925fe776f9d2e09575e65f0bf8eba5090edddd655dffc", + "sha256:a08d49f5fa2a2243262fe5581cb89f6c0c7cc525b8d6411719ab9400a9dc4a82", + "sha256:a435c251246075337eb9fdc4160fd15c8a87cc0679d8d61fb5255d8d5a12f044", + "sha256:a799f03c0ec6d8687f425d7d6c075e8055a9a808f1ba87604d91f20507631d8d", + "sha256:aea72ce5b3a016b578cc05c04a2f68d9cafacf5d784b6fe832e66381cb62c719", + "sha256:c145e94c6da2af7eaf1fd827293ac1090a61a9b80150bebe99f8966a02378db9", + "sha256:c8a7b470c88c779301b73b23cabdbbd94b83b93040b2ccffa409e06df23831c0", + "sha256:c9e31b36abbd7b94c547d9047f13e1546e3ba967044cf4f9718575fcb7b81bb6", + "sha256:d960b7a03c33c328c723cfc2f8902a6291645f4efa0a5c1d4c5fa008cdc1ea77", + "sha256:da21fae4c173781b012217c9444f13c67449957a4d45184a9718268732c09564", + "sha256:db26c0fea0bd7d33c356da98bafd2c0dfb8f338e45e2824ff8f4f3e61b5c5f25", + "sha256:dc296c3f16ec620cfb4daf0f672e3c90f3920ece8261b2760cd0ebd9cd4daa55", + "sha256:e8da67cb2e9333ec30d53cfb96e27a4865d1648688e5471699070d35d8ab38cf", + "sha256:fb4f047a63f91f22aade4438aaf790400b96644e802daab4293e9b799802f93f", + "sha256:fef9939176cba0c2526ebeefffb8b9807543dc0954877b7226f751ec1294a869" + ], + "index": "pypi", + "version": "==0.24.1" + }, "pluggy": { "hashes": [ "sha256:19ecf9ce9db2fce065a7a0586e07cfb4ac8614fe96edf628a264b1c70116cf8f", @@ -556,11 +611,11 @@ }, "pytest": { "hashes": [ - "sha256:067a1d4bf827ffdd56ad21bd46674703fce77c5957f6c1eef731f6146bfcef1c", - "sha256:9687049d53695ad45cf5fdc7bbd51f0c49f1ea3ecfc4b7f3fde7501b541f17f4" + "sha256:592eaa2c33fae68c7d75aacf042efc9f77b27c08a6224a4f59beab8d9a420523", + "sha256:ad3ad5c450284819ecde191a654c09b0ec72257a2c711b9633d677c71c9850c4" ], "index": "pypi", - "version": "==4.3.0" + "version": "==4.3.1" }, "pytest-cov": { "hashes": [ @@ -593,6 +648,13 @@ ], "version": "==2.8.0" }, + "pytz": { + "hashes": [ + "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", + "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c" + ], + "version": "==2018.9" + }, "requests": { "hashes": [ "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index 346ea5b9..cb9edf22 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -181,15 +181,15 @@ def crossover(individual_one, individual_two): for two in grouping_two: if set(one) == set(two): dupl = False - for group in offspring: - for student in one: - if student in group: - dupl = True - break - if dupl: + for group in offspring: + for student in one: + if student in group: + dupl = True break - if not dupl: - offspring.append(one) + if dupl: + break + if not dupl: + offspring.append(one) # print("added equals: {}".format(offspring)) From fa52184f6a476c0d780513eb7689067feabb5953 Mon Sep 17 00:00:00 2001 From: Jonathan Reibel Date: Tue, 12 Mar 2019 15:59:30 -0400 Subject: [PATCH 31/39] Added pylint disable statements. --- Pipfile | 1 + gatorgrouper/utils/group_genetic.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/Pipfile b/Pipfile index 85d3b187..93f27522 100644 --- a/Pipfile +++ b/Pipfile @@ -30,5 +30,6 @@ social-auth-app-django = "*" numpy = "*" ansicolors = "*" + [pipenv] allow_prereleases = true diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index cb9edf22..a934d65b 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -9,6 +9,7 @@ import workbook + class Student: """Represent student.""" @@ -166,6 +167,11 @@ def evolve( def crossover(individual_one, individual_two): + + # pylint: disable=too-many-branches + # pylint: disable=too-many-statements + # pylint: disable=too-many-locals + """Add smaller groupings of students to the larger groups""" grouping_one = individual_one.grouping[:] From 105c04116ba6cc4f52632efd0c2fab7f24129cb8 Mon Sep 17 00:00:00 2001 From: Jonathan Reibel Date: Tue, 12 Mar 2019 16:01:10 -0400 Subject: [PATCH 32/39] Added more disable statements --- gatorgrouper/utils/group_genetic.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index a934d65b..e75a7689 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -279,6 +279,10 @@ def mutate(mutations, grouping: List[List[Student]]): def spawn( + + # pylint: disable=too-many-branches + # pylint: disable=too-many-statements + # pylint: disable=too-many-locals # pylint: disable = C0330 # Black would reformat the code in the way that does not pass pylint prev_population: List[Individual], From 39a644f3897abcf22de90d0f6ded801146df58ce Mon Sep 17 00:00:00 2001 From: Jonathan Reibel Date: Tue, 12 Mar 2019 16:08:54 -0400 Subject: [PATCH 33/39] Finished implementing disable statements. Pylint passing. --- gatorgrouper/utils/group_genetic.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index e75a7689..655dd07e 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -9,7 +9,6 @@ import workbook - class Student: """Represent student.""" @@ -37,6 +36,7 @@ def __hash__(self): class Individual: + # pylint: disable=too-few-public-methods """Represent individual.""" def __init__(self, grouping: List[List[Student]], fitness): @@ -279,11 +279,11 @@ def mutate(mutations, grouping: List[List[Student]]): def spawn( - # pylint: disable=too-many-branches # pylint: disable=too-many-statements # pylint: disable=too-many-locals - # pylint: disable = C0330 + # pylint: disable = W0613 + # pylint: disable = C0330 # Black would reformat the code in the way that does not pass pylint prev_population: List[Individual], mutation_rate: float, @@ -390,6 +390,7 @@ def select(population: List[Individual]): def calculate_fitness(grouping: List[List[Student]]): # pylint: disable=R0914 """Calculate compatibility between students""" + # pylint: disable=too-many-branches # pylint: disable=global-statement global best_grouping global best_fitness From 67e2a2176d857639f2ae15eb4edaf1cbac172f2e Mon Sep 17 00:00:00 2001 From: Jonathan Reibel Date: Tue, 12 Mar 2019 16:22:42 -0400 Subject: [PATCH 34/39] fixed formatting errors. --- gatorgrouper/utils/group_genetic.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index 655dd07e..c683ef80 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -279,16 +279,17 @@ def mutate(mutations, grouping: List[List[Student]]): def spawn( - # pylint: disable=too-many-branches - # pylint: disable=too-many-statements - # pylint: disable=too-many-locals - # pylint: disable = W0613 - # pylint: disable = C0330 + # pylint: disable=C0330 + # pylint: disable=too-many-branches + # pylint: disable=too-many-statements + # pylint: disable=too-many-locals # Black would reformat the code in the way that does not pass pylint + prev_population: List[Individual], mutation_rate: float, elitism_rate: float, create_rate: float, + # pylint: disable = W0613 crossover_rate: float, mutations, ): From b58b747c5b6c9b4989fa255103f153b83b106cf1 Mon Sep 17 00:00:00 2001 From: Spencer Huang Date: Thu, 14 Mar 2019 11:54:23 -0400 Subject: [PATCH 35/39] Remove workbook since it is not needed --- gatorgrouper/utils/workbook.py | 100 --------------------------------- 1 file changed, 100 deletions(-) delete mode 100644 gatorgrouper/utils/workbook.py diff --git a/gatorgrouper/utils/workbook.py b/gatorgrouper/utils/workbook.py deleted file mode 100644 index 923167f7..00000000 --- a/gatorgrouper/utils/workbook.py +++ /dev/null @@ -1,100 +0,0 @@ -"""Integrate GatorGrouper with Google Sheets. -This code is under MIT license from -https://github.com/yeeunmariakim/gatorgrouper/blob/master/workbook.py""" - -import csv -import math -import logging -import gspread -from oauth2client.service_account import ServiceAccountCredentials -import pandas as pd - -from group_genetic import Student -import constants - - -EMAIL_COL = None -PREFERENCES_COL = None -SKILLS_COLS = set() - -STUDENTS = None -GROUPING_SIZE = None - - -def get(group_size): - """Retrieve data from Google Sheets and write to a CSV file.""" - # pylint: disable=global-statement - global EMAIL_COL - global PREFERENCES_COL - global SKILLS_COLS - - logging.info("Authenticating to Google Sheets...") - - scope = [ - "https://spreadsheets.google.com/feeds", - "https://www.googleapis.com/auth/drive", - ] - creds = ServiceAccountCredentials.from_json_keyfile_name( - "client_secret.json", scope - ) - client = gspread.authorize(creds) - - logging.info("Opening spreadsheet...") - sheet = client.open(constants.WORKBOOK).sheet1 - - logging.info("Extracting data from spreadsheet...") - records = sheet.get_all_records() - - formatted_records = list() - for entry in records: - formatted_entry = list() - for index, (question, response) in enumerate(entry.items()): - if question == "Email Address": - EMAIL_COL = ( - index - 1 - ) # subtracting one because timestamp column not collected - formatted_entry.append(response) - elif "prefer" in question: - PREFERENCES_COL = index - 1 - formatted_entry.append(response) - elif "skill" in question: - SKILLS_COLS.add(index - 1) - formatted_entry.append(response) - formatted_records.append(formatted_entry) - - # pylint: disable=W1201 - logging.debug("Writing formatted records to " + constants.WORKBOOK_CSV + "...") - with open(constants.WORKBOOK_CSV, "w") as output: - writer = csv.writer(output, quoting=csv.QUOTE_ALL) - for item in formatted_records: - writer.writerow(item) - - global STUDENTS - global GROUPING_SIZE - - # EMAIL_COL = 0 - # PREFERENCES_COL = 1 - # SKILLS_COLS = [2, 3, 4, 5, 6] - - DATA = pd.read_csv(constants.WORKBOOK_CSV, header=None) - - EMAILS = DATA.iloc[:, EMAIL_COL] - - STUDENTS = list() - for current_row, email in enumerate(EMAILS): - skills = list() - for skill_col in SKILLS_COLS: - skills.append(DATA.iat[current_row, skill_col]) - preferences_str = DATA.iat[current_row, PREFERENCES_COL] - - if isinstance(preferences_str, float) and math.isnan(preferences_str): - preferences = [] - else: - preferences = preferences_str.replace(" ", "").split(",") - - STUDENTS.append(Student(email, skills, preferences)) - - # for student in STUDENTS: - # print(str(student) + "\n") - - GROUPING_SIZE = math.floor(len(STUDENTS) / group_size) From f5e1541d3dc077986308be7a2a91df7ee2d7c389 Mon Sep 17 00:00:00 2001 From: Spencer Huang Date: Thu, 14 Mar 2019 11:54:42 -0400 Subject: [PATCH 36/39] Remove import statement that uses workbook --- gatorgrouper/utils/group_genetic.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index c683ef80..d6450ade 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -6,7 +6,6 @@ import math import numpy as np from colors import bold -import workbook class Student: From ab78a4d2aa85baaba43b52cdfa34a3be997a6d89 Mon Sep 17 00:00:00 2001 From: Lancasterwu Date: Thu, 14 Mar 2019 11:56:05 -0400 Subject: [PATCH 37/39] Fixed pipfile --- Pipfile | 6 ++-- Pipfile.lock | 100 +++++++++------------------------------------------ 2 files changed, 18 insertions(+), 88 deletions(-) diff --git a/Pipfile b/Pipfile index 0ead7e1f..16ee2c83 100644 --- a/Pipfile +++ b/Pipfile @@ -20,7 +20,8 @@ mixer = "*" pylint = "*" mutmut = "*" hypothesis = "*" -pandas = "*" +numpy = "*" +ansicolors = "*" [packages] pathlib = "*" @@ -28,9 +29,6 @@ py = "*" django = "*" networkx = "*" social-auth-app-django = "*" -numpy = "*" -ansicolors = "*" - [pipenv] allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock index b08f1571..ebcf01b7 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "6e23b1d68685796c120977a1a08e1e1eb2ecb563e7b382cb53adc1b06aabac5a" + "sha256": "5e09f79339fe2a5b730412236fcbe0a3798433738dc54ea4317b0c5783752bc6" }, "pipfile-spec": 6, "requires": {}, @@ -14,14 +14,6 @@ ] }, "default": { - "ansicolors": { - "hashes": [ - "sha256:00d2dde5a675579325902536738dd27e4fac1fd68f773fe36c21044eb559e187", - "sha256:99f94f5e3348a0bcd43c82e5fc4414013ccc19d70bd939ad71e0133ce9c372e0" - ], - "index": "pypi", - "version": "==1.1.8" - }, "certifi": { "hashes": [ "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", @@ -73,35 +65,6 @@ "index": "pypi", "version": "==2.2" }, - "numpy": { - "hashes": [ - "sha256:1980f8d84548d74921685f68096911585fee393975f53797614b34d4f409b6da", - "sha256:22752cd809272671b273bb86df0f505f505a12368a3a5fc0aa811c7ece4dfd5c", - "sha256:23cc40313036cffd5d1873ef3ce2e949bdee0646c5d6f375bf7ee4f368db2511", - "sha256:2b0b118ff547fecabc247a2668f48f48b3b1f7d63676ebc5be7352a5fd9e85a5", - "sha256:3a0bd1edf64f6a911427b608a894111f9fcdb25284f724016f34a84c9a3a6ea9", - "sha256:3f25f6c7b0d000017e5ac55977a3999b0b1a74491eacb3c1aa716f0e01f6dcd1", - "sha256:4061c79ac2230594a7419151028e808239450e676c39e58302ad296232e3c2e8", - "sha256:560ceaa24f971ab37dede7ba030fc5d8fa173305d94365f814d9523ffd5d5916", - "sha256:62be044cd58da2a947b7e7b2252a10b42920df9520fc3d39f5c4c70d5460b8ba", - "sha256:6c692e3879dde0b67a9dc78f9bfb6f61c666b4562fd8619632d7043fb5b691b0", - "sha256:6f65e37b5a331df950ef6ff03bd4136b3c0bbcf44d4b8e99135d68a537711b5a", - "sha256:7a78cc4ddb253a55971115f8320a7ce28fd23a065fc33166d601f51760eecfa9", - "sha256:80a41edf64a3626e729a62df7dd278474fc1726836552b67a8c6396fd7e86760", - "sha256:893f4d75255f25a7b8516feb5766c6b63c54780323b9bd4bc51cdd7efc943c73", - "sha256:972ea92f9c1b54cc1c1a3d8508e326c0114aaf0f34996772a30f3f52b73b942f", - "sha256:9f1d4865436f794accdabadc57a8395bd3faa755449b4f65b88b7df65ae05f89", - "sha256:9f4cd7832b35e736b739be03b55875706c8c3e5fe334a06210f1a61e5c2c8ca5", - "sha256:adab43bf657488300d3aeeb8030d7f024fcc86e3a9b8848741ea2ea903e56610", - "sha256:bd2834d496ba9b1bdda3a6cf3de4dc0d4a0e7be306335940402ec95132ad063d", - "sha256:d20c0360940f30003a23c0adae2fe50a0a04f3e48dc05c298493b51fd6280197", - "sha256:d3b3ed87061d2314ff3659bb73896e622252da52558f2380f12c421fbdee3d89", - "sha256:dc235bf29a406dfda5790d01b998a1c01d7d37f449128c0b1b7d1c89a84fae8b", - "sha256:fb3c83554f39f48f3fa3123b9c24aecf681b1c289f9334f8215c1d3c8e2f6e5b" - ], - "index": "pypi", - "version": "==1.16.2" - }, "oauthlib": { "hashes": [ "sha256:0ce32c5d989a1827e3f1148f98b9085ed2370fc939bf524c9c851d8714797298", @@ -200,6 +163,14 @@ } }, "develop": { + "ansicolors": { + "hashes": [ + "sha256:00d2dde5a675579325902536738dd27e4fac1fd68f773fe36c21044eb559e187", + "sha256:99f94f5e3348a0bcd43c82e5fc4414013ccc19d70bd939ad71e0133ce9c372e0" + ], + "index": "pypi", + "version": "==1.1.8" + }, "appdirs": { "hashes": [ "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", @@ -272,14 +243,6 @@ "index": "pypi", "version": "==2.0.15" }, - "colorama": { - "hashes": [ - "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", - "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48" - ], - "markers": "sys_platform == 'win32'", - "version": "==0.4.1" - }, "coverage": { "hashes": [ "sha256:029c69deaeeeae1b15bc6c59f0ffa28aa8473721c614a23f2c2976dec245cd12", @@ -382,12 +345,12 @@ }, "hypothesis": { "hashes": [ - "sha256:5d73252c5dddba45875162f15e473024d154d61af11dafacc3507edefb63c9dc", - "sha256:6e679d1dba80b623517948f3d83425e3c96b393e0e7909e4b597cc4f143a8465", - "sha256:b6f3d36307ab0ae9739fb80520eb303d8fa7e2b5963cb1979d463f065ef1a87b" + "sha256:6c715cd2ab3ed58f946484cc8967c15be968e6d6ef9eb2cd2079eb32eced4cbc", + "sha256:71f79108f21b5a27e76ac5b2dac3bf65af4e4c05d1b77394d680eb61e45352fd", + "sha256:f3954c8b2d1dff6b349fe191874ea51d37caa8c75e4951638b6d526592bc257c" ], "index": "pypi", - "version": "==4.10.0" + "version": "==4.11.5" }, "idna": { "hashes": [ @@ -402,6 +365,7 @@ "sha256:96151fca2c6e736503981896495d344781b60d18bfda78dc11b290c6125ebdb6" ], "version": "==4.3.15" + }, "junit-xml": { "hashes": [ "sha256:602f1c480a19d64edb452bf7632f76b5f2cb92c1938c6e071dcda8ff9541dc21" @@ -545,31 +509,6 @@ ], "version": "==19.0" }, - "pandas": { - "hashes": [ - "sha256:02c830f951f3dc8c3164e2639a8961881390f7492f71a7835c2330f54539ad57", - "sha256:179015834c72a577486337394493cc2969feee9a04a2ea09f50c724e4b52ab42", - "sha256:3894960d43c64cfea5142ac783b101362f5008ee92e962392156a3f8d1558995", - "sha256:435821cb2501eabbcee7e83614bd710940dc0cf28b5afbc4bdb816c31cec71af", - "sha256:8294dea9aa1811f93558702856e3b68dd1dfd7e9dbc8e0865918a07ee0f21c2c", - "sha256:844e745ab27a9a01c86925fe776f9d2e09575e65f0bf8eba5090edddd655dffc", - "sha256:a08d49f5fa2a2243262fe5581cb89f6c0c7cc525b8d6411719ab9400a9dc4a82", - "sha256:a435c251246075337eb9fdc4160fd15c8a87cc0679d8d61fb5255d8d5a12f044", - "sha256:a799f03c0ec6d8687f425d7d6c075e8055a9a808f1ba87604d91f20507631d8d", - "sha256:aea72ce5b3a016b578cc05c04a2f68d9cafacf5d784b6fe832e66381cb62c719", - "sha256:c145e94c6da2af7eaf1fd827293ac1090a61a9b80150bebe99f8966a02378db9", - "sha256:c8a7b470c88c779301b73b23cabdbbd94b83b93040b2ccffa409e06df23831c0", - "sha256:c9e31b36abbd7b94c547d9047f13e1546e3ba967044cf4f9718575fcb7b81bb6", - "sha256:d960b7a03c33c328c723cfc2f8902a6291645f4efa0a5c1d4c5fa008cdc1ea77", - "sha256:da21fae4c173781b012217c9444f13c67449957a4d45184a9718268732c09564", - "sha256:db26c0fea0bd7d33c356da98bafd2c0dfb8f338e45e2824ff8f4f3e61b5c5f25", - "sha256:dc296c3f16ec620cfb4daf0f672e3c90f3920ece8261b2760cd0ebd9cd4daa55", - "sha256:e8da67cb2e9333ec30d53cfb96e27a4865d1648688e5471699070d35d8ab38cf", - "sha256:fb4f047a63f91f22aade4438aaf790400b96644e802daab4293e9b799802f93f", - "sha256:fef9939176cba0c2526ebeefffb8b9807543dc0954877b7226f751ec1294a869" - ], - "index": "pypi", - "version": "==0.24.1" "parso": { "hashes": [ "sha256:4580328ae3f548b358f4901e38c0578229186835f0fa0846e47369796dd5bcc9", @@ -686,13 +625,6 @@ ], "version": "==2.8.0" }, - "pytz": { - "hashes": [ - "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", - "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c" - ], - "version": "==2018.9" - }, "requests": { "hashes": [ "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", @@ -736,9 +668,9 @@ }, "tri.declarative": { "hashes": [ - "sha256:e661d95f8537bcbb666f07799ba8594478c21f27defc458d2b28aa9682c3c597" + "sha256:def34727c6372c18cb4634a228ef8a6b9fe48d6b9117f9cfa8e49b1a9fb47135" ], - "version": "==1.1.0" + "version": "==1.2.0" }, "tri.struct": { "hashes": [ From 90c875175ea0f706dc37eeb84dc236ebcd0c3836 Mon Sep 17 00:00:00 2001 From: Lancasterwu Date: Thu, 14 Mar 2019 12:02:24 -0400 Subject: [PATCH 38/39] Use black to reformate group_genetic --- gatorgrouper/utils/group_genetic.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index d6450ade..939ae9e7 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -283,7 +283,6 @@ def spawn( # pylint: disable=too-many-statements # pylint: disable=too-many-locals # Black would reformat the code in the way that does not pass pylint - prev_population: List[Individual], mutation_rate: float, elitism_rate: float, From 5c6369f6091bb2fadc9726f5b5bf956c1db8f34d Mon Sep 17 00:00:00 2001 From: Mohammad Khan Date: Thu, 14 Mar 2019 19:43:31 -0400 Subject: [PATCH 39/39] Start implementating genetic algorithm --- Pipfile | 1 + Pipfile.lock | 70 ++++++++++++++++++++++++-- gatorgrouper/utils/constants.py | 5 +- gatorgrouper/utils/group_genetic.py | 11 +++-- gatorgrouper/utils/mutations.py | 33 +++++++++++++ gatorgrouper/utils/parse_arguments.py | 1 + gatorgrouper/utils/workbook.py | 71 +++++++++++++++++++++++++++ gatorgrouper_cli.py | 4 ++ 8 files changed, 185 insertions(+), 11 deletions(-) create mode 100644 gatorgrouper/utils/mutations.py create mode 100644 gatorgrouper/utils/workbook.py diff --git a/Pipfile b/Pipfile index 16ee2c83..44bb0b1b 100644 --- a/Pipfile +++ b/Pipfile @@ -29,6 +29,7 @@ py = "*" django = "*" networkx = "*" social-auth-app-django = "*" +pandas = "*" [pipenv] allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock index ebcf01b7..de4dc5f9 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "5e09f79339fe2a5b730412236fcbe0a3798433738dc54ea4317b0c5783752bc6" + "sha256": "5339c42ca7b656d60c36fdb0738510cb7b9dc2e978b4b7f4fac78ceaa0480e22" }, "pipfile-spec": 6, "requires": {}, @@ -65,6 +65,34 @@ "index": "pypi", "version": "==2.2" }, + "numpy": { + "hashes": [ + "sha256:1980f8d84548d74921685f68096911585fee393975f53797614b34d4f409b6da", + "sha256:22752cd809272671b273bb86df0f505f505a12368a3a5fc0aa811c7ece4dfd5c", + "sha256:23cc40313036cffd5d1873ef3ce2e949bdee0646c5d6f375bf7ee4f368db2511", + "sha256:2b0b118ff547fecabc247a2668f48f48b3b1f7d63676ebc5be7352a5fd9e85a5", + "sha256:3a0bd1edf64f6a911427b608a894111f9fcdb25284f724016f34a84c9a3a6ea9", + "sha256:3f25f6c7b0d000017e5ac55977a3999b0b1a74491eacb3c1aa716f0e01f6dcd1", + "sha256:4061c79ac2230594a7419151028e808239450e676c39e58302ad296232e3c2e8", + "sha256:560ceaa24f971ab37dede7ba030fc5d8fa173305d94365f814d9523ffd5d5916", + "sha256:62be044cd58da2a947b7e7b2252a10b42920df9520fc3d39f5c4c70d5460b8ba", + "sha256:6c692e3879dde0b67a9dc78f9bfb6f61c666b4562fd8619632d7043fb5b691b0", + "sha256:6f65e37b5a331df950ef6ff03bd4136b3c0bbcf44d4b8e99135d68a537711b5a", + "sha256:7a78cc4ddb253a55971115f8320a7ce28fd23a065fc33166d601f51760eecfa9", + "sha256:80a41edf64a3626e729a62df7dd278474fc1726836552b67a8c6396fd7e86760", + "sha256:893f4d75255f25a7b8516feb5766c6b63c54780323b9bd4bc51cdd7efc943c73", + "sha256:972ea92f9c1b54cc1c1a3d8508e326c0114aaf0f34996772a30f3f52b73b942f", + "sha256:9f1d4865436f794accdabadc57a8395bd3faa755449b4f65b88b7df65ae05f89", + "sha256:9f4cd7832b35e736b739be03b55875706c8c3e5fe334a06210f1a61e5c2c8ca5", + "sha256:adab43bf657488300d3aeeb8030d7f024fcc86e3a9b8848741ea2ea903e56610", + "sha256:bd2834d496ba9b1bdda3a6cf3de4dc0d4a0e7be306335940402ec95132ad063d", + "sha256:d20c0360940f30003a23c0adae2fe50a0a04f3e48dc05c298493b51fd6280197", + "sha256:d3b3ed87061d2314ff3659bb73896e622252da52558f2380f12c421fbdee3d89", + "sha256:dc235bf29a406dfda5790d01b998a1c01d7d37f449128c0b1b7d1c89a84fae8b", + "sha256:fb3c83554f39f48f3fa3123b9c24aecf681b1c289f9334f8215c1d3c8e2f6e5b" + ], + "version": "==1.16.2" + }, "oauthlib": { "hashes": [ "sha256:0ce32c5d989a1827e3f1148f98b9085ed2370fc939bf524c9c851d8714797298", @@ -72,6 +100,32 @@ ], "version": "==3.0.1" }, + "pandas": { + "hashes": [ + "sha256:071e42b89b57baa17031af8c6b6bbd2e9a5c68c595bc6bf9adabd7a9ed125d3b", + "sha256:17450e25ae69e2e6b303817bdf26b2cd57f69595d8550a77c308be0cd0fd58fa", + "sha256:17916d818592c9ec891cbef2e90f98cc85e0f1e89ed0924c9b5220dc3209c846", + "sha256:2538f099ab0e9f9c9d09bbcd94b47fd889bad06dc7ae96b1ed583f1dc1a7a822", + "sha256:366f30710172cb45a6b4f43b66c220653b1ea50303fbbd94e50571637ffb9167", + "sha256:42e5ad741a0d09232efbc7fc648226ed93306551772fc8aecc6dce9f0e676794", + "sha256:4e718e7f395ba5bfe8b6f6aaf2ff1c65a09bb77a36af6394621434e7cc813204", + "sha256:4f919f409c433577a501e023943e582c57355d50a724c589e78bc1d551a535a2", + "sha256:4fe0d7e6438212e839fc5010c78b822664f1a824c0d263fd858f44131d9166e2", + "sha256:5149a6db3e74f23dc3f5a216c2c9ae2e12920aa2d4a5b77e44e5b804a5f93248", + "sha256:627594338d6dd995cfc0bacd8e654cd9e1252d2a7c959449228df6740d737eb8", + "sha256:83c702615052f2a0a7fb1dd289726e29ec87a27272d775cb77affe749cca28f8", + "sha256:8c872f7fdf3018b7891e1e3e86c55b190e6c5cee70cab771e8f246c855001296", + "sha256:90f116086063934afd51e61a802a943826d2aac572b2f7d55caaac51c13db5b5", + "sha256:a3352bacac12e1fc646213b998bce586f965c9d431773d9e91db27c7c48a1f7d", + "sha256:bcdd06007cca02d51350f96debe51331dec429ac8f93930a43eb8fb5639e3eb5", + "sha256:c1bd07ebc15285535f61ddd8c0c75d0d6293e80e1ee6d9a8d73f3f36954342d0", + "sha256:c9a4b7c55115eb278c19aa14b34fcf5920c8fe7797a09b7b053ddd6195ea89b3", + "sha256:cc8fc0c7a8d5951dc738f1c1447f71c43734244453616f32b8aa0ef6013a5dfb", + "sha256:d7b460bc316064540ce0c41c1438c416a40746fd8a4fb2999668bf18f3c4acf1" + ], + "index": "pypi", + "version": "==0.24.2" + }, "pathlib": { "hashes": [ "sha256:6940718dfc3eff4258203ad5021090933e5c04707d5ca8cc9e73c94a7894ea9f" @@ -94,6 +148,13 @@ ], "version": "==1.7.1" }, + "python-dateutil": { + "hashes": [ + "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", + "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + ], + "version": "==2.8.0" + }, "python3-openid": { "hashes": [ "sha256:0086da6b6ef3161cfe50fb1ee5cceaf2cda1700019fda03c2c5c440ca6abe4fa", @@ -208,11 +269,11 @@ }, "black": { "hashes": [ - "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", - "sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5" + "sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf", + "sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c" ], "index": "pypi", - "version": "==18.9b0" + "version": "==19.3b0" }, "certifi": { "hashes": [ @@ -491,7 +552,6 @@ "sha256:dc235bf29a406dfda5790d01b998a1c01d7d37f449128c0b1b7d1c89a84fae8b", "sha256:fb3c83554f39f48f3fa3123b9c24aecf681b1c289f9334f8215c1d3c8e2f6e5b" ], - "index": "pypi", "version": "==1.16.2" }, "oauth2client": { diff --git a/gatorgrouper/utils/constants.py b/gatorgrouper/utils/constants.py index fd223ae9..307b8cda 100644 --- a/gatorgrouper/utils/constants.py +++ b/gatorgrouper/utils/constants.py @@ -4,6 +4,7 @@ ALGORITHM_ROUND_ROBIN = "rrobin" ALGORITHM_RANDOM = "random" ALGORITHM_GRAPH = "graph" +ALGORITHM_GENETIC = "genetic" DEFAULT_METHOD = ALGORITHM_RANDOM DEFAULT_GRPSIZE = 3 DEFAULT_NUMGRP = 3 @@ -24,5 +25,5 @@ # Define configuration variables DEFAULT_GROUP_SIZE = 2 -WORKBOOK = "GatorGrouper (Responses)" -WORKBOOK_CSV = WORKBOOK.replace(" ", "_") + ".csv" +WORKBOOK = "GatorGrouper_(Responses).csv" +# WORKBOOK_CSV = WORKBOOK.replace(" ", "_") + ".csv" diff --git a/gatorgrouper/utils/group_genetic.py b/gatorgrouper/utils/group_genetic.py index 939ae9e7..e3323db2 100644 --- a/gatorgrouper/utils/group_genetic.py +++ b/gatorgrouper/utils/group_genetic.py @@ -5,7 +5,9 @@ import random import math import numpy as np -from colors import bold +from gatorgrouper.utils import constants +from gatorgrouper.utils import workbook +# from colors import bold class Student: @@ -78,16 +80,17 @@ def __str__(self): def create(): """Create the groups of student""" - students_to_group = workbook.STUDENTS[:] + + students_to_group = ["a","b","c","d"] random.shuffle(students_to_group) grouping = list() - for _ in range(workbook.GROUPING_SIZE): + for _ in range(DEFAULT_GRPSIZE): grouping.append(list()) for index, student in enumerate(students_to_group): - grouping[index % workbook.GROUPING_SIZE].append(student) + grouping[index % DEFAULT_GRPSIZE].append(student) if grouping < 1: print("CREATED TOO SMALL GROUPING") diff --git a/gatorgrouper/utils/mutations.py b/gatorgrouper/utils/mutations.py new file mode 100644 index 00000000..760397b4 --- /dev/null +++ b/gatorgrouper/utils/mutations.py @@ -0,0 +1,33 @@ +import sys +import random +from typing import List +from gatorgrouper.utils import group_genetic + + +def swap(grouping): + # print("MUTATION") + group_count = len(grouping) + # print("TOTAL GROUPS: {}".format(group_count)) + # print("BEFORE: {}".format(grouping)) + first, second = random.sample(range(len(grouping)), 2) + first_index = random.randrange(len(grouping[first])) + second_index = random.randrange(len(grouping[second])) + # print("swapping student {} in group {} with student {} in group {}".format(first_index, first, second_index, second)) + temp = grouping[second][second_index] + grouping[second][second_index] = grouping[first][first_index] + grouping[second][second_index] = temp + # grouping[first][first_index], grouping[second][second_index] = grouping[second][second_index], grouping[first][first_index] + # print("AFTER: {}".format(grouping)) + + return grouping + + +def multi_swap(grouping): + num_swaps = random.randrange(1, 6) + for _ in range(num_swaps): + grouping = swap(grouping) + return grouping + + +def get(): + return [swap, multi_swap] diff --git a/gatorgrouper/utils/parse_arguments.py b/gatorgrouper/utils/parse_arguments.py index 82f5b45c..bb65707c 100644 --- a/gatorgrouper/utils/parse_arguments.py +++ b/gatorgrouper/utils/parse_arguments.py @@ -52,6 +52,7 @@ def parse_arguments(args): constants.ALGORITHM_GRAPH, constants.ALGORITHM_ROUND_ROBIN, constants.ALGORITHM_RANDOM, + constants.ALGORITHM_GENETIC, ], default=constants.DEFAULT_METHOD, required=False, diff --git a/gatorgrouper/utils/workbook.py b/gatorgrouper/utils/workbook.py new file mode 100644 index 00000000..2e778d21 --- /dev/null +++ b/gatorgrouper/utils/workbook.py @@ -0,0 +1,71 @@ +"""Integrate GatorGrouper with Google Sheets.""" + +import csv +import math +import logging +import gspread +import pandas as pd +from oauth2client.service_account import ServiceAccountCredentials + +from gatorgrouper.utils import group_genetic +from gatorgrouper.utils import constants + + +EMAIL_COL = None +PREFERENCES_COL = None +SKILLS_COLS = set() + +STUDENTS = None +GROUPING_SIZE = None + +def get(group_size): + """Retrieve data from Google Sheets and write to a CSV file.""" + + global EMAIL_COL + global PREFERENCES_COL + global SKILLS_COLS + + # formatted_records = list() + # for entry in records: + # formatted_entry = list() + # for index, (question, response) in enumerate(entry.items()): + # if question == 'Email Address': + # EMAIL_COL = index - 1 # subtracting one because timestamp column not collected + # formatted_entry.append(response) + # elif "prefer" in question: + # PREFERENCES_COL = index - 1 + # formatted_entry.append(response) + # elif "skill" in question: + # SKILLS_COLS.add(index - 1) + # formatted_entry.append(response) + # formatted_records.append(formatted_entry) + + global STUDENTS + global GROUPING_SIZE + + # EMAIL_COL = 0 + # PREFERENCES_COL = 1 + # SKILLS_COLS = [2, 3, 4, 5, 6] + + DATA = pd.read_csv(constants.WORKBOOK_CSV, header=None) + + EMAILS = DATA.iloc[:, EMAIL_COL] + + STUDENTS = list() + for current_row, email in enumerate(EMAILS): + skills = list() + for skill_col in SKILLS_COLS: + skills.append(DATA.iat[current_row, skill_col]) + preferences_str = DATA.iat[current_row, PREFERENCES_COL] + + if isinstance(preferences_str, float) and math.isnan(preferences_str): + preferences = [] + else: + preferences = preferences_str.replace(" ", "").split(",") + + STUDENTS.append(Student(email, skills, preferences)) + + # for student in STUDENTS: + # print(str(student) + "\n") + + GROUPING_SIZE = math.floor(len(STUDENTS) / group_size) diff --git a/gatorgrouper_cli.py b/gatorgrouper_cli.py index 607ebbc4..384a5844 100644 --- a/gatorgrouper_cli.py +++ b/gatorgrouper_cli.py @@ -10,6 +10,8 @@ from gatorgrouper.utils import group_graph from gatorgrouper.utils import display from gatorgrouper.utils import constants +from gatorgrouper.utils import group_genetic +from gatorgrouper.utils import mutations if __name__ == "__main__": # pragma: no cover @@ -62,6 +64,8 @@ objective_weights=GG_ARGUMENTS.objective_weights, objective_measures=GG_ARGUMENTS.objective_measures, ) + elif GG_ARGUMENTS.method == constants.ALGORITHM_GENETIC: + GROUPED_STUDENT_IDENTIFIERS = group_genetic.evolve(60, 0.33, 0.16, 0.18, 0.66, mutations.get()) else: GROUPED_STUDENT_IDENTIFIERS = group_creation.group_random_num_group( SHUFFLED_STUDENT_IDENTIFIERS, GG_ARGUMENTS.num_group