{"cells":[{"metadata":{"colab_type":"text","id":"VEM6TNHQRTF9"},"cell_type":"markdown","source":"# <center> NFL 1st and Future - Analytics </center>\n\n* ### Gilberto Subias-Garcia\n  * ### KAGGLE ID:  4126574\n  * ### gilberto_sg@outlook.com\n  \n<br>\n\n* ### Aaron Martin Castillo-Medina\n  * ### KAGGLE ID: 1307762\n  * ### amcm329@hotmail.com"},{"metadata":{},"cell_type":"markdown","source":"###  <center> Plot </center>\n\n##### The following notebook is a storytelling regarding data understanding, prediction and final recommendations with the aid of some technical and analytical topics.\n##### That being said, there are some chapters including merely thecnical and analytical explanations, nevertheless if the user skips those corresponding parts the results will remain explainable."},{"metadata":{},"cell_type":"markdown","source":"### <center> Data Cleansing </center>\n##### This part is only mentioned because, despite the fact that this is not included in the main questions to be answered, we consider that it is still important to take a look at the way we took the data from and how we processed it.\n\n\n##### We basically worked on a Collaboratory environment with 8 GB in RAM; the processor's features are unknown but we faced some difficulties with the data reading, that's why we came across with some useful tricks to read the inputs."},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"colab_type":"code","id":"b5HcoxBMUq4g","outputId":"5dd7f4dc-924e-4796-d272-943e804294fa","trusted":false},"cell_type":"code","source":"import pickle\nimport pandas as pd\nfrom zipfile import ZipFile\nfrom google.colab import drive\n\n#Reading the sources from Google Drive\ndrive.mount('/content/drive',force_remount=True)\n\n#Because the sources are big enough, the program crashes when the .csv files ares being read, therefore \n#a workaround was created.\nzip_file = ZipFile('/content/drive/My Drive/Special_Analysis/nfl-playing-surface-analytics.zip')\n\n#Reading all the files inside the .zip container.\ndf = pd.read_csv(zip_file.open(\"PlayerTrackData.csv\"))\ndf2 = pd.read_csv(zip_file.open(\"PlayList.csv\"))\ndf3 = pd.read_csv(zip_file.open(\"InjuryRecord.csv\"))","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":443},"colab_type":"code","id":"w1vfpMyrtLd5","outputId":"587fe4b7-93c8-42ad-fb75-a2824353d969","trusted":false},"cell_type":"code","source":"#Printing a piece of the data just to make sure of the correct lecture.\nprint(\"Player Track Data\")\nprint(df.head())\n\nprint(\"PlayList\")\nprint(df2.head())\n\nprint(\"Injury Record\")\nprint(df3.head())","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":425},"colab_type":"code","id":"scZK5T6maAJ4","outputId":"6d1a73dc-0884-4000-e315-7f1baec965c2","trusted":false},"cell_type":"code","source":"import pandas as pd\nimport pickle\n\n#The next step consists on creating a training dataset; based on the information retrieved on the website, supposedly all\n#three dataframes have in common the field \"PlayKey\", so we merge them and export the corresponding result into a\n#pickle file.\n\n#print(df.groupby(['PlayKey']).count())\n#print(df2.groupby(['PlayKey']).count())\n#print(df3.groupby(['PlayKey']).count())\n\ndf_casi = pd.merge(df2,df3, how='inner', on = 'PlayKey')\n\ndf_final = pd.merge(df_casi, df, how='inner', on='PlayKey')#.to_pickle(\"/content/drive/My Drive/Special_Analysis/df_final.pickle\")","execution_count":0,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"##### The next part creates a synthetic variable (SumResults), basically it resumes the days that te players were recovering by simply converting taking those flags and converting them as a number in base 10. This variable will be useful in further steps."},{"metadata":{"colab":{},"colab_type":"code","id":"_FE_-3Ink-uw","trusted":false},"cell_type":"code","source":"def suma(row):\n    return row[\"DM_M1\"] + row[\"DM_M7\"]*2 + (row[\"DM_M28\"]*2**2) +  (row[\"DM_M42\"]*(2**3)) \t\t\t\n\n#Applying the suma function to all players.\ndf_final[\"SumResults\"] = df_final.apply (lambda row: suma(row), axis=1)","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":141},"colab_type":"code","id":"JNFrCpGUHMyn","outputId":"7cf08446-aa0f-46fe-fe36-dd8c56feee60","trusted":false},"cell_type":"code","source":"#Converting dummy variables and saving the final dataset.\ncolumns_dummies = [ \n                    'RosterPosition',\n                    'StadiumType',\n                    'FieldType',\n                    'Weather',\n                    'PlayType',\n                    'Position',\n                    'PositionGroup',\n                    'Surface',\n                    'event',\n                    #'BodyPart'\n                   ]\n\ndf_final_2 = pd.get_dummies(df_final, columns=columns_dummies)\nprint(df_final_2.columns)\n\n#Shuffling and saving results in the final pickle.\ndf_final_2 = df_final_2.sample(frac=1).reset_index(drop=True)\ndf_final_2.to_pickle(\"/content/drive/My Drive/Special_Analysis/df_final_2.pickle\")","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":355},"colab_type":"code","id":"frN5FDyG5lLQ","outputId":"74b70170-7203-42e3-d49a-820771b42b86","trusted":false},"cell_type":"code","source":"#Reading the final dataset in order to check if everything is in order.\ndf_final_1 = pd.read_pickle('/content/drive/My Drive/Special_Analysis/df_final_2.pickle') \ndf_final_1.head()","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"lSY5xWitU3Po"},"cell_type":"markdown","source":"### <center> Feature Selection </center>"},{"metadata":{},"cell_type":"markdown","source":"##### After all data cleansing, one of our first and most important approaches is the fact to not only employ the variables in order to retrieve useful intel, but to get the MINIMAL amount of variables that better answer the initial questions and their correlations.\n##### Because of this, we used several methods to find out these valuable variables, and at the end we merged all of them to get a better approximation to the answers."},{"metadata":{"colab":{},"colab_type":"code","id":"On5HGkpHaJEX","trusted":false},"cell_type":"code","source":"#First of all we need to load some packages.\n\nimport numpy as np\nimport pandas as pd\nimport random as aleatorio \nimport operator as operador \n\nfrom sklearn import preprocessing\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.ensemble import RandomForestRegressor\n\nfrom sklearn.svm import SVC,LinearSVC\nfrom sklearn.linear_model import ElasticNet\nfrom sklearn.linear_model import LinearRegression\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.metrics import log_loss,roc_auc_score\nfrom sklearn.model_selection import train_test_split","execution_count":0,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### <center> Genetic Algorithms </center>"},{"metadata":{},"cell_type":"markdown","source":"###### This is basically one way to retrieve only those useful variables by creating a bunch of possible models (with their respective training and testing) and selecting those \"stronger\" tan the majority. We test two \"models\": Linear Regression and Logistic Regression.\n\n##### At the end we get those variables whose models were the best but beforehand a target must be created, that is, we get all the players' features and we train our models considering those elements, for instance, we mark a \"1\" if the players' injuries took up to 15 days and their bodyparts consider knee or ankle because those are the injuries with less time of recovery, and \"0\" otherwise.\n\n##### Why are we considering only knee or ankle? Because of the available amount of data of these injuries, it was pointless to add \"foot\" because of its lack of data as you can appreciate bellow."},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":157},"colab_type":"code","id":"dOfGqO0wpCMr","outputId":"f74c7851-cafe-4658-b8ec-696f0fa0a69f","trusted":false},"cell_type":"code","source":"df_final[df_final['BodyPart'] == 'Foot'].groupby(['SumResults']).count()","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":217},"colab_type":"code","id":"Z-2u1kMdmoL_","outputId":"d647debf-4662-45dc-8bca-38497cc9bc80","trusted":false},"cell_type":"code","source":"df_final[df_final['BodyPart'] == 'Knee'].groupby(['SumResults']).count()","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":217},"colab_type":"code","id":"LT124Xa6ozlR","outputId":"b173ba63-2db2-4bc4-8492-fb74927f2700","scrolled":true,"trusted":false},"cell_type":"code","source":"df_final[df_final['BodyPart'] == 'Ankle'].groupby(['SumResults']).count()","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":123},"colab_type":"code","id":"-qk57CgUBEhd","outputId":"7629b564-72d8-4e86-f5ec-a73418b28132","trusted":false},"cell_type":"code","source":"#Creating target with the features defined above\ndef target(row):\n    flag = 0\n    if row[\"SumResults\"] == 1 or row[\"SumResults\"] == 3 or (row[\"SumResults\"] == 7 and row[\"BodyPart\"] == \"Foot\"):\n       flag = 1\n\n    return flag\n\n#Applying the target function to all players.\ndf_final[\"Target\"] = df_final.apply (lambda row: target(row), axis=1)\n\n#Counting results.\nprint(df_final.groupby(['Target']).count())","execution_count":0,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"##### The genetic algorithm its about being executed, it will provide a set of the \"strongest\" variables for the usage of both Logistic and Linear Regression.\n##### For more references, the genetic algorithm used is NSGA-II (Non Sorting Genetic Algorithm II)"},{"metadata":{"trusted":false},"cell_type":"code","source":"class Individual:\n      \"\"\"\n         | La base de toda operación lógica.\n         | Consiste en una abstracción de un elemento simple en función de un ecosistema.\n          Si bien la parte esencial es el cromosoma, en esta implementación se añaden algunos elementos extra\n          con la finalidad de facilitar ciertas operaciones.\n\n         :param complete_chromosome: El cromosoma que conformará al Individuo.\n         \n         :type complete_chromosome: Array\n         \n         :returns: Individual\n         :rtype: Instance\n      \"\"\"\n\n             \n      #dummy columns viene desde la transformación del set, antes\n      def __init__(self,\n                   complete_chromosome,\n                   complete_chromosome_length,\n                   predictive_variable,\n                   training_set,\n                   testing_set,\n                   number_of_columns,\n                   available_global_columns,\n                   forbidden_columns\n                   ):\n          #Se almacenan los atributos para poder usarlos posteriormente.\n          self.__decision_variables = [number_of_columns] \n          self.__complete_chromosome = complete_chromosome\n          self.__complete_chromosome_length = complete_chromosome_length\n          #self.__model = LinearRegression()\n          #self.__model = LogisticRegression(solver = 'liblinear')\n          #self.__model = LinearSVC(random_state=0, tol=1e-5)\n          #self.__model = LinearSVC(random_state = 0, tol = 1e-5,max_iter = 10000)\n          #self.__model = SVC(decision_function_shape='ovo',gamma=\"auto\")\n          self.__model = ElasticNet(alpha=0.1, l1_ratio=0.7)\n          self.__predictive_variable = predictive_variable\n          self.__training_set = training_set\n          self.__testing_set = testing_set\n          self.__available_global_columns = available_global_columns\n          self.__forbidden_columns = forbidden_columns\n          self.__query = [] \n\t\t  \n          \n          #Aquí se almacenarán las funciones objetivo ya evaluadas.\n          #Cabe mencionar que éstas son:\n          #Número de variables en el modelo i.e. número de columnas en el conjunto de entrenamiento.\n          #AUROC \n          #LOGLOSS\n          #\n          #Salvo en el caso del AUROC que se busca maximizar (minimizando el mínimo),\n          #en los demás se opta por la minimización.\n          self.__evaluated_functions = [number_of_columns,0,0]\n\n          #Los siguientes atributos auxilian en las comparaciones hechas para poder encontrar al\n          #mejor Individuo (véase la clase Community).\n          self.__dominates = 0\n          self.__is_dominated = 0\n          self.__rank = 0\n          self.__fitness = 0.0\n          self.__niche_count = 0.0\n\n          #La siguiente variable se utiliza sobre todo en técnicas de Selección.\n          self.__expected_value = 0.0\n\n          self.__generate_query() \n\n\n      def get_complete_chromosome(self):\n          \"\"\"\n             Regresa el cromosoma del Individuo.\n \n             :returns: El cromosoma.\n             :rtype: Array\n          \"\"\"\n\n          #Regresa el valor de la variable asociada al cromosoma.\n          return self.__complete_chromosome\n      \n\t  \n      def get_decision_variables(self):\n          return self.__decision_variables\n\n\n      def get_query(self):\n\t      return self.__query\n\n\t\t  \n      def get_evaluated_functions(self):\n          \"\"\"\n             Regresa las funciones objetivo evaluadas.\n \n             :returns: Las funciones objetivo evaluadas.\n             :rtype: List\n          \"\"\"\n\n          #Se regresa el valor de esta variable.\n          return self.__evaluated_functions\n      \n\t  \n      def get_expected_value(self):\n\t      return self.__expected_value\n\n\n      def get_pareto_dominates(self):\n          \"\"\"\n             Regresa el número de soluciones que son dominadas por \n             el actual Individuo.\n \n             :returns: El número de soluciones dominadas.\n             :rtype: Integer\n          \"\"\"\n\n          #Se regresa este atributo.\n          return self.__dominates\n\n\n      def set_pareto_dominates(self,value):\n          \"\"\"\n             Actualiza el número de soluciones dominadas por el\n             Individuo actual.\n \n             :param value: El valor a actualizar.\n             :type value: Integer\n          \"\"\"\n\n          #Se actualiza al valor correspondiente.\n          self.__dominates = value     \n\n\n      def get_pareto_dominated(self):\n          \"\"\"\n             Regresa el número de soluciones que dominan al \n             Individuo actual.\n \n             :returns: El número de soluciones que dominan a la actual.\n             :rtype: Integer\n          \"\"\"\n\n          #Se regresa el valor\n          return self.__is_dominated\n\n\n      def set_pareto_dominated(self,value):\n          \"\"\"\n             Actualiza el número de soluciones que dominan a la\n             solución actual.\n \n             :param value: El valor a actualizar.\n             :type value: Integer\n          \"\"\"\n\n          #Se actualiza el valor.\n          self.__is_dominated = value     \n\n      \n      def get_rank(self):\n          \"\"\"\n             Regresa la puntuación **(rank)** que se le designó al Individuo\n             **(véase la clase Community)**.\n \n             :returns: El rango.\n             :rtype: Float\n          \"\"\"\n\n          #Se regresa el rango.\n          return self.__rank\n\n\n      def set_rank(self,rank):\n          \"\"\"\n             Actualiza el rango del Individuo.\n \n             :param rank: El valor a actualizar.\n             :type rank: Float\n          \"\"\"\n\n          #Se actualiza al valor actual.\n          self.__rank = rank\n\n\n      def get_fitness(self):\n          \"\"\"\n             Regresa el Fitness del Individuo.\n \n             :returns: El Fitness.\n             :rtype: Float\n          \"\"\"\n\n          #Se regresa el valor.\n          return self.__fitness\n\n\n      def set_fitness(self,value):\n          \"\"\"\n             Actualiza el valor del Fitness.\n \n             :param value: El valor a actualizar.\n             :type value: Float\n          \"\"\"\n\n          #Se actualiza el valor.\n          self.__fitness = value     \n\n     \n      def get_niche_count(self):\n          \"\"\"\n             Regresa el valor niche para el Individuo.\n \n             :returns: El tamaño niche.\n             :rtype: Float\n          \"\"\"\n\n          #Se regresa el valor.\n          return self.__niche_count\n \n\n      def set_niche_count(self,value):\n          \"\"\"\n             Actualiza el valor niche.\n \n             :param value: El valor a actualizar.\n             :type value: Float\n          \"\"\"\n\n          #Se actualiza el valor\n          self.__niche_count = value\n\n\n      def set_expected_value(self,value):\n          \"\"\"\n             Actualiza el valor esperado del Individuo.\n \n             :param value: El valor a actualizar.\n             :type value: Float\n          \"\"\"\n\n          #Se actualiza el valor.\n          self.__expected_value = value\n  \n\n      def evaluate_functions(self):\n          self.__model.fit(self.__training_set[self.__query], self.__training_set[self.__predictive_variable])\n          result = self.__model.predict(self.__testing_set[self.__query])\n          self.__evaluated_functions[1] = -1*roc_auc_score(self.__testing_set[self.__predictive_variable],result)\n          self.__evaluated_functions[2] = log_loss(self.__testing_set[self.__predictive_variable],result)\n          \n          \n      def __generate_query(self):\n          for x in range (self.__complete_chromosome_length):\n              current_gene = self.__complete_chromosome[x]\n              if current_gene == '1':\n                 current_name = self.__available_global_columns[x]\n\t\t\t\t \n                 if current_name not in self.__forbidden_columns:\n                    self.__query.append(current_name)\n                 \n\n      def print_info(self):\n          \"\"\"\n             Imprime las características básicas del Individuo **(en consola)**.\n          \"\"\"\n          print(\"    Complete chromosome: \" + str(self.__complete_chromosome))\n          print(\"    Decision variables: \" + str(self.__decision_variables))\n          print(\"    Evaluated functions (same position than Vector Functions): \" + str(self.__evaluated_functions))\n          print(\"    Rank:    \" + str(self.__rank) + \". Fitness: \" + str(self.__fitness) + \". Expected value: \" + str(self.__expected_value))\n          print(\"    Pareto dominates: \" + str(self.__dominates) + \". Pareto is dominated: \" + str(self.__is_dominated) + \". Niche count: \" + str(self.__niche_count))\n          print(\"\\n\")\n\n\n#*************************************************************************************************************************************\n\n \nclass Population:\n      \"\"\"\n         Consiste en un conjunto de instancias de la clase Individual, proporcionando además métodos y atributos que \n         se manifiestan tanto en grupo como de manera individual.\n\n         :param population_size: El tamaño de la población.\n         :param vector_variables: Lista con las variables de decisión y sus rangos.\n         :param available_expressions: Diccionario que contiene algunas funciones escritas como azúcar sintáctica\n                                       para que puedan ser utilizadas más fácilmente por el usuario y evaluadas\n                                       más ŕapidamente en el programa **(véase Controller/XML/PythonExpressions.xml)**.\n         \n         :type population_size: Integer\n         :type vector_variables: List\n         \n         :returns: Population\n         :rtype: Instance\n      \"\"\"\n\n\n      def __init__(\n               \t   self,\n                   population_size,\n\t\t\t\t   vector_variables,\n\t\t\t\t   predictive_variable,\n                   training_set,\n                   testing_set,\n\t\t\t\t   available_global_columns,\n\t\t\t\t   forbidden_columns\n                  ):\n\n          #Se almacenan los valores para que puedan ser usado posteriormente, a veces por clases externas .\n          #Los siguientes atributos indican características básicas que tendrán los individuos.\n          self.__population_size = population_size\n          self.__vector_variables = vector_variables \n          self.__predictive_variable = predictive_variable\n          self.__training_set = training_set\n          self.__testing_set = testing_set\n          self.__available_global_columns = available_global_columns\n          self.__forbidden_columns = forbidden_columns\n\t\t  \n          #La siguiente estructura almacenará a los individuos, que no es otra cosa que un arreglo de tamaño\n          #fijo.\n          self.__population = [0.0]*self.__population_size  \n\n          #las siguientes variables contendrán números que se calcularán con métodos grupales\n          self.__total_fitness = 0.0\n          self.__total_expected_value = 0.0\n\n          #Esta estructura almacena los valores mínimo y máximo respectivamente de cada \n          #variable de decisión. Se necesitan para poder obtener el valor sigma share.\n          self.__decision_variables_extreme_values = [[0,0]]\n\n          #Esta estructura almacena los valores mínimo y máximo respectivamente de cada \n          #función objetivo. Se necesitan para poder obtener el valor sigma share.\n          #Esl 3 es por cada función objetivo que se quiere calcular\n          self.__objective_functions_extreme_values = [[0,0]]*3\n\n\n      def get_individuals(self):\n          \"\"\"\n             Regresa los individuos de la Población.\n \n             :returns: Estructura que contiene a los Individuos de la Población.\n             :rtype: Array\n          \"\"\"\n\n          #Se regresa la estructura.\n          return self.__population\n\n\n      def get_size(self):\n          \"\"\"\n             Otorga el tamaño de la Población.\n \n             :returns: El tamaño de la Población.\n             :rtype: Integer\n          \"\"\"\n         \n          #Se regresa el atributo concerniente al tamaño de la población.\n          return self.__population_size\n          \n\n      def get_vector_variables(self):\n          \"\"\"\n             Regresa el vector de variables de decisión.\n \n             :returns: Conjunto que contiene las variables de decisión con sus rangos.\n             :rtype: List\n          \"\"\"\n\n          #Se obtiene el atributo relativo a las variables de decisión.\n          return self.__vector_variables\n\n\n      def get_total_fitness(self):\n          \"\"\"\n             Captura el Fitness total de la Población.\n \n             :returns: El valor del Fitness poblacional.\n             :rtype: Float\n          \"\"\"\n         \n          #Se regresa el valor concerniente al Fitness total de la población.\n          return self.__total_fitness\n\n\n      def set_total_fitness(self,value):\n          \"\"\"\n             Actualiza el Fitness total de la Población.\n \n             :param value: El valor a actualizar.\n\n             :type value: Float\n          \"\"\"\n         \n          #Se actualiza el valor de la variable correspondiente.\n          self.__total_fitness = value\n\n\n      def get_total_expected_value(self):\n          \"\"\"\n             Regresa el valor esperado de la Población.\n \n             :returns: El valor esperado.\n             :rtype: Float\n          \"\"\"\n         \n          #Se regresa el valor esperado de la población.\n          return self.__total_expected_value\n\n\n      def get_objective_functions_extreme_values(self):\n          \"\"\"\n             Regresa el listado de los valores máximo y mínimo de las \n             funciones objetivo para el cálculo de sigma share.\n \n             :returns: El listado con los valores máximo y mínimo para las\n                       funciones objetivo.\n             :rtype: List\n          \"\"\"\n         \n          #Se regresa el listado de las deltas.\n          return self.__objective_functions_extreme_values\n\n\n      def set_objective_functions_extreme_values(self,objective_functions_extreme_values):\n          \"\"\"\n             Actualiza el listado de valores máximo y mínimo de las\n             funciones objetivo para el cálculo de sigma share.\n             \n             :param objective_functions_extreme_values: Una lista con los valores máximo y mínimo\n                                                        de cada una de las funciones objetivo.\n\n             :type objective_functions_extreme_values: List\n          \"\"\"\n         \n          #Se actualiza el valor de la variable en cuestión.\n          self.__objective_functions_extreme_values = objective_functions_extreme_values\n \n\n      def get_decision_variables_extreme_values(self):\n          \"\"\"\n             Regresa el listado de los valores máximo y mínimo de las \n             variables de decisión para el cálculo de sigma share.\n \n             :returns: Una colección con los valores máximo y mínimo para las\n                       variables de decisión.\n             :rtype: Dictionary\n          \"\"\"\n         \n          #Se regresa el listado de las deltas.\n          return self.__decision_variables_extreme_values\n\n\n      def set_decision_variables_extreme_values(self,decision_variables_extreme_values):\n          \"\"\"\n             Actualiza el listado de valores máximo y mínimo de las\n             variables de decisión para el cálculo de sigma share.\n             \n             :param decision_variables_extreme_values: Un conjunto con los valores máximo y mínimo\n                                                       de cada una de las variables de decisión.\n\n             :type decision_variables_extreme_values: Dictionary\n          \"\"\"\n         \n          #Se actualiza el valor de la variable en cuestión.\n          self.__decision_variables_extreme_values = decision_variables_extreme_values\n \n\n      def add_individual(self,position,number_of_columns,complete_chromosome,complete_chromosome_length):\n          \"\"\"\n             Añade un individuo a la Población.\n              \n             :param position: La posición dentro del arreglo de individuos \n                              donde se colocará el nuevo elemento.\n             :param complete_chromosome: El cromosoma del Individuo.\n\n             :type position: Integer\n             :type complete_chromosome: Array\n          \"\"\"\n\n          #Se agrega dentro del arreglo de individuos la instancia del individuo nuevo.\n          #Dado que todos los atributos para crear una instancia ya han sido capturados\n          #sólo se necesita el nuevo cromosoma. Como se puede apreciar, este método en \n          #realidad sólo sustituye elementos, no agrega posiciones extra para colocas\n          #nuevos individuos.\n\t\t  \n          self.__population[position] = Individual(\n                                                   complete_chromosome,\n                                                   complete_chromosome_length,\n                                                   self.__predictive_variable,\n                                                   self.__training_set,\n                                                   self.__testing_set,\n                                                   number_of_columns,\n                                                   self.__available_global_columns,\n                                                   self.__forbidden_columns,       \n                                                  )    \n\n                    \n      def calculate_population_properties(self):\n          \"\"\"\n             Calcula atributos individuales con base en los valores de toda la Población.\n          \"\"\"\n\n          #Por cada individuo se hace lo siguiente:\n          for individual in self.__population:\n\n              #Se calcula el valor esperado de la población, el cual consiste en: \n              #fitness/(fitness total/tamaño de la población)\n              try:\n                  expected_value = individual.get_fitness()/(self.__total_fitness/self.__population_size)\n\n              except:\n                  expected_value = 0.0\n\n              #Se agrega este valor calculado en el individuo actual.\n              individual.set_expected_value(expected_value)\n\n              #Se calcula el valor esperado de la población\n              self.__total_expected_value += expected_value     \n\n\n      def sort_individuals(self,method,is_descendent):\n          \"\"\"\n             Ordena los Individuos de acuerdo a algún criterio dado.\n         \n             :param method: El método o atributo sobre el cual se hará la comparación.\n             :param is_descendent: Indica si el ordenamiento es ascendente o descendente.\n \n             :type method: String\n             :type is_descendent: Boolean\n          \"\"\"\n\n          #Se ordena la población con base en \"method\" y el orden lo indica el atributo \"is_descendant\".\n          self.__population.sort(key = operador.methodcaller(method),reverse = is_descendent)\n\n\n      def shuffle_individuals(self):\n          \"\"\"\n             Desordena los elementos de la Población.\n          \"\"\"\n\n          #Se desordena la lista de individuos.\n          aleatorio.shuffle(self.__population)\n\n\n      def print_info(self):\n          \"\"\"\n             Imprime en texto las características de los Individuos\n             de la Población, tanto grupales como individuales **(en consola)**.\n          \"\"\"\n\n          print(\"Total fitness: \" + str(self.__total_fitness))\n          print(\"Total expected value: \" + str(self.__total_expected_value))\n          print(\"Individuals: \")\n          for x in range (self.__population_size):\n              print(\"Number: \" + str(x))\n\n              #Se imprime cada individuo.\n              self.__population[x].print_info()      \n\n\n#*************************************************************************************************************************************\n\n    \nclass Community:\n      \"\"\"\n         | Proporciona toda la infraestructura lógica para poder construir poblaciones y operar con éstas,\n          además de transacciones relacionadas con sus elementos de manera individual.\n         | Se le llama Community porque aludiendo a su significado una Community **(ó Comunidad)**\n          consta de al menos una Population **(o Población)**. De esta manera se deduce que en algún momento\n          habrán métodos que involucren a más de una población.\n\n         :param vector_variables: Lista que contiene las variables de decisión previamente \n                                  saneadas por Controller/Controller.py.\n         :param sharing_function_parameters: Diccionario que contiene todos los parámetros adicionales a la técnica\n                                             de Fitness seleccionada por el usuario.\n         :param selection_parameters: Diccionario que contiene todos los parámetros adicionales a la técnica\n                                      de selección **(Selection)** usada por el usuario.\n         :param crossover_parameters: Diccionario que contiene todos los parámetros adicionales a la técnica\n                                      de cruza **(Crossover)** manejada por el usuario.\n         :param mutation_parameters: Diccionario que contiene todos los parámetros adicionales a la técnica\n                                     de mutación **(Mutation)** seleccionada por el usuario.\n         \n         :type vector_variables: List\n         :type representation_parameters: Dictionary\n         :type sharing_function_parameters: Dictionary\n         :type selection_parameters: Dictionary\n         :type crossover_parameters: Dictionary\n         :type mutation_parameters: Dictionary\n         :returns: Community\n         :rtype: Instance\n      \"\"\"\n\n      def __init__(\n                   self,\n                   vector_variables,\n                   sharing_function_parameters,\n                   selection_parameters,\n                   crossover_parameters,\n                   mutation_parameters,\n                   predictive_variable,\n                   training_set,\n                   testing_set,\n                   available_global_columns,\n                   forbidden_columns\n                   ):\n\n          #Se almacenan todos los elementos en atributos privados para\n          #su posterior uso. Dado que se crea una Community por cada MOEA, conviene\n          #tener las características irrepetibles para un desempeño mayor.\n          self.__vector_variables = vector_variables\n          self.__sharing_function_parameters = sharing_function_parameters\n          self.__selection_parameters = selection_parameters\n          self.__crossover_parameters = crossover_parameters\n          self.__mutation_parameters = mutation_parameters\n          \n          self.__predictive_variable = predictive_variable\n          self.__training_set = training_set\n          self.__testing_set = testing_set\n          self.__available_global_columns = available_global_columns\n          self.__forbidden_columns = forbidden_columns\n            \n          self.__chromosome_size = len(available_global_columns)            \n          \n          #Elemento que albergará el tamaño de cromosomas por cada función objetivo.\n          self.__length_chromosomes = []\n\n          #Se añade el vector de variables a los parámetros de Sharing Function, ya que este valor se utilizará\n          #para el cálculo de Sigma Share (véase Model/SharingFunction).         \n          self.__sharing_function_parameters[\"vector_variables\"] = self.__vector_variables\n\n      \n      def __create_chromosome(self):\n          \"\"\"\n          \"\"\"\n          number_of_columns = 0\n          chromosome = \"\"\n    \n          for x in range (self.__chromosome_size):\n             current_gene = str(aleatorio.randint(0,1))\n             chromosome += current_gene\n        \n             if current_gene == \"1\":\n                number_of_columns += 1\n             \n          return number_of_columns, chromosome\n\n\n      def get_chromosome_size(self):\n          \"\"\"\n          \"\"\"\n          return self.__chromosome_size\n\n\n      def init_population(self,population_size):\n          \"\"\"\n             Crea una población de manera aleatoria.\n\n             :param population_size: El tamaño de la población. \n\n             :type population_size: Integer\n             :returns: Population\n             :rtype: Instance\n          \"\"\"\n\n          #Se ejecuta la función \"calculate_length_subchromosomes\", la cual regresa el tamaño del cromosoma\n          #por cada función objetivo, creando así un super cromosoma que constará de todos los tamaños de los subcromosomas. \n          #El resultado depende de la técnica de representación utilizada (véase Model/ChromosomalRepresentation). \n          self.__length_subchromosomes = [self.__chromosome_size]\n\n          #Se agrega el tamaño de los subcromosomas como parámetro adicional para las técnicas de Sharing Function,\n          #de la cual se hablará más adelante.\n          self.__sharing_function_parameters[\"length_subchromosomes\"] = self.__length_subchromosomes\n\n          #A continuación se crea una instancia de la clase Population, cuyos elementos aún no se inicializan.\n          population = Population(\n                                  population_size,\n                                  self.__vector_variables,\n\t\t\t\t\t\t\t\t  self.__predictive_variable,\n\t\t\t\t\t\t\t\t  self.__training_set,\n\t\t\t\t\t\t\t\t  self.__testing_set,\n\t\t\t\t\t\t\t\t  self.__available_global_columns,\n\t\t\t\t\t\t\t\t  self.__forbidden_columns\n                                 )\n\n          #Entonces, con base en el resultado de self.__length_subchromosomes se inicializan los Individuos\n          #de la Población, indicando además el tamaño de cada subcromosoma y por ende, el tamaño del super\n          #cromosoma.\n          for x in range (population_size):\n\n              #Se manda llamar la función \"create_chromosome\" dependiendo de la representación elegida (véase\n              #Model/ChromosomalRepresentation).\n              number_of_columns,complete_chromosome = self.__create_chromosome()\n              \n              #Se crea un individio con base en el cromosoma creado.\n              population.add_individual(x,number_of_columns,complete_chromosome,self.__chromosome_size)\n  \n          return population\n\n\n      def create_population(self,set_chromosomes):\n          \"\"\"\n             Crea una población usando un conjunto de cromosomas como base.\n\n             :param set_chromosomes: Conjunto de cromosomas. \n\n             :type set_chromosomes: List\n             :returns: Population\n             :rtype: Instance\n          \"\"\"\n\n          #Se crea una instancia de la población (Population) con sus elementos aún no inicializados.\n          population = Population(\n                                  len(set_chromosomes),\n\t\t\t\t\t\t\t\t  self.__vector_variables,\n\t\t                          self.__predictive_variable,\n\t\t\t\t\t\t\t\t  self.__training_set,\n\t\t\t\t\t\t\t\t  self.__testing_set,\n\t\t\t\t\t\t\t\t  self.__available_global_columns,\n\t\t\t\t\t\t\t\t  self.__forbidden_columns\n                                  )\n\n          #Se inicializan cada individuo con un elemento del conjunto de cromosomas.\n          for x in range(len(set_chromosomes)):\n              number_of_columns = 0\n              current_chromosome = set_chromosomes[x]\n              \n              for gene in current_chromosome:\n                  if gene == \"1\":\n                     number_of_columns += 1 \n                     \n              population.add_individual(x,number_of_columns,current_chromosome,self.__chromosome_size)\n\n          return population\n\n    \n      def evaluate_population_functions(self,population):\n          \"\"\"\n             | Evalúa cada uno de los subcromosomas de los individuos de la \n              población **(Population)**.\n             | De manera adicional obtiene el listado de los valores extremos tanto\n              de variables de decisión como de funciones objetivo para el \n              cálculo del sigma share **(véase el método __using_sharing_function)**. \n           \n             :param population: La población sobre la que se hará la operación. \n             :type population: Instance\n          \"\"\"\n\n          #Se obtienen las estructuras donde se almacenarán los valores mínimo y máximo para cada variable\n          #de decisión y lo mismo para cada función objetivo respectivamente (estos valores se suelen utilizar\n          #para el cálculo del sigma share, el cual es un factor determinante en el Sharing Function).\n          decision_variables_extreme_values = population.get_decision_variables_extreme_values()\n          objective_functions_extreme_values = population.get_objective_functions_extreme_values()\n          \n          #Se toman los individuos de la Población.\n          individuals = population.get_individuals()\n          for individual in individuals:\n\n              #Por cada Individuo se toma su correspondiente cromosoma (que será en realidad el súper cromosoma).\n              #complete_chromosome = individual.get_complete_chromosome()          \n\n              #A continuación se devuelven las variables de decisión evaluadas por cada individuo con ayuda del método\n              #\"evaluate_subchromosomes\". La obtención de las variables de decisión dependerá de la \n              #representación usada (véase Model/ChromosomalRepresentation).\n              #decision_variables = getattr(self.__representation_instance,\"evaluate_subchromosomes\")(complete_chromosome,self.__length_subchromosomes,self.__vector_variables,self.__number_of_decimals,self.__representation_parameters)\n\n              #Al final en el Individuo se evalúan las funciones objetivo con base en las variables de decisión recién obtenidas.\n              #(véase la clase Individual).\n              individual.evaluate_functions()\n\n              #Por cada Individuo se obtienen sus variables de decisión evaluadas, así como las\n              #respectivas funciones objetivo.\n              current_evaluated_variables = individual.get_decision_variables()\n              current_evaluated_functions = individual.get_evaluated_functions()\n\n              for x in range (1):\n                  #----------------------------------------------------------------------------------\n\n                  #Se obtiene el valor de la función objetivo actual.\n                  current_decision_variable_value = current_evaluated_variables[x] \n\n                  #A continuación se almacenan los valores mínimo y máximo actuales\n                  #de la variable de decisión actual.\n                  current_extreme_values = decision_variables_extreme_values[x]\n                  current_minimal_value = current_extreme_values[0]\n                  current_maximal_value = current_extreme_values[1]\n\n                  #Si el valor actual es menor al que estaba guardado, se hace la actualización\n                  #correspondiente para el valor mínimo.\n                  if current_decision_variable_value < current_minimal_value:\n                     decision_variables_extreme_values[x][0] = current_decision_variable_value \n\n                  #Si el valor actual es mayor al que estaba guardado, se hace la actualización\n                  #correspondiente para el valor máximo.\n                  if current_decision_variable_value > current_maximal_value:\n                     decision_variables_extreme_values[x][1] = current_decision_variable_value\n\n\n              #Ahora se realiza la actualización para los valores mínimo y máximo de cada\n              #función objetivo.\n              for x in range (3):\n               \n                  #Se obtiene el valor de la función objetivo actual.\n                  current_function_value = current_evaluated_functions[x] \n\n                  #A continuación se almacenan los valores mínimo y máximo actuales\n                  #de la variable de decisión actual.\n                  current_extreme_values = objective_functions_extreme_values[x]\n                  current_minimal_value = current_extreme_values[0]\n                  current_maximal_value = current_extreme_values[1]\n\n                  #Si el valor actual es menor al que estaba guardado, se hace la actualización\n                  #correspondiente para el valor mínimo.\n                  if current_function_value < current_minimal_value:\n                     objective_functions_extreme_values[x][0] = current_function_value \n\n                  #Si el valor actual es mayor al que estaba guardado, se hace la actualización\n                  #correspondiente para el valor máximo.\n                  if current_function_value > current_maximal_value:\n                     objective_functions_extreme_values[x][1] = current_function_value\n                     \n\n          #Al final se reinsertan en la población los valores actualizados para los valores mínimos y máximos\n          #de tanto las variables de decisión y las funciones objetivo.\n          population.set_decision_variables_extreme_values(decision_variables_extreme_values)\n          population.set_objective_functions_extreme_values(objective_functions_extreme_values)\n\n\n      def __compare_dominance(self,current,challenger,allowed_functions):\n          \"\"\"\n             .. note:: Este método es privado.\n \n             Permite realizar la comparación de las funciones objetivo de los \n             individuos current y challenger tomadas una a una para indicar así quién es el dominado y quién\n             es el que domina. Cabe mencionar que más apropiadamente se le conoce como dominancia\n             fuerte de Pareto.\n\n             :param current: El Individuo inicial para comprobar dominancia.\n             :param challenger: El Individuo que reta al inicial para comprobar dominancia.\n             :param allowed_functions: Lista que indica cuáles son las funciones objetivo que deben \n                                       compararse.\n\n             :type current: Instance\n             :type challenger: Instance\n             :type allowed_functions: List\n             :returns: True si current domina a challenger, False en otro caso.\n             :rtype: Boolean\n          \"\"\"\n          \n          #Aquí se almacenará el resultado.\n          result = False\n\n          #Aquí se almacenan los contadores para cuando un valor es menor a otro (lt, less than)\n          #y un valor es menor o igual a otro (let, less equal than).\n          lt = 0\n          let = 0\n\n          #Se toman las funciones de ambos individuos.\n          current_evaluated_functions = current.get_evaluated_functions()\n          challenger_evaluated_functions = challenger.get_evaluated_functions()\n\n          #Aquí se indica las posiciones de las funciones objetivo que se deben comparar en caso\n          #de que allowed_functions NO contenga la palabra \"All\".\n          if allowed_functions != \"All\":\n             current_evaluated_functions = [current_evaluated_functions[i] for i in allowed_functions] \n             challenger_evaluated_functions = [challenger_evaluated_functions[i] for i in allowed_functions] \n\n          #A continuación se comṕaran las funciones objetivo tomadas una a una considerando ya las que\n          #fueron filtradas por la lista allowed_functions.\n          for x in range(len(current_evaluated_functions)):\n\n                 #Se toman los valores correspondientes a la posición indicada.\n                 current_value = current_evaluated_functions[x]\n                 challenger_value = challenger_evaluated_functions[x]\n                 \n                 #Aquí radica la comparación fuerte de Pareto, para que current domine a challenger\n                 #éste tiene que ser en todas sus funciones objetivo menor e igual con respecto a challenger\n                 #y existir al menos una función objetivo en la que sea estrictamente menor.\n                 \n                 #Se busca la condición contraria, que current_value sea mayor que challenger,\n                 #en este caso automáticamente se regresa un resultado Falso.\n                 if current_value > challenger_value:\n                    return result\n\n                 #Si current_value y challenger_value son\n                 #estrictamente menores se cumple la condición <   \n                 if current_value < challenger_value:\n                    lt += 1\n\n                 #Si current_value y challenger_value son iguales \n                 #se cumple la condición de <=\n                 if current_value <= challenger_value:\n                    let += 1\n                       \n          #Para que se pueda considerar dominancia y dado que ya se consideró\n          #el caso en que el current_value es mayor que challenger, basta con\n          #verificar que los contadores correspondientes sean mayores que 0. \n          if lt > 0 and let > 0:\n             result = True\n    \n          #Se regresa el resultado.\n          return result\n          \n               \n      def calculate_population_pareto_dominance(self,population,allowed_functions):\n          \"\"\"\n             Realiza la comparación de dominancia entre todos los elementos de la Población con base\n             en la evaluación de sus funciones objetivo.\n             \n             :param population: La Población sobre la que se hará la operación.\n             :param allowed_functions: Lista que indica las funciones objetivo permitidas para hacer la \n                                       comparación.\n\n             :type population: Instance\n             :type allowed_functions: List\n          \"\"\"\n\n          #Se toman los individuos de la Población.\n          individuals = population.get_individuals()\n\n          #A continuación se hace una comparación de todos los Individuos contra todos; es por ello que\n          #se crea un ciclo anidado paara poder hacer tarl operación.\n          for x in range(population.get_size()):\n\n              #Se obtiene el individuo current\n              current = individuals[x]\n              \n              for y in range(population.get_size()):     \n\n                  #Aquí se garantiza que un mismo individuo no se puede comparar consigo mismo.\n                  if y != x:\n\n                     #Se obtiene el individuo challenger.\n                     challenger = individuals[y]\n\n                     #Se ejecuta la operación de comparación entre current y challenger.\n                     dominance_condition = self.__compare_dominance(current,challenger,allowed_functions)\n                    \n                     #Si se llega al resultado True, significa que current domina a challenger\n                     #o equivalentemente challenger es dominado por current, por lo cual se actualizan sus\n                     #respectivos contadores que controlan el número de individuos que dominan y son dominados.\n                     #(véase Model/Community/Population/Individual.py).\n                     if dominance_condition == True:\n                        #Se actualiza el valor de current que indica que ahora domina a uno más.\n                        current.set_pareto_dominates(current.get_pareto_dominates() + 1)\n\n                        #Se actualiza el valor de challenger que indica que ahora es dominado por uno más.\n                        challenger.set_pareto_dominated(challenger.get_pareto_dominated() + 1)\n \n\n      def assign_goldberg_pareto_rank(self,population,additional_info = False,allowed_functions = \"All\"):\n          \"\"\"\n             | Asigna una puntuación **(ó rank)** a cada uno de los Individuos de una Población con base en su dominancia\n              de Pareto.\n             | En términos generales, el algoritmo trabaja con niveles, es decir, primero toma los Individuos no\n              dominados y les asigna un valor 0, luego los elimina del conjunto y nuevamente aplica la \n              operación sobre los no dominados del nuevo conjunto, a los que les asigna el valor 1, y así\n              sucesivamente hasta no quedar Individuos.\n             | Esta técnica es usada principalmente por N.S.G.A. II.\n\n             :param population: La Población sobre la que se hará la operación.\n             :param additional_info: Un valor que le indica a la función que debe regresar información \n                                     adicional.\n             :param allowed_functions: Lista que contiene las posiciones de las funciones que son admisibles \n                                       para hacer comparaciones. Por defecto tiene el valor \"All\".\n \n             :type population: Instance\n             :type additional_info: Boolean\n             :type allowed_functions: List\n             :returns: Si additional_info es True: un arreglo con dos elementos: en el primero \n                       se almacena una lista con los niveles de dominancia disponibles, mientras que el \n                       segundo consta de una estructura que contiene todos los posibles niveles y asociados \n                       a éstos, los cromosomas de los Individuos que los conforman.\n                       Si additional_info es False: el método es void **(no regresa nada)**.\n             :rtype: List\n          \"\"\"\n\n          #Contiene los cromosomas de los Individuos del nivel 0.\n          f1 = []\n          \n          #Contiene los identificadores asociados a los Individuos que conforman el \n          #nivel \"i\" actual.\n          current_fi = []\n\n          #Aquí se alojarán los niveles de dominancia que no estén vacíos.\n          pareto_fronts_list = []\n\n          #En esta estructura se almacenarán los identificadores de los Individuos que están\n          #siendo dominados por un Individuo \"p\".\n          sp = {}\n          \n          #Se crea una estructura donde se guardarán los niveles, donde para cada nivel se almacenan\n          #los cromosomas de los Individuos que constituyen cada nivel.\n          pareto_fronts = {}\n      \n          #Se obtiene el tamaño de la Población.\n          population_size = population.get_size()\n \n          #Se obtienen los Individuos de una Población.\n          individuals = population.get_individuals()\n\n          #Esta variable almacena el nivel de dominancia actual.\n          current_front = population_size\n\n          #Por cada Individuo en la Población se hace lo siguiente.\n          for x in range (population_size):\n\n              #Este número indicará el número de soluciones que \n              #dominan a una solución \"x\".\n              np = 0\n\n              #Se crea en la estructura apropiada una referencia de \n              #los Individuos que dominará la solución \"x\".\n              sp[x] = []\n\n              #Se obtiene el Individuo actual.\n              current_individual = individuals[x]\n\n              #Se verifica el proceso de dominancia con los demás Individuos.\n              for y in range (population_size):\n\n                  #La dominancia no tiene sentido para el mismo Individuo, de modo que\n                  #se descarta.\n                  if x != y:\n                     \n                     #Se obtiene el Individuo que será comparado con el Individuo x.\n                     challenger = individuals[y]\n\n                     #Se verifica la dominancia de x con y. De ser positiva la operación, se agrega\n                     #el identificador \"y\" a la lista de los elementos dominados por el Individuo \"x\".\n                     if self.__compare_dominance(current_individual,challenger,allowed_functions) == True:\n                        sp[x].append(y)\n\n                     #En caso de ser negativo se verifica que \"y\" domine a \"x\", por lo que de ser \n                     #verdadero se incrementa el número de soluciones que dominan a \"x\".\n                     elif self.__compare_dominance(challenger,current_individual,allowed_functions) == True:        \n                          np += 1\n\n              #Se actualiza la información del número de dominados para una solución \"x\"\n              #en la estructura apropiada.\n              individuals[x].set_pareto_dominated(np)\n\n              #Se busca el valor np más bajo, ya que no necesariamente es 0.\n              if np < current_front:\n                 current_front = np \n              \n          #Se obtienen los Invididuos cuyo np haya sido el más bajo,\n          #esto corresponde al primer nivel de dominancia 0.\n          for identifier in range(population_size):\n             \n              #Se obtiene el Individuo actual.\n              current_individual = individuals[identifier]\n\n              #Si el número de individuos que dominan al Individuo actual\n              #es el mínimo entonces se agrega en la estructura f1.\n              if current_individual.get_pareto_dominated() == current_front:\n\n                 #Se actualiza el ranking del Individuo de la estructura f1 al \n                 #frente actual + 1                   \n                 current_individual.set_rank(current_front + 1)\n\n                 #Se añade el cromosoma correspondiente a la estructura f1.\n                 f1.append(current_individual.get_complete_chromosome())      \n\n                 #Se añade el identificador del Individuo correspondiente a la \n                 #estructura f1.\n                 current_fi.append(identifier)\n                 \n          #El frente de dominancia de nivel 0 es el que se conforma con los cromosomas de los \n          #Individuos que no están dominados.\n          pareto_fronts[current_front] = f1\n\n          #Se añade a la lista el nivel de dominancia inicial.\n          pareto_fronts_list.append(current_front)\n          \n          #A continuación se incrementa el nivel de dominancia actual en una unidad.\n          current_front += 1\n           \n          #Mientras la lista con los identificadores no sea vacía\n          #se hace lo siguiente:\n          while current_fi != []:\n                #print(\"entro en el fi porque hay cosas\"\n                #Las siguientes estructuras albergarán\n                #los identificadores y cromosomas de los Individuos\n                #de los siguientes niveles de dominancia respectivamente.\n                h_ids = []\n                h_chromosomes = []\n\n                #Se toman los identificadores del conjunto actual. \n                for z in current_fi:\n\n                    #Se obtiene el conjunto de identificadores relativos a las \n                    #soluciones que domina el Individuo asociado al identificador \"z\".\n                    current_sp = sp[z]\n\n                    #Se usa cada identificador \"k\".\n                    for k in current_sp:\n\n                        #A continuación se obtiene el Individuo correspondiente\n                        #al identificador \"k\".\n                        q = individuals[k]#population_dict[k]\n\n                        #Se disminuye en una unidad el valor de los Individuos\n                        #que dominan a q.\n                        nq = q.get_pareto_dominated() - 1 \n                        q.set_pareto_dominated(nq)\n\n                        #Si dicho valor es 0 significa que es parte del nivel de dominancia actual\n                        #por lo que debe de agregarse su cromosoma en la estructura h_chromosome y \n                        #su identificador en h_id.\n                        if nq == 0:\n                      \n                           #Se actualiza el número de elementos que dominan a la solución actual.\n                           q.set_pareto_dominated(current_front)\n\n                           #Se actualiza el rango actual\n                           q.set_rank(current_front + 1)\n\n                           #Se agrega el identificador.\n                           h_ids.append(k)\n \n                           #Se agrega el cromosoma.\n                           h_chromosomes.append(q.get_complete_chromosome())\n\n                #Puede darse el caso en que un nivel de dominancia se encuentre vacío, \n                #por ello es que se verifica que el nivel actual no esté vacío.                                  \n                if h_chromosomes != []:\n\n                   #Se agrega el nivel actual con el resultado de la lista h_chromosome\n                   pareto_fronts[current_front] = h_chromosomes\n\n                   #Este valor se agrega a una lista que indica que un nivel de dominancia no está\n                   #vacío.\n                   pareto_fronts_list.append(current_front)\n\n                #Se actualiza el nivel de dominancia actual.\n                current_front += 1 \n                   \n                #La estructura de identificadores pasa a ser el nuevo current_fi para que puedan\n                #verificarse nuevos elementos.\n                current_fi = h_ids\n        \n          #Al final se regresa la lista que contiene la lista de niveles de dominancia disponibles\n          #y la estructura con los niveles de dominancia y sus respectivos elementos.\n          if additional_info == True:\n             return [pareto_fronts_list,pareto_fronts]\n\n\n      def assign_population_fitness(self,population):\n          \"\"\"\n             Se implementa la asignación de Proportional Fitness **(ó Fitness Proporcional)**\n             con base en la información especificada con anterioridad.\n          \"\"\"\n\n          #Este valor almacenará la suma de F0 de cada Individuo.\n          total_values = 0.0\n\n          #Este valor almacenará el Fitness total de la Población.\n          total_fitness = 0.0\n\n          #Se obtiene el número de Individuos de la Población.\n          population_size = population.get_size()\n\n          #Primero se obtiene la suma de F0 de todos los Individuos.\n          for individual in population.get_individuals():\n              total_values += population_size - individual.get_rank()\n\n          #Usando la cantidad anterior, se hace lo siguiente para cada Individuo:\n          for individual in population.get_individuals():\n\n              #Se calcula el Fitness de manera proporcional.\n              current_fitness = (population_size - individual.get_rank())/total_values\n\n              #Se actualiza el Fitness del Individuo.\n              individual.set_fitness(current_fitness)\n\n              #Se agrega el Fitness actual al total de Fitness de la Población.\n              total_fitness += current_fitness\n\n \n          #Se actualiza el Fitness de la Población.   \n          population.set_total_fitness(total_fitness)\n\n          #Se actializan propiedades relativas al Fitness para cada Individuo.\n          population.calculate_population_properties()\n    \n    \n      def __calculate_distance(self,individual_i,individual_j,sharing_function_parameters):\n          \"\"\"\n             Con base en la información proporcionada anteriormente, se implementa\n             el cálculo de la distancia entre dos Individuos apoyándose de la técnica\n             conocida como Distancia de Hamming **(ó Hamming Distance)**.\n          \"\"\"\n\n          #Aqui se almacenará la distancia de Hamming.\n          hamming_distance = 0.0\n    \n          #Se toman los cromosomas de los Individuos participantes.\n          chromosome_i = individual_i.get_complete_chromosome()\n          chromosome_j = individual_j.get_complete_chromosome()\n    \n          #Se obtiene la longitud de una de las cadenas cromosómicas, la cual es la misma\n          #en ambos Individuos.\n          length_chromosome = len(chromosome_i)\n\n          #Por cada gen en los cromosomas se hace lo siguiente:\n          for x in range (length_chromosome):\n \n              #Se obtiene el gen de cada Individuo localizado en la misma posición\n              #en el cromosoma.\n              gen_i = chromosome_i[x]\n              gen_j = chromosome_j[x]\n\n              #Se realiza la comparación pertinente, si sus alelos son diferentes\n              #se actualiza el valor de la Distancia de Hamming.\n              if gen_i != gen_j:\n                 hamming_distance += 1\n      \n          #Se regresa la distancia de Hamming.\n          return hamming_distance\n          \n        \n      def __calculate_sigma_share(population,sharing_function_parameters):\n          \"\"\"\n             Basándose en las indicaciones mencionadas anteriormente, se\n             lleva a cabo la implementación de la obtención del valor Sigma Share.\n          \"\"\"\n        \n          #Aquí se almacena el tamaño total del cromosoma.\n          distance = 0.0\n\n          #A continuación se obtienen las longitudes de los subcromosomas\n          #(véase Model/ChromosomalRepresentation).\n          length_subchromosomes = sharing_function_parameters[\"length_subchromosomes\"]\n\n          #Se obtiene de la sección View el parámetro asociado al porcentaje de tolerancia,\n          #es decir, la tolerancia máxima de genes del cromosoma visto como porcentaje\n          #para considerar a dos cromosomas dentro del mismo Niche.\n          percentage_of_acceptance = sharing_function_parameters[\"percentage_of_acceptance\"]\n \n          #Se suman las longitudes de los subcromosomas y se almacena el resultado\n          #en la variable distance.\n          for current_length in length_subchromosomes: \n              distance += current_length\n\n          #Se regresa el número máximo real de genes en el cromosoma que permiten\n          #considerar una distancia como válida, es decir, que se encuentra en el mismo Niche.\n          return int(distance*percentage_of_acceptance)\n\n\n      def __using_sharing_function(self,individual_i,individual_j,alpha_share,sigma_share):\n          \"\"\"\n             .. note:: Este método es privado.\n\n             | Devuelve un valor que ayuda al cálculo del Sharing Function.\n             | A grandes rasgos el sharing function sirve para hacer una selección más precisa de los\n              mejores Individuos cuando se da el caso de que tienen el mismo número de Individuos dominados.\n\n             :param individual_i: Individuo sobre el que se hará la operación.\n             :param individual_j: Individuo sobre el que se hará la operación.\n             :param alpha_share: El valor necesario para poder calcular la distancia entre Individuos.\n             :param sigma_share: El valor necesario para poder calcular la distancia entre Individuos.\n             \n             :type individual_i: Instance\n             :type individual_j: Instance\n             :type alpha_share: Float\n             :type sigma_share: Float\n             :returns: El resultado que contribuirá al sharing function. \n             :rtype: Float\n          \"\"\"\n\n          #Aquí se colocará el resultado del cálculo de la distancia\n          result = 0.0\n\n          #Se calcula la distancia entre los individuos usando la técnica que el usuario eligió en la sección gráfica\n          #(véase Model/SharingFunction).\n          dij = self.__calculate_distance(individual_i,individual_j,self.__sharing_function_parameters)\n\n          #De acuerdo a la técnica, si la distancia resulta menor a sigma se hace la siguiente operación.\n          if dij < sigma_share:\n             result = 1.0 - (dij/sigma_share)**alpha_share\n\n          #Se regresa el resultado\n          return result\n        \n\n      def calculate_population_niche_count(self,population):\n          \"\"\"\n             Calcula el valor conocido como niche count que no es mas que la suma de los sharing function\n             de todos los individuos j con el individuo i, con i != j.\n\n             :param population: Conjunto sobre el que se hará la operación.\n             \n             :type population: Instance\n          \"\"\"\n\n          #De acuerdo al trabajo escrito y a la documentación el valor de alpha típicamente\n          #se asigna a 1, no obstante en este proyecto se le da la libertad al usuario de\n          #seleccionar el valor libremente. Aquí se obtiene dicho valor con base en la información\n          #ingresada por el usuario.           \n          alpha_share = self.__sharing_function_parameters[\"alpha_sharing_function\"]\n\n          #A continuación se hace el cálculo del Sigma Share para poder obtener el Niche Count,\n          #esto tomando en cuenta el tipo de distancia que haya elegido el usuario.\n          sigma_share = self.__calculate_sigma_share(self.__sharing_function_parameters)\n\n          #Se aplica un recorrido de los Individuos de la Población con ellos mismos\n          #para calcular el Niche Count de cada uno.\n          for individual_i in population.get_individuals():\n \n              #El valor mínimo de Niche Count para un Individuo será 1.\n              result = 1.0\n              for individual_j in population.get_individuals():\n\n                  #Así se garantiza que no se hará sharing function de los individuos con ellos mismos.\n                  if individual_i != individual_j:\n\n                     #Se calcula el niche count para cada individuo_i de la población\n                     result += self.__using_sharing_function(individual_i,individual_j,alpha_share,sigma_share)\n        \n              #Al final se añade este valor al individuo i\n              individual_i.set_niche_count(result)\n\n\n      def calculate_population_shared_fitness(self,population):\n          \"\"\"\n             Calcula el Shared Fitness **(ó Fitness Compartido)** de cada uno\n             de los Individuos de la Población.\n\n             :param population: Conjunto sobre el que se hará la operación.\n             \n             :type population: Instance\n          \"\"\"\n          \n          for individual in population.get_individuals():\n             \n              #El cálculo del sharing function por individuo es: fitness / niche count. Se hace\n              #esto por cada Individuo.\n              individual.set_fitness(individual.get_fitness()/individual.get_niche_count())      \n          \n  \n      def execute_selection(self,parents):\n          \"\"\"\n             De acuerdo a la información provista anteriormente, se implementa\n             el método conocido como Stochastic Universal Sampling **(ó Muestreo Estocástico Universal)**.\n          \"\"\"\n\n          #Se crea una estructura para almacenar los cromosomas de los\n          #Individuos seleccionados. A su vez se crea la variable Pointer.\n          chromosome_set = []\n          pointer = aleatorio.random()\n\n          #Se inicializan las variables correspondientes a la acumulación de Pointers\n          #y Valores Esperados respectivamente.\n          cumulative_pointers = 0\n          cumulative_expected_value = 0\n    \n          #Con este valores se seleccionarán los Individuos y determinará si son aptos para\n          #la etapa de reproducción.\n          population_count = 0\n          population_selected = 0\n\n          #Se obtiene el tamaño de la Población y los Individuos, no sin antes habiéndolos \n          #desordenado primero para garantizar una selección más justa.  \n          population_size = parents.get_size() \n          parents.shuffle_individuals()\n          individuals = parents.get_individuals()\n    \n          #Se toma el primer Individuo como referencia para poder comenzar la\n          #operación.\n          current_individual = individuals[0]      \n\n          #El siguiente proceso se realizará hasta que se hayan seleccionado \n          #para la reproducción tantos Individuos como el tamaño de la Población.\n          while population_selected < population_size:\n                #Se toma el Valor Esperado del Individuo actual.\n                current_expected_value = current_individual.get_expected_value()\n\n                #Si el Pointer es mayor que el Valor Esperado del actual Individuo considerando los valores\n                #acumulados entonces se actualiza el candidato, pues lo anterior indica que se debe tomar el\n                #siguiente Individuo espaciado con el valor Pointer.\n                if cumulative_pointers + pointer > cumulative_expected_value + current_expected_value:\n                   #Se actualiza el apuntador al siguiente Individuo.\n                   population_count += 1   \n\n                   #Se actualiza el Valor Esperado Acumulado\n                   cumulative_expected_value += current_expected_value\n\n                   #Se selecciona el Individuo siguiente(se puede ver que usa el operador '%' \n                   #para hacer cíclica la elección en caso de que se haya agotado la lista previamente).\n                   current_individual = individuals[population_count % population_size]\n          \n                #Independientemente de la operación anterior, se actualizan lod siguientes valores para permitir\n                #seguir obteniendo Individuos hasta que se satisfaga la demanda.\n                population_selected += 1\n                cumulative_pointers += pointer\n\n                #Se agrega el cromosoma del Individuo seleccionado actualmente.\n                chromosome_set.append(current_individual.get_complete_chromosome())\n             \n          return chromosome_set\n\n\n      def __execute_crossover(self,chromosome_a,chromosome_b,chromosome_parameters):\n          \"\"\"\n             Usando como base la información proporcionada anteriormente, se implementa\n             el método conocido como N-Points Crossover **(ó Cruza en 'N' Puntos)**.\n          \"\"\"\n          \n          #De la sección de parámetros se obtiene la probabilidad de cruza.\n          crossover_probability = crossover_parameters[\"probability_crossover_general\"]    \n    \n          #Se inicializan los cromosomas hijos, los cuales contendrán la información\n          #de la cruza entre los padres.\n          chromosome_child_1 = []\n          chromosome_child_2 = []\n           \n          #Se crea el número aleatorio que servirá para verificar la probabilidad\n          #de cruza.\n          crossover_number = aleatorio.random()\n\n          #Si el número creado anteriormente es menor o igual al parámetro de la\n          #probabilidad de cruza, entonces se procede con la etapa de recombinación\n          #genética.\n          if crossover_number <= crossover_probability:\n             #Al entrar en la etapa de recombinación genética, primero se averigua\n             #el número de puntos de corte que se solicitarán para la operación.\n             how_many_points = crossover_parameters[\"how_many_points_npoints_crossover\"]\n\n             #Se guardan referencias a los cromosomas originales para no modificarlos.\n             my_chromosome_a = chromosome_a\n             my_chromosome_b = chromosome_b\n\n             #Aquí se almacenarán los bloques alternados de cada hijo.\n             mixed_chromosome_1 = \"\" \n             mixed_chromosome_2 = \"\"\n\n             #Variable que contiene el tamaño del cromosoma (para averiguar los puntos\n             #de corte).\n             length_chromosome = len(chromosome_a)\n\n             #Aquí serán almacenados los puntos de corte.\n             sections_list = []\n\n             #Esta variable permite alternar bloques de una manera rápida.\n             flag = 0\n           \n             #En caso de que no se cumpla la restricción de puntos de corte,\n             #se lanza una excepción.\n             if how_many_points > length_chromosome - 1:\n                raise ValueError(\"Number of points ({0}) exceeds chromosome's length ({1})\".format(how_many_points,length_chromosome)) \n      \n             #Aquí se considera el caso en que el número de puntos de cruza sea\n             #'n-1', se utiliza una lista por comprensión para llenar la lista\n             #de puntos de corte más rápidamente\"\"\"\n             if length_chromosome == how_many_points + 1:\n                real_sections_list = [x for x in range(length_chromosome + 1)]\n\n             #En caso de tratarse de una menor cantidad de puntos\n             #se procede a seleccionar de manera aleatoria los puntos\n             #de corte de acuerdo a la variable how_many_points.     \n             else: \n\n                #Se agrega siempre el punto 0 para que tenga coherencia\n                #la extracción de bloques.\n                sections_list.append(0)\n                sections_list.append(length_chromosome)\n                how_many_points_auxiliar = how_many_points\n        \n                #El siguiente ciclo permite seleccionar los puntos de corte,\n                #asegurándose de no repetirlos y/o proporcionar valores inválidos.  \n                while how_many_points_auxiliar != 0:\n                      number = 1 + aleatorio.randint(0,length_chromosome - 2)\n                \n                      #Aquí se verifica que los puntos de corte no estén repetidos.\n                      if not(number in sections_list):\n                         sections_list.append(number) \n                         how_many_points_auxiliar -= 1            \n                   \n                #Se ordenan los puntos de corte para poder extraer \n                #los bloques más fácilmente.\n                real_sections_list = sorted(sections_list)\n          \n             #Una vez creada la lista de puntos de corte, se procede a crear a los hijos.\n             #Para ello se toman porciones de acuerdo a los índices de la lista.\n             for x in range(1,len(real_sections_list)):\n              \n                 #Esta sección permite la alternancia de los bloques definidos por los puntos\n                 #de corte.\n                 if flag == 0:\n                    mixed_chromosome_1 += my_chromosome_a[real_sections_list[x-1]:real_sections_list[x]]\n                    mixed_chromosome_2 += my_chromosome_b[real_sections_list[x-1]:real_sections_list[x]]\n                                   \n                 elif flag == 1:\n                      mixed_chromosome_1 += my_chromosome_b[real_sections_list[x-1]:real_sections_list[x]]\n                      mixed_chromosome_2 += my_chromosome_a[real_sections_list[x-1]:real_sections_list[x]]\n                    \n                 #Esta variable permite aplicar la alternancia de bloques tantas\n                 #veces como sea necesario.\n                 flag = (flag + 1) % 2        \n \n             #Se actualizan las variables destinadas a los hijos una vez terminada la concatenación\n             #de bloques.\n             chromosome_child_1 = mixed_chromosome_1\n             chromosome_child_2 = mixed_chromosome_2\n      \n          #Si el número creado para la probabilidad de cruza es mayor que el parámetro\n          #de probabilidad de cruza entonces no se aplica ninguna operación y los hijos\n          #resultan en copias idénticas de los padres.\n          else:\n              chromosome_child_1 = chromosome_a\n              chromosome_child_2 = chromosome_b\n              \n          #Al final se regresa un arreglo conteniendo a los 2 hijos.\n          return [chromosome_child_1,chromosome_child_2]\n          \n      \n      def __execute_mutation(self,chromosome,mutation_parameters):\n          \"\"\"\n             Usando la información mostrada anteriormente, se desarrolla la función\n             conocida como Binary Mutation **(ó Mutación Binaria)**.\n          \"\"\"\n\n          #Aquí se almacenará el cromosoma mutado.\n          mutated_chromosome = \"\"  \n\n          #Se obtiene el valor que representará a la probabilidad de Mutación\n          #establecida por el usuario.\n          mutation_probability = mutation_parameters[\"probability_mutation_general\"]\n    \n          #Por cada gen en el cromosoma se realiza lo siguiente:\n          for gen in chromosome: \n\n              #Se crea el número que servirá de verificación \n              #para la probabilidad de cruza.\n              number = aleatorio.random()\n\n              #Si el número en cuestión es menor o igual que el parámetro de la\n              #probabilidad de cruza entonces se procede a cambiar el alelo asociado\n              #al gen actual.\n              if number <= mutation_probability:\n           \n                 #Dado que se trata de una representación Binaria, la transformación\n                 #es muy simple, si hay un 0 entonces el nuevo alelo asociado al gen se\n                 #transfomará en 1 y viceversa.\n                 if gen == \"0\":\n                    mutated_chromosome += \"1\"\n        \n                 elif gen == \"1\":\n                    mutated_chromosome += \"0\"\n    \n              #En caso de que la operación inherente a la probabilidad de cruza no\n              #se haya cumplido, el gen actual no se modifica.     \n              else:\n                  mutated_chromosome += gen       \n             \n          #Finalmente se regresa el cromosoma mutado. \n          return mutated_chromosome\n\n\n      def execute_crossover_and_mutation(self,selected_parents_chromosomes):\n          \"\"\"\n             Realiza la cruza y mutación de los individuos. Para el caso de la cruza ésta se lleva a cabo siempre\n             entre dos individuos, mientras que la mutación es unaria.\n\n             :param selected_parents_chromosomes: El conjunto de cromosomas sobre los cuales se aplicarán dichos operadores genéticos.\n            \n             :type selected_parents_chromosomes: List\n             :returns: Una instancia del tipo Model.Community.Population.\n             :rtype: Instance   \n          \"\"\"\n\n          #Se toma el tamaño de la población (que es el equivalente a tomar el tamaño de los individuos seleccionados), también\n          #se inicializa una población para que ahí se almacenen los hijos mutados.\n          size = len(selected_parents_chromosomes)          \n          children = Population(\n\t\t                        size,\n\t\t                        self.__vector_variables,\n\t\t                        self.__predictive_variable,\n\t\t\t\t\t\t\t\tself.__training_set,\n\t\t\t\t\t\t\t\tself.__testing_set,\n\t\t\t\t\t\t\t\tself.__available_global_columns,\n\t\t\t\t\t\t\t\tself.__forbidden_columns\n\t\t\t\t\t\t\t    )\n\n          #Si se tiene una población impar simplemente se añade un elemento al azar de los seleccionados automáticamente\n          #a la siguiente generación no sin antes haber sido mutado.\n          if size % 2 != 0:\n             size -= 1  \n             index = aleatorio.randint(0,size)\n             lucky_chromosome = selected_parents_chromosomes[index]\n             selected_parents_chromosomes.remove(selected_parents_chromosomes[index])\n             modified_lucky_chromosome = self.__execute_mutation(lucky_chromosome,self.__mutation_parameters)\n             \n             number_of_columns = 0\n             for gene in modified_lucky_chromosome:\n                 if gene == \"1\":\n                    number_of_columns +=1\n                    \n             children.add_individual(size,number_of_columns,modified_lucky_chromosome,self.__chromosome_size)\n          \n          #Tomando siempre un conjunto de cromosomas par, la cruza se realiza de la siguiente manera:\n          count = 0\n          for x in range(1,size,2):\n\n              #Se toman dos cromosomas consecutivos.\n              chromosome_a = selected_parents_chromosomes[x - 1]\n              chromosome_b = selected_parents_chromosomes[x]\n\n              #Se realiza la cruza sobre éstos, usando la instancia que se creó previamente con la técnica de cruza seleccionada\n              #(véase Model/Operator/Crossover y Controller/Verifier.py), así como los parámetros que\n              #se guardaron en la definición de la clase; la técnica de cruza devolverá 2 hijos.\n              [child_1,child_2] = self.__execute_crossover(chromosome_a,chromosome_b,self.__crossover_parameters)\n\n              #Ahora cada hijo es mutado de manera individual, utilizando una instancia de la técnica de mutación que fue elegida\n              #por el usuario en la sección gráfica (véase Model/Operator/Mutation y Controller/Verifier.py) y los parámetros\n              #que fueron guardados al inicio de la declaración de la clase.\n              modified_child_1 = self.__execute_mutation(child_1,self.__mutation_parameters)\n              modified_child_2 = self.__execute_mutation(child_2,self.__mutation_parameters)\n\n              number_of_columns_1 = 0\n              number_of_columns_2 = 0\n              \n              for y in range (len(modified_child_1)):\n                  if modified_child_1[y] == \"1\":\n                     number_of_columns_1 +=1\n\n                  if modified_child_2[y] == \"1\":\n                     number_of_columns_2 +=1\n\n              #Se agregan los cromosomas a la población creada con anterioridad.\n              children.add_individual(x - 1,number_of_columns_1,modified_child_1,self.__chromosome_size)\n              children.add_individual(x,number_of_columns_2,modified_child_2,self.__chromosome_size)\n              count +=2\n\n          return children\n   \n      \n      def get_best_individual(self,population):\n          \"\"\"\n             Obtiene el mejor individuo dentro de una población. Para estos fines el mejor individuo es aquél que\n             tenga mejor dominancia.\n\n             :param population: La población sobre la cual se hará la búsqueda.\n            \n             :type population: Instance\n             :returns: El individuo que cumple con la característica de la mayor dominancia.\n             :rtype: Instance    \n          \"\"\"\n\n          #Se guarda una copia de la población para no alterar la original. \n          sorted_population = population\n\n          #Se manda llamar a un método de la población que ordena los individuos de acuerdo a algún criterio\n          #(véase Model/Community/Population.py). El parámetro False determina el orden descendente del ordenamiento.\n          sorted_population.sort_individuals(\"get_pareto_dominated\",False)\n\n          #Se toma el primer individuo de los individuos.\n          individuals = sorted_population.get_individuals()\n          best_individual = individuals[0]\n          return best_individual\n\n\n      def __get_best_individual_results(self,population):\n          \"\"\"\n             .. note:: Este método es privado.\n             \n             Obtiene los valores de las variables de decisión y de las funciones objetivo\n             por cada individuo.\n\n             :param population: Una lista que contiene los mejores individuos por generación.\n\n             :type population: List\n             :returns: Una lista que contiene por un lado la tupla (generacion, funciones)\n                       y por otro la tupla (generación, variables). Esto por cada generación.\n             :rtype: List   \n          \"\"\"\n\n          #Se crean los elementos donde al final se llenará la información.\n          generations = []        \n          decision_variables = []\n          objective_functions = []\n          queries = [] \n\n          #Por cada individuo se hace lo siguiente:\n          for x in range (len(population)):\n              individual = population[x]\n\n              #Se agrega la generación\n              generations.append(x + 1)\n\n              #Se agrega la función objetivo. \n              objective_functions.append(individual.get_evaluated_functions())\n\n              #Se agrega la variable de decisión.\n              decision_variables.append(individual.get_decision_variables())\n        \n              queries.append(individual.get_query())\n\t\t\t  \n          #Se regresa la tupla (generaciones, funciones) y (generaciones, variables).\n          return [generations,objective_functions],[generations,decision_variables],[generations,queries]\n\n\n      def __get_pareto_results(self,population):\n          \"\"\"\n             .. note:: Este método es privado.\n             \n             | Obtiene el frente de Pareto, el complemento del frente de Pareto y el óptimo de Pareto.\n             | Para una mejor orientación léase la parte escrita del proyecto.\n\n             :param population: La población sobre la cual se obtendrán estos elementos.\n\n             :type population: Instance\n             :returns: Una lista que contiene el frente de Pareto, su complemento y el óptimo de Pareto.\n             :rtype: List   \n          \"\"\"\n     \n          #Se crean las estructuras donde se guardarán el frente de Pareto, el complemento del frente de Pareto\n          #y el óptimo de Pareto.\n          pareto_front = []\n          pareto_optimal = []\n          pareto_complement = []\n\n          #Se toman los individuos de la población.\n          #Además se toma un individuo de muestra.\n          individuals = population.get_individuals()\n          sample = individuals[0]\n          \n          #Con base en la muestra se crean casillas para cada una de las funciones objetivo para el\n          #frente de Pareto y su complemento.\n          for function in sample.get_evaluated_functions():\n              pareto_front.append([])\n              pareto_complement.append([]) \n                   \n          #Con base en la muestra también se crean casillas para cada una de las variables de decisión\n          #para el óptimo de Pareto.\n          for variable in sample.get_decision_variables():\n              pareto_optimal.append([])\n\n          #Por cada individuo se hace lo siguiente:\n          for individual in individuals:\n              \n              #Si el individuo no tiene elementos que lo dominen, significa que es parte\n              #del frente de Pareto, por lo que entonces se hace lo siguiente:\n              individual_functions = individual.get_evaluated_functions()\n              if individual.get_pareto_dominated() == 0:  \n\n                 #Primero se agregan sus evaluaciones en las funciones objetivo a la estructura\n                 #del frente de Pareto.\n                 for x in range(len(pareto_front)):\n                     pareto_front[x].append(individual_functions[x])\n\n                 #A continuación se agregan las evaluaciones en las variables de decisión \n                 #a la estructura del óptimo de Pareto.\n                 individual_decision_variables = individual.get_decision_variables()\n                 for x in range(len(pareto_optimal)):\n                     pareto_optimal[x].append(individual_decision_variables[x])\n\n              #Si el individuo tiene al menos algún elemento que lo domine, significa que es del complemento\n              #del frente de Pareto, por lo que simplemente se agregan las funciones objetivo a la \n              #respectiva estructura.\n              else:\n                 for x in range(len(pareto_complement)):\n                     pareto_complement[x].append(individual_functions[x])\n\n          #Al final se regresa la tupla (frente de Pareto, complemento del frente de Pareto, óptimo de Pareto).\n          return [pareto_front,pareto_complement,pareto_optimal]\n\n\n      def get_results(self,best_individual_along_generations,external_set_population):\n          \"\"\"\n             Recolecta la información y la almacena en una estructura que contiene dos categorías principales: \n             funciones objetivo y variables de decisión. Por cada una existen las subcategorías Pareto y mejor \n             individuo, en referencia al óptimo o frente de Pareto **(según corresponda)** y a los valores del mejor \n             individuo por generación **(véase View/Additional/ResultsGrapher/GraphFrame.py)**.\n\n             :param best_individual_along_generations: Una lista que contiene los mejores individuos por generación.\n             :param external_set_population: La población sobre la cual se efectuarán las operaciones.\n \n             :type best_individual_along_generations: List\n             :type external_set_population: Instance\n             :returns: Un diccionario con los elementos mostrados en la descripción.\n             :rtype: Dictionary  \n          \"\"\"\n\n          #Se crea la estructura final donde se almacenará toda la información.\n          information = {}          \n\n          #Se obtienen los valores para los mejores individuos por generación.\n          objective_functions, decision_variables, queries = self.__get_best_individual_results(best_individual_along_generations)\n\n          #Se obtienen el frente de Pareto, su complemento y el óptimo de Pareto.\n          #Por una petición de la Dra. Katya Rodríguez Vázquez se omite el complemento de Pareto en las\n          #impresiones finales, por ello es que se solicitará el complemento aquí por si en algún momento el usuario lo\n          #necesita pero no se va a utilizar en la sección de impresión (View/Additional/ResultsGrapher/GraphFrame.py)\n          #dado que este método no regresará esa parte.\n          pareto_front, pareto_complement, pareto_optimal = self.__get_pareto_results(external_set_population)\n\n          #Se crea la primera categoría (funciones objetivo) de la información final y se llena con los datos mostrados a continuación.\n          information[\"objective_functions\"] = {           \n                                                \n                                                \"best individual\": {\n                                                                    \"functions\": objective_functions,\n                                                                   }\n                                               }\n\n          #Se crea la segunda categoría (variables de decisión) de la información final y se llena con los datos mostrados a continuación.\n          information[\"decision_variables\"] = {\n                                               \n                                               \"best individual\": {\n                                                                   \"variables\": decision_variables,\n                                                                  }\n                                              }\n\t\t\t\t\t\t\t\t\t\t\t  \n       \t  #Se crea la segunda categoría (variables de decisión) de la información final y se llena con los datos mostrados a continuación.\n          information[\"queries\"] = {\n                                              \n                                               \"best individual\": {\n                                                                   \"queries\": queries,\n                                                                  }\n                                              }\n\n          return information\n\n\n#*************************************************************************************************************************************\n\n    \nclass VariableSelector:\n    \n    def __init__(self,\n                 generations,\n                 population_size,\n                 vector_variables,\n                 sharing_function_parameters,\n                 selection_parameters,\n                 crossover_parameters,\n                 mutation_parameters,\n                 predictive_variable,\n                 test_size,\n                 non_dummy_columns,\n                 forbidden_columns,\n                 ):\n                 \n        self.__comunidad = None\n        self.__generations = generations\n        self.__population_size = population_size\n        self.__vector_variables = vector_variables\n        self.__sharing_function_parameters = sharing_function_parameters\n        self.__selection_parameters = selection_parameters\n        self.__crossover_parameters = crossover_parameters\n        self.__mutation_parameters = mutation_parameters\n        self.__predictive_variable = predictive_variable\n        self.__test_size = test_size\n        self.__non_dummy_columns = non_dummy_columns\n        self.__forbidden_columns = forbidden_columns\n        self.__available_global_columns = None\n        self.__training_set = None\n        self.__testing_set = None\n      \n        self.__create_sets()\n\n          \n    def __create_sets(self):\n        \"\"\"\n        \"\"\"\n        print(\"Creating sets....\")  \n        \n        df_training=pd.read_pickle('/content/drive/My Drive/Special_Analysis/df_final_2.pickle') \n        \n        df_training=df_training.drop(['PlayerKey_x', 'PlayerKey_y','GameID_x', 'GameID_y', 'PlayKey','DM_M1', 'DM_M7', 'DM_M28', 'DM_M42'], axis=1)\n        \n        #print(df_training.columns.values)\n      \n        df_training = df_training.fillna(0)\n\t   \n        df_training_complete, df_testing = train_test_split(df_training,test_size=self.__test_size)\n\n        df_training_complete=df_training_complete.fillna(0)\n        df_testing=df_testing.fillna(0)\n         \n        self.__available_global_columns = df_training_complete.columns.values.tolist()\n\n        self.__training_set = df_training_complete\n        self.__testing_set = df_testing\n        print(\"Sets created.\")       \n        \n        \n    def execute_moea(self):\n        \"\"\"\n           En esta parte se lleva a cabo la implementación del M.O.E.A. denominado\n           N.S.G.A. II **(Non-dominated Sorting Genetic Algorithm ó Algoritmo Genético \n           de Ordenamiento No Dominado)**.\n\n           La forma de proceder del método es la siguiente:\n\n           1.- Se crea una Población Padre **(de tamaño n)**, a la cual se le evalúan las funciones objetivo de sus Individuos, se les asigna un Ranking **(Goldberg)** y posteriormente se les otorga un Fitness.\n\n           2.- Con base en la Población Padre se aplica el operador de Selección para elegir a los Individuos que serán aptos para reproducirse.\n\n           3.- Usando a los elementos del punto 2, se crea una Población Hija **(de tamaño n)**. \n \n           4.- Se crea una súper Población **(llamémosle S, de tamaño 2n)** que albergará todos los Individuos tanto de la Población Padre como Hija; a *S* se le evalúan las funciones objetivo de sus Individuos, se les asigna un Ranking **(Goldberg)** y posteriormente se les otorga un Fitness. \n\n           5.- La súper Población *S* se divide en subcategorías de acuerdo a los niveles de dominancia que existan, es decir, existirá la categoría de dominancia 0, la cual almacena Individuos que tengan una dominancia de 0 Individuos **(ningún Individuo los domina)**, existirá la categoría de dominancia 1 con el significado análogo y así sucesivamente hasta haber cubierto todos los niveles de dominancia existentes.\n\n           6.- Se construye la nueva Población Padre, pare ello constará de los Individuos de *S* donde la prioridad será el nivel de dominancia, es decir, primero se añaden los elementos del nivel 0,luego los del nivel 1 y así en lo sucesivo hasta haber adquirido n elementos.\n               Se debe aclarar que la adquisición de Individuos por nivel debe ser total, esto significa que no se pueden dejar Individuos sueltos para el mismo nivel de dominancia. \n\n               Supongamos que a un nivel k existen tantos Individuos que su presunta adquisición supera el tamaño n, en este caso se debe hacer lo siguiente:\n    \n           6.1.- Se crea una Población provisional **(Prov)** con los Individuos del nivel k, se evalúan las funciones objetivo a cada uno de sus Individuos, se les asigna un Ranking **(Goldberg)** y posteriormente se les asigna el Fitness.\n\n               Con los valores anteriores se calcula el Niche Count **(véase Model/SharingFunction)** de los Individuos; una vez hecho ésto se seleccionan desde Prov los Individuos faltantes con los mayores Niche Count, esto hasta completar el tamaño n de la nueva Población Padre.\n\n           7.- Al haber conformado la nueva Población Padre, se evalúan las funciones objetivo de sus Individuos, se les asigna el Ranking correspondiente **(Goldberg)** y se les atribuye su Fitness.\n\n           8.- Se repiten los pasos 2 a 7 hasta haber alcanzado el límite de generaciones **(iteraciones)**.\n\n           | Como su nombre lo indica, la característica de este algoritmo es la clasificación \n             de los Individuos en niveles para su posterior selección.\n\n           | Esto al principio propicia una Presión Selectiva moderada por la ausencia de elementos \n             con dominancia baja que suele existir en las primeras generaciones, sin embargo en iteraciones \n             posteriores se agudiza la Presión Selectiva ya que eventualmente la mayoría de los Individuos \n             serán alojados en las primeras categorías de dominancia, cubriendo casi instantáneamente \n             la demanda de Individuos necesaria en el paso 6, por lo que las categorías posteriores serán \n             cada vez menos necesarias con el paso de los ciclos.\n\n           | Por otra parte la fusión de las Poblaciones en *S* garantiza que siempre se conserven a \n             los mejores Individuos independientemente de la generación transcurrida, a eso se le llama Elitismo.\n           | Por cierto que en el algoritmo original no existe un nombre oficial para *S* sino más bien se señala como\n             una estructura genérica, sin embargo se le ha formalizado con un identificador para guiar apropiadamente al \n             usuario en el flujo del algoritmo.\n \n           | Para finalizar se señala que el uso del ranking de Goldberg **(véase Model/Community/Community.py)** \n             es indispensable.\n        \"\"\"\n\n        print(\"Welcome to NSGA-II\")     \n        #Se crea una instancia de Community ya que la mayoría de los métodos auxiliares\n        #residen allí.\n        self.__comunidad = Community(self.__vector_variables,\n                                     self.__sharing_function_parameters,\n                                     self.__selection_parameters,\n                                     self.__crossover_parameters,\n                                     self.__mutation_parameters,\n                                     self.__predictive_variable,\n                                     self.__training_set,\n                                     self.__testing_set,\n                                     self.__available_global_columns,\n                                     self.__forbidden_columns)\n\n        #Se crea una estructura para almacenar al mejor Individuo por generación.\n        best_individual_along_generations = []\n\n        #Se crea la Población Padre.\n        parents = self.__comunidad.init_population(population_size)\n\n        print(\"Starting variable selection...\")\n        #try: \n            #Se evalúan las funciones objetivo de los Individuos de la Población\n            #Padre.\n        print(\"Step 1\")\n        self.__comunidad.evaluate_population_functions(parents)\n\n        #Se asigna el Ranking (Goldberg) correspondiente a los Individuos de la\n        #Población Padre. \n        print(\"Step 2\")  \n        self.__comunidad.assign_goldberg_pareto_rank(parents)\n\n        #Usando el Ranking, se asigna el Fitness a los Individuos de la \n        #Población Padre.\n        print(\"Step 3\")\n        self.__comunidad.assign_population_fitness(parents)\n \n        #El siguiente procedimiento se realizará hasta haber alcanzado\n        #el número límite de generaciones.\n        for x in range (1,generations + 1):\n                print(\"Generación :\", x)\n\n                #Se seleccionan los Individuos de la Población Padre elegidos para reproducirse-\n                selected_parents_chromosomes = self.__comunidad.execute_selection(parents)\n \n                #Con base en los seleccionados en el paso anterior, se crea la Población Hija.\n                children = self.__comunidad.execute_crossover_and_mutation(selected_parents_chromosomes)\n\n                #El primer paso del algoritmo consiste en fusionar las poblaciones Padre e\n                #Hija en una súper Poblacion, para ello se hace lo siguiente:\n                #Se crea una estructura para almacenar los cromosomas de los Individuos.\n                parents_and_children = []\n\n                #Los cromososmas de los Individuos de la Población Padre son añadidos\n                #a dicha estructura.\n                for parent in parents.get_individuals():\n                    parents_and_children.append(parent.get_complete_chromosome())\n\n                #Los cromososmas de los Individuos de la Población Hija son añadidos\n                #a dicha estructura.\n                for child in children.get_individuals():\n                    parents_and_children.append(child.get_complete_chromosome())\n\n                #La súper Población de tamaño 2n es creada. Como dato de implementación tiene sentido \n                #que la súper Poblacion tenga tamaño de 2n y entonces tenga una dominancia máxima de 2n - 1.\n                new_population = self.__comunidad.create_population(parents_and_children)\n \n                #Se evalúan las funciones objetivo de los Individuos de la súper\n                #Población.\n                self.__comunidad.evaluate_population_functions(new_population)\n\n                #Se asigna el Ranking (Goldberg) a los Individuos de la \n                #súper Población.\n                [auxiliar_pareto_fronts,pareto_fronts] = self.__comunidad.assign_goldberg_pareto_rank(new_population,True)\n\n                #Dado que debe haber n seleccionados y la súper Población consta de 2n Individuos se debe aplicar\n                #un filtro, de modo que esta estructura albergará a los elegidos.\n                chosen = []\n\n                #Ahora se van tomando los cromosomas que pertenezcan primero al nivel de dominancia 0 (no hay ningún \n                #Individuo que los domine), luego a los del nivel de dominancia 1 (hay 1 Individuo que los domina) y así \n                #sucesivamente hasta haber seleccionado n elementos.\n                #Cabe mencionar que se deben seleccionar los elementos del nivel completo siempre y cuando su tamaño no\n                #exceda n.\n                #Con esta variable se obtiene el nivel actual de dominancia.\n                current_front_index = 0\n\n                while len(chosen) != parents.get_size():\n\n                      #Se obtiene el de nivel de dominancia actual.\n                      current_front = auxiliar_pareto_fronts[current_front_index]\n\n                      #Se verifica que los Individuos del nivel actual no sobrepasen el tamaño n,\n                      #en caso de no rebasar el límite se agregan todos a la estructura chosen.\n                      if len (chosen) + len(pareto_fronts[current_front]) <= parents.get_size():\n                         for current_chromosome in pareto_fronts[current_front]:\n                             chosen.append(current_chromosome)\n  \n                      #En caso de que, al momento de seleccionar un nivel k de dominancia, los elementos de la Población\n                      #excedan el tamaño n, entonces se hace lo siguiente:\n                      else:\n\n                           #Es menester mencionar que en algunos casos la diferencia puede ser mucha porque no se entró en \n                           #el primer if, lo que significa que de entrada los primeros niveles de dominancia pueden ser tener \n                           #demasiados Individuos, mas que n. Por esa razón con el transcurso de las generaciones, sólo\n                           #se verificará el nivel 0 de dominancia, a lo más, el nivel 1 y todos esos Individuos\n                           #se agregarán en esta sección de código.\n                           #Aquí se calcula el número de individuos que exceden a la población.\n                           difference = parents.get_size() - len(chosen)\n                      \n                           #Lo que se hará entonces es agregar todos los Individuos del nivel de dominancia actual\n                           #hasta cumplir con el tamaño n.\n                           #Para ello se debe crear una población Provisional aparte y\n                           #asignarle un Niche Count para que puedan ser seleccionados los faltantes.  \n                           provisional = self.__comunidad.create_population(pareto_fronts[current_front])\n \n                           #Se evalúan las funciones objetivo de los Individuos de la Población\n                           #provisional.\n                           self.__comunidad.evaluate_population_functions(provisional)\n\n                           #Se asigna el Ranking de Goldberg para los Individuos de la Población\n                           #provisional.\n                           self.__comunidad.assign_goldberg_pareto_rank(provisional)\n\n                           #Con base en el Ranking se asigna un Fitness a dichos Individuos.\n                           self.__comunidad.assign_population_fitness(provisional)\n\n                           #Ahora se calcula el Niche Count de la Población.\n                           self.__comunidad.calculate_population_niche_count(provisional)\n\n                           #Con base en este valor se ordenan a los Individuos de manera\n                           #ascendente (un menor niche count significa que una solución tiene \n                           #menos vecinos por ende es más probable que dicha solución\n                           #sea no dominada).\n                           provisional.sort_individuals(\"get_niche_count\",False)\n\n                           #Se toman los Individuos de la Población provisional.\n                           individuals = provisional.get_individuals()\n\n                           #A continuación se añaden los elementos faltentes a la estructura\n                           #chosen.\n                           for x in range (difference):\n                               chosen.append(individuals[x].get_complete_chromosome())\n                      current_front_index += 1\n\n                #Se crea la nueva Población Padre asociada a los elementos \n                #de la estructura chosen.\n                parents = self.__comunidad.create_population(chosen)\n\n                #Se evalúan las funciones objetivo de los Individuos de la \n                #nueva Población Padre.\n                self.__comunidad.evaluate_population_functions(parents)\n\n                #Se asigna el Ranking (Goldberg) a los Individuos de la \n                #nueva Población Padre.\n                self.__comunidad.assign_goldberg_pareto_rank(parents)\n\n                #Se asigna el Fitness con base en el Ranking a los Individuos\n                #de la nueva Población Padre.\n                self.__comunidad.assign_population_fitness(parents)\n\n                best = self.__comunidad.get_best_individual(parents)\n                print(best.get_evaluated_functions())\n                #print(best.get_query())\n                #Se añade el mejor Individuo por generación a la estructura creada para tal fin.\n                best_individual_along_generations.append(best)\n\n        #except Exception as e:\n                #En caso de un error interno las generaciones automáticamente llegan a su límite\n                #para cerrar la ventana en la parte de View.\n                #generations_queue.append((execution_task_count,generations)) \n\n                #Posteriormente se regresa el siguiente diccionario con la información relativa\n                #al origen del error.\n        #        error = {\n        #                 \"response\": \"ERROR\",\n        #                 \"class\": \"NSGAII\", \n        #                 \"method\": \"execute_moea\",\n        #                 \"message\": \"An error has occurred during execution of NSGAII algorithm\",\n        #                 \"type\": (str(e))\n        #                }   \n\n        #        return error\n\n        #Los resultados tienen el formato precisado dentro de la función get_results que se encuentra\n        #en la clase Community. Es sumamente importante que el usuario revise esta función\n        #ya que de ésta depende la graficación de resultados (véase View/Additional/ResultsGrapher/ResultsGrapherToplevel.py).\n        #El conjunto que almacena todos los Individuos para impresión de resultados es el de la Población Padre.\n        results = self.__comunidad.get_results(best_individual_along_generations,parents)\n\n        #Se regresan dichos resultados.\n        return results\n\n      \n#---------------------------------------------\n\ngenerations = 800\npopulation_size = 100\n#population_size = 4\n\n#Como poner esto para el sigma share.\nvector_variables = [],\n\nsharing_function_parameters = {\n                               \"alpha_sharing_function\":4,\n                               \"percentage_of_acceptance\":0.4,\n                              }\n                              \nselection_parameters = {}\ncrossover_parameters = {\n                        \"probability_crossover_general\": 0.7,    \n                        \"how_many_points_npoints_crossover\":1,\n                       }\n                       \nmutation_parameters = {\n                       \"probability_mutation_general\": 0.1,\n                      }\n\npredictive_variable = \"Target\"\ntest_size = 0.3\n\nnon_dummy_columns = [ ]\n\nforbidden_columns = [\n                     \"Target\"\n                    ]\n\nprint(\"Welcome to variable selection\")   \n\nfrom google.colab import drive\ndrive.mount('/content/drive',force_remount=True)\n\nvariable_selector = VariableSelector(generations,\n                                     population_size,\n                                     vector_variables,\n                                     sharing_function_parameters,\n                                     selection_parameters,\n                                     crossover_parameters,\n                                     mutation_parameters,\n                                     predictive_variable,\n                                     test_size,\n                                     non_dummy_columns,\n                                     forbidden_columns,             \n                                    )\n                                     \n\nresults = variable_selector.execute_moea()\n\nprint(results[\"queries\"][\"best individual\"][\"queries\"][-1])\n#print(results[\"objective_functions\"][\"best individual\"][\"functions\"][-1])\n\npkl_filename = \"/content/drive/My Drive/Special_Analysis/results.pickle\"\nwith open(pkl_filename, 'wb') as file:\n    pickle.dump(results, file)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"##### The best variables for each configuration were:\n\n\n* Linear Regression (0.99 AUROC)\n  * x\n  * y\n  * RosterPosition_Linebacker\n  * RosterPosition_Running Back\n  * RosterPosition_Wide Receiver\n  * StadiumType_Indoors\n  * StadiumType_Outddors\n  * FieldType_Natural\n  * Weather_Clear\n  * Weather_Clear and warm\n  * Weather_Indoor\n  * Weather_Partly Cloudy\n  * Weather_Sun & clouds\n  * PlayType_Kickoff Returned\n  * PlayType_Punt Returned\n  * Position_CB\n  * Position_DT\n  * Position_LB\n  * Position_TE\n  * PositionGroup_DB\n  * event_pass_outcome_incomplete\n  * BodyPart_Ankle\n  * BodyPart_Knee\n    \n<br>\n\n* Logistic Regression (0.98 AUROC)\n  * s \n  * SumResults \n  * RosterPosition_Defensive Lineman \n  * StadiumType_Dome \n  * StadiumType_Open \n  * StadiumType_Retractable Roof  \n  * Weather_Clear and warm \n  * Weather_Clear skies \n  * PlayType_Kickoff Returned \n  * PlayType_Pass \n  * PlayType_Rush \n  * Position_CB \n  * event_first_contact \n  * event_line_set \n  * event_pass_forward \n  * event_penalty_declined \n  * event_punt_land"},{"metadata":{},"cell_type":"markdown","source":"##### Now it is turn to use the results (sets of variables) provided by the genetic algorithm in order to get a model so we can obtain the corresponding object model and use it for the next tests.\n##### We are getting object models for a Random Forest and Logistic Regression because these ones are likely to be more explainable (because that's what we wanted since the beginning)"},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":34},"colab_type":"code","id":"013R5K5P8pnk","outputId":"76c484e1-9c1b-4763-a75f-c300ba664481","trusted":false},"cell_type":"code","source":"from sklearn.ensemble import RandomForestClassifier\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.linear_model import LogisticRegression\n\n#Linear Regression\ncols_linear = [\n               'x',\n               'y',\n               'RosterPosition_Linebacker',\n               'RosterPosition_Running Back',\n               'RosterPosition_Wide Receiver',\n               'StadiumType_Indoors',\n               'StadiumType_Outddors',\n               'FieldType_Natural',\n               'Weather_Clear',\n               'Weather_Clear and warm',\n               'Weather_Indoor',\n               'Weather_Partly Cloudy',\n               'Weather_Sun & clouds',\n               'PlayType_Kickoff Returned',\n               'PlayType_Punt Returned',\n               'Position_CB',\n               'Position_DT',\n               'Position_LB',\n               'Position_TE',\n               'PositionGroup_DB',\n               'event_pass_outcome_incomplete',\n               'BodyPart_Ankle',\n               'BodyPart_Knee'\n              ]\n\n#Logit\ncols_logit = [\n              's', \n              'SumResults', \n              'RosterPosition_Defensive Lineman', \n              'StadiumType_Dome', \n              'StadiumType_Open', \n              'StadiumType_Retractable Roof',  \n              'Weather_Clear and warm', \n              'Weather_Clear skies', \n              'PlayType_Kickoff Returned', \n              'PlayType_Pass', \n              'PlayType_Rush', \n              'Position_CB', \n              'event_first_contact', \n              'event_line_set', \n              'event_pass_forward', \n              'event_penalty_declined', \n              'event_punt_land'\n             ]\n\ntrain, test = train_test_split(df_final_1, test_size=0.3)\n\nX_train_rf=train[cols_logit]\nY_train_rf=train['Target']\nX_test_rf=test[cols_logit]\nY_test_rf=test['Target']\n\nlogistic = LogisticRegression(solver = 'liblinear')\nlogistic.fit(X_train_rf, Y_train_rf)\n\nX_train_rf=train[cols_linear]\nY_train_rf=train['Target']\nX_test_rf=test[cols_linear]\nY_test_rf=test['Target']\n\n\nrandom_forest = RandomForestClassifier(n_estimators=1)\nrandom_forest.fit(X_train_rf, Y_train_rf)\n\n\npkl_filename = \"/content/drive/My Drive/Special_Analysis/logistic_regression_object_model.pickle\"\nwith open(pkl_filename, 'wb') as file:\n    pickle.dump(logistic, file)\n\nprint(\"Logistic Score: \", logistic.score(X_test_rf, Y_test_rf))\n\npkl_filename = \"/content/drive/My Drive/Special_Analysis/random_forest_object_model.pickle\"\nwith open(pkl_filename, 'wb') as file:\n    pickle.dump(random_forest, file)\n\nprint(\"Ranfom Forest: \", random_forest.score(X_test_rf, Y_test_rf))","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"fMfFKfkwVCvo"},"cell_type":"markdown","source":"#### <center>Dependency Plot </center>"},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":297},"colab_type":"code","id":"CU48VhZRV7U7","outputId":"77b6d136-d580-4f98-a760-87c1fe879e3c","trusted":false},"cell_type":"code","source":"feature_importance = abs(model.coef_[0])\nfeature_importance = 100.0 * (feature_importance / feature_importance.max())\nsorted_idx = np.argsort(feature_importance)\npos = np.arange(sorted_idx.shape[0]) + .5\n\nfeatfig = plt.figure()\nfeatax = featfig.add_subplot(1, 1, 1)\nfeatax.barh(pos, feature_importance[sorted_idx], align='center')\nfeatax.set_yticks(pos)\nfeatax.set_yticklabels(np.array(df_final.columns)[sorted_idx], fontsize=8)\nfeatax.set_xlabel('Relative Feature Importance')\n\nplt.tight_layout()   \nplt.show()","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"gLHwGMVQWV-X"},"cell_type":"markdown","source":"This is a fair look of the more important features in our model."},{"metadata":{"colab_type":"text","id":"HA29P2j5ojqm"},"cell_type":"markdown","source":"#### <center> Elastic Net </center>"},{"metadata":{"colab":{},"colab_type":"code","id":"4Q4qs0xrrw1j","trusted":false},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport seaborn as sns\nimport matplotlib\nimport matplotlib.pyplot as plt\nfrom scipy.stats import skew\n\n%config InlineBackend.figure_format = 'png' #set 'png' here when working on notebook\n%matplotlib inline","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"ekAgCX6dpu2R","trusted":false},"cell_type":"code","source":"from sklearn.linear_model import ElasticNet\nfrom sklearn.model_selection import cross_val_score\nfrom itertools import product\n\ndef rmse_cv(model):\n    rmse= np.sqrt(-cross_val_score(model, X_train, Y_train, scoring='neg_mean_squared_error', cv = 5))\n    return(rmse)","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"JzLLAl1EpwcG","trusted":false},"cell_type":"code","source":"alphas = [0.0005, 0.001, 0.01, 0.03, 0.05, 0.1]\nl1_ratios = [1.5, 1.1, 1, 0.9, 0.8, 0.7, 0.5]","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"9uHus9Otpxjl","trusted":false},"cell_type":"code","source":"cv_elastic = [rmse_cv(ElasticNet(alpha = alpha, l1_ratio=l1_ratio)).mean() \n            for (alpha, l1_ratio) in product(alphas, l1_ratios)]","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":423},"colab_type":"code","id":"WNJvKlOfpzV1","outputId":"d01d8f1a-0c80-48e5-eb29-b428185a62b0","trusted":false},"cell_type":"code","source":"matplotlib.rcParams['figure.figsize'] = (12.0, 6.0)\nidx = list(product(alphas, l1_ratios))\np_cv_elastic = pd.Series(cv_elastic, index = idx)\np_cv_elastic.plot(title = \"Validation - Just Do It\")\nplt.xlabel(\"alpha - l1_ratio\")\nplt.ylabel(\"rmse\")","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":423},"colab_type":"code","id":"v6SvssY3r3tU","outputId":"484c1fa9-1bdf-4ffa-b5b5-143205fecddf","trusted":false},"cell_type":"code","source":"# Zoom in to the first 10 parameter pairs\nmatplotlib.rcParams['figure.figsize'] = (12.0, 6.0)\nidx = list(product(alphas, l1_ratios))[:10]\np_cv_elastic = pd.Series(cv_elastic[:10], index = idx)\np_cv_elastic.plot(title = \"Validation - Just Do It\")\nplt.xlabel(\"alpha - l1_ratio\")\nplt.ylabel(\"rmse\")","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"1DzaFOlOsQH6","trusted":false},"cell_type":"code","source":"elastic = ElasticNet(alpha=0.0005, l1_ratio=0.5)","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":72},"colab_type":"code","id":"9Ooowg_8sTjI","outputId":"4e2103c2-97cc-42c1-ea8b-d2f91db32cbb","trusted":false},"cell_type":"code","source":"elastic.fit(X_train, Y_train)","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"UFEkXv9Wsomt","trusted":false},"cell_type":"code","source":"coef = pd.Series(elastic.coef_, index = X_train.columns)","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"colab_type":"code","id":"a_hn-fb5srLh","outputId":"435e916f-3cd9-43e7-e630-33b8b6a0a030","trusted":false},"cell_type":"code","source":"print(\"Elastic Net picked \" + str(sum(coef != 0)) + \" variables and eliminated the other \" +  str(sum(coef == 0)) + \" variables\")","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"PdaxeJpksuCd","trusted":false},"cell_type":"code","source":"imp_coef = pd.concat([coef.sort_values().head(10),\n                     coef.sort_values().tail(10)])","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":625},"colab_type":"code","id":"PSZDwkrzswHV","outputId":"54590086-ca44-46b8-b415-967c1a85669a","trusted":false},"cell_type":"code","source":"matplotlib.rcParams['figure.figsize'] = (8.0, 10.0)\nimp_coef.plot(kind = \"barh\")\nplt.title(\"Coefficients in the Elastic Net Model\")","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"lB_Wod7IXP6R"},"cell_type":"markdown","source":"Applying Elastic Net to the cols, result the last plot that is suggesting to eliminate 6 variables and get only 11 for consideration at modeling."},{"metadata":{"colab_type":"text","id":"O7yoRFXvTJzp"},"cell_type":"markdown","source":"#### <center> LIME </center>"},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":617},"colab_type":"code","id":"8lU70BrdTdW5","outputId":"89e96f1e-41c6-47c4-bb15-8cd0f3f8abea","trusted":false},"cell_type":"code","source":"!pip install --target=$nb_path lime","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"QtMnvs9kwGiI","trusted":false},"cell_type":"code","source":"def prob(data):\n    return np.array(list(zip(1-random_forest.predict(data),random_forest.predict(data))))","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"Jt4RN7EiSNp8","trusted":false},"cell_type":"code","source":"import lime\nimport lime.lime_tabular\nimport numpy as np","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"-pArfpWawGiL","trusted":false},"cell_type":"code","source":"explainer = lime.lime_tabular.LimeTabularExplainer(df_final[cols].astype(int).values,  \nmode='classification',training_labels=df_final['Target'],feature_names=cols)","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"_SLahAyhwGiN","trusted":false},"cell_type":"code","source":"feat=cols","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"XE_ABDtvwGiP","trusted":false},"cell_type":"code","source":"i = 1\nexp = explainer.explain_instance(df_final.loc[i,feat].astype(int).values, prob, num_features=15)","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":476},"colab_type":"code","id":"PA1bPUDdwGiQ","outputId":"2f5ed7ea-9d67-40de-fc3a-f317680ec01a","trusted":false},"cell_type":"code","source":"exp.show_in_notebook(show_table=True)","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"B99soTZGXrbn"},"cell_type":"markdown","source":"LIME suggest that the most important feature is the one of summing up all the days that the player injured do not play games."},{"metadata":{"colab_type":"text","id":"X5UIKQfAQSRY"},"cell_type":"markdown","source":"### <center> Summary </center>\n\n#### The final part consists on answering the questions based on the data we have gathered, cleaned and analyzed with a few techniques."},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":128},"colab_type":"code","id":"yCzPXalwKvQN","outputId":"a5193687-7091-4dfe-fe0c-3de5c06bd6d4","trusted":false},"cell_type":"code","source":"from google.colab import drive\ndrive.mount('/content/drive',force_remount=True)","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":163},"colab_type":"code","id":"PEAhy9P0Kq9D","outputId":"1363eea5-d975-4658-e841-7e60bb1c41bb","trusted":false},"cell_type":"code","source":"#@title\n\nfrom datetime import datetime, timedelta\nfrom google.colab import auth\nauth.authenticate_user()\n\nimport gspread\nfrom oauth2client.client import GoogleCredentials\n\ngc = gspread.authorize(GoogleCredentials.get_application_default())","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"U89gtwiYK0Xl","trusted":false},"cell_type":"code","source":"import pickle\nimport matplotlib\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport statsmodels.api as sm\nfrom functools import reduce\nfrom datetime import datetime\nimport matplotlib.pyplot as plt\nfrom sklearn.cluster import KMeans\nfrom sklearn.feature_selection import RFE\nfrom sklearn.linear_model import LinearRegression\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import pairwise_distances_argmin_min\nfrom sklearn.linear_model import RidgeCV, LassoCV, Ridge, Lasso, ElasticNetCV , ElasticNet\n\n%matplotlib inline\nfrom mpl_toolkits.mplot3d import Axes3D\n#sns.set_context('poster')\n#sns.set_color_codes()\n#plot_kwds = {'alpha' : 0.25, 's' : 80, 'linewidths':0}\n#plt.rcParams['figure.figsize'] = (16, 9)\n#plt.style.use('ggplot')","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"vcllCWUbcZ2w","trusted":false},"cell_type":"code","source":"df_injury = pd.read_csv('/content/drive/My Drive/NFL/InjuryRecord.csv')\ndf_player_track = pd.read_csv('/content/drive/My Drive/NFL/PlayerTrackData.csv')\ndf_play_list = pd.read_csv('/content/drive/My Drive/NFL/PlayList.csv')","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"yqs-wHFTIzzh"},"cell_type":"markdown","source":"#### <center> Player Movement </center>"},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"colab_type":"code","id":"J8ASdtcumVvX","outputId":"31046750-541d-4f93-bd35-e0e417906df0","trusted":false},"cell_type":"code","source":"df_player_track.shape","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"H8AtXL2Up9eI","trusted":false},"cell_type":"code","source":"plt.rcParams['figure.figsize'] = [50, 10]","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":318},"colab_type":"code","id":"DxCie2gLsGzL","outputId":"41d7977c-f72f-4880-8da4-f42e17fa7615","trusted":false},"cell_type":"code","source":"total = df_player_track.isnull().sum().sort_values(ascending=False)\nporciento = (df_player_track.isnull().sum()/df_player_track.isnull().count()).sort_values(ascending=False)\ndato_nulo = pd.concat([total, porciento], axis=1, keys=['Total', '%'])\ndato_nulo.head(50)","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"colab_type":"code","id":"CFuBPxm8sV2Q","outputId":"07325ead-c4ea-4322-ce3f-8daa91b24215","trusted":false},"cell_type":"code","source":"print(df_player_track['o'].median(skipna=True))","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"colab_type":"code","id":"sSEQwpGPsgfx","outputId":"a5030bad-4848-470b-b425-3b05abf2d364","trusted":false},"cell_type":"code","source":"print(df_player_track['dir'].median(skipna=True))","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"GTor-i-9sde8","trusted":false},"cell_type":"code","source":"df_player_track['o'].fillna(179.91, inplace=True)\ndf_player_track['dir'].fillna(180.06, inplace=True)","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":318},"colab_type":"code","id":"PDLYPokhsp1h","outputId":"ac1a91dd-f0fc-49d6-cbf1-9dd24ae3de92","trusted":false},"cell_type":"code","source":"total = df_player_track.isnull().sum().sort_values(ascending=False)\nporciento = (df_player_track.isnull().sum()/df_player_track.isnull().count()).sort_values(ascending=False)\ndato_nulo = pd.concat([total, porciento], axis=1, keys=['Total', '%'])\ndato_nulo.head(50)","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":822},"colab_type":"code","id":"lX7zPFWXpCVz","outputId":"8e12c0e8-17bb-4015-e0b3-d39cf4dc27f7","trusted":false},"cell_type":"code","source":"df_player_track['event'].value_counts().plot.bar(title='Freq dist of Play Details')\nplt.xticks(rotation=45)\nplt.tight_layout()\n","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"p8G9icm8EZq8"},"cell_type":"markdown","source":"We can look that the top events in our dataset are: ball_snap, line_set and huddle_break_offense_tackle."},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":745},"colab_type":"code","id":"dyIL8WEkqara","outputId":"7d577a22-e7d9-4e8c-b897-478e38c2b58d","trusted":false},"cell_type":"code","source":"col_names = ['time','x', 'y', 'dis', 's', 'o','dir']\n\nfig, ax = plt.subplots(len(col_names), figsize=(16,12))\n\nfor i, col_val in enumerate(col_names):\n\n    sns.distplot(df_player_track[col_val], hist=True, ax=ax[i])\n    ax[i].set_title('Freq dist '+col_val, fontsize=10)\n    ax[i].set_xlabel(col_val, fontsize=8)\n    ax[i].set_ylabel('Count', fontsize=8)\n\nplt.show()\nplt.tight_layout()","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"CGOmYI3Wu91I"},"cell_type":"markdown","source":"We can see that time, distance and speed are skewed to the right, it means that the most values lie in the lower range values. In the other hand, we can look to x (player position along the long axis of the field) and y (player position along the short axis of the field) that are normal distributed. Finally, we have orientation and direction that have an uniform distribution. "},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"colab_type":"code","id":"9usM2sai1DDk","outputId":"d6cf3883-03da-42cb-b7ca-d58b8aac1368","trusted":false},"cell_type":"code","source":"col_names = ['time','x', 'y', 'dis', 's', 'o','dir']\n\nfig, ax = plt.subplots(len(col_names), figsize=(8,40))\n\nfor i, col_val in enumerate(col_names):\n\n    sns.boxplot(y=df_player_track[col_val], ax=ax[i])\n    ax[i].set_title('Box plot - {}'.format(col_val), fontsize=10)\n    ax[i].set_xlabel(col_val, fontsize=8)\n\nplt.show()","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"colab_type":"code","id":"AKvZTh7M1j8p","outputId":"b2679b4b-64a2-4ede-e5aa-04733cb7caac","trusted":false},"cell_type":"code","source":"def percentile_based_outlier(data, threshold=95):\n    diff = (100 - threshold) / 2\n    minval, maxval = np.percentile(data, [diff, 100 - diff])\n    return (data < minval) | (data > maxval)\n\ncol_names = ['time','x', 'y', 'dis', 's', 'o','dir']\n\nfig, ax = plt.subplots(len(col_names), figsize=(8,40))\n\nfor i, col_val in enumerate(col_names):\n    x = df_player_track[col_val][:1000]\n    sns.distplot(x, ax=ax[i], rug=True, hist=False)\n    outliers = x[percentile_based_outlier(x)]\n    ax[i].plot(outliers, np.zeros_like(outliers), 'ro', clip_on=False)\n\n    ax[i].set_title('Outlier detection - {}'.format(col_val), fontsize=10)\n    ax[i].set_xlabel(col_val, fontsize=8)\n\nplt.show()","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"-XNO-0fs15Wv"},"cell_type":"markdown","source":"The values marked with a dot below in the x-axis of the graph are the ones that are removed from the column based on the set threshold percentile (95 in our case), and is also the default value when it comes to percentile-based outlier removal.\n\n"},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":505},"colab_type":"code","id":"FtNFQ_xC1_qe","outputId":"e61febe8-7b17-4a10-da02-ccf8674d9e04","trusted":false},"cell_type":"code","source":"f, ax = plt.subplots(figsize=(10, 8))\ncorr = df_player_track.corr()\nsns.heatmap(corr,\n            xticklabels=corr.columns.values,\n            yticklabels=corr.columns.values,\n            cmap=\"Blues\")","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"JJT9Ujii2UPG"},"cell_type":"markdown","source":"##### Above we can see the correlation network of all the variables selected, correlation value lies between -1 to +1. Highly correlated variables will have correlation value close to +1 and less correlated variables will have correlation value close to -1.\n\n##### In this dataset, we can that distance and speed are highly correlated. However for the rest of variables it seems that they are not correlated and the diagonal elements of the matrix value are always 1 as we are finding the correlation between the same columns thus the inference here is that all the numerical attributes are important and need to be considered for building the model."},{"metadata":{"colab_type":"text","id":"w8bPdqmD8IhB"},"cell_type":"markdown","source":"#### <center> Player-Play with Injury </center>"},{"metadata":{"colab":{},"colab_type":"code","id":"ermoRaZn_OTe","trusted":false},"cell_type":"code","source":"%matplotlib inline\nfrom nltk.corpus import stopwords\nfrom nltk.stem import PorterStemmer\nfrom sklearn.preprocessing import LabelEncoder\nimport re\nfrom wordcloud import WordCloud, ImageColorGenerator\nfrom sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"x3-Lm-6d8XtV","trusted":false},"cell_type":"code","source":"df_playlist_injury=pd.merge(df_injury,df_play_list,on=['PlayerKey','GameID','PlayKey'],how=\"inner\",indicator=True)","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"colab_type":"code","id":"jXXKg83N94hV","outputId":"e405bb41-f552-4b07-e9f0-5f6a36ed13b7","trusted":false},"cell_type":"code","source":"df_playlist_injury.shape","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":288},"colab_type":"code","id":"e9Eol2f__WKT","outputId":"41d790a5-4534-4cfb-d5e8-2e8b4116ff6e","trusted":false},"cell_type":"code","source":"df_playlist_injury.describe()","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":712},"colab_type":"code","id":"YtxwhuEC-F0G","outputId":"72798452-8b56-478b-bb84-44ec453cef6d","trusted":false},"cell_type":"code","source":"df_playlist_injury['Surface'].value_counts().plot.bar(title='Freq dist of Field_Surface')\nplt.xticks(rotation=45)\nplt.tight_layout()\nprint(df_playlist_injury['Surface'].value_counts())","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"CHepPYxIGJrF"},"cell_type":"markdown","source":"We have similar numbers of injuries in each type of fields."},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":405},"colab_type":"code","id":"ut0NBYG0BSAc","outputId":"43970228-efa3-4670-930a-2de32eab6c30","trusted":false},"cell_type":"code","source":"table = pd.pivot_table(df_playlist_injury, values='PlayerKey', index=['Surface','BodyPart'], aggfunc=lambda x: x.count() )\ntable_2 = pd.pivot_table(df_playlist_injury, values='PlayerKey', index=['Surface'],columns='BodyPart', aggfunc=lambda x: x.count() )\ntable.unstack().plot(kind='bar', stacked=True,colormap=\"Blues\",edgecolor ='black')\ntable.unstack()\nplt.title('Players Injury Per Type of Field')\nplt.xlabel('Type of Field')\nplt.ylabel('No. Players')\nplt.legend( ('Ankle', 'Foot','Knee'))\nplt.grid()\nprint(table_2)","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"92dI2H9nEmqg"},"cell_type":"markdown","source":"With this plot it is clear that ankle and knee are the most common injuries, furthermore, there are almost equally if we compare the type of the field. "},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":893},"colab_type":"code","id":"zy7hrSz3Cbz6","outputId":"e5e1596d-6bcd-40fb-c545-045133b1341a","trusted":false},"cell_type":"code","source":"g = sns.catplot(\"BodyPart\",\n                col=\"Surface\",\n                data=df_playlist_injury, kind=\"count\",\n                hue='RosterPosition',\n                height=12, aspect=.7);\n\ng.fig.suptitle('Number of Player per Roster Position and grouped by injury in each type of field.')\nplt.tight_layout()\n","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"vhmidZZaE_YD"},"cell_type":"markdown","source":"In this visualizations, we can see that if the field is synthetic, the line backer, the wide receiver, and the cornerback are the most recurrent positions that are injured. On the other hand, in the natural one, the linebacker still the king of injuries, and also the wide receiver, but, the safety becomes one of the most ones in having injurend in the knee and ankle.\n\n"},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":576},"colab_type":"code","id":"vp0VwdzD-p9A","outputId":"2e3f6ec0-5023-4a46-f65b-b3389a447617","trusted":false},"cell_type":"code","source":"f, ax = plt.subplots(figsize=(10, 8))\ncorr = df_playlist_injury.corr()\nsns.heatmap(corr,\n            xticklabels=corr.columns.values,\n            yticklabels=corr.columns.values)","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"OU8-nDsWGukA"},"cell_type":"markdown","source":"We can notice that DM_M1 is totally corralated with all the variables, this is due to the join of data that we did (this dataset has injury players.) "},{"metadata":{"colab_type":"text","id":"yO39eiygHVLE"},"cell_type":"markdown","source":"##### Player movement per Player-Play with Injury"},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":108},"colab_type":"code","id":"RCFIzszsIAU1","outputId":"79af8179-aef0-4d9f-cc0e-592fc5064761","trusted":false},"cell_type":"code","source":"df_playlist_injury.columns","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"EkvTrl_AHlBG","trusted":false},"cell_type":"code","source":"df_playlist_injury_playtrack=pd.merge(df_playlist_injury,df_player_track,left_on='PlayKey',right_on='PlayKey',how=\"inner\")","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"colab_type":"code","id":"Bka0_oEVIraW","outputId":"fec00901-ca55-430b-b4b5-069f1d623e28","trusted":false},"cell_type":"code","source":"df_playlist_injury_playtrack.shape","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":305},"colab_type":"code","id":"zNIe5poXJG7O","outputId":"4b15733e-ef75-4d10-a274-10d4cc3080df","trusted":false},"cell_type":"code","source":"df_playlist_injury_playtrack.head()","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":767},"colab_type":"code","id":"Oghx7pzlJG7X","outputId":"46e42296-ebe0-4d60-d977-04feb76318c4","trusted":false},"cell_type":"code","source":"table = pd.pivot_table(df_playlist_injury_playtrack, values=['PlayerKey','s'], index=['Surface','BodyPart'], aggfunc={'PlayerKey': lambda x: x.count(),'s': [min, max, np.mean]} )\ntable_2 = pd.pivot_table(df_playlist_injury_playtrack, values= ['PlayerKey','s'], index=['Surface'],columns='BodyPart', aggfunc={'PlayerKey': lambda x: x.count(),'s': [min, max, np.mean]} )\ntable.unstack().plot(kind='bar', stacked=True,colormap=\"Blues\",edgecolor ='black')\ntable.unstack()\nplt.title('Speed per player that has and injury Per Type of Field')\nplt.xlabel('Type of Field')\nplt.ylabel('No. Players')\nplt.legend( ('Ankle', 'Foot','Knee'))\nplt.grid()\nprint(table)","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":839},"colab_type":"code","id":"m0k1ZB36RRYR","outputId":"c55aa9c6-e38f-4833-e06f-78c381c89a6c","trusted":false},"cell_type":"code","source":"data = df_playlist_injury_playtrack.groupby(['Surface','BodyPart'])['s'].agg({'LowValue':'min','HighValue':'max','Mean':'mean','Median':'median'})\ndata.reset_index().plot(x='BodyPart',kind='bar',colormap='Blues')\nplt.title('Statistics Speed per injury  in each kind of field')\nprint(data)","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"Ij3bh72bSNlW"},"cell_type":"markdown","source":"It looks like the speed in the synthetic field is higher in all the metrics per type of injury."},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":839},"colab_type":"code","id":"l4HXfbHwSda7","outputId":"ab82e376-0d53-4f4e-83c0-fd1b559dfa5e","trusted":false},"cell_type":"code","source":"data = df_playlist_injury_playtrack.groupby(['Surface','BodyPart'])['dis'].agg({'LowValue':'min','HighValue':'max','Mean':'mean','Median':'median'})\ndata.reset_index().plot(x='BodyPart',kind='bar',colormap='Blues')\nplt.title('Statistics Distance per injury in each kind of field')\nprint(data)","execution_count":0,"outputs":[]},{"metadata":{"colab_type":"text","id":"C6m3ke56Slu0"},"cell_type":"markdown","source":"In this visualization, it looks like a little bit higher distance traveled by the player in the synthetic surface in each kind of injury."},{"metadata":{"colab_type":"text","id":"kb6HYZ5BKwf5"},"cell_type":"markdown","source":"##### Acceleration/Deceleration"},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":35},"colab_type":"code","id":"XI42rhtmdxu9","outputId":"861c1d32-9b4c-4da7-9789-da855388c826","trusted":false},"cell_type":"code","source":"df_playlist_injury_playtrack['PlayerKey'].nunique()","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"qYU0txt-a9U9","trusted":false},"cell_type":"code","source":"df_playlist_injury_playtrack['a']=(max(df_playlist_injury_playtrack['s']) - min(df_playlist_injury_playtrack['s'])  /  (max(df_playlist_injury_playtrack['time'])  - min(df_playlist_injury_playtrack['time'])  ) )","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":567},"colab_type":"code","id":"N-gDYN_Ta_U-","outputId":"50032920-c0f9-4a01-c200-7759823db5e9","trusted":false},"cell_type":"code","source":"data = df_playlist_injury_playtrack.groupby(['Surface','BodyPart'])['a'].agg({'LowValue':'min','HighValue':'max','Mean':'mean','Median':'median'})\ndata.reset_index().plot(x='BodyPart',kind='bar',colormap='Blues')\nplt.title('Statistics Aceleration per injury in each kind of field')\nprint(data)","execution_count":0,"outputs":[]},{"metadata":{"colab":{},"colab_type":"code","id":"-w6bQxOiRLLx","trusted":false},"cell_type":"code","source":"df_playlist_injury_playtrack['da']=(max(df_playlist_injury_playtrack['s']) * max(df_playlist_injury_playtrack['s'])   - min(df_playlist_injury_playtrack['s']) * min(df_playlist_injury_playtrack['s']) ) /  (2 * sum(df_playlist_injury_playtrack['dis']) )","execution_count":0,"outputs":[]},{"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":567},"colab_type":"code","id":"9ZQWUFBFag1J","outputId":"9ec61a02-75be-4a37-9034-9cd8dd3ccd90","trusted":false},"cell_type":"code","source":"data = df_playlist_injury_playtrack.groupby(['Surface','BodyPart'])['da'].agg({'LowValue':'min','HighValue':'max','Mean':'mean','Median':'median'})\ndata.reset_index().plot(x='BodyPart',kind='bar',colormap='Blues')\nplt.title('Statistics Decelaration per injury in each kind of field')\nprint(data)","execution_count":0,"outputs":[]}],"metadata":{"accelerator":"TPU","colab":{"collapsed_sections":[],"machine_shape":"hm","name":"Analysis.ipynb","provenance":[],"toc_visible":true},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.5"}},"nbformat":4,"nbformat_minor":1}