-
Notifications
You must be signed in to change notification settings - Fork 167
/
tipuesearch_content.json
1 lines (1 loc) · 957 KB
/
tipuesearch_content.json
1
{"pages":[{"title":"Acerca de mi","text":"Yo vivo… En la ciudad de Buenos Aires, Argentina . Mi pais cuenta con una gran variedad de bellezas naturales, que invito a todos a conocer; yo mismo aun no conozco ni la mitad de mi hermoso país, espero algún día tener la posibilidad de recorrerlo de punta a punta. Yo soy… Contador Publico y Licenciado en Administración. He estudiado mis dos carreras en la Universidad Nacional de La Matanza . Muchos se preguntaran ¿qué hace un contador escribiendo sobre programación?; la verdad que yo tampoco sé la respuesta a esa pregunta! Yo trabajo… Como consultor, para TGV . Mi carrera profesional comenzó en Molas y Asociados , un estudio contable; allí adquirí un conocimiento generalista de la contabilidad y los sistemas administrativos, pasando por la liquidación de impuestos, la teneduría de libros y la auditoría. Luego mi carrera continuo en IBM , allí me desempeñé como analista de Revenue recognition; IBM me mostro lo que es trabajar para una gran multinacional y despertó mi pasión por la tecnología. Posteriormente ingrese a Grupo ASSA , allí pude materializar la union entre mis conocimientos administrativos-contables y mi pasion por la teconología, trabajando en la consultoria del ERP JD Edwards . Finalmente, despues de 5 años en Grupo Assa, ingresé a TGV en busca de nuevos desafíos profesionales. Para los que quieran conocer mi perfil profesional en más profundidad pueden visitar mi perfil en LinkedIn . Mis pasiones… Mis pasiones son: La Tecnología , esta es la pasión que me llevó a diseñar y comenzar este blog, me encanta estar al tanto de las últimas corrientes tecnologicas. La Literatura , me gusta mucho leer, leo de todo y variado, con una leve predileccion por las novelas de ciencia ficcion; mis novelas favoritas son: 1984, de George Orwell y La rebelion de Atlas, de Ayn Rand . El Ajedrez , el juego que me enseño mi padre a los 6 años, lo he jugado desde entonces y se convirtió en el juego más apasionante que jamás haya jugado. El Buceo , que mi hizo conocer lugares increibles. El Trail Running , no hay experiencia más enriquecedora que correr por los hermosos paisajes de cerros y montañas. River Plate , el club de futbol de mis amores, el más grande de la Argentina!! Mi Mision… Contribuir al desarrollo de un mundo más inteligente y productivo a través del uso de las tecnologías de la información.","tags":"pages","url":"https://relopezbriega.github.io/pages/acerca-de-mi.html"},{"title":"Contacto","text":"Hola, Muchas gracias por visitar el Blog!. Si quieren saber más de mí, recuerden visitar acerca de mí Los que quieran ponerse en contacto conmigo pueden hacerlo complentando el siguiente formulario; me comprometo a responder a la brevedad. Comentarios o preguntas son bienvenidas. Su nombre Su email Mensaje","tags":"pages","url":"https://relopezbriega.github.io/pages/contacto.html"},{"title":"Modelos basados en Agentes con Python","text":"\"Conocer la realidad implica construir sistemas en continua transformación que se corresponden, más o menos, a la realidad\" Jean Piaget Introducción A medida que el mundo se hace más interconectado y complejo, nuestra habilidad para comprenderlo se hace menos efectiva. Los modelos simples que solíamos utilizar ya no alcanzan para responder muchas de nuestras preguntas. Para poder entender las dinámicas de los sistemas complejos ; necesitamos de nuevas herramientas. El poder de cálculo de las computadoras actuales nos permite realizar nuevos tipos de experimentos. Una de las nuevas metodologías que disponemos para analizar los sistemas complejos son los modelos basados en agentes . Modelos basados en Agentes La idea central de los modelos basados en agentes es que muchos de los fenómenos que vemos en el mundo pueden ser modelados con agentes , un ambiente , y la descripción de las interacciones entre los agentes y entre los agentes y el ambiente . Un agente es un individuo autónomo o un objeto con propiedades particulares, acciones y posibles objetivos. El ambiente es el territorio en el que los agentes interactúan. Las interacciones pueden ser de los agentes entre sí o de éstos con el ambiente y pueden llegar a ser bastante complejas e incluso pueden llegar a cambiar con el paso del tiempo. Como las interacciones están construidas en base a un intercambio de información, luego de una interacción el agente puede actualizar su estado interno o tomar distintas decisiones. Los modelos basados en agentes constituyen una nueva generación de métodos computacionales que permiten modelar la estructura de un sistema complejo y simular su evolución dinámica a lo largo del tiempo. Constituyen un tercer modo de hacer ciencia, distinto y complementario a los dos métodos científicos clásicos: la inducción y la deducción. Un modelo basado en agentes es un tipo particular de modelo científico que se implementa como una simulación computacional. Por lo tanto, son modelos formales que deben ser distinguidos tanto de los matemáticos (basados en ecuaciones diferenciales o de otro tipo) como de los estadísticos (orientados por variables y expresados como ecuaciones de regresión, estructurales, o de otro tipo). Vínculo micro-macro Los modelos basados en agentes abordan el vínculo micro-macro en una doble dirección. En primer lugar, permiten modelar y simular el vínculo de lo micro a lo macro , es decir, \"cómo las interacciones locales y descentralizadas entre agentes autónomos y heterogéneos generan una determinada regularidad\" macrosocial . Se suele utilizar el concepto de emergente para referirse a la aparición de \"cualidades o propiedades de un sistema que presentan un carácter de novedad con relación a las cualidades o propiedades de los elementos considerados aisladamente\". Los fenómenos emergentes son, en consecuencia, difíciles de explicar y predecir en la medida en que las cualidades nuevas a nivel macro de un sistema no pueden deducirse ni reducirse al conocimiento analítico de las partes a nivel micro . En segundo lugar, los modelos basados en agentes contribuyen a comprender el vínculo de lo macro a lo micro , relativo al modo en que \"las estructuras sociales construyen e influyen las acciones futuras y las interacciones entre los actores individuales\". El interés analítico de esta fase del modelado del vínculo micro-macro se centra en comprender cómo \"los individuos elaboran representaciones mentales de construcciones sociales\" que influyen en su propia conducta práctica. En otros términos, la acción social (nivel micro) no puede escindirse del modo en que los individuos piensan y razonan sobre el orden social (nivel macro). Esta problemática ha sido conceptualizada bajo el nombre emergente de segundo orden , es decir, a la aptitud reflexiva de los agentes para razonar sobre las propiedades emergentes que la misma interacción social produce (emergencia de primer orden). Pensar con modelos Aprender es explorar. Organizar e interpretar datos con modelos se ha convertido en una competencia fundamental para la estrategia en los negocios, la planificación urbana, la economía, la medicina, la ingeniería y la ecología, entre muchas otras. Pero no necesariamente debemos quedarnos con un solo modelo, muchas veces la aplicación de un conjunto de modelos puede ayudarnos a dar sentido a fenómenos complejos . La idea central es que el pensamiento con múltiples modelos produce sabiduría a través de un conjunto diverso de marcos lógicos. Los distintos modelos acentúan diferentes fuerzas causales. Sus conocimientos e implicaciones se superponen y se entrelazan. Al utilizar muchos modelos como marcos, desarrollamos una comprensión más profunda de la realidad. Algunas de las ventajas de utilizar modelos son: Los modelos son explicativos porque señalan los mecanismos esenciales que subyacen un fenómeno. Pueden funcionar como una prueba de que los mecanismos hipotéticos son suficientes para dar cuenta de una observación. Los modelos nos proporcionan una prueba de concepto de que algo es posible. Los modelos facilitan la experimentación. Los modelos se pueden ejecutar repetidamente para notar variaciones en su dinámica y sus resultados. Los parámetros del modelo se pueden variar para ver el efecto en su comportamiento y resultados. Los modelos nos proporcionan analogías. Dado que los modelos son simplificaciones de la realidad, nos permiten encontrar similitudes con otras simplificaciones similares, incluso si los fenómenos modelados son aparentemente muy diferentes. Los modelos se pueden utilizar como vehículo de comunicación y educación . Los modelos nos brindan una herramienta educativa que encapsula conocimientos que pueden no estar fácilmente disponibles en la observación del mundo real. Mesa Una de las razones por las que amo Python ; es que siempre es posible encontrar alguna librería para hacer casi cualquier cosa en Ciencia con su ayuda. Obviamente, modelar modelos basados en agentes no podía ser una excepción. Mesa es un paquete de Python de código abierto con licencia Apache 2.0 que nos permite crear rápidamente modelos basados en agentes utilizando componentes centrales incorporados (como programadores de agentes y cuadrículas espaciales) o implementaciones personalizadas; visualizarlos usando una interfaz basada en el navegador; y analizar sus resultados con las herramientas de análisis de datos Python . El principio rector de la arquitectura de Mesa es la modularidad; hace suposiciones mínimas sobre la forma que tomará un modelo. Se divide en tres categorías principales: modelado, análisis y visualizaciones. El componente principal de modelado, nos brinda todo lo que necesitamos para construir un modelo. La clase Model para guardar los parámetros a nivel modelo; una o más clases Agent que describen a los agentes del modelo; un Scheduler que controla la activación de los agentes y maneja el tiempo y el espacio o red en la que se desarrollan las interacciones. Los componentes de análisis son el data collector utilizado para grabar los datos de cada ejecución del modelo y los batch runners para automatizar múltiples ejecuciones con distintos parámetros. Finalmente, los componentes de visualización se utilizan para mapear desde un objeto modelo a uno o más representaciones visuales a través de una interfaz de servidor con el navegador web. Modelando el COVID19 - Modelo SIR Tiempo de pasar a un ejemplo concreto, y que mejor que aprovechar la popularidad de los modelos epidemiológicos que trajo la pandemia global del COVID19 . Para este ejemplo en particular vamos a utilizar el modelo SIR ; el cual es uno de los más simples para captar las características típicas de los brotes epidémicos. El nombre del modelo proviene de las iniciales S (población susceptible), I (población infectada) y R (población recuperada); relaciona las variaciones de las tres poblaciones (Susceptible, Infectada y Recuperada) a través de la tasa de infección y el período infeccioso promedio. Veamos como implementarlo para una población de 10000 individuos. In [1]: Ver Código # Importando las librerías que vamos a utilizar import pandas as pd import numpy as np import matplotlib.pyplot as plt from datetime import date as datemethod from datetime import datetime from mesa import Agent from mesa import Model from mesa.time import RandomActivation from mesa.space import NetworkGrid from mesa.datacollection import DataCollector from mesa_SIR import SIR import mesa_SIR.calculations_and_plots as c_p # graficos incrustados % matplotlib inline parametros = { 'I0' : 0.01 , 'ptrans' : 0.25 , 'progression_period' : 3 , 'progression_sd' : 2 , 'population' : 10000 , 'interactions' : 6 , 'reinfection_rate' : 0.00 , 'death_rate' : 0.0200 , 'recovery_days' : 15 , 'recovery_sd' : 7 , 'severe' : 0.18 , 'steps' : 20 } # Parámetros ################################################################################################################### # ptrans = Probabilidad de transmisión. # population = Total de población. # progression_period = Número de días hasta que se busca tratamiento. # progression_sd = Desvio estandar para buscar tratamiento. # interactions = Número de interacciones por persona (baja con distancia social) # reinfection_rate = Probalidad de volver a enfermar luego de recuperado. # I0 = Probalidad inicial de estar infectado. # death_rate = Probabilidad de muerte luego de ser infectado. # recovery_days = Promedio de días para recuperarse # recovery_sd = Desvio estandar de los días de recuperación. # severe = Probabilidad de desarrollar síntomas severos. # steps = número de días de la simulación. #Agent class class humano ( Agent ): def __init__ ( self , unique_id , model ): super () . __init__ ( unique_id , model ) self . pos = unique_id self . infected , self . susceptible , self . severe = self . model . SIR_instance . initial_infection () self . was_infected = False self . recovered = False self . alive = True self . day = 0 self . induced_infections = 0 self . infected_others = False def step ( self ): self . model . SIR_instance . interact ( self ) self . day += 1 class modelo_COVID ( Model ): def __init__ ( self ): super () . __init__ ( Model ) self . susceptible = 0 self . dead = 0 self . recovered = 0 self . infected = 0 interactions = parametros [ 'interactions' ] self . population = parametros [ 'population' ] self . SIR_instance = SIR . Infection ( self , ptrans = parametros [ 'ptrans' ], reinfection_rate = parametros [ 'reinfection_rate' ], I0 = parametros [ \"I0\" ], severe = parametros [ \"severe\" ], progression_period = parametros [ \"progression_period\" ], progression_sd = parametros [ \"progression_sd\" ], death_rate = parametros [ \"death_rate\" ], recovery_days = parametros [ \"recovery_days\" ], recovery_sd = parametros [ \"recovery_sd\" ]) G = SIR . build_network ( interactions , self . population ) self . grid = NetworkGrid ( G ) self . schedule = RandomActivation ( self ) self . dead_agents = [] self . running = True for node in range ( self . population ): new_agent = humano ( node , self ) self . grid . place_agent ( new_agent , node ) self . schedule . add ( new_agent ) self . datacollector = DataCollector ( model_reporters = { \"infectados\" : lambda m : c_p . compute ( m , 'infected' ), \"recuperados\" : lambda m : c_p . compute ( m , 'recovered' ), \"susceptibles\" : lambda m : c_p . compute ( m , \"susceptible\" ), \"muertes\" : lambda m : c_p . compute ( m , \"dead\" ), \"R0\" : lambda m : c_p . compute ( m , \"R0\" ), \"casos_severos\" : lambda m : c_p . compute ( m , \"severe\" )}) self . datacollector . collect ( self ) def step ( self ): self . schedule . step () self . datacollector . collect ( self ) if self . dead == self . schedule . get_agent_count (): self . running = False else : self . running = True In [2]: # Ejecución del modelo # Guardar salida del modelo today = datemethod . strftime ( datetime . utcnow (), '%Y%m %d Z%H%M%S' ) filename = 'COVID_output_' + today + '.csv' output_path = '/home/raul/Documentos/' output_file = output_path + filename # iniciar modelo covid = modelo_COVID () dias = parametros [ \"steps\" ] for i in range ( dias ): covid . step () # generar salida. output_data = covid . datacollector . get_model_vars_dataframe () In [3]: output_data Out[3]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } infectados recuperados susceptibles muertes R0 casos_severos 0 94 0 9906 0.0 0.000000 12 1 1357 10 8633 0.0 2.791667 229 2 6748 88 3161 3.0 2.310806 1193 3 9322 311 355 12.0 2.119270 1651 4 9288 638 53 21.0 2.105173 1629 5 8860 1088 9 43.0 2.097285 1545 6 8265 1665 3 67.0 2.097300 1409 7 7572 2337 1 90.0 2.098090 1283 8 6731 3157 1 111.0 2.098667 1126 9 5839 4029 1 131.0 2.098212 968 10 4858 4997 0 145.0 2.097477 791 11 3826 6013 0 161.0 2.097345 609 12 2901 6929 0 170.0 2.097471 453 13 2108 7713 0 179.0 2.097772 323 14 1402 8417 0 181.0 2.097577 217 15 864 8952 0 184.0 2.097814 145 16 489 9324 0 187.0 2.098052 83 17 246 9564 0 190.0 2.098052 40 18 110 9699 0 191.0 2.098052 20 19 46 9763 0 191.0 2.098052 9 20 22 9787 0 191.0 2.098052 5 In [4]: title = 'Modelo COVID' plot1 = c_p . plot_SIR ( output_data , title ) In [5]: c_p . plot_severe ( output_data , title ) Como vemos en este ejemplo simplificado, la infección se expande a un velocidad muy considerable; en tan solo 3 días la mayoría de la población está infectada. Podemos jugar con los parámetros, por por ejemplo con el de interacciones, para ver el efecto que puede tener reducir la circulación social en la expansión de la infección (el ya tan famoso aplanamiento de la curva en Argentina). Los modelos basados en agentes constituyen una herramienta más del enorme arsenal que nos ofrecen las computadoras para asistirnos en el proceso de toma de decisiones. No deberíamos perder la oportunidad de explorarlos! Saludos! Este post fue escrito por Raúl e. López Briega utilizando Jupyter notebook . Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Analisis de datos","url":"https://relopezbriega.github.io/blog/2020/08/09/modelos-basados-en-agentes-con-python/"},{"title":"Introducción al pensamiento lógico con Python","text":"\"No, no estás pensando...solo estás siendo lógico\" Niels Bohr Introducción Cuando pensamos, estamos realizando un complejo proceso que se inicia con la creación de imágenes mentales en nuestro cerebro. Estas imágenes las integramos, emparejamos, proyectamos o asociamos con nuestros conceptos o esquemas mentales que representan nuestra realidad en un proceso simbólico que necesitamos estructurar en secuencias que sigan un orden racionalmente definido. Gracias a este proceso, podemos prever lo que sucederá, evaluar las consecuencias de nuestros actos; y anticiparnos para evitar episodios desfavorables. Naturalmente, al ser seres humanos, este proceso de pensamiento puede verse afectado por nuestras emociones y otros factores físicos o sociales que prejuzgan nuestra manera de representar las cosas. Una de las herramientas que tenemos para evitar errores en nuestro pensamiento es la lógica . ¿Qué es la Lógica? Una de las principales herramientas que poseemos para intentar comprender la realidad que nos rodea es la lógica . Más formalmente la lógica es un arte en la ciencia de la comprensión o el razonamiento. Se trata de avanzar en nuestra comprensión de una manera ordenada y correcta y que nos brinde facilidad. Se centra en el estudio de los procedimientos válidos y no válidos de pensamiento, es decir, en procesos como la demostración , la inferencia o la deducción, así como en conceptos como las falacias , las paradojas y la verdad. El propósito de la lógica es guiar de manera precisa los pensamientos que, de no ser exactos, fallarían. Lógica y argumentación Los seres humanos tenemos la capacidad para establecer y verificar hechos , cambiar o justificar opiniones , y en general, dar sentido a las cosas. Hacemos esto a través de la razón ; si bien todos somos capaces de razonar , alarmantemente solo un porcentaje pequeño de nosotros lo hace realmente bien. La lógica es la ciencia que se usa para evaluar un argumento . Un argumento es un intento de persuadir a alguién de algo a través de darle razones para aceptar una conclusión dada. Utilizamos y escuchamos argumentos todos los días e incluso muchas veces sin darnos cuenta de ello. Si queremos reconocer un buen argumento de uno malo y ganar argumentaciones, entonces debemos dominar los mecanismos de la racionalidad pura, es decir, las leyes de la lógica . Un argumento esta hecho de premisas y una conclusión . Las premisas suelen ser referidas también como razones o evidencias ; en esencia, son simples proposiciones o declaraciones que se presentan para ser aceptadas como válidas. La conclusión no se más que la proposición final del argumento . Si el argumento es válido, las premisas implican la conclusión . Lógica inductiva y deductiva En general, usamos dos tipos de lógica , deductiva e inductiva . Las inferencias deductivas empiezan con un conocimiento general y predicen una observación específica; es decir que, en esta forma de razonamiento, la conclusión sigue necesariamente a las premisas . El ejemplo clásico es: premisa 1: Todos los humanos son mortales. premisa 2: Socrates es humano. conclusión: Socrates es mortal. Las inferencias inductivas hacen lo contrario. Comienzan con observaciones de una experiencia particular y llegan a conclusiones generales. En la inducción , la conclusión se basa simplemente en una probabilidad . El ejemplo clásico es: premisa 1: El sol ha salido todos los días hasta ahora. conclusión: Por lo tanto, el sol saldrá mañana. La solución de problemas demasiado complicados para ser resueltos por medio del sentido común se logra mediante largas series de inferencias mezcladas, tanto inductivas como deductivas , que se entrelazan entre la experiencia observada y se formalizan como el método científico . El propósito real del método científico es asegurarnos de que la Naturaleza no nos ha inducido a pensar que sabemos algo que en realidad ignoramos. Un solo desliz lógico y se derrumba todo un edificio científico. Falacias La importancia de conocer los mecanismos de la lógica radica en tratar de comprender si un argumento que alguien dice es lógicamente consistente; ya que si es inconsistente, implica que no deberíamos creer lo que esa persona nos dice. Lo más importante a tener en cuenta es la verdad de sus premisas ; es decir, si éstas premisas se sustentan en evidencias y observaciones de la realidad. Una falacia es un error de razonamiento que invalida un argumento. Una de las características básicas del pensamiento lógico es la capacidad para detectar errores en las conclusiones o en las premisas de un determinado argumento para poder evitar los razonamientos falaces, ya que éstos nos dificultan llegar a conocer la verdad de los hechos y nos hacen más susceptibles a la manipulación y la tergiversación. Las falacia son, en definitiva, afirmaciones sin fundamento que a menudo se proclaman con una convicción que las hace sonar como si fueran hechos probados. Basta dedicar cinco minutos a escuchar las noticias o a leer la sección de política para encontrar cientos de ejemplos de falacias ! Veamos algunas de las más comunes: Falacia \"ad hominem\": Los ataques personales son contrarios a los argumentos racionales. En lógica y retórica, un ataque personal se llama \"ad hominem\", que en latín significa \"contra el hombre\". En lugar de avanzar en un buen razonamiento, una falacia ad hominem reemplaza la argumentación lógica con un lenguaje ofensivo no relacionado con la verdad del asunto. (Muy común en la política argentina). Falacia de la apelación a la autoridad: Esta falacia argumentativa, también denominada \"ad verecundiam\", ocurre cuando hacemos mal uso de una autoridad. Este mal uso de la autoridad puede ocurrir de varias maneras; por ejemplo: podemos citar solo a las autoridades, alejándonos convenientemente de otras pruebas comprobables y concretas como si la opinión de los expertos fuera siempre correcta; o podemos citar autoridades irrelevantes, autoridades pobres o autoridades falsas. Falacia populista: Esta falacia, también denominada argumento \"ad populum\", supone que algo es cierto (o correcto o bueno) porque otras personas están de acuerdo con la persona que lo afirma; esto es, se acepta algo que se dice porque es popular. Falacia de la generalización apresurada: Una generalización apresurada es una declaración general sin evidencia suficiente para respaldarla. Ésta se produce a partir de la prisa por llegar a una conclusión, lo que lleva a la persona que argumenta a cometer algún tipo de suposición ilógica o a emitir estereotipos, conclusiones injustificadas o exageraciones. Falacia del falso dilema: Esta falacia argumentativa ocurre cuando fallamos al limitar las opciones a únicamente dos, cuando de hecho hay más opciones para elegir. A veces las opciones son entre una cosa, la otra, o ambas cosas juntas (no se excluyen entre sí). Y a veces hay una amplia gama de opciones. Obviamente, esta es una muestra muy pequeña de las trampas en las que puede caer nuestro pensamiento. Hay cientos de ellas! Juegos de lógica A mí mamá le gustan las revistas de pasatiempos, esas revistas que traen cientos de juegos entre los que se incluyen sopas de letras, palabras cruzadas, autodefinidos, etc. Si bien ella tiene una habilidad increíble para resolver todos los juegos relacionados con palabras...siempre tuvo muuuchas dificultades con los del tipo Quién es Quién; en esos juegos, nos presentan un número de afirmaciones y en base a ellas debemos aplicar lógica deductiva para obtener la información faltante. Aquí va un ejemplo: Tenemos la siguiente información sobre cuatro personas: Esteban tiene un auto azul La persona que tiene un gato vive en España. Matías vive en Argentina La persona con el auto negro vive en Brasil. Juan tiene un gato. Alex vive en Brasil La persona que tiene un perro vive en Colombia. ¿Quién tiene un conejo? El objetivo entonces es, a partir de estas afirmaciones, deducir quién es el dueño del conejo. Aquellos lo suficientemente valientes para intentar resolver el enigma con sus propios medios, pueden dejar de leer y recurrir al viejo método de papel y lápiz para arribar a la solución...los que prefieren aplicar la ley del menor esfuerzo (como es mi caso) pueden continuar leyendo para ver como con la ayuda de Python y la librería kanren , una librería que nos ayuda a implementar el paradigma de la programación lógica ; podemos resolver este enigma lógico y encontrar su solución. In [1]: Ver Código # Importando las librerías que vamos a utilizar from kanren import run , var , eq , membero from kanren.core import lall In [2]: # Enigma de Lógica # Declaro la variable gente gente = var () In [3]: # Defino las reglas reglas = lall ( # hay 4 personas ( eq , ( var (), var (), var (), var ()), gente ), # Esteban tiene un auto azul ( membero , ( 'Esteban' , var (), 'azul' , var ()), gente ), # La persona que tiene un gato vive en España. ( membero , ( var (), 'gato' , var (), 'España' ), gente ), # Matías vive en Argentina ( membero , ( 'Matías' , var (), var (), 'Argentina' ), gente ), # La persona con el auto negro vive en Brasil. ( membero , ( var (), var (), 'Negro' , 'Brasil' ), gente ), # Juan tiene un gato. ( membero , ( 'Juan' , 'gato' , var (), var ()), gente ), # Alex vive en Brasil ( membero , ( 'Alex' , var (), var (), 'Brasil' ), gente ), # La persona que tiene un perro vive en Colombia. ( membero , ( var (), 'perro' , var (), 'Colombia' ), gente ), # ¿Quién tiene un conejo? ( membero , ( var (), 'conejo' , var (), var ()), gente ) ) In [4]: # Ejecuto la solución soluciones = run ( 0 , gente , reglas ) In [5]: # Extraigo la respuesta dueño = [ nombre for nombre in soluciones [ 0 ] if 'conejo' in nombre ][ 0 ][ 0 ] In [6]: # Imprimo los resultados. print ( ' \\n ' + dueño + ' es el dueño del conejo' ) print ( ' \\n Detalle del enigma:' ) atributos = [ 'Nombre' , 'Mascota' , 'Color de auto' , 'País' ] print ( ' \\n ' + ' \\t\\t ' . join ( atributos )) print ( '=' * 60 ) for item in soluciones [ 0 ]: print ( '' ) print ( ' \\t\\t ' . join ([ str ( x ) for x in item ])) Matías es el dueño del conejo Detalle del enigma: Nombre Mascota Color de auto País ============================================================ Esteban perro azul Colombia Juan gato ~_9 España Matías conejo ~_11 Argentina Alex ~_13 Negro Brasil Como podemos ver en el detalle del enigma, con la información proporcionada todavía hay datos que no podemos deducir, pero a pesar de la información incompleta, el programa sí pudo deducir la pregunta que estábamos buscando. Matías, que vive en Argentina, es el dueño del conejo. Aquí finaliza este artículo. Si bien la lógica es una herramienta muy útil dentro de la compleja maquinaria de nuestro pensamiento, y nos ayuda a pensar con más claridad y menos errores; no por ello debemos confiar ciegamente en ella y dejar de seguir nuestros instintos. Obviamente este artículo va dedicado a una de las personas que más amo en el mundo, mi mamá! y su constante lucha con los juegos de lógica. Tal vez con algún día los pueda resolver... 😆 Saludos! Este post fue escrito por Raúl e. López Briega utilizando Jupyter notebook . Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Machine Learning","url":"https://relopezbriega.github.io/blog/2020/07/12/introduccion-al-pensamiento-logico-con-python/"},{"title":"Sistemas dinámicos, Complejidad y Caos con Python","text":"\"LLamamos caos al orden que todavía no comprendemos\" Edward Lorenz Introducción Vivimos en un mundo tan bello como complejo. El enfoque tradicional de la ciencia,que busca generalmente reducir a los sistemas complejos en cada una de sus partes, comprender cada parte, y luego entender todo el sistema, parece no ser suficiente. Cuando las partes comienzan a conectarse e interactuar entre sí, las bases científicas de este enfoque comienzan a fallar, y se torna casi imposible predecir el comportamiento del sistema. Los simples supuestos como la linealidad , la independencia , o la distribución normal parecen no ser de mucha utilidad a la hora de modelar los fenómenos incluso más sencillos del mundo que nos rodea. Es a partir de esta realidad, que nuevos enfoques se comenzaron a desarrollar y surgieron nuevos campos de estudio multidisciplinarios como los sistemas dinámicos , la complejidad , o la teoría del caos . ¿Qué es un sistema complejo? Es difícil definir rigurosamente que es un Sistema complejo , podemos decir que es un campo de investigación interdisciplinario que busca explicar cómo un gran número de entidades relativamente simples (las cuales generalmente se denominan agentes ) se organizan, sin el beneficio de ningún controlador central, en un todo colectivo que crea patrones, usa información y, en algunos casos, evoluciona y aprende. Es el caso típico en que el todo es mucho más que la suma de sus partes . Aunque no existe una buena definición de que es un Sistema complejo , si podemos describir algunas de las características principales que poseen estos sistemas, como ser: El sistema contiene una colección de muchos agentes que interactúan. Las interacciones entre estos agentes pueden surgir porque están físicamente cerca el uno del otro, o porque son miembros de algún tipo de grupo, o porque comparten alguna información común. En la medida en que los agentes están vinculados entre sí a través de sus interacciones, también se puede pensar que forman parte de una red o grafo . Por lo que el análisis de redes es una herramienta importante para comprender estos sistemas. El comportamiento de estos agentes se ve afectado por una memoria o retroalimentación . Esto significa que algo del pasado afecta algo en el presente, o que algo que sucede en un lugar afecta lo que está sucediendo en otro, en otras palabras, una especie de efecto de arrastre. El resultado neto de que todos tengan tal memoria puede ser que el sistema como un todo también recuerde. Por lo tanto, un patrón o secuencia global particular puede manifestarse en el sistema. Los agentes pueden adaptar sus estrategias de acuerdo a su historia. Esto simplemente significa que un agente puede adaptar su comportamiento por sí mismo, con la esperanza de mejorar su rendimiento. El sistema es típicamente abierto . Es decir, que el sistema puede ser influenciado por su entorno. Así mismo, en el comportamiento característico de los Sistemas complejos , podemos observar los siguientes rasgos: El sistema parece estar vivo . El sistema evoluciona de una manera no trivial y, a menudo complicada, impulsada por una ecología de agentes que interactúan y se adaptan bajo la influencia de la retroalimentación . El sistema exhibe fenómenos emergentes que generalmente son sorprendentes y pueden ser extremos. En terminología científica, el sistema está lejos de alcanzar un equilibrio . Esto básicamente significa que cualquier cosa puede suceder, y si se espera lo suficiente, generalmente lo hará. Tales fenómenos generalmente son inesperados en términos de cuándo surgen, de ahí su aspecto de sorpresa. Pero el sistema también tenderá a exhibir fenómenos emergentes que a su vez son sorprendentes en el sentido de que no podrían haber sido predichos en base al conocimiento de las propiedades de los agentes individuales. Los fenómenos emergentes suelen surgir en ausencia de cualquier tipo de mano invisible o controlador central. En otras palabras, un Sistema complejo puede evolucionar de una manera complicada por sí mismo. Por esta razón, a menudo se considera que los Sistemas complejos son mucho más que la suma de sus partes. El sistema muestra una mezcla complicada de comportamiento ordenado y desordenado. De manera más general, todos los Sistemas complejos parecen poder moverse entre el orden y el desorden por su propia cuenta. ¿Qué es la teoría del caos? La teoría del caos , más que una teoría, es un paradigma que supuso en su momento una gran revolución científica, al reflejar que muchos sistemas que eran considerados deterministas y previsibles tenían severos límites en dicha previsibilidad. Es decir, que no eran tan útiles como se creía a la hora de predecir eventos futuros. Iniciada por Henri Poincaré y popularizada gracias al trabajo del matemático y meteorólogo Edward Lorenz , la teoría del caos se ha utilizado en campos como las matemáticas y la meteorología para explicar la inexactitud y la dificultad para obtener resultados previsibles de la realidad. El efecto mariposa Esta teoría es ampliamente conocida por lo que se denomina el efecto mariposa , según el cual el débil aleteo de una mariposa puede ser la causa de un huracán a miles de kilómetros de distancia . Se indica de este modo que la existencia de una variable concreta puede provocar o alterar otras, influyéndose progresivamente hasta obtener un resultado fuera de lo esperado. En síntesis, podemos considerar que la teoría del caos establece que pequeños cambios en las condiciones iniciales crean grandes diferencias respecto al resultado final, con lo que una gran mayoría de los sucesos y sistemas no resultan totalmente predecibles. Es importante tener en cuenta que a pesar de las apariencias, el caos al que se refiere esta teoría no implica una falta de orden, sino que los hechos y la realidad no se ajustan a un modelo lineal . El mapa logístico Para entender mejor qué es el caos exploremos el famoso mapa logístico , sin duda una de las ecuaciones más famosas en la ciencia de los sistemas dinámicos. El mapa logístico representa un modelo sencillo para intentar explicar la dinámica de una población de la que se ha supuesto que tiene un crecimiento cada vez más lento a medida que se acerca a una cantidad de individuos considerada como límite. El modelo simplifica al combinar los efectos de la tasa de natalidad y la tasa de mortalidad en un solo número, llamado $R$. El tamaño de la población se reemplaza por un concepto relacionado \"fracción de la capacidad de carga\", llamado $x$. Su expresión matemática es la siguiente: $$x_{t+1} = Rx_t(1 - x_t)$$ Esta ecuación define las reglas o dinámicas de nuestro sistema: $x$ representa la población en un momento dado $t$, y $R$ representa la tasa de crecimiento. En otras palabras, el nivel de población en un momento dado es una función del parámetro de la tasa de crecimiento y el nivel de población del intervalo de tiempo anterior. Si la tasa de crecimiento es demasiado baja, la población se extinguirá. Las tasas de crecimiento más altas podrían establecerse hacia un valor estable o fluctuar a través de una serie de auges y declives de la población. Como podemos ver, es una ecuación muy simple! la más simple en capturar la esencia del caos ! El mapa logístico se vuelve muy interesante a medida que vamos variando el valor de $R$; veamos algunos ejemplos numéricos con la ayuda de Python y la librería pynamical . In [1]: Ver Código # Importando las librerías que vamos a utilizar import pandas as pd import numpy as np from fractal import Fern , Lorentz from pynamical import simulate , bifurcation_plot , phase_diagram , phase_diagram_3d import matplotlib.pyplot as plt import warnings warnings . filterwarnings ( 'ignore' ) # graficos incrustados % matplotlib inline In [2]: # modelo logistico para 20 generaciones con 7 tasas de crecimiento entre 0.5 y 3.5. poblacion = simulate ( num_gens = 20 , rate_min = 0.5 , rate_max = 3.5 , num_rates = 7 ) poblacion . applymap ( lambda x : ' {:03.3f} ' . format ( x )) Out[2]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } 0.5 1.0 1.5 2.0 2.5 3.0 3.5 0 0.500 0.500 0.500 0.500 0.500 0.500 0.500 1 0.125 0.250 0.375 0.500 0.625 0.750 0.875 2 0.055 0.188 0.352 0.500 0.586 0.562 0.383 3 0.026 0.152 0.342 0.500 0.607 0.738 0.827 4 0.013 0.129 0.338 0.500 0.597 0.580 0.501 5 0.006 0.112 0.335 0.500 0.602 0.731 0.875 6 0.003 0.100 0.334 0.500 0.599 0.590 0.383 7 0.002 0.090 0.334 0.500 0.600 0.726 0.827 8 0.001 0.082 0.334 0.500 0.600 0.597 0.501 9 0.000 0.075 0.333 0.500 0.600 0.722 0.875 10 0.000 0.069 0.333 0.500 0.600 0.603 0.383 11 0.000 0.065 0.333 0.500 0.600 0.718 0.827 12 0.000 0.060 0.333 0.500 0.600 0.607 0.501 13 0.000 0.057 0.333 0.500 0.600 0.716 0.875 14 0.000 0.054 0.333 0.500 0.600 0.610 0.383 15 0.000 0.051 0.333 0.500 0.600 0.713 0.827 16 0.000 0.048 0.333 0.500 0.600 0.613 0.501 17 0.000 0.046 0.333 0.500 0.600 0.711 0.875 18 0.000 0.044 0.333 0.500 0.600 0.616 0.383 19 0.000 0.042 0.333 0.500 0.600 0.710 0.827 In [3]: # Graficando los resultados colores = [ 'yellow' , 'green' , 'blue' , 'peru' , 'black' , 'red' , 'grey' ] for color , tasa in reversed ( list ( zip ( colores , poblacion . columns ))): ax = poblacion [ tasa ] . plot ( kind = 'line' , figsize = [ 10 , 6 ], linewidth = 2.5 , alpha = 0.95 , c = color ) ax . grid ( True ) ax . set_ylim ([ 0 , 1 ]) ax . legend ( title = 'Tasa de Crecimiento' , loc = 3 , bbox_to_anchor = ( 1 , 0.525 )) ax . set_title ( 'Resultados del modelo logistico por tasa de crecimiento' ) ax . set_xlabel ( 'Generacion' ) ax . set_ylabel ( 'Populacion' ) plt . show () Atractores Hasta aquí, el sistema parece tener un comportamiento bastante predecible y parece ser estable. En los sistemas dinámicos , un Atractor es un valor, o conjunto de valores hacia los que el sistema tienen a evolucionar en el tiempo y estabilizarse. En nuestro ejemplo, cuando el parámetro de crecimiento es 0.5, el sistema tiene un Atractor de punto fijo en el nivel de población 0 como se muestra en la línea amarilla. En otras palabras, el valor de la población tiende a 0 con el tiempo a medida que el modelo va evolucionando. Cuando el parámetro de tasa de crecimiento es 3.5, el sistema oscila entre cuatro valores, como se muestra en la línea gris. Este otro Atractor se llama de ciclo límite . Lo verdaderamente interesante se produce cuando aumentamos el valor de la tasa de crecimiento a un valor superior a 4, en ese momento el sistema se vuelve caótico! Diagrama de bifurcación Para poder ver claramente como el sistema evoluciona hacia un comportamiento caótico, en los sistemas dinámicos se suele recurrir a la ayuda de un diagrama de bifurcación . Analizando la evolución de nuestra función, podemos ver que en las diferentes iteraciones con los valores de $R$ entre 0.5 y 4.0; el sistema fue evolucionando hacia Atractor de punto fijo , luego hacia otro de ciclo límite de 2 valores, un siguiente de 4, luego otro de 8, y así sucesivamente hasta llegar al caos . Cada uno de estos ciclos son llamadas bifurcaciones . Estas bifurcaciones a menudo se resumen en un llamado diagrama de bifurcación que traza el Atractor en el que termina el sistema en función del valor de un \"parámetro de control\", que en nuestro caso es el valor de la tasa de crecimiento $R$. In [4]: # Modelo con 100 generaciones haciendo crecer R en 1000 pasos. pops = simulate ( num_gens = 100 , rate_min = 0 , rate_max = 4 , num_rates = 1000 , num_discard = 1 ) #Graficando el diagrama de bifurcación bifurcation_plot ( pops ) Como se puede ver en el gráfico, en algún punto más allá de un valor de $R$ de 3.5 las bifurcaciones se vuelven caóticas y se vuelve imposible de predecir. Edward Lorenz, no linealidad y los atractores extraños No se puede escribir un artículo sobre caos , sin mencionar a Edward Lorenz quien fuera uno de sus pioneros. Lorenz se topó con las particularidades del caos al trabajar en modelos matemáticos para intentar predecir el clima (algo que ni siquiera podemos hacer bien hoy en día!). Fue el primero en notar (se podría decir que por accidente) el fenómeno de la dependencia sensible a los valores de las condiciones iniciales de algunos sistemas. La característica principal de estos sistemas es su no linealidad . Un sistema es lineal cuando podemos comprender sus partes individualmente y luego unirlas en un todo coherente. Las relaciones lineales se pueden capturar con una línea recta en un gráfico; son fáciles de pensar: cuanto más, mejor. Las ecuaciones lineales se pueden resolver, lo que las hace adecuadas para libros de texto. En cambio, un sistema no lineal es aquel en el que el todo es diferente de la suma de las partes; no se pueden resolver o expresar con claridad en un gráfico. Son la pesadilla de cualquier matemático. Profundizando en el estudio de estos sistemas no lineales y su complejidad , Lorenz simplificó algunas ecuaciones de dinámica de fluidos (llamadas ecuaciones de Navier-Stokes ) y terminó con un conjunto de tres ecuaciones no lineales : $$\\frac{dx}{dt} = P(y-x)$$$$\\frac{dy}{dt} = x(R-z) - y$$$$\\frac{dz}{dt} = xy - cz$$ donde $P$ es el número de Prandtl que representa la relación entre la viscosidad del fluido y su conductividad térmica; $R$ es el número de Rayleigh y representa la diferencia de temperatura entre la parte superior e inferior del sistema, . Los valores que utilizó Lorenz fueron $P = 10$, $R = 28$ y $c = 8/3$. En la superficie, estas tres ecuaciones parecen fáciles de resolver. Sin embargo, representan un sistema dinámico extremadamente complicado. Al graficar los resultados en un espacio fásico ; nos encontramos con lo que hoy se conoce como el Atractor de Lorenz . El Atractor de Lorenz es un ejemplo de un atractor extraño . Los atractores extraños son únicos en comparación a los otros Atractores mencionados, en el sentido en que uno no sabe exactamente en qué parte del Atractor estará el sistema. Dos puntos en el Atractor que están cerca uno del otro al mismo tiempo estarán arbitrariamente separados en momentos posteriores. La única restricción es que el estado del sistema permanezca en el atractor. Los atractores extraños también son únicos porque nunca se cierran sobre sí mismos y el movimiento del sistema nunca se repite. Los atractores extraños tienen una estructura fractal . Esta imagen mágica del Atractor de Lorenz , que se asemeja a las alas de una mariposa, se convirtió en un emblema para los primeros exploradores del caos . Reveló la fina estructura oculta dentro de un flujo desordenado de datos. El juego del caos Siempre me han fascinado el azar y los procesos aleatorios, tal cual refleja la frase de Lorenz que abre el artículo; como a pesar de tener un comportamiento errático y que desafía toda lógica, parecen seguir un orden subyacente que no llegamos a captar del todo. Y nada mejor para captar ese sentimiento que el juego del caos de Barnsley . Para jugar al juego del caos solo se necesita papel, lápiz y una moneda. Se elige un punto de partida en algún lugar del papel, no importa donde; y luego se inventan dos reglas, una regla de cara y una regla de cruz. Cada una de estas reglas nos dirán cómo dibujar nuevos puntos. Por ejemplo \"Muévase dos centímetros hacia el noreste\" o \"Muévase 25 por ciento más cerca del centro\". Ahora solo debemos comenzar a lanzar la moneda al aire y dibujar puntos en el papel, usando la regla de cara cuando la moneda sale cara y la regla de cruz cuando sale cruz. A medida que se van acumulando los puntos en el papel, vamos a poder observar que describen una cierta figura...la cual se va haciendo más clara a medida que aumenta el número de puntos. La idea sobre la que descansa el juego del caos , es hacer uso de 2 propiedades de los fractales (para conocer más sobre fractales pueden visitar mi artículo anterior ). En primer lugar, que los fractales existen como un límite a un proceso aleatorio . Por analogía, uno podría imaginar un mapa de Argentina dibujado con tiza en el piso de una habitación. Un topógrafo con herramientas estándar encontraría complicado medir el área de estas formas incómodas, con costas fractales. Pero supongamos que se arrojaran granos de arroz al aire uno por uno, lo que les permitiría caer al suelo al azar y que luego se contaran los granos que caen dentro del mapa. A medida que pasa el tiempo, el resultado comienza a acercarse al área de las formas, como el límite de un proceso aleatorio. En términos dinámicos, las formas de Barnsley demostraron ser Atractores . La segunda propiedad de los fractales que utiliza el juego del caos es esta cualidad de estar formados por pequeñas copias de la imagen principal. En este sentido, mientras más fractal es la imagen, más simples van a ser las reglas subyacentes para construirlas. Para demostrar como funciona el juego del caos, podemos ayudarnos de Python para generar el famoso helecho de Barnsley . Al utilizar primero 1000 puntos, podemos apreciar que parece generarse una imagen, pero su forma todavía no es nítida...cuando utilizamos 60000 puntos ya podemos ver con claridad la forma de la hoja de un helecho. In [5]: # Dibujar una hoja de helecho utilizando el juego del caos de Barnsley con 10000 puntos fern = Fern () fern . plot ( 1000 ) In [6]: # Dibujar una hoja de helecho utilizando el juego del caos de Barnsley con 60000 puntos fern = Fern () fern . plot ( 60000 ) Con esto llegamos al final del artículo. La ida central de las ciencias que estudian la naturaleza es hacer de lo maravilloso un lugar común! Mostrar que la complejidad , vista correctamente, es sólo una máscara para lo simple!; y encontrar patrones escondidos en el aparente caos ! Saludos! Este post fue escrito por Raúl e. López Briega utilizando Jupyter notebook . Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Matematica","url":"https://relopezbriega.github.io/blog/2020/03/26/sistemas-dinamicos-complejidad-y-caos-con-python/"},{"title":"Fractales con Python","text":"\"El científico no estudia la naturaleza por la utilidad que le pueda reportar; la estudia por el gozo que le proporciona, y este gozo se debe a la belleza que hay en ella...\" Henri Poincaré \"Ni las nubes son esféricas, ni las montañas cónicas, ni las costas circulares, ni el tronco de un árbol cilíndrico, ni un rayo viajan en línea recta...\" Benoît Mandelbrot Introducción Para muchas personas, la palabra Geometría evoca círculos , cuadrados , cubos y otros objetos regulares o lisos. En nuestra vida cotiana, vemos edificios, muebles o automóviles, que hacen un uso amplio de tales formas. Sin embargo, muchos fenómenos en la naturaleza y la ciencia son cualquier cosa menos regulares o suaves. Por ejemplo, un paisaje natural puede incluir arbustos, árboles, montañas y nubes, que son demasiado complejas para ser representadas por las formas geométricas clásicas . Sorprendentemente, los objetos aparentemente complejos e irregulares a menudo se pueden describir en términos notablemente simples. La Geometría fractal proporciona un marco en el que un proceso simple, que implica una operación básica repetida muchas veces, puede dar lugar a un resultado altamente irregular. Las construcciones fractales pueden representar objetos naturales pero también dan lugar a una gran variedad de otras formas, que pueden ser de extraordinaria complejidad. A menudo se escucha la frase \" la belleza de los fractales \", una frase que refleja la complejidad interminable de los diseños fractales junto con la simplicidad que subyace en su forma siempre repetida. El padre de los Fractales Benoît Mandelbrot , un científico colorido y poco convencional, un matemático polaco nacionalizado francés y estadounidense; es generalmente referido como el \" padre de los fractales \" . En su libro de 1982 \"La geometría fractal de la naturaleza\" , argumentó que los objetos muy irregulares deben considerarse como algo muy común, en lugar de excepcional; y además, muchos fenómenos de la Física , la Biología , las Finanzas y las Matemáticas tienen irregularidades de siguen un patrón similar. Mandelbrot introdujo la palabra fractal como una descripción general de una gran clase de objetos irregulares, y destacó la necesidad de desarrollar una matemática fractal, o en algunos casos recuperarla de documentos olvidados. Desde la década de 1980, los fractales han atraído un interés generalizado. Prácticamente todas las áreas de la ciencia han sido examinadas desde un punto de vista fractal , con la Geometría fractal convirtiéndose en un área importante de las Matemáticas , tanto como un tema de interés por derecho propio y como una herramienta para una amplia gama de aplicaciones. Construyendo Fractales - La curva de Koch Para apreciar la belleza de los fractales comencemos construyendo uno de ellos. Imaginemos una línea recta y dividámosla en tres partes iguales; luego borramos la pieza del medio y la reemplazamos por otros dos lados de un triángulo equilátero (es decir, de lados iguales) en la misma base. Esto nos dará una cadena de cuatro segmentos más cortos, unidos y en línea recta como la siguiente figura: Ahora hagamos exactamente lo mismo con cada una de estas cuatro piezas: quitamos los tercios medios y los reemplazamos por los otros dos lados de los triángulos equiláteros en la misma base, para obtener una nueva cadena de 16 piezas rectas. De esta forma, podemos continuar repitiendo este proceso una y otra vez. Ayudémosnos de Python para ver como crece en complejidad y hermosura esta figura al repitir un proceso tan simple una y otra vez. In [1]: Ver Código # Importando las librerías que vamos a utilizar from fractal import Sierpinski , Vicsek , Tree , Dragon , Koch , Hilbert , Levy In [2]: koch = Koch () koch . plot () Esta figura que acabamos de construir es conocida como Curva de Koch , y fue introducida y estudiada por el matemático sueco Helge von Koch . Propiedades de los Fractales Analizando esta construcción, podemos resumir las principales propiedades que exiben la mayoría de los fractales Estructura fina: Al ampliar la curva, por mucho que la amplifiquemos, las irregularidades en su forma siempre son evidentes. Esta es una consecuencia directa de la construcción en la que los segmentos de línea muy pequeños se trataron de la misma manera que el original pero en una escala mucho menor. Auto-similitud: La curva está compuesta de pequeñas copias de sí misma. Los métodos clásicos de geometría y matemáticas no son aplicables: La figura es demasiado irregular para ser descrito en el lenguaje geométrico tradicional y, a diferencia de las formas clásicas, no puede expresarse mediante una fórmula \"simple\". El \"Tamaño\" depende de la escala a la que se mide: A diferencia de figuras tradicionales como el círculo, dónde podemos tratar de medir la longitud tomando pequeños tramos y multiplicando por el número de pasos; tratar de medir la longitud de la curva de Koch dividiéndola en pasos cada vez más cortos solo proporciona estimaciones cada vez mayores para su longitud. Una construcción recursiva simple: Si bien la curva parace un objeto complejo, en realidad se trata de una construcción recursiva que consiste en aplicar unos pasos simples una y otra vez. Una apariencia natural: Con un poco de imaginación, la mayoría de los objetos fractales toman formas que recuerdan a la Naturaleza . Otros Ejemplos de Fractales Algunos otros ejemplos de fractales son: La curva de Levy C Esta curva lleva el nombre por el matemático francés Paul Pierre Lévy quien, en 1938, fue el primero en exhibir sus propiedades de autosimilaridad y proveer una construcción geométrica. In [3]: levy = Levy () levy . plot () La curva de Hilber Este fractal , descripto por David Hilbert es una variante de las curvas de Peanno que recubren todo el plano. In [4]: hilbert = Hilbert () hilbert . plot () La curva del Dargon Este fractal hizo su aparición por primera vez en la columna de juegos matematicos de Martín Gardner en Scientific American . In [5]: dragon = Dragon () dragon . plot () El triangulo de Sierpinski Propuesto por el matemático polaco Wacław Sierpiński . Este fractal se puede construir a partir de cualquier triángulo . In [6]: sierpinski = Sierpinski () sierpinski . plot () El Fractal de Vicsek Es un fractal propuesto por Tamás Vicsek . Es utilizado en el diseño de antenas compactas, particularmente para teléfonos celulares. In [7]: vicsek = Vicsek () vicsek . plot () Fractales en el mundo real Los fractales que vimos hasta aquí viven en el mundo idealizado de las Matemáticas , donde es teóricamente posible repetir cada paso de construcción en forma indefinida, o ver un objeto a escalas arbitrariamente pequeñas. Por supuesto, el \"mundo real\" no es así, en la realidad , solo encontramos fractales aproximados. Si nos acercamos demasiado a un objeto real, se perderá cualquier auto-similitud, y eventualmente nos encontraremos con una estructura molecular o atómica. Sin embargo, puede ser muy útil considerar los objetos naturales como fractales si exhiben irregularidades o auto-similitudes cuando se ven en un rango significativo de escalas. Algunos casos de fractales que podemos encontrar en la Madre Naturaleza son: Costas y paisajes: Las formas geográficas, como las costas, los paisajes y los causes de los ríos muestran muchas características fractales. Nubes: La forma de las nubes es complicada e irragular, tomando muchas de las características de los fractales . El brócoli romanesco: Su estructura general está compuesta por una serie de conos repetidos a escalas cada vez más pequeñas. Ramas de los árboles: Las ramas de los árboles crecen y se bifurcan siguiendo un patrón de autosimilitud como el de los fractales . In [8]: tree = Tree () tree . plot () Aquí concluye este fascinante paseo por el mundo de los fractales . Espero que hayan disfrutado del artículo. Saludos! Este post fue escrito por Raúl e. López Briega utilizando Jupyter notebook . Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Matematica","url":"https://relopezbriega.github.io/blog/2020/03/24/fractales-con-python/"},{"title":"Jugando la Copa América con Python","text":"\"Tengo dos problemas para jugar al fútbol. Uno es la pierna izquierda. El otro es la pierna derecha.\" Roberto Fontarrosa Introducción Futbol...Fubol...o Fuchibol...sin dudas uno de los deportes más hermosos del mundo y una pasión de multitudes! Cada vez que la pelota rueda sobre el verde cesped, los corazones se aceleran, el aire se entrecorta y la tensión se acumula en todos los estadios... Quién no ha soñado alguna vez con convertirse en un astro del balonpié...para ver luego como ese sueño se hace añicos y terminar rememorando los viejos tiempos en que las rodillas no dolian, o la panza no hacía de contrapeso y podíamos deslizarnos por el campo eludiendo rivales con la pelota entre los pies!...O para aquellos que no fuimos tan agraciados con un talento natural y éramos más rústicos...el éxito residía en no dejar pasar al rival a como dé lugar...aplicando tal vez un par de patadas si la situación lo requería. Siempre que comienza una nueva competición futbolística, y sobre todo cuando juega la selección nacional, que representa los colores de todo un país...la pasión y el entusiasmo se renuevan para volver a alentar al equipo en la búsqueda de la gloria! Como sabemos, ya falta muy poco para que comience la Copa América , torneo en que las selecciones de sudamérica se mediran para determinar quien es el mejor del continente. En Argentina, donde a la mayoría nos encanta discutir sobre futbol y hacer predicciones, cada vez que empieza un torneo como este, solemos jugar al prode ...juego que consiste en predecir los resultados que se van a dar en la competición en cuestión. Y que mejor que aplicar un poco de estadística y ayudarnos de Python para intentar predecir los resultados y ver que suerte acompañará a la gloriosa selección albiceleste! Predicción de resultados de la Copa América Para intentar predecir los resultados de la Copa América voy a utilizar un modelo simple de Regresión Logística que adapté de Kaggle ; en dónde las principales variales para predecir los resultados serán el ranking FIFA de cada equipo, el talento de sus jugadores y el historial de enfrentamientos. Los datos Los datos que utilicé los armé en base a otros datasets que obtuve de Kaggle . Se los pueden descargar de los siguientes links: equipos : Este dataset contiene los datos de los jugadores convocados en cada selección. partidos : Este dataset contine el historial de partidos en que se enfrentaron las selecciones que participan de la copa. copa : Este dataset contine la composición de los grupos de la Copa América . Comencemos con el modelo... In [1]: Ver Código # Importando las librerías que vamos a utilizar import pandas as pd import numpy as np from pandasql import sqldf import matplotlib.pyplot as plt from sklearn import linear_model from sklearn import ensemble from sklearn.model_selection import train_test_split from sklearn.metrics import confusion_matrix , roc_curve , roc_auc_score from sklearn.pipeline import Pipeline from sklearn.preprocessing import PolynomialFeatures #utilizar SQL para filtrar pandas df query = lambda q : sqldf ( q , globals ()) # graficos incrustados % matplotlib inline In [2]: # importando los datos # jugadores de las selecciones equipos = pd . read_csv ( 'data/selecciones.csv' ) # completando el overall de jugadores faltantes equipos [ 'Overall' ] = equipos [ 'Overall' ] . fillna ( 65 ) # Historial de partidos entre las selecciones partidos = pd . read_csv ( 'data/partidos.csv' ) # Grupos Copa América Brasil 2019 copa = pd . read_csv ( 'data/CopaAmerica.csv' ) Hay quienes dicen que los mundiales de futbol son un gran metegol con el que juegan los Dioses...aplicando esa analogía yo tomé las estadísticas de los jugadores del videojuego FIFA19 para determinar el talento general que le corresponde a cada selección y ese número se ve reflejado en la columna Overal del dataset de equipos ...agreguemos también ese dato a los dataset de partidos y de copa para facilitar el acceso a ese dato. In [3]: # Agregando el pontencial del equipo al dataset de partidos potencial = equipos . groupby ( 'selección' ) . mean ()[ 'Overall' ] partidos = partidos . merge ( potencial , left_on = [ 'local' ], right_on = [ 'selección' ]) partidos = partidos . merge ( potencial , left_on = [ 'visitante' ], right_on = [ 'selección' ], suffixes = ( '_local' , '_visitante' )) # Agregar diferencia de potencial entre los equipos partidos [ 'dif_potencial' ] = partidos [ 'Overall_local' ] - partidos [ 'Overall_visitante' ] # Agregando el pontencial del equipo al dataset de copa copa = copa . merge ( potencial , left_on = [ 'equipo' ], right_on = [ 'selección' ]) copa = copa . set_index ([ 'equipo' ]) In [4]: # Graficando el potencial de cada seleccion # de acuerdo a la jerarquía de sus jugadores plot = potencial . sort_values ( ascending = False ) . plot ( kind = 'bar' ) Como vemos en este gráfico...los grandes candidatos de acuerdo a la calidad de sus jugadores son Brasil, Argentina y Uruguay..ahora armemos el modelo Modelo In [5]: # Armado del modelo de regresión lineal para predecir los resultados # los predictores a utilizar seran la diferencia del ranking fifa y el potencial # de los jugadores de las selecciones X , y = partidos . loc [:,[ 'ranking_local' , 'dif_ranking' , 'dif_potencial' ]], partidos [ 'gana_local' ] X_train , X_test , y_train , y_test = train_test_split ( X , y , test_size = 0.2 , random_state = 42 ) logreg = linear_model . LogisticRegression ( solver = 'lbfgs' , C = 1e-5 ) features = PolynomialFeatures ( degree = 2 ) model = Pipeline ([ ( 'polynomial_features' , features ), ( 'logistic_regression' , logreg ) ]) model = model . fit ( X_train , y_train ) # Graficos fpr , tpr , _ = roc_curve ( y_test , model . predict_proba ( X_test )[:, 1 ]) plt . figure ( figsize = ( 15 , 5 )) ax = plt . subplot ( 1 , 3 , 1 ) ax . plot ([ 0 , 1 ], [ 0 , 1 ], 'k--' ) ax . plot ( fpr , tpr ) ax . set_title ( 'AUC score es {0:0.2} ' . format ( roc_auc_score ( y_test , model . predict_proba ( X_test )[:, 1 ]))) ax . set_aspect ( 1 ) ax = plt . subplot ( 1 , 3 , 2 ) cm = confusion_matrix ( y_test , model . predict ( X_test )) ax . imshow ( cm , cmap = 'Blues' , clim = ( 0 , cm . max ())) ax . set_xlabel ( 'Predicción' ) ax . set_title ( 'Performance en el Test set' ) ax = plt . subplot ( 1 , 3 , 3 ) cm = confusion_matrix ( y_train , model . predict ( X_train )) ax . imshow ( cm , cmap = 'Blues' , clim = ( 0 , cm . max ())) ax . set_xlabel ( 'Predicción' ) ax . set_title ( 'Performance en el Training set' ) plot = plt . show () El modelo tiene una precisión del 72% no es un gran resultado...pero es decente teniendo en cuenta que los resultados del futbol no se caracterizan por ser fáciles de predecir...si hay algo hermoso que tiene este deporte...es que siempre te da sorpresas! continuemos ahora con la simulación de la fase de grupos Simulación de fase de grupos In [6]: # Simulando los partidos de la fase de grupos from itertools import combinations # margen de error para definir empate en casos parejos margin = 0.05 copa [ 'puntos' ] = 0 copa [ 'total_prob' ] = 0 for grupo in sorted ( set ( copa [ 'grupo' ])): print ( '___Grupo {} :___' . format ( grupo )) for local , visitante in combinations ( copa . query ( 'grupo == \" {} \"' . format ( grupo )) . index , 2 ): print ( \" {} vs. {} : \" . format ( local , visitante ), end = '' ) row = pd . DataFrame ( np . array ([[ np . nan , np . nan , np . nan ]]), columns = X_test . columns ) row [ 'ranking_local' ] = copa . loc [ local , 'ranking FIFA' ] opp_rank = copa . loc [ visitante , 'ranking FIFA' ] local_pot = copa . loc [ local , 'Overall' ] opp_pot = copa . loc [ visitante , 'Overall' ] row [ 'dif_ranking' ] = row [ 'ranking_local' ] - opp_rank row [ 'dif_potencial' ] = local_pot - opp_pot local_win_prob = model . predict_proba ( row )[:, 1 ][ 0 ] copa . loc [ local , 'total_prob' ] += local_win_prob copa . loc [ visitante , 'total_prob' ] += 1 - local_win_prob # Asignando los puntos en a los equipos en el grupo points = 0 if local_win_prob <= 0.5 - margin : print ( \" {} gana con prob de {:.3f} \" . format ( visitante , 1 - local_win_prob )) copa . loc [ visitante , 'puntos' ] += 3 if local_win_prob > 0.5 - margin : points = 1 if local_win_prob >= 0.5 + margin : points = 3 copa . loc [ local , 'puntos' ] += 3 print ( \" {} gana con prob de {:.3f} \" . format ( local , local_win_prob )) if points == 1 : print ( \"empatan\" ) copa . loc [ local , 'puntos' ] += 1 copa . loc [ visitante , 'puntos' ] += 1 ___Grupo A:___ Brasil vs. Bolivia: Brasil gana con prob de 0.785 Brasil vs. Venezuela: Brasil gana con prob de 0.622 Brasil vs. Peru: Brasil gana con prob de 0.591 Bolivia vs. Venezuela: Venezuela gana con prob de 0.639 Bolivia vs. Peru: Peru gana con prob de 0.667 Venezuela vs. Peru: empatan ___Grupo B:___ Argentina vs. Colombia: empatan Argentina vs. Paraguay: Argentina gana con prob de 0.637 Argentina vs. Qatar: Argentina gana con prob de 0.765 Colombia vs. Paraguay: Colombia gana con prob de 0.573 Colombia vs. Qatar: Colombia gana con prob de 0.687 Paraguay vs. Qatar: Paraguay gana con prob de 0.655 ___Grupo C:___ Uruguay vs. Ecuador: Uruguay gana con prob de 0.681 Uruguay vs. Japon: Uruguay gana con prob de 0.623 Uruguay vs. Chile: empatan Ecuador vs. Japon: empatan Ecuador vs. Chile: Chile gana con prob de 0.675 Japon vs. Chile: Chile gana con prob de 0.586 In [7]: # ver posiciones de grupos copa = copa . sort_values ( by = [ 'grupo' , 'puntos' , 'total_prob' ], ascending = [ True , False , False ]) . reset_index () In [8]: # Seleccionar quienes pasan de ronda next_round = copa . groupby ( 'grupo' ) . nth ([ 0 , 1 , 2 ]) . reset_index () # primeros 3 next_round [[ 'grupo' , 'equipo' , 'puntos' ]] Out[8]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } grupo equipo puntos 0 A Brasil 9 1 A Peru 4 2 A Venezuela 4 3 B Argentina 7 4 B Colombia 7 5 B Paraguay 3 6 C Uruguay 7 7 C Chile 7 8 C Japon 1 Ahora que ya tenemos las predicciones de quienes superarán la primera fase y continuarán compitiendo para alzar la copa y la gloria...simulemos la fase final y veamos quien se convierte en campeón! Simulando fase final In [9]: # Armo los cruces...el peor 3ro queda afuera de cuartos cruces = [ 0 , 5 , 1 , 4 , 3 , 7 , 6 , 2 ] next_round = next_round . loc [ cruces ] next_round = next_round . set_index ( 'equipo' ) In [10]: # Simulo rondas finales finales = [ 'cuartos de final' , 'semifinal' , 'final' ] copa = copa . set_index ([ 'equipo' ]) for f in finales : print ( \"___Ronda {} ___\" . format ( f )) iterations = int ( len ( next_round ) / 2 ) winners = [] for i in range ( iterations ): local = next_round . index [ i * 2 ] visitante = next_round . index [ i * 2 + 1 ] print ( \" {} vs. {} : \" . format ( local , visitante ), end = '' ) row = pd . DataFrame ( np . array ([[ np . nan , np . nan , np . nan ]]), columns = X_test . columns ) row [ 'ranking_local' ] = copa . loc [ local , 'ranking FIFA' ] opp_rank = copa . loc [ visitante , 'ranking FIFA' ] local_pot = copa . loc [ local , 'Overall' ] opp_pot = copa . loc [ visitante , 'Overall' ] row [ 'dif_ranking' ] = row [ 'ranking_local' ] - opp_rank row [ 'dif_potencial' ] = local_pot - opp_pot local_win_prob = model . predict_proba ( row )[:, 1 ][ 0 ] copa . loc [ local , 'total_prob' ] += local_win_prob copa . loc [ visitante , 'total_prob' ] += 1 - local_win_prob local_win_prob = model . predict_proba ( row )[:, 1 ][ 0 ] if model . predict_proba ( row )[:, 1 ] <= 0.5 : print ( \" {0} gana con prob de {1:.3f} \" . format ( visitante , 1 - local_win_prob )) winners . append ( visitante ) else : print ( \" {0} gana con prob de {1:.3f} \" . format ( local , local_win_prob )) winners . append ( local ) next_round = next_round . loc [ winners ] print ( \" \\n \" ) ___Ronda cuartos de final___ Brasil vs. Paraguay: Brasil gana con prob de 0.642 Peru vs. Colombia: Colombia gana con prob de 0.540 Argentina vs. Chile: Argentina gana con prob de 0.532 Uruguay vs. Venezuela: Uruguay gana con prob de 0.591 ___Ronda semifinal___ Brasil vs. Colombia: Brasil gana con prob de 0.541 Argentina vs. Uruguay: Uruguay gana con prob de 0.503 ___Ronda final___ Brasil vs. Uruguay: Brasil gana con prob de 0.512 Si bien el modelo es simple...y seguramente se puede armar un poco más sofisticado...los resultados se ven bastante lógicos de acuerdo a la tradición y el presente futbolístico de cada selección. Como vemos, de acuerdo a este modelo...la albiceleste pierde en semifinales en forma reñida contra los charrúas...el campeón termina siendo Brasil...esperemos que los resultados esten errados y en esta ocasión Messi pueda lograr el tan ansiado título con la selección! Saludos! Este post fue escrito por Raúl e. López Briega utilizando Jupyter notebook . Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Analisis de datos","url":"https://relopezbriega.github.io/blog/2019/06/09/jugando-la-copa-america-con-python/"},{"title":"El sistema de numeración Binario","text":"\"Sólo hay 10 tipos de personas en el mundo: Los que entienden binario y los que no lo hacen.\" Introducción El sistema de números binarios es el más fundamental sistema numérico que se utiliza en todas las computadoras. Cualquier dispositivo digital que vemos hoy en día, desde celulares hasta Smart TVs utilizan el sistema binario ! Este sistema sigue reglas muy similares al sistema decimal (el que usamos diariamente) pero en lugar de utilizar una base decimal de 10 números, se utiliza una base de 2 números, cero y uno . Es decir que los números van a ser expresados como una cadena de 1s y 0s. Una de las formas más sencillas de entender este sistema de numeración , es comparándolo con algo que ya conocemos y que manejamos cotidianamente como es el sistema decimal . Comencemos viendo como convertir los números entre estos dos sistemas. Convertir un número Binario a Decimal Como en cualquier sistema de numeración , en los números binarios cada dígito tiene distinto valor dependiendo de la posición en que se encuentre dentro de la cadena. Para convertir un número binario en su equivalente en decimal, solamente debemos conocer las potencias de 2 y luego es cuestión de contar las posiciones. Algo a tener en cuenta es que al trabajar con binarios los algoritmos en general se aplican de derecha a izquierda. Entonces, vamos a empezar a contar desde la derecha y a cada posición le vamos a asignar una potencia de 2 empezando por el 0. Por ejemplo, si queremos convertir el 10011000 al sistema decimal , deberíamos hacer lo siguiente: Le asignamos las potencias a cada posición y luego multiplicamos los resultados de las potencias por los 1 y 0; por último sumamos los resultados y obtenemos la conversión en el sistema decimal 128 + 16 + 8 = 152. Convertir un número Decimal a Binario Realizar el proceso inverso y convertir un número decimal a un número binario también es bastante sencillo. Como el sistema binario solo tiene 2 alternativas 0 y 1; el número 2 se vuelve fundamental. Para convertir un número decimal a su equivalente en binario entonces tenemos que comenzar a dividir por 2 hasta obtener restos de 1 y 0. Por ejemplo si queremos convertir el número 152, debemos ir dividiendo el mismo por 2 como podemos ver en el siguiente cuadro: Una vez que completamos este proceso, simplemente tomamos los restos desde abajo hacia arriba y obtenemos el número binario equivalente, que en este caso es 10011000. Suma de números Binarios La suma de números binarios es bastante fácil, solo hay que tener en cuenta la siguiente tabla y luego es un proceso simple y mecánico: Es decir 0 + 0 = 0 ; 0 + 1 = 1 ; 1 + 0 = 1 y 1 + 1 = 10 . En este último caso en realidad el resultado de 1 + 1 es 0 y se arrastra un 1 a la izquierda. Por ejemplo, para sumar los siguientes números: 10011000 (que representa al número 152 en el sistema decimal) y 10101 (que representa al número 21), deberíamos aplicar las reglas del cuadro de la siguiente forma: El proceso siempre se empieza de la derecha hacia la izquierda. Tener en cuenta que en la tercera posición, el resultado de 0 + 0 es 1 porque se arrastró el valor de la suma anterior de 1 + 1 = 0. El resultado final que obtuvimos fue 10101101 el cual representa al 173 en el sistema decimal !. Resta de números Binarios Para la resta de números binarios el algoritmo es también muy fácil y similar al que se utiliza en el sistema decimal . El cuadro a tener en cuenta en este caso es: Es decir 0 - 0 = 0 ; 1 - 0 = 1 ; 1 - 1 = 0 y 0 - 1 = 1 . En este último caso en realidad como no se puede restar 1 a 0, se pide prestado una unidad de la posición siguiente como hacemos en la resta en el sistema decimal . Por ejemplo, para restar los siguientes números: 11001001 (que representa al 201 en decimales) y 1000011 (que representa al 67), deberíamos aplicar las reglas del cuadro de la siguiente forma: Nuevamente el algoritmo se aplica de derecha a izquierda, comenzamos con 1 – 1 que da 0, luego 0 – 1 que da 1 y arrastramos a 1 a la izquierda, por lo que la siguiente columna vuelve a ser 0 – 1 que da 1, como volvimos a arrastrar la siguiente es 1 – 1 igual a 0 y después ya es más simple porque ya no tenemos arrastres. El resultado final es 10000110 que representa al número 134 en el sistema decimal ! Multiplicación de números Binarios El algoritmo de multiplicación es similar al que utilizamos en el sistema decimal pero es mucho más sencillo ya que solo tenemos que multiplicar por 1 y 0! La tabla que utilizamos para los cálculos es la siguiente: Para poder realizar la multiplicación al igual que como ocurre con el sistema decimal , tenemos que saber sumar. Por ejemplo, si queremos multiplicar 10110 (22 en el sistema decimal) por 1001 ( que representa al 9) el proceso sería el siguiente: Vamos multiplicando dígito a dígito y luego sumamos los resultados; el resultado final es 11000110 que equivale a 198 en el sistema decimal. División de números Binarios El algoritmo de división de números binarios es el mismo que se utiliza para el sistema decimal con la salvedad que solo podemos trabajar con 1s y 0s y que las restas y multiplicaciones se deben realizar también en binario. Si por ejemplo queremos dividir 101010 (que representa al 42 en el sistema decimal) entre 110 (que representa al 6); el proceso sería el siguiente: Tomamos los primeros 3 dígitos 101 como 101 (5 en decimal) es más chico que 110 (6 en decimal) no nos sirve, entonces tomamos un dígito más 1010. Como es más grande entonces entra 1 vez en 110, por lo que ya tenemos el primer dígito del resultado. Multiplicamos 1 por 110 y lo restamos a 1010, el resultado es 100, como 100 es menor que 110 bajamos el siguiente dígito, por lo que nos queda 1001 para dividir entre 110. Como 1001 es mayor que 110, volvemos a aplicar el procedimiento y restar 110. Como 110 entra una vez en 110, otra vez obtenemos 1 y volvemos a aplicar el procedimiento. De esta forma arribamos al resultado final de 111 ( 7 en el sistema decimal). Como la división es un poco más difícil que las otras operaciones, realicemos un ejemplo más, en este caso vamos a dividir 101000 (40 en decimal) entre 1000 (8 en decimal). Partimos de los primeros 4 dígitos, como 1010 es mayor que 1000, el primer dígito de nuestro resultado es 1. Luego de restar 1000 a 1010, el resultado es 10, como es más chico que 1000 bajamos el siguiente dígito; como 100 sigue siendo más chico que 1000; el segundo dígito de nuestro resultado es 0. Continuamos aplicando el mismo procedimiento; como luego de la resta, 100 sigue siendo más chico que 1000, bajamos el siguiente dígito. Ahora como 1000 entra una vez en 1000, podemos obtener el siguiente dígito del resultado y arribar al resultado final! Como vemos, el resultado final es 101 que equivale a 5 en el sistema decimal . Números Binarios con Python Ahora que ya conocemos como trabajar con el sistema de números binarios en forma manual; podemos simplificar las cosas ayudándonos de Python y su interprete interactivo. En Python podemos convertir un número decimal en su equivalente en binario utilizando la función bin() In [3]: # convertir decimal a binario bin ( 152 ) Out[3]: '0b10011000' In [4]: # Convertir binario a decimal int ( 0b10011000 ) Out[4]: 152 In [7]: # suma de binarios bin ( 0b10011000 + 0b10101 ) Out[7]: '0b10101101' In [9]: # resta de binarios bin ( 0b11001001 - 0b1000011 ) Out[9]: '0b10000110' In [10]: # multiplicacion bin ( 0b10110 * 0b1001 ) Out[10]: '0b11000110' In [12]: # división bin ( 0b101010 // 0b110 ) Out[12]: '0b111' In [13]: # división bin ( 0b101000 // 0b1000 ) Out[13]: '0b101' Como vemos, las operaciones entre los sistema de numeración son equivalentes, ya que lo que cambia es la forma en que representamos los números y no la esencia de la operación. Aquí concluye este artículo, como podemos ver el sistema de numeración binario posee una simpleza muy bella que lo hace extremadamente útil; con tan solo dos estados...uno y cero, podemos representar cosas extremadamente complejas como son todos los productos de la revolución digital que utilizamos hoy en día! Este post fue escrito por Raúl e. López Briega utilizando Jupyter notebook . Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Matematica","url":"https://relopezbriega.github.io/blog/2019/03/09/el-sistema-de-numeracion-binario/"},{"title":"Introducción a la Teoría de la información con python","text":"\"La información es la resolución de la incertidumbre\" Claude Shannon \"Lo que está en el corazón de cada ser vivo no es un fuego , ni un aliento cálido , ni una chispa de vida . Es información, palabras, instrucciones ... Si quieres entender la vida... piensa en la tecnología de la información\" Richard Dawkins Introducción Muchas veces hemos escuchado decir que vivimos en la era de la información . La información parece estar en todo los que nos rodea. Ya sea que consideremos a las computadoras, la evolución, la física, la inteligencia artificial , o nuestro cerebro; podemos llegar a la conclusión de que su comportamiento esta principalmente determinado por la forma en que procesan la información . La idea de la información nació del antiguo arte de la codificación y decodificación de códigos. Los encargados de esconder los secretos de estado durante la segunda guerra mundial utilizaban, en esencia, métodos para ocultar información y transmitirla de un lugar a otro. Cuando el arte de quebrar estos códigos se combinó con la ciencia de la Termodinámica , la rama de la física encargada del estudio de la interacción entre el calor y otras manifestaciones de la energía; surgió lo que hoy conocemos como Teoría de la información . Esta teoría fue una idea revolucionaria que inmediatamente transformó el campo de las comunicaciones y preparó el camino para la era de las computadoras . Pero las ideas de la Teoría de la información no solo gobiernan las comunicaciones y los bits y bytes de las computadoras modernas; sino que también describen el comportamiento del mundo subatómico , e incluso de toda la vida en la Tierra. ¿Qué es la información? Hasta no hace no tanto tiempo atrás, nuestro conocimiento de la información era bastante vago y limitado. En 1948, Claude Shannon publicó un artículo titulado \"Una teoría matemática de la comunicación\" , el cual transformó para siempre la forma en que entendemos la información . La Teoría de la información de Shannon proporciona una definición matemática de información y describe con precisión cuánta información se puede comunicar entre los diferentes elementos de un sistema. La teoría de Shannon respalda nuestra comprensión de cómo se relacionan las señales y el ruido , y por qué existen límites definidos para la velocidad a la que se puede comunicar la información dentro de cualquier sistema, ya sea creado por el hombre o biológico. La habilidad de separar la señal del ruido , para extraer la información en los datos, se ha vuelto crucial en las telecomunicaciones modernas. La Teoría de la información es tan poderosa porque la información es física. La información no es solo un concepto abstracto, y no solo son hechos o figuras, fechas o nombres. Es una propiedad concreta de la materia y la energía que es cuantificable y mensurable. Es tan real como el peso de un trozo de plomo o la energía almacenada en una ojiva atómica, y al igual que la masa y la energía, la información está sujeta a un conjunto de leyes físicas que dictan cómo puede comportarse, cómo la información puede ser manipulada, transferida, duplicada, borrada o destruida. Y todo en el universo debe obedecer las leyes de la información , porque todo en el universo está formado por la información que contiene. Según la perspectiva de la información de Shannon , el significado no es importante, sino que lo que importa es cuánta información es transmitida por un mensaje. Una de las grandes intuiciones que tuvo Shannon fue darse cuenta que cualquier pregunta que tenga una respuesta finita puede ser respondida por una cadena de preguntas por sí o por no. Así es como surge el concepto de Bit . Bits Un Bit es la unidad fundamental en la que podemos medir la información y nos permite decidir entre dos alternativas igualmente probables. La palabra Bit deriva de bi nary digi t , o sea dígito binario , los cuales son representados por 1s y 0s. Pero si bien la palabra Bit deriva de bi nary digi t no debemos confundirlos, ya que representan entidades distintas. Un Bit representa una cantidad de información definitiva. En cambio, un dígito binario es el valor de una variable binaria , el cual, como ya dijimos, puede ser 0 o 1; pero un dígito binario no representa información en sí misma. Ahora bien, volviendo a las preguntas por sí o por no que mencionamos antes; responder cada una de estas preguntas requiere un Bit de información . Sólo necesitamos un Bit para responder una pregunta como ¿sos un hombre o una mujer?; el 0 puede significar hombre y el 1 mujer. Con simplemente transmitir ese dígito en el mensaje, estamos transmitiendo la respuesta. Pero aquí viene otra de las grandes intuiciones de Shannon ; tampoco importa la forma que tome el mensaje, puede ser una luz roja versus una luz verde; o una bandera blanca y otra roja; realmente no importa el medio que se utilice, el mensaje siempre contiene un Bit de información . Y ¿qué pasa con otro tipo de preguntas? preguntas como adivinar un número entero entre 1 y 1000, o como, ¿cuál es la capital de Islandia? Estas preguntas también pueden ser respondidas con una cadena de Bits . El lenguaje no es más que una cadena de símbolos y cualquier símbolo puede ser representado con una cadena de Bits . Por lo tanto, cualquier respuesta que pueda ser escrita en un lenguaje puede ser representada con una cadena de Bits , de 1s y 0s. Los Bits son el medio fundamental de la información . Esta realización, que cualquier información, cualquier respuesta, puede ser codificada en una cadena de Bits , nos abre la puerta para pensar que entonces debe existir una forma de medir cuánta información hay en un mensaje.¿Cuál es la mínima cantidad de Bits para codificar un mensaje? Por ejemplo, para responder la pregunta planteada anteriormente de adivinar un número entero entre 1 y 1000, no se necesitan más que 10 Bits !. Shannon encontró que una pregunta con $N$ posibles resultados puede ser respondida con una cadena de Bits de $log_2 N$ Bits ; es decir que solo necesitamos $log_2 N$ Bits de información para distinguir entre $N$ posibilidades. Si no me creen, más abajo les dejo un botón para jugar a adivinar el número. (Si les consume más de 10 bits llegar a la respuesta correcta, no están utilizando la estrategia correcta!). Todo esto último relacionado a cómo medir cuánta información contiene un mensaje nos lleva a otro de los conceptos fundamentales de la Teoría de la información , el concepto de Entropía . Jugar a Adivinar el número! function adivinar_numero(){ var numero_a_adivinar = Math.floor(Math.random()*1000); var bits = 1; var numero_usuario = prompt(\"Adivine un número entero entre 1 y 1000\\nIngrese un número entre 1 y 1000: \"); while (numero_usuario != numero_a_adivinar) { if (numero_usuario < numero_a_adivinar) { numero_usuario = prompt(\"Su número es muy bajo!\\nIngrese otro número entre 1 y 1000:\"); bits++; } else { numero_usuario = prompt(\"Su número es muy alto!\\nIngrese otro número entre 1 y 1000:\"); bits++; } } alert(\"Felicidades el número es \" + numero_usuario + \" y ha utilizado \" + bits + \" bits!\"); } Entropía La idea central de la Teoría de la información de Shannon es la Entropía . La información y la Entropía están intimimamente relacionadas, ya que esta última es en sí misma una medida de información . Cuando Shannon comenzó a desarrollar su teoría, encontró una formula que le permitía analizar la información en un mensaje en términos de Bits . Esta formula que encontró mide, a grandes rasgos, cuan poco predecible es una cadena de Bits . Mientras menos predecible, existen menos probabilidades de poder generar el mensaje completo desde una cadena más pequeña de Bits . Es decir, que al intentar medir cuan poco predecible es una cadena de Bits , Shannon esperaba poder encontrar cuánta información contenía el mensaje. Ahora bien, ¿cuál es la cadena de 0s y 1s menos probables? Pues aquella que sea completamente aleatoria, como cuando lanzamos una moneda al aire y tenemos 50% de probabilidades de obtener cara o seca. Mientras más aleatoria es una cadena de símbolos, es menos predecible y menos redundante; y por lo tiende a contener una mayor cantidad de información por símbolo. Si bien esto parece una paradoja, ¿cómo algo que es completamente aleatorio contiene más información que algo que no lo es? Acoso, ¿lo aleatorio no es no lo contrario de información ? Parece ser contra intuitivo, pero en realidad no lo es. Se puede observar facílmente con un ejemplo. Supongamos que arrojamos una moneda al aire 16 veces y representamos a la cara con un 1 y a la seca con un 0. Podemos obtener una cadena como la siguiente: 1101001110001011. Esta cadena es aleatoria y por lo tanto no podemos encontrar ningún patrón en ella que nos diga cuál va a ser el próximo valor que podemos obtener más alla de la chance de 50% habitual, por tal motivo, no podemos comprimir la cadena y cada símbolo contiene un Bit de información . Ahora supongamos que la moneda esta sesgada y que siempre sale cara; en este caso nuestra cadena será la siguiente: 1111111111111111. Esta cadena es sumamente predecible, no nos aporta ninguna sorpresa y tenemos una probabilidad de 100% de adivinar que el siguiente dígito también será un 1. Es totalmente redundante y por lo tanto no nos aporta ninguna información . Cada símbolo contiene un Bit de información . Sin sorpresa, no hay información . La fórmula matemática que encontró Shannon para medir la Entropía de un mensaje es muy similar a la que se utiliza en Termodinámica para medir el grado de desorden de un sistema. Es la siguiente: $$ H(x) = - \\sum_{i} p(i) log_2 p(i) $$ Cuando Shannon se dio cuenta de que la Entropía de una secuencia de símbolos estaba relacionada con la cantidad de información que la cadena de símbolos tiende a contener, de repente tuvo una herramienta para cuantificar la información y la redundancia en un mensaje. Fue capaz de demostrar, matemáticamente, cuánta información puede transmitirse en cualquier medio y que existe un límite fundamental para la cantidad de información que puede transmitir con un equipo determinado. Veamos algunos ejemplos de como calcular la Entropía con la ayuda de Python : In [1]: Ver Código import matplotlib.pyplot as plt import numpy as np import warnings # ingnorar mensajes de advertencias en el notebook warnings . filterwarnings ( 'ignore' ) # graficos en el notebook % matplotlib inline def entropia ( X ): \"\"\"Devuelve el valor de entropia de una muestra de datos\"\"\" probs = [ np . mean ( X == valor ) for valor in set ( X )] return round ( np . sum ( - p * np . log2 ( p ) for p in probs ), 3 ) def entropia_prob_pq ( x ): \"\"\"Devuelve la entropia de una probabilidad de dos posibilidades\"\"\" return round (( - x * np . log2 ( x )) + ( - ( 1 - x ) * np . log2 (( 1 - x ))), 3 ) def entropia_posibilidades ( x ): \"\"\"Devuelve la entropía para la cantidad de posibilidades independientes x\"\"\" return round ( np . log2 ( x ), 3 ) In [2]: # Graficando la información como sorpresa # Mientras menos probable, más sorpresa y más información contiene. vent = np . vectorize ( entropia_posibilidades ) X = np . linspace ( 0 , 1 , 11 ) plt . plot ( X , vent ( X ) *- 1 ) plt . title ( \"Información como sorpresa\" ) plt . grid ( color = 'b' , linestyle = '-' , linewidth =. 3 ) plt . xlabel ( r 'Probabilidades $p(x)$' ) plt . ylabel ( r 'sorpresa $H(x) = log_2 1/p(x)$' ) plt . show () In [3]: # Graficando la entropia en el caso de 2 posibilidades con # probabilidad p y (1- p) # vectorizar la función para poder pasarle un vector de parámetro vent = np . vectorize ( entropia_prob_pq ) X = np . linspace ( 0 , 1 , 11 ) plt . plot ( X , vent ( X )) plt . title ( \"Entropia para 2 posibilidades con probabilidad p\" ) plt . grid ( color = 'b' , linestyle = '-' , linewidth =. 3 ) plt . xlabel ( 'Probabilidades p' ) plt . ylabel ( 'Bits' ) plt . show () In [4]: # La entropia de una muestra de 2 posibilidades completamente # aleatorias, en la que cualquiera de los 2 valores tiene la # misma probabilidad (p=0.5) de ser seleccionada es de 1 bit # Muestra de 10000 valores aleatorios entre 0 y 1 X = np . random . randint ( 0 , 2 , size = 10000 ) entropia ( X ), entropia_posibilidades ( 2 ) Out[4]: (1.0, 1.0) In [5]: # La entropia de una muestra de 8 posibilidades completamente # aleatorias es igual a 3 bits. # Muestra de 10000 valores aleatorios entre 0 y 7 X = np . random . randint ( 0 , 8 , size = 10000 ) entropia ( X ), entropia_posibilidades ( 8 ) Out[5]: (3.0, 3.0) Redundancia Otro de los conceptos fundamentales de la Teoría de la información es el de Redundancia . La Redundancia son esas pistas adicionales en una sentencia o mensaje que nos permiten entender su significado incluso si el mensaje esta incompleto o distorsionado; son esos caracteres extra en una cadena de símbolos, la parte predecible que nos permite completar la información faltante. Cualquier sentencia de cualquier lenguaje es altamente redundante. Todo sentencia nos proporciona información adicional para que podemos descifrarla. Esta Redundancia es fácil de ver, simplemente tr-t- d- l--r -st- m-ns-j-. A pesar de que quitemos todas la vocales, igualmente se podemos entender la sentencia. Para nosotros, la redundancia del lenguaje es algo bueno, porque hace que un mensaje sea más fácil de comprender incluso cuando el mensaje está parcialmente modificado por el entorno. Podemos entender a un amigo hablando en un restaurante abarrotado de gente o hablando con un teléfono celular con mucha estática gracias a la Redundancia . La Redundancia es un mecanismo de seguridad; nos asegura que el mensaje se transmita incluso si se daña levemente en el camino. Todos los idiomas tienen estas redes de seguridad integradas compuestas de patrones, estructuras y un conjunto de reglas que los hacen redundantes. Usualmente no estamos al tanto de esas reglas, pero nuestro cerebro las usa inconscientemente mientras leemos, hablamos, escuchamos y escribimos. Cuando eliminamos toda la redundancia en una cadena de símbolos, lo que queda es su núcleo concreto y cuantificable. Eso es la información , ese algo central e irreductible que se encuentra en la esencia de toda sentencia. Para explorar en carne propia como la información es una medida de sorpresa y como la mayoría de los mensajes contienen bastantes Bits redundantes, les dejo otro juego; la idea es adivinar nombres que empiezan con \"R\" de Raúl a medida que se van descubriendo nuevas letras. Les garantizo que podrán descubrir los nombres sin tener que llegar que se descubra la última letra! Jugar a Adivinar el nombre! function adivinar_nombres(){ var nombres_R = [ \"ramses\", \"rodolfo\", \"regina\", \"ruth\", \"ramiro\", \"ramon\", \"roxana\", \"rebeca\", \"raquel\", \"ruben\", \"rosario\", \"renata\", \"raul\", \"romina\", \"roberto\", \"ricardo\", \"rafael\", \"rosa\", \"rodrigo\", \"rocio\" ] var index = Math.floor(Math.random()*20) - 1; var mi_nombre = nombres_R[index]; var bits = 1; var tu_nombre = prompt(\"Adivina el nombre! Empieza con R y tiene \" + mi_nombre.length + \" letras: \").toLowerCase(); var letras = 2; while (mi_nombre != tu_nombre) { mi_nombre_parcial = mi_nombre.substring(0, letras); if (mi_nombre_parcial === mi_nombre) { break; } tu_nombre = prompt(\"Inténtalo otra vez! Empieza con \" + mi_nombre_parcial + \" y tiene \"+ mi_nombre.length + \" letras: \").toLowerCase(); bits++; letras++; } alert(\"El nombre es \" + mi_nombre.toUpperCase() + \"! y has utilizado \" + bits + \" bits! Los restantes \" + (mi_nombre.length - bits) + \" son redundantes!\" ); } Información e incertidumbre Nuestra experiencia del mundo nos lleva a concluir que muchos eventos son impredecibles y algunas veces bastante inesperados. Estos pueden variar desde el resultado de simples juegos de azar como arrojar una moneda e intentar adivinar si será cara o cruz, al colapso repentino de los gobiernos, o la caída dramática de los precios de las acciones en el mercado bursátil. Cuando tratamos de interpretar tales eventos, es probable que tomemos uno de dos enfoques: nos encogeremos de hombros y diremos que fue por casualidad o argumentaremos que podríamos haber sido más capaces de predecir, por ejemplo, el colapso del gobierno si hubiéramos tenido más información sobre las acciones de ciertos ministros. En cierto sentido, podemos decir que estos dos conceptos de información e incertidumbre están más estrechamente relacionados de lo que podríamos pensar. De hecho, cuando nos enfrentamos a la incertidumbre , nuestra tendencia natural es buscar información que nos ayude a reducir esa incertidumbre en nuestras mentes. Las herramientas que nos proporciona la Teoría de la información están en las bases de todos los modelos que desarrollamos para intentar predecir y lidiar con la incertidumbre del futuro. Aquí concluye esta introducción, el trabajo de Shannon habrió un campo enorme del conocimiento científico. Por años, criptógrafos habían intentado esconder información y reducir la redundancia sin siquiera saber como medirlas; o los ingenieros trataron de diseñar maneras eficientes de transmitir mensajes sin conocer los límites que la Naturaleza ponía a su eficiencia. La Teoría de la información de Shannon revolucionó la criptografía , el procesamiento de señales , las ciencias de la computación , la física , y un gran número de otros campos. Para cerrar, les dejo la implementación de los juegos utilizados en el artículo utilizando Python ! :) Saludos! In [6]: Ver Código import random random . seed ( 1982 ) def adivinar_numero (): mi_numero = random . randint ( 1 , 1000 ) bits = 1 tu_numero = int ( input ( \"Adivine un número entero entre 1 y 1000 \\n Ingrese un número entre 1 y 1000: \" )) while tu_numero != mi_numero : if tu_numero < mi_numero : tu_numero = int ( input ( \"Su número es muy bajo! \\n Ingrese otro número entre 1 y 1000:\" )) else : tu_numero = int ( input ( \"Su número es muy alto! \\n Ingrese otro número entre 1 y 1000:\" )) bits += 1 print ( \"Felicidades el número es {0} y ha utilizado {1} bits!\" . format ( mi_numero , bits )) In [7]: Ver Código def adivinar_nombre (): nombres = [ \"ramses\" , \"rodolfo\" , \"regina\" , \"ruth\" , \"ramiro\" , \"ramon\" , \"roxana\" , \"rebeca\" , \"raquel\" , \"ruben\" , \"rosario\" , \"renata\" , \"raul\" , \"romina\" , \"roberto\" , \"ricardo\" , \"rafael\" , \"rosa\" , \"rodrigo\" , \"rocio\" ] index = random . randint ( 0 , 19 ) mi_nombre = nombres [ index ] tu_nombre = input ( \"Adivina el nombre! Empieza con R y tiene {} letras: \" . format ( len ( mi_nombre ))) letras = 2 bits = 1 while tu_nombre . lower () != mi_nombre : mi_nombre_parcial = mi_nombre [: letras ] if mi_nombre_parcial == mi_nombre : break tu_nombre = input ( \"Inténtalo otra vez! Empieza con {0} y tiene {1} letras:\" . format ( mi_nombre_parcial , letras )) bits += 1 letras += 1 print ( \"El nombre es {0} y has utilizado {1} bits! Los restantes {2} son redundantes!\" . format ( mi_nombre . upper (), bits , len ( mi_nombre ) - bits )) In [ ]: adivinar_numero () In [ ]: adivinar_nombre () Este post fue escrito utilizando Jupyter notebook . Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Matematica","url":"https://relopezbriega.github.io/blog/2018/03/30/introduccion-a-la-teoria-de-la-informacion-con-python/"},{"title":"Procesamiento del Lenguaje Natural con Python","text":"\"El lenguaje sirve no sólo para expresar el pensamiento, sino para hacer posibles pensamientos que no podrían existir sin él.\" Bertrand Russell Introducción El lenguaje es una de las herramientas centrales en nuestra vida social y profesional. Entre otras cosas, actúa como un medio para transmitir ideas, información, opiniones y sentimientos; así como para persuadir, pedir información, o dar ordenes. Asimismo, el lenguaje humano es algo que esta en constante cambio y evolución; y que puede llegar a ser muy ambiguo y variable . Tomemos por ejemplo la frase \"comí una pizza con amigos\" comparada con \"comí una pizza con aceitunas\" ; su estructura es la misma, pero su significado es totalmente distinto. De la misma manera, un mismo mensaje puede ser expresado de formas diferentes; \"comí una pizza con amigos\" puede también ser expresado como \"compartí una pizza con amigos\" . Los seres humanos somos muy buenos a la hora de producir e interpretar el lenguaje , podemos expresar, percibir e interpretar significados muy elaborados en fracción de segundos casi sin dificultades; pero al mismo tiempo, somos también muy malos a la hora de entender y describir formalmente las reglas que lo gobiernan. Por este motivo, entender y producir el lenguaje por medio de una computadora es un problema muy difícil de resolver. Éste problema, es el campo de estudio de lo que en inteligencia artificial se conoce como Procesamiento del Lenguaje Natural o NLP por sus siglas en inglés. ¿Qué es el Procesamiento del Lenguaje Natural? El Procesamiento del Lenguaje Natural o NLP es una disciplina que se encuentra en la intersección de varias ciencias, tales como las Ciencias de la Computación , la Inteligencia Artificial y Psicología Cognitiva . Su idea central es la de darle a las máquinas la capacidad de leer y comprender los idiomas que hablamos los humanos. La investigación del Procesamiento del Lenguaje Natural tiene como objetivo responder a la pregunta de cómo las personas son capaces de comprender el significado de una oración oral / escrita y cómo las personas entienden lo que sucedió, cuándo y dónde sucedió; y las diferencias entre una suposición, una creencia o un hecho. En general, en Procesamiento del Lenguaje Natural se utilizan seis niveles de comprensión con el objetivo de descubrir el significado del discurso. Estos niveles son: Nivel fonético: Aquí se presta atención a la fonética , la forma en que las palabras son pronunciadas. Este nivel es importante cuando procesamos la palabra hablada, no así cuando trabajamos con texto escrito. Nivel morfológico: Aquí nos interesa realizar un análisis morfológico del discurso; estudiar la estructura de las palabras para delimitarlas y clasificarlas. Nivel sintáctico: Aquí se realiza un análisis de sintaxis , el cual incluye la acción de dividir una oración en cada uno de sus componentes. Nivel semántico: Este nivel es un complemente del anterior, en el análisis semántico se busca entender el significado de la oración. Las palabras pueden tener múltiples significados, la idea es identificar el significado apropiado por medio del contexto de la oración. Nivel discursivo: El nivel discursivo examina el significado de la oración en relación a otra oración en el texto o párrafo del mismo documento. Nivel pragmático: Este nivel se ocupa del análisis de oraciones y cómo se usan en diferentes situaciones. Además, también cómo su significado cambia dependiendo de la situación. Todos los niveles descritos aquí son inseparables y se complementan entre sí. El objetivo de los sistemas de NLP es incluir estas definiciones en una computadora y luego usarlas para crear una oración estructurada y sin ambigüedades con un significado bien definido. Aplicaciones del Procesamiento del Lenguaje Natural Los algoritmos de Procesamiento del Lenguaje Natural suelen basarse en algoritmos de aprendizaje automático . En lugar de codificar manualmente grandes conjuntos de reglas, el NLP puede confiar en el aprendizaje automático para aprender estas reglas automáticamente analizando un conjunto de ejemplos y haciendo una inferencia estadística . En general, cuanto más datos analizados, más preciso será el modelo. Estos algoritmos pueden ser utilizados en algunas de las siguientes aplicaciones: Resumir texto: Podemos utilizar los modelos de NLP para extraer las ideas más importantes y centrales mientras ignoramos la información irrelevante. Crear chatbots: Podemos utilizar las técnicas de NLP para crear chatbots que puedan interactuar con las personas. Generar automáticamente etiquetas de palabras clave : Con NLP también podemos realizar un análisis de contenido aprovechando el algoritmo de LDA para asignar palabras claves a párrafos del texto. Reconocer entidades : Con NLP podemos identificar a las distintas entidades del texto como ser una persona, lugar u organización. Análisis de sentimiento : También podemos utilizar NLP para identificar el sentimiento de una cadena de texto, desde muy negativo a neutral y a muy positivo. Librerías de Python para Procesamiento del Lenguaje Natural Actualmente, Python es uno de los lenguajes más populares para trabajar en el campo la Inteligencia Artificial . Para abordar los problemas relacionados con el Procesamiento del Lenguaje Natural Python nos proporciona las siguientes librerías: NLTK : Es la librería líder para el Procesamiento del Lenguaje Natural . Proporciona interfaces fáciles de usar a más de 50 corpus y recursos léxicos, junto con un conjunto de bibliotecas de procesamiento de texto para la clasificación, tokenización, el etiquetado, el análisis y el razonamiento semántico. TextBlob : TextBlob simplifica el procesamiento de texto proporcionando una interfaz intuitiva a NLTK . Posee una suave curva de aprendizaje al mismo tiempo que cuenta con una sorprendente cantidad de funcionalidades. Stanford CoreNLP : Paquete desarrollado por la universidad de Stanford , para muchos constituye el estado del arte sobre las técnicas tradicionales de Procesamiento del Lenguaje Natural . Si bien esta escrita en Java, posee una interface con Python . Spacy : Es una librería relativamente nueva que sobresale por su facilidad de uso y su velocidad a la hora de realizar el procesamiento de texto. Textacy : Esta es una librería de alto nivel diseñada sobre Spacy con la idea de facilitar aun más las tareas relacionadas con el Procesamiento del Lenguaje Natural . Gensim : Es una librería diseñada para extraer automáticamente los temas semánticos de los documentos de la forma más eficiente y con menos complicaciones posible. pyLDAvis : Esta librería está diseñado para ayudar a los usuarios a interpretar los temas que surgen de un análisis de tópicos. Nos permite visualizar en forma muy sencilla cada uno de los temas incluidos en el texto. Como veremos, el NLP también se está sumando a la popularidad del Deep Learning , por lo que muchos de los frameworks que se utilizan en Deep Learning pueden ser aplicados para realizar modelos de NLP . Corpus lingüístico Hoy en día, es indispensable el uso de buenos recursos lingüísticos para el desarrollo de los sistemas de NLP . Estos recursos son esenciales para la creación de gramáticas, en el marco de aproximaciones simbólicas; o para llevar a cabo la formación de módulos basados en el aprendizaje automático . Un corpus lingüístico es un conjunto amplio y estructurado de ejemplos reales de uso de la lengua. Estos ejemplos pueden ser textos (los más comunes), o muestras orales (generalmente transcritas). Un corpus lingüístico es un conjunto de textos relativamente grande, creado independientemente de sus posibles formas o usos. Es decir, en cuanto a su estructura, variedad y complejidad, un corpus debe reflejar una lengua, o su modalidad, de la forma más exacta posible; en cuanto a su uso, preocuparse de que su representación sea real. La idea es que representen al lenguaje de la mejor forma posible para que los modelos de NLP puedan aprender los patrones necesarios para entender el lenguaje . Encontrar un buen corpus sobre el cual trabajar no suele ser una tarea sencilla; un corpus que se suele utilizar para entrenar modelos es el que incluye la información extraída de wikipedia . Procesamiento del Lenguaje Natural con Python Hasta aquí la introducción, ahora llegó el momento de ensuciarse un poco las manos y comenzar a explorar algunos ejemplos de las herramientas que nos ofrece Python para trabajar con problemas de Procesamiento del Lenguaje Natural . Comencemos por ejemplo, descargando el corpus de wikipedia en español. Esto lo podemos hacer fácilmente utilizando Textacy . In [1]: Ver Código # Importando las librerías que vamos a utilizar import pandas as pd import numpy as np import matplotlib.pyplot as plt import textacy from textacy.datasets import Wikipedia from collections import Counter , defaultdict import warnings ; warnings . simplefilter ( 'ignore' ) # graficos incrustados % matplotlib inline # función auxiliar def leer_texto ( texto ): \"\"\"Funcion auxiliar para leer un archivo de texto\"\"\" with open ( texto , 'r' ) as text : return text . read () In [2]: # Descargando copus de wikipedia wp = Wikipedia ( data_dir = '/home/raul/Documents/data' , lang = 'es' , version = 'latest' ) wp . download () In [3]: # Chequeando la información descargada wp . info Out[3]: {'data_dir': '/home/raul/Documents/data', 'description': 'All articles for a given language- and version-specific Wikipedia site snapshot.', 'name': 'wikipedia', 'site_url': 'https://meta.wikimedia.org/wiki/Data_dumps'} In [4]: for text in wp . texts ( min_len = 1000 , limit = 2 ): print ( text [: 375 ], \" \\n \" ) Andorra Andorra, oficialmente Principado de Andorra , es un pequeño país soberano del suroeste de Europa. Constituido en Estado independiente, de derecho, democrático y social, cuya forma de gobierno es el coprincipado parlamentario. Su territorio está organizado en siete parroquias, con una población total en 2016 de 78.264 habitantes. Su capital es Andorra la Vieja. Ti Argentina La República Argentina, conocida simplemente como Argentina, es un país soberano de América del Sur, ubicado en el extremo sur y sudeste de dicho subcontinente. Adopta la forma de gobierno republicana, representativa y federal. El Estado argentino es un Estado federal descentralizado, integrado por un Estado nacional y veintitrés estados provinciales autónomos Como podemos ver, con la ayuda de Textacy es muy fácil descargar la información de wikipedia para luego poder utilizarla de base para nuestro corpus . Veamos otros problemas que también podemos resolver con la ayuda de Textacy ; como ser los casos de detectar el idioma o de procesar todo un texto y analizarlo. In [5]: # Detectando el idioma con taxtacy saludos = [ \"Hola\" , \"Hello\" , \"Bonjour\" , \"Guten Tag\" , \"Buon giorno\" , \"Bom dia\" ] for saludo in saludos : print ( textacy . text_utils . detect_language ( saludo )) es en fr de it pt In [6]: # Cargando el modelo en español de spacy nlp = textacy . data . spacy . load ( 'es_core_web_md' ) In [7]: # detalle de stop words # las stop words son las palabras más comunes de un corpus que en general # queremos eliminar porque no son significativas para un análisis. # Ocurren muchas veces, pero aportan muy poca información stop = list ( textacy . data . spacy . es . STOP_WORDS ) stop [: 15 ] Out[7]: ['cuanto', 'sera', 'trabajo', 'tan', 'ya', 'claro', 'encuentra', 'arriba', 'despacio', 'primero', 'pocas', 'tiempo', 'aquéllos', 'días', 'enseguida'] In [8]: # Procesando un texto # Procesando 1984 de George Orwell - mi novela favorita texto = leer_texto ( 'ORWELL_1984.txt' ) texto_procesado = nlp ( texto ) In [9]: # Cuántas sentencias hay en el texto? sentencias = [ s for s in texto_procesado . sents ] print ( len ( sentencias )) 8114 In [10]: # imprimir las primeras 10 sentencias para verificar el texto print ( sentencias [ 1 : 11 ]) [Winston Smith, con la barbilla clavada en el pecho en su esfuerzo por burlar el molestísimo viento, se deslizó rápidamente por entre las puertas de cristal de las Casas de la Victoria, aunque, no con la suficiente rapidez para evitar que una ráfaga polvorienta se colara con él. , El vestíbulo olía a legumbres cocidas y a esteras viejas., Al fondo, un cartel de colores, demasiado grande para hallarse en un interior, estaba pegado a la pared., Representaba sólo un enorme rostro de más de un metro de anchura: la cara de un hombre de unos cuarenta y cinco años con un gran bigote negro y facciones hermosas y endurecidas., Winston se dirigió hacia las escaleras., Era inútil intentar subir en el ascensor., No funcionaba con frecuencia y en esta época la corriente se cortaba durante las horas de día., Esto era parte de las restricciones con que se preparaba la Semana del Odio., Winston tenía que subir a un séptimo piso.] In [11]: # sentencias con las que aparece el Gran Hermano [ sent for sent in texto_procesado . sents if 'Gran Hermano' in sent . string ][ - 10 :] Out[11]: [—¿Morirá el Gran Hermano?, ael Gran Hermano., Pensó en el Gran Hermano., ¿Cuáles eran sus verdaderos sentimientos hacia el Gran Hermano?, Dime: ¿cuáles son los verdaderos sentimientos que te inspira el Gran Hermano?, Tienes que amar ael Gran Hermano., l Gran Hermano., l Gran Hermano., Amaba ael Gran Hermano. , Hubiera sido posible, por ejemplo, decir el «Gran Hermano inbueno».] In [12]: Ver Código def encontrar_personajes ( doc ): \"\"\" Devuelve una lista de los personajes de un `doc` con su cantidad de ocurrencias :param doc: NLP documento parseado por Spacy :return: Lista de Tuplas con la forma [('winston', 686), (\"o'brien\", 135), ('julia', 85),] \"\"\" personajes = Counter () for ent in doc . ents : if ent . label_ == 'PERSON' : personajes [ ent . lemma_ ] += 1 return personajes . most_common () In [13]: # Extrayendo los personajes principales del texto y contando cuantas veces # son nombrados. print ( encontrar_personajes ( texto_procesado )[: 20 ]) [('winston', 686), (\"o'brien\", 135), ('julia', 85), ('partido', 85), ('parsons', 36), ('syme', 29), ('goldstein', 29), ('pensamiento', 22), ('odio', 13), ('ministerio', 13), ('', 11), ('katharine', 11), ('winston ?', 10), ('rutherford', 9), ('ogilvy', 8), ('aaronson', 8), ('charrington', 8), ('—la', 7), ('withers', 6), ('ingsoc', 6)] In [14]: Ver Código def obtener_adj_pers ( doc , personaje ): \"\"\" Encontrar todos los adjetivos relacionados a un personaje en un `doc` :param doc: NLP documento parseado por Spacy :param personaje: un objeto String :return: lista de adjetivos relacionados a un `personaje` \"\"\" adjetivos = [] for ent in doc . ents : if ent . lemma_ == personaje : for token in ent . subtree : if token . pos_ == 'ADJ' : adjetivos . append ( token . lemma_ ) for ent in doc . ents : if ent . lemma_ == personaje : if ent . root . dep_ == 'nsubj' : for child in ent . root . head . children : if child . dep_ == 'acomp' : adjetivos . append ( child . lemma_ ) return adjetivos In [15]: # Encontrar adjetivos que describen a algún personaje. print ( obtener_adj_pers ( texto_procesado , \"winston\" )) ['superior', 'separado', 'fuertes', 'abierto', 'oscura', 'dirigiéndose', 'negro', 'saltones', 'tristes', 'burlones', 'solo', 'humano', 'sostenemos', 'dubitativo', 'completa', 'definitiva', 'sobresaltado', 'fascinado', 'extraña', 'sobrado', 'propio', 'solos', 'joven', 'sorprendido', 'sorprendido', 'hermosa', 'breve', 'cortante', 'primera', 'junto', 'obediente', 'metafísico', 'blanca', 'sonriente', 'sentado', 'irresoluto', 'sumergido', 'feliz'] In [16]: Ver Código def personaje_verbo ( doc , verbo ): \"\"\" Encontrar los personajes que utilizan determinado `verbo` en `doc` :param doc: NLP documento parseado por Spacy :param verbo: un objeto String :return: lista de personajes que utilizan `verbo` \"\"\" contar_verbo = Counter () for ent in doc . ents : if ent . label_ == 'PERSON' and ent . root . head . lemma_ == verbo : contar_verbo [ ent . text ] += 1 return contar_verbo . most_common ( 10 ) In [17]: # Encontrar personajes que utilizan determinado verbo personaje_verbo ( texto_procesado , \"dijo\" ) Out[17]: [('Winston', 7), ('Julia', 4), ('Syme', 2), ('Julia—. Espera', 1), ('Parsons', 1), ('—le', 1)] In [18]: # Trabajando con las entidades del texto # Una entidad nombrada es cualquier objeto del mundo real como una persona, # ubicación, organización o producto con un nombre propio. # tipos de entidades del texto set ( ent . label_ for ent in texto_procesado . ents ) Out[18]: {'CARDINAL', 'LOC', 'MISC', 'ORDINAL', 'ORG', 'PERSON'} In [19]: # Entidades nombradas de tipo ORG [ ent for ent in texto_procesado . ents if ent . label_ == 'ORG' ][: 10 ] Out[19]: [INGSOC, Partido:, ES LA, Departamento de Registro, Dos Minutos de Odio, Departamento de Novela, Partido Interior, Partido Interior, Departamento de Registro, Dos Minutos] In [20]: # Partes de la oración (POS) # En las partes de la oración se etiquetan las palabras de acuerdo a lo # que significan segun su contexto. Algunas de estas etiquetas pueden # ser: Adjetivos, verbos, adverbios, conjunciones, pronombres, sustantivos. # Etiquetas del texto set ( token . pos_ for token in texto_procesado ) Out[20]: {'ADJ', 'ADP', 'ADV', 'AUX', 'CONJ', 'DET', 'INTJ', 'NOUN', 'NUM', 'PART', 'PRON', 'PROPN', 'PUNCT', 'SCONJ', 'SPACE', 'SYM', 'VERB'} In [21]: # Etiquetas de tipo ADJ [ token . orth_ for token in texto_procesado if token . pos_ == 'ADJ' ][ 1 : 11 ] Out[21]: ['frío', 'clavada', 'molestísimo', 'suficiente', 'polvorienta', 'cocidas', 'viejas', 'grande', 'pegado', 'enorme'] In [22]: # Etiquetas de tipo PROPN [ token . orth_ for token in texto_procesado if token . pos_ == 'PROPN' ][ 1 : 11 ] Out[22]: ['GEORGE', 'ORWELL', 'PARTE', 'CAPITULO', 'Winston', 'Smith', 'Casas', 'Victoria', 'Winston', 'Semana'] Como demuestran estos ejemplos Textacy / Spacy son herramientas muy poderosas que nos pueden ayudar a analizar y obtener información valiosa de un texto en forma rápida y sencilla. Deep Learning y Procesamiento del Lenguaje Natural Durante mucho tiempo, las técnicas principales de Procesamiento del Lenguaje Natural fueron dominadas por métodos de aprendizaje automático que utilizaron modelos lineales como las máquinas de vectores de soporte o la regresión logística , entrenados sobre vectores de características de muy alta dimensional pero muy escasos. Recientemente, el campo ha tenido cierto éxito en el cambio hacia modelos de deep learning sobre entradas más densas. Las redes neuronales proporcionan una poderosa maquina de aprendizaje que es muy atractiva para su uso en problemas de lenguaje natural . Un componente importante en las redes neuronales para el lenguaje es el uso de una capa de word embedding , una asignación de símbolos discretos a vectores continuos en un espacio dimensional relativamente bajo. Cuando se utiliza word embedding , se transforman los distintos símbolos en objetos matemáticos sobre los que se pueden realizar operaciones. En particular, la distancia entre vectores puede equipararse a la distancia entre palabras, facilitando la generalización del comportamiento de una palabra sobre otra. Esta representación de palabras como vectores es aprendida por la red como parte del proceso de entrenamiento. Subiendo en la jerarquía, la red también aprende a combinar los vectores de palabras de una manera que es útil para la predicción. Esta capacidad alivia en cierta medida los problemas de dispersión de los datos. Hay dos tipos principales de arquitecturas de redes neuronales que resultan muy útiles en los problemas de Procesamiento del Lenguaje Natural : las Redes neuronales prealimentadas y las Redes neuronales recurrentes . Para ejemplificar, veamos como podemos utilizar word embedding utilizando el modelo word2vec de Gensim . In [23]: Ver Código # importando gensim y TSNE para graficar import gensim from sklearn.manifold import TSNE Using TensorFlow backend. In [24]: # transformando el texto para pasarlo al modelo de gensim texto = [[ str ( palabra ) . lower () for palabra in sent if str ( palabra ) not in stop ] for sent in sentencias ] # generando el diccionario diccionario = gensim . corpora . Dictionary ( texto ) In [25]: # creando el modelo modelo = gensim . models . Word2Vec ( texto , workers = 4 , size = 100 , min_count = 5 , window = 10 , sample = 1e-3 ) In [26]: # representación de la palabra hermano como vector. modelo [ 'hermano' ] Out[26]: array([-0.22692642, -0.08890257, -0.12868501, -0.17392403, 0.22627664, 0.10127033, -0.09027202, 0.10692301, -0.30289358, 0.06429829, 0.17862263, 0.20448232, -0.54694331, -0.40681064, 0.61438572, 0.0217872 , 0.080202 , 0.46306548, 0.09076022, -0.02869571, -0.46194851, 0.28670114, 0.38570273, 0.32555154, 0.13098474, -0.03134775, -0.09577781, 0.06859019, -0.15935177, 0.61558241, 0.07509102, -0.24245416, -0.44668666, -0.77279037, 0.84581488, -0.54047441, -0.18756895, -0.12506978, -0.52870399, 0.1898849 , -0.00930689, 0.36932173, 0.22370262, -0.67407966, -0.45509291, -0.00848365, 0.62967575, 0.16172817, 0.09978516, 0.15064637, -0.34957823, 0.20686783, 0.1038606 , -0.09155462, 0.08276461, 0.31154567, -0.3129864 , -0.45181432, -0.12060832, 0.30541465, -0.37994722, 0.13566031, 0.16380484, 0.32732216, 0.15746659, 0.69340295, -0.25527388, 0.37333885, 0.23317885, -0.4710786 , -0.22506852, 0.14103019, -0.30253953, 0.00573605, -0.14745024, -0.50815731, -0.37789851, -0.3400358 , 0.62753612, 0.04747195, -0.07443633, 0.4276363 , -0.28931141, 0.29784235, -0.07251735, -0.07709371, -0.1003265 , -0.29098341, 0.47159177, 0.41372281, -0.10831725, -0.04670507, 0.07489309, 0.00146162, -0.02867368, -0.2771121 , 0.37281424, -0.53325164, 0.19094327, 0.51455575], dtype=float32) In [27]: # como convertimos las palabras en vectores y los vectores capturan muchas # regularidades lingüísticas, podemos aplicar operaciones vectoriales para # extraer muchas propiedades interesantes. # palabras similares a persona modelo . most_similar_cosmul ( 'persona' ) Out[27]: [('mano', 0.9999627470970154), ('se', 0.9999592304229736), ('mesa', 0.9999556541442871), ('lo', 0.999954879283905), ('podía', 0.9999546408653259), ('personas', 0.9999545812606812), ('habría', 0.9999533891677856), ('sitio', 0.9999532103538513), ('no', 0.999953031539917), ('pero', 0.999951958656311)] In [28]: # por último podemos graficar el modelo vocab = list ( modelo . wv . vocab ) X = modelo [ vocab ] # aplicamos TSNE tsne = TSNE ( n_components = 2 ) X_tsne = tsne . fit_transform ( X ) # transformamos en DataFrame df = pd . concat ([ pd . DataFrame ( X_tsne ), pd . Series ( vocab )], axis = 1 ) df . columns = [ 'x' , 'y' , 'palabra' ] In [29]: # creamos el gráfico fig = plt . figure ( figsize = ( 10 , 8 )) ax = fig . add_subplot ( 1 , 1 , 1 ) # reducimos el dataset a 15 palabras para el ejemplo df_e = df . head ( 15 ) ax . scatter ( df_e [ 'x' ], df_e [ 'y' ]) for i , txt in enumerate ( df_e [ 'palabra' ]): ax . annotate ( txt , ( df_e [ 'x' ] . iloc [ i ], df_e [ 'y' ] . iloc [ i ])) plt . show () Aquí termina este artículo, obviamente el Procesamiento del Lenguaje Natural es un campo muy amplio y quedaron muchas cosas sin desarrollar. Espero que esta introducción les haya sido de utilidad. Saludos! Este post fue escrito por Raúl e. López Briega utilizando Jupyter notebook . Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"IA","url":"https://relopezbriega.github.io/blog/2017/09/23/procesamiento-del-lenguaje-natural-con-python/"},{"title":"Introducción al Deep Learning","text":"Este artículo fue publicado originalmente en el sitio de capacitaciones de IAAR . Introducción El Deep Learning es sin duda el área de investigación más popular dentro del campo de la inteligencia artificial . La mayoría de las nuevas investigaciones que se realizan, trabajan con modelos basados en las técnicas de Deep Learning ; ya que las mismas han logrado resultados sorprendes en campos como Procesamiento del lenguaje natural y Visión por computadora . Pero... ¿qué es este misterioso concepto que ha ganado tanta popularidad? y... ¿cómo se relaciona con el campo de la inteligencia artificial y el Machine Learning ?. Inteligencia artificial, Machine learning y Deep learning En general se suelen utilizar los términos de inteligencia artificial , Machine Learning y Deep Learning en forma intercambiada. Sin embargo, éstos términos no son los mismo y abarcan distintas cosas. Inteligencia Artificial El término inteligencia artificial es el más general y engloba a los campos de Machine Learning y Deep Learning junto con otras técnicas como los algoritmos de búsqueda , el razonamiento simbólico , el razonamiento lógico y la estadística . Nació en los años 1950s, cuando un grupo de pioneros de la computación comenzaron a preguntarse si se podía hacer que las computadoras pensaran . Una definición concisa de la inteligencia artificial sería: el esfuerzo para automatizar las tareas intelectuales que normalmente realizan los seres humanos . Machine Learning El Machine Learning o Aprendizaje automático se refiere a un amplio conjunto de técnicas informáticas que nos permiten dar a las computadoras la capacidad de aprender sin ser explícitamente programadas . Hay muchos tipos diferentes de algoritmos de Aprendizaje automático , entre los que se encuentran el aprendizaje por refuerzo , los algoritmos genéticos , el aprendizaje basado en reglas de asociación , los algoritmos de agrupamiento , los árboles de decisión , las máquinas de vectores de soporte y las redes neuronales . Actualmente, los algoritmos más populares dentro de este campo son los de Deep Learning . Deep Learning El Deep Learning o aprendizaje profundo es un subcampo dentro del Machine Learning , el cuál utiliza distintas estructuras de redes neuronales para lograr el aprendizaje de sucesivas capas de representaciones cada vez más significativas de los datos. El profundo o deep en Deep Learning hace referencia a la cantidad de capas de representaciones que se utilizan en el modelo; en general se suelen utilizar decenas o incluso cientos de capas de representación . las cuales aprenden automaticamente a medida que el modelo es entrenado con los datos. ¿Qué es el Deep Learning? Antes de poder entender que es el Deep Learning , debemos en primer lugar conocer dos conceptos fundamentales: las redes neuronales artificiales y la Propagación hacia atrás . Redes Neuronales Las redes neuronales son un modelo computacional basado en un gran conjunto de unidades neuronales simples ( neuronas artificiales ), de forma aproximadamente análoga al comportamiento observado en los axones de las neuronas en los cerebros biológicos. Cada una de estas neuronas simples, va a tener una forma similar al siguiente diagrama: En donde sus componentes son: $x_1, x_2, \\dots, x_n$: Los datos de entrada en la neurona, los cuales también puede ser que sean producto de la salida de otra neurona de la red. $x_0$: La unidad de sesgo; un valor constante que se le suma a la entrada de la función de activación de la neurona. Generalmente tiene el valor 1. Este valor va a permitir cambiar la función de activación hacia la derecha o izquierda, otorgándole más flexibilidad para aprender a la neurona. $w_0, w_1, w_2, \\dots, w_n$: Los pesos relativos de cada entrada. Tener en cuenta que incluso la unidad de sesgo tiene un peso. a: La salida de la neurona. Que va a ser calculada de la siguiente forma: $$a = f\\left(\\sum_{i=0}^n w_i \\cdot x_i \\right)$$ Aquí $f$ es la función de activación de la neurona. Esta función es la que le otorga tanta flexibilidad a las redes neuronales y le permite estimar complejas relaciones no lineales en los datos. Puede ser tanto una función lineal , una función logística , hiperbólica , etc. Cada unidad neuronal está conectada con muchas otras y los enlaces entre ellas pueden incrementar o inhibir el estado de activación de las neuronas adyacentes. Estos sistemas aprenden y se forman a sí mismos, en lugar de ser programados de forma explícita, y sobresalen en áreas donde la detección de soluciones o características es difícil de expresar con la programación convencional. Propagación hacia atrás La propagación hacia atrás o backpropagation es un algoritmo que funciona mediante la determinación de la pérdida (o error) en la salida y luego propagándolo de nuevo hacia atrás en la red. De esta forma los pesos se van actualizando para minimizar el error resultante de cada neurona. Este algoritmo es lo que les permite a las redes neuronales aprender. ¿Cómo funciona el Deep Learning? En general, cualquier técnica de Machine Learning trata de realizar la asignación de entradas (por ejemplo, imágenes) a salidas objetivo (Por ejemplo, la etiqueta \"gato\"), mediante la observación de un gran número de ejemplos de entradas y salidas. El Deep Learning realiza este mapeo de entrada-a-objetivo por medio de una red neuronal artificial que está compuesta de un número grande de capas dispuestas en forma de jerarquía. La red aprende algo simple en la capa inicial de la jerarquía y luego envía esta información a la siguiente capa. La siguiente capa toma esta información simple, lo combina en algo que es un poco más complejo, y lo pasa a la tercer capa. Este proceso continúa de forma tal que cada capa de la jerarquía construye algo más complejo de la entrada que recibió de la capa anterior. De esta forma, la red irá aprendiendo por medio de la exposición a los datos de ejemplo. La especificación de lo que cada capa hace a la entrada que recibe es almacenada en los pesos de la capa, que en esencia, no son más que números. Utilizando terminología más técnica podemos decir que la transformación de datos que se produce en la capa es parametrizada por sus pesos . Para que la red aprenda debemos encontrar los pesos de todas las capas de forma tal que la red realice un mapeo perfecto entre los ejemplos de entrada con sus respectivas salidas objetivo. Pero el problema reside en que una red de Deep Learning puede tener millones de parámetros , por lo que encontrar el valor correcto de todos ellos puede ser una tarea realmente muy difícil, especialmente si la modificación del valor de uno de ellos afecta a todos los demás. Para poder controlar algo, en primer lugar debemos poder observarlo. En este sentido, para controlar la salida de la red neuronal , deberíamos poder medir cuan lejos esta la salida que obtuvimos de la que se esperaba obtener. Este es el trabajo de la función de pérdida de la red . Esta función toma las predicciones que realiza el modelo y los valores objetivos (lo que realmente esperamos que la red produzca), y calcula cuán lejos estamos de ese valor, de esta manera, podemos capturar que tan bien esta funcionando el modelo para el ejemplo especificado. El truco fundamental del Deep Learning es utilizar el valor que nos devuelve esta función de pérdida para retroalimentar la red y ajustar los pesos en la dirección que vayan reduciendo la pérdida del modelo para cada ejemplo. Este ajuste, es el trabajo del optimizador , el cuál implementa la propagación hacia atrás . Resumiendo, el funcionamiento sería el siguiente: inicialmente, los pesos de cada capa son asignados en forma aleatoria, por lo que la red simplemente implementa una serie de transformaciones aleatorias. En este primer paso, obviamente la salida del modelo dista bastante del ideal que deseamos obtener, por lo que el valor de la función de pérdida va a ser bastante alto. Pero a medida que la red va procesando nuevos casos, los pesos se van ajustando de forma tal de ir reduciendo cada vez más el valor de la función de pérdida . Este proceso es el que se conoce como entrenamiento de la red , el cual repetido una suficiente cantidad de veces, generalmente 10 iteraciones de miles de ejemplos, logra que los pesos se ajusten a los que minimizan la función de pérdida . Una red que ha minimizado la pérdida es la que logra los resultados que mejor se ajustan a las salidas objetivo, es decir, que el modelo se encuentra entrenado . Arquitecturas de Deep Learning La estructura de datos fundamental de una red neuronal está vagamente inspirada en el cerebro humano. Cada una de nuestras células cerebrales (neuronas) está conectada a muchas otras neuronas por sinapsis. A medida que experimentamos e interactuamos con el mundo, nuestro cerebro crea nuevas conexiones, refuerza algunas conexiones y debilita a los demás. De esta forma, en nuestro cerebro se desarrollan ciertas regiones que se especializan en el procesamiento de determinadas entradas . Así vamos a tener un área especializada en la visión, otra que se especializa en la audición, otra para el lenguaje, etc. De forma similar, dependiendo del tipo de entradas con las que trabajemos, van a existir distintas arquitecturas de redes neuronales que mejor se adaptan para procesar esa información. Algunas de las arquitecturas más populares son: Redes neuronales prealimentadas Las Redes neuronales prealimentadas fueron las primeras que se desarrollaron y son el modelo más sencillo. En estas redes la información se mueve en una sola dirección: hacia adelante. Los principales exponentes de este tipo de arquitectura son el perceptrón y el perceptrón multicapa . Se suelen utilizar en problemas de clasificación simples. Redes neuronales convolucionales Las redes neuronales convolucionales son muy similares a las redes neuronales ordinarias como el perceptron multicapa ; se componen de neuronas que tienen pesos y sesgos que pueden aprender. Cada neurona recibe algunas entradas, realiza un producto escalar y luego aplica una función de activación. Al igual que en el perceptron multicapa también vamos a tener una función de pérdida o costo sobre la última capa, la cual estará totalmente conectada. Lo que diferencia a las redes neuronales convolucionales es que suponen explícitamente que las entradas son imágenes, lo que nos permite codificar ciertas propiedades en la arquitectura; permitiendo ganar en eficiencia y reducir la cantidad de parámetros en la red. En general, las redes neuronales convolucionales van a estar construidas con una estructura que contendrá 3 tipos distintos de capas: Una capa convolucional , que es la que le da le nombre a la red. Una capa de reducción o de pooling , la cual va a reducir la cantidad de parámetros al quedarse con las características más comunes. Una capa clasificadora totalmente conectada, la cual nos va dar el resultado final de la red. Algunas implementaciones específicas que podemos encontrar sobre este tipo de redes son: inception v3 , ResNet , VGG16 y xception , entre otras. Todas ellas han logrado excelentes resultados. Redes neuronales recurrentes Los seres humanos no comenzamos nuestro pensamiento desde cero cada segundo, sino que los mismos tienen una persistencia. Las Redes neuronales prealimentadas tradicionales no cuentan con esta persistencia, y esto parece una deficiencia importante. Las Redes neuronales recurrentes abordan este problema. Son redes con bucles de retroalimentación, que permiten que la información persista. Una Red neural recurrente puede ser pensada como una red con múltiples copias de ella misma, en las que cada una de ellas pasa un mensaje a su sucesor. Esta naturaleza en forma de cadena revela que las Redes neurales recurrentes están íntimamente relacionadas con las secuencias y listas; por lo que son ideales para trabajar con este tipo de datos. En los últimos años, ha habido un éxito increíble aplicando Redes neurales recurrentes a una variedad de problemas como: reconocimiento de voz, modelado de lenguaje, traducción, subtítulos de imágenes y la lista continúa. Las redes de memoria de largo plazo a corto plazo - generalmente llamadas LSTMs - son un tipo especial de Redes neurales recurrentes , capaces de aprender dependencias a largo plazo. Ellas también tienen una estructura como cadena, pero el módulo de repetición tiene una estructura diferente. En lugar de tener una sola capa de red neuronal, tiene cuatro, que interactúan de una manera especial permitiendo tener una memoria a más largo plazo. Para más información sobre diferentes arquitecturas de redes neuronales pueden visitar el siguiente artículo de wikipedia . Logros del Deep Learning En los últimos años el Deep Learning ha producido toda una revolución en el campo del Machine Learning , con resultados notables en todos los problemas de percepción , como ver y escuchar , problemas que implican habilidades que parecen muy naturales e intuitivas para los seres humanos, pero que desde hace tiempo se han mostrado difíciles para las máquinas. En particular, el Deep Learning ha logrado los siguientes avances, todos ellos en áreas históricamente difíciles del Machine Learning . Un nivel casi humano para la clasificación de imágenes. Un nivel casi humano para el reconocimiento del lenguaje hablado. Un nivel casi humano en el reconocimiento de escritura. Grandes mejoras en traducciones de lenguas. Grandes mejoras en conversaciones text-to-speech . Asistentes digitales como Google Now o Siri. Un nivel casi humano en autos autónomos. Mejores resultados de búsqueda en la web. Grandes mejoras para responder preguntas en lenguaje natural. Alcanzado Nivel maestro (superior al humano) en varios juegos. En muchos sentidos, el Deep Learning todavía sigue siendo un campo misterioso para explorar, por lo que seguramente veremos nuevos avances en nuevas áreas utilizando estas técnicas. Tal vez algún día el Deep Learning ayuda a los seres humanos a hacer ciencia, desarrollar software y mucho más. ¿Por qué estos sorprendentes resultados surgen ahora? Muchos de los conceptos del Deep Learning se desarrollaron en los años 80s y 90s, algunos incluso mucho antes. Sin embargo, los primeros resultados exitosos del Deep Learning surgieron en los últimos 5 años. ¿qué fue lo que cambio para lograr la popularidad y éxito de los modelos basados en Deep Learning en estos últimos años? Si bien existen múltiples factores para explicar esta revolución del Deep Learning , los dos principales componentes parecen ser la disponibilidad de masivos volúmenes de datos , lo que actualmente se conoce bajo el nombre de Big Data ; y el progreso en el poder de computo , especialmente gracias a los GPUs . Entonces, dentro de los factores que explican esta popularidad de los modelos de Deep Learning podemos encontrar: La disponibilidad de conjuntos de datos enormes y de buena calidad . Gracias a la revolución digital en que nos encontramos, podemos generar conjuntos de datos enormes con los cuales alimentar a los algoritmos de Deep Learning , los cuales necesitan de muchos datos para poder generalizar . Computación paralela masiva con GPUs . En líneas generales, los modelos de redes neuronales no son más que complicados cálculos numéricos que se realizan en paralelo. Gracias al desarrollo de los GPUs estos cálculos ahora se pueden realizar en forma mucho más rápida, permitiendo que podamos entrenar modelos más profundos y grandes. Funciones de activación amigables para Backpropagation . La progación hacia atrás o Backpropagation es el algoritmo fundamental que hace funcionar a las redes neuronales ; pero la forma en que trabaja implica cálculos realmente complicados. La transición desde funciones de activación como tanh o sigmoid a funciones como ReLU o SELU han simplificado estos problemas. Nuevas arquitecturas . Arquitecturas como Resnets , inception y GAN mantienen el campo actualizado y continúan aumentando las flexibilidad de los modelos. Nuevas técnicas de regularización . Técnicas como dropout , batch normalization y data-augmentation nos permiten entrenar redes más grandes con menos peligro de sobreajuste . Optimizadores más robustos . La optimización es fundamental para el funcionamiento de las redes neuronales . Mejoras sobre el tradicional procedimiento de SGD , como ADAM han ayudado a mejorar el rendimiento de los modelos. Plataformas de software . Herramientas como TensorFlow , Theano , Keras , CNTK , PyTorch , Chainer , y mxnet nos permiten crear prototipos en forma más rápida y trabajar con GPUs sin tantas complicaciones. Nos permiten enfocarnos en la estructura del modelo sin tener que preocuparnos por los detalles de más bajo nivel. Otra razón por la que el Deep Learning ha tenido tanta repercusión últimamente además de ofrecer un mejor rendimiento en muchos problemas; es que el Deep Learning esta haciendo la resolución de problemas mucho más fácil, ya que automatiza completamente lo que solía ser uno de los pasos más difíciles y cruciales en el flujo de trabajo de Machine Learning : la ingeniería de atributos . Antes del Deep Learning , para poder entrenar un modelo, primero debíamos refinar las entradas para adaptarlas al tipo de transformación del modelo; teníamos que cuidadosamente seleccionar los atributos más representativos y desechar los poco informativos. El Deep Learning , en cambio, automatiza este proceso; aprendemos todos los atributos de una sola pasada y el mismo modelo se encarga de adaptarse y quedarse con lo más representativo. Frameworks para Deep Learning En estos momentos, si hay un campo en donde Python sobresale sobre cualquier otro lenguaje, es en su soporte para frameworks de Deep Learning . Existen una gran variedad y de muy buena calidad, entre los que se destacan: TensorFlow : TensorFlow es un frameworks desarrollado por Google. Es una librería de código libre para computación numérica usando grafos de flujo de datos. PyTorch : PyTorch es un framework de Deep Learning que utiliza el lenguaje Python y cuenta con el apoyo de Facebook. Theano : Theano es una librería de Python que permite definir, optimizar y evaluar expresiones matemáticas que involucran tensores de manera eficiente. CNTK : CNTK es un conjunto de herramientas, desarrolladas por Microsoft, fáciles de usar, de código abierto que entrena algoritmos de Deep Learning para aprender como el cerebro humano. Keras : Keras es una librería de alto nivel, muy fácil de utilizar. Está escrita y mantenida por Francis Chollet, miembro del equipo de Google Brain. Permite a los usuarios elegir si los modelos que se construyen seran ejecutados en el grafo simbólico de Theano , TensorFlow o CNTK . MXNet : MXNet es una librería flexible y eficiente para armar modelos de Deep Learning con soporte para varios idiomas. ¿Cómo mantenerse actualizado en el campo de Deep Learning? El campo del Deep Learning se mueve muy rapidamente, con varios papers que se publican por mes; por tal motivo, mantenerse actualizado con las últimas tendencias del campo puede ser bastante complicado. Algunos consejos pueden ser: Estarse atento a las publicaciones en arxiv , especialmente a la sección de machine learning . La mayoría de los papers más relevantes, los vamos a poder encontrar en esa plataforma. Seguir el blog de keras en el cual podemos encontrar como implementar varios modelos utilizando esta genial librería. Seguir el blog de openai en dónde detallan las investigaciones que van realizando, especialmente trabajando con GANs . Seguir el blog de Google research ; en dónde se viene haciendo bastante foco en los modelos de Deep Learning . Utilizar la sección de Machine Learning de reddit . Suscribirse al podcast Talking machines ; en dónde se entrevista a los principales exponentes del campo de la inteligencia artificial . Por último, obviamente estar atentos a las publicaciones que se realizan en IAAR . Aquí concluye el artículo; espero que les sirva como una introducción para que puedan ingresar en el fascinante y prometedor mundo del Deep Learning . Saludos! Este post fue escrito por Raúl e. López Briega utilizando Jupyter notebook . Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Redes Neuronales","url":"https://relopezbriega.github.io/blog/2017/06/13/introduccion-al-deep-learning/"},{"title":"Boosting en Machine Learning con Python","text":"\"La opinión de toda una multitud es siempre más creíble que la de una minoría.\" Miguel de Unamuno Introducción La meta de construir sistemas que puedan adaptarse a sus entornos y aprender de su experiencia ha atraído a investigadores de muchos campos, como la Informática , Matemáticas , Física , Neurociencia y la Ciencia cognitiva . Intuitivamente, para que un algoritmo de aprendizaje sea efectivo y preciso en sus predicciones, debería reunir tres condiciones básicas: Debería ser entrenado con suficientes datos de entrenamiento. Sus resultados deberían ajustarse bastante bien a los ejemplos de entrenamiento (lo que significaría tener una tasa de error baja). Debería ser lo suficientemente \"simple\". Esta última condición, que las reglas más simples suelen ser las mejores, se conoce a menudo con el nombre de \" La navaja de Occam \". Muchos algoritmos se han creado y existen aún muchos por descubrir; pero unos de los que ha ganado mucha atracción en los últimos años por su simpleza y su gran éxito en competencias como kraggle , son los algoritmos de Boosting . ¿Qué es Boosting? Boosting es un enfoque de Machine Learning basado en la idea de crear una regla de predicción altamente precisa combinando muchas reglas relativamente débiles e imprecisas . Una teoría notablemente rica ha evolucionado en torno al Boosting , con conexiones a una amplia gama de ramas de la ciencia, incluyendo estadísticas , teoría de juegos , optimización convexa y geometría de la información . Los algoritmos de Boosting han tenido éxito práctico con aplicaciones, por ejemplo, en biología , visión y procesamiento del lenguaje natural . En varios momentos de su historia, el Boosting ha sido objeto de controversia por el misterio y la paradoja que parece presentar. El Boosting asume la disponibilidad de un algoritmo de aprendizaje base o débil que, dado ejemplos de entrenamiento etiquetados, produce un clasificador base o débil . El objetivo de Boosting es el de mejorar el rendimiento del algoritmo de aprendizaje al tratarlo como una \"caja negra\" que se puede llamar repetidamente, como una subrutina. Si bien el algoritmo de aprendizaje base puede ser rudimentario y moderadamente inexacto, no es del todo trivial ni poco informativo y debe obtener resultados mejores a los que se podrían obtener en forma aleatoria. La idea fundamental detrás de Boosting es elegir conjuntos de entrenamiento para el algoritmo de aprendizaje base de tal manera que lo obligue a inferir algo nuevo sobre los datos cada vez que se lo llame. Uno de los primeros algoritmos de Boosting en tener éxito en problemas de clasificación binaria fue AdaBoost . AdaBoost AdaBoost es la abreviatura de adaptive boosting , es un algoritmo que puede ser utilizado junto con otros algoritmos de aprendizaje para mejorar su rendimiento. AdaBoost funciona eligiendo un algoritmo base (por ejemplo árboles de decisión ) y mejorándolo iterativamente al tomar en cuenta los casos incorrectamente clasificados en el conjunto de entrenamiento. En AdaBoost asignamos pesos iguales a todos los ejemplos de entrenamiento y elegimos un algoritmo base . En cada paso de iteración, aplicamos el algoritmo base al conjunto de entrenamiento y aumentamos los pesos de los ejemplos incorrectamente clasificados. Iteramos n veces, cada vez aplicando el algoritmo base en el conjunto de entrenamiento con pesos actualizados. El modelo final es la suma ponderada de los resultados de los n algoritmos base . AdaBoost en conjunto con árboles de decisión se ha mostrado sumamente efectivo en varios problemas de Machine Learning . Veamos un ejemplo en Python . In [2]: Ver Código # Importando las librerías que vamos a utilizar import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from sklearn.model_selection import train_test_split from sklearn.ensemble import GradientBoostingClassifier from sklearn.ensemble import AdaBoostClassifier from sklearn.metrics import accuracy_score from sklearn.tree import DecisionTreeClassifier from sklearn.tree import export_graphviz from sklearn.datasets import load_breast_cancer from sklearn.preprocessing import LabelEncoder from sklearn.preprocessing import OneHotEncoder import graphviz import xgboost as xgb # graficos incrustados % matplotlib inline # parametros esteticos de seaborn sns . set_palette ( \"deep\" , desat =. 6 ) sns . set_context ( rc = { \"figure.figsize\" : ( 8 , 4 )}) In [3]: # Utilzando el dataset breast cancer cancer = load_breast_cancer () # dataset en formato tabular pd . DataFrame ( data = cancer . data , columns = cancer . feature_names ) . head () Out[3]: .dataframe thead tr:only-child th { text-align: right; } .dataframe thead th { text-align: left; } .dataframe tbody tr th { vertical-align: top; } mean radius mean texture mean perimeter mean area mean smoothness mean compactness mean concavity mean concave points mean symmetry mean fractal dimension ... worst radius worst texture worst perimeter worst area worst smoothness worst compactness worst concavity worst concave points worst symmetry worst fractal dimension 0 17.99 10.38 122.80 1001.0 0.11840 0.27760 0.3001 0.14710 0.2419 0.07871 ... 25.38 17.33 184.60 2019.0 0.1622 0.6656 0.7119 0.2654 0.4601 0.11890 1 20.57 17.77 132.90 1326.0 0.08474 0.07864 0.0869 0.07017 0.1812 0.05667 ... 24.99 23.41 158.80 1956.0 0.1238 0.1866 0.2416 0.1860 0.2750 0.08902 2 19.69 21.25 130.00 1203.0 0.10960 0.15990 0.1974 0.12790 0.2069 0.05999 ... 23.57 25.53 152.50 1709.0 0.1444 0.4245 0.4504 0.2430 0.3613 0.08758 3 11.42 20.38 77.58 386.1 0.14250 0.28390 0.2414 0.10520 0.2597 0.09744 ... 14.91 26.50 98.87 567.7 0.2098 0.8663 0.6869 0.2575 0.6638 0.17300 4 20.29 14.34 135.10 1297.0 0.10030 0.13280 0.1980 0.10430 0.1809 0.05883 ... 22.54 16.67 152.20 1575.0 0.1374 0.2050 0.4000 0.1625 0.2364 0.07678 5 rows × 30 columns In [4]: # Separando los datos en sets de entrenamiento y evaluación X_train , X_test , y_train , y_test = train_test_split ( cancer . data , cancer . target , random_state = 1 ) # Armando un simple arbol de decisión tree = DecisionTreeClassifier ( max_depth = 2 , random_state = 0 ) tree . fit ( X_train , y_train ) print ( 'Precisión modelo inicial train/test {0:.3f} / {1:.3f} ' . format ( tree . score ( X_train , y_train ), tree . score ( X_test , y_test ))) Precisión modelo inicial train/test 0.962/0.888 In [5]: # Dibujando el modelo export_graphviz ( tree , out_file = \"tree.dot\" , class_names = [ \"malignant\" , \"benign\" ], feature_names = cancer . feature_names , impurity = False , filled = True ) with open ( \"tree.dot\" ) as f : dot_graph = f . read () graphviz . Source ( dot_graph ) Out[5]: Tree 0 worst perimeter <= 106.05 samples = 426 value = [157, 269] class = benign 1 worst concave points <= 0.1589 samples = 259 value = [9, 250] class = benign 0->1 True 4 worst texture <= 20.645 samples = 167 value = [148, 19] class = malignant 0->4 False 2 samples = 253 value = [4, 249] class = benign 1->2 3 samples = 6 value = [5, 1] class = malignant 1->3 5 samples = 16 value = [4, 12] class = benign 4->5 6 samples = 151 value = [144, 7] class = malignant 4->6 In [6]: # Utilizando AdaBoost para aumentar la precisión ada = AdaBoostClassifier ( base_estimator = tree , n_estimators = 500 , learning_rate = 1.5 , random_state = 1 ) # Ajustando los datos ada = ada . fit ( X_train , y_train ) In [7]: # Imprimir la precisión. y_train_pred = ada . predict ( X_train ) y_test_pred = ada . predict ( X_test ) ada_train = accuracy_score ( y_train , y_train_pred ) ada_test = accuracy_score ( y_test , y_test_pred ) print ( 'Precisión modelo con AdaBoost train/test {0:.3f} / {1:.3f} ' . format ( ada_train , ada_test )) Precisión modelo con AdaBoost train/test 1.000/0.965 Para este ejemplo utilizamos el conjunto de datos breast cancer que ya viene en cargado en scikit-learn ; la idea es clasificar casos de cáncer de pecho según varios atributos de los tumores. En primer lugar, creamos un clasificador simple, un árbol de decisión de hasta dos niveles de profundidad. Este clasificador tuvo un rendimiento bastante bueno, logrando una precisión del 96% con los datos de entrenamiento y del 89% con los datos de evaluación. Luego aplicamos AdaBoost sobre el mismo modelo para mejorar la precisión. Vemos que el modelo con AdaBoost logra una precisión del 100% en los datos de entrenamiento y del 96% en los datos de evaluación. Debemos tener en cuenta que una precisión del 100% sobre los datos de entrenamiento, puede ser un indicio de que el modelo tal vez este sobreajustado . El sobreajuste es uno de los riesgo que suele traer aparejado la utilización de las técnicas de Boosting . A partir del éxito inicial de AdaBoost , las técnicas de Boosting fueron evolucionando hacia un modelo estadístico más generalizado, tratando el problema como un caso de optimización numérica en dónde el objetivo es minimizar la función de perdida del modelo mediante la adición de los algoritmos de aprendizaje débiles utilizando un procedimiento de optimización del tipo de gradiente descendiente . Esta generalización permitió utilizar funciones arbitrarias de pérdida diferenciables, ampliando la técnica más allá de los problemas de clasificación binaria hacia problemas de regresión y de clasificación multi-variable. Esta nueva familia de algoritmos de Boosting se conocen bajo el nombre de Gradient boosting . Gradient Boosting El Gradient boosting implica tres elementos: Una función de perdida a optimizar . Un algoritmo de aprendizaje débil para hacer las predicciones. Un modelo aditivo para añadir los algoritmos de aprendizaje débiles que minimizan la función de perdida . Función de pérdida La función de perdida utilizada va a depender del tipo de problema al que nos enfrentamos. La principal característica que debe poseer, es que sea diferenciable . Existen varias funciones de pérdida estándar. Por ejemplo, para problemas de regresión podemos utilizar un error cuadrático y para problemas de clasificación podemos utilizar una pérdida logarítmica o una entropía cruzada . Algoritmo de aprendizaje débil El algoritmo de aprendizaje débil que se utiliza en el Gradient boosting es el de árboles de decisión . Específicamente se usan árboles de regresión que producen valores reales para las divisiones y cuya salida se puede sumar, permitiendo que los resultados de los modelos subsiguientes sean agregados y corrijan los errores promediando las predicciones. Es común restringir a los árboles de decisión de manera específica para asegurarnos que el algoritmo permanezca débil . Se suelen restringir el número máximo de capas, nodos, divisiones u hojas. Modelo aditivo Los árboles de decisión son agregados de a uno a la vez, y los árboles existentes en el modelo no cambian. Para determinar los parámetros que tendrán cada uno de los árboles de decisión que son agregados al modelo se utiliza un procedimiento de gradiente descendiente que minimizará la función de perdida . De esta forma se van agregando árboles con distintos parámetros de forma tal que la combinación de ellos minimiza la pérdida del modelo y mejora la predicción. Árboles de decisión con Gradient boosting es uno de los modelos más poderosos y más utilizados para problemas de aprendizaje supervisado. Su principal inconveniente es que requieren un ajuste cuidadoso de los parámetros y puede requerir mucho tiempo de entrenamiento. Al igual que otros modelos basados en árboles, el algoritmo funciona y escala bien con una mezcla de características binarias y continuas. Asimismo, también arrastra el problema de los árboles de decisión en los casos en que los datos están dispersos y tienen una alta dimensionalidad. Veamos un ejemplo con scikit-learn utilizando los mismos datos de breast cancer del ejemplo anterior. In [8]: # Armando el modelo con parametro max_depth gbrt = GradientBoostingClassifier ( random_state = 0 , n_estimators = 500 , max_depth = 1 , learning_rate = 0.01 ) # Ajustando el modelo gbrt . fit ( X_train , y_train ) print ( 'Precisión Gradient Boosting train/test {0:.3f} / {1:.3f} ' . format ( gbrt . score ( X_train , y_train ), gbrt . score ( X_test , y_test ))) Precisión Gradient Boosting train/test 0.991/0.937 In [9]: # Graficando la importancia de cada atributo n_atributos = cancer . data . shape [ 1 ] plt . barh ( range ( n_atributos ), gbrt . feature_importances_ , align = 'center' ) plt . yticks ( np . arange ( n_atributos ), cancer . feature_names ) plt . xlabel ( \"Importancia de atributo\" ) plt . ylabel ( \"Atributo\" ) plt . show (); Los principales parámetros de los modelos de árboles de decisión con Gradient boosting son el número de árboles, n_estimators , y la tasa de aprendizaje, learning_rate , que controla el grado en que a cada árbol se le permite corregir los errores de los árboles anteriores. Estos dos parámetros están altamente interconectados en el sentido de que si bajamos el valor en la tasa de aprendizaje vamos a necesitar un número mayor de árboles para construir un modelo de complejidad similar. Como podemos ver en el ejemplo, aplicando Gradient boosting con árboles de tan solo un nivel de profundidad, logramos una precisión del 99 % sobre los datos de entrenamiento y del 93 % sobre los datos de evaluación. Si deseamos aplicar el algoritmo de Gradient boosting a un problema de gran escala, entonces la librería que sobresale por su facilidad de utilización y rendimiento es XGBoost . XGBoost XGBoost significa eXtreme Gradient Boosting. Es el algoritmo que ha estado dominando recientemente los problemas Machine learning y las competiciones de Kaggle con datos estructurados o tabulares. XGBoost es una implementación de árboles de decisión con Gradient boosting diseñada para minimizar la velocidad de ejecución y maximizar el rendimiento. Posee una interface para varios lenguajes de programación, entre los que se incluyen Python , R , Julia y Scala . Internamente, XGBoost representa todos los problemas como un caso de modelado predictivo de regresión que sólo toma valores numéricos como entrada. Si nuestros datos están en un formato diferente, primero vamos a tener que transformarlos para poder hacer uso de todo el poder de esta librería. El hecho de trabajar sólo con datos numéricos es lo que hace que esta librería sea tan eficiente. Veamos como la podemos utilizar en Python con un ejemplo. Para este caso vamos a trabajar con el dataset UCI breast-cancer , el cual contiene todos datos categóricos que vamos a tener que transformar. Este conjunto de datos describe los detalles técnicos de las biopsias de cáncer de mama y la tarea de predicción es predecir si el paciente tiene o no una recurrencia del cáncer, o no. In [10]: # Ejemplo de XGBoost # cargando los datos cancer2 = pd . read_csv ( 'https://relopezbriega.github.io/downloads/datasets-uci-breast-cancer.csv' ) cancer2 . head () Out[10]: .dataframe thead tr:only-child th { text-align: right; } .dataframe thead th { text-align: left; } .dataframe tbody tr th { vertical-align: top; } Unnamed: 0 age menopause tumor-size inv-nodes node-caps deg-malig breast breast-quad irradiat Class 0 0 '40-49' 'premeno' '15-19' '0-2' 'yes' '3' 'right' 'left_up' 'no' 'recurrence-events' 1 1 '50-59' 'ge40' '15-19' '0-2' 'no' '1' 'right' 'central' 'no' 'no-recurrence-events' 2 2 '50-59' 'ge40' '35-39' '0-2' 'no' '2' 'left' 'left_low' 'no' 'recurrence-events' 3 3 '40-49' 'premeno' '35-39' '0-2' 'yes' '3' 'right' 'left_low' 'yes' 'no-recurrence-events' 4 4 '40-49' 'premeno' '30-34' '3-5' 'yes' '2' 'left' 'right_up' 'no' 'recurrence-events' In [11]: # Divido los datos en data y target. cancer_data = cancer2 . values cancer2 . data = cancer_data [:, 0 : 9 ] cancer2 . data = cancer2 . data . astype ( str ) cancer2 . target = cancer_data [:, 9 ] cancer2 . data . shape Out[11]: (286, 9) In [12]: # Aplico el enconding para transformar los datos de entrada a valores # numericos utilizando OneHotEncoder encoded_data = None for i in range ( 0 , cancer2 . data . shape [ 1 ]): label_encoder = LabelEncoder () feature = label_encoder . fit_transform ( cancer2 . data [:, i ]) feature = feature . reshape ( cancer2 . data . shape [ 0 ], 1 ) onehot_encoder = OneHotEncoder ( sparse = False ) feature = onehot_encoder . fit_transform ( feature ) if encoded_data is None : encoded_data = feature else : encoded_data = np . concatenate (( encoded_data , feature ), axis = 1 ) # Aplico LaberEncoder a los valores de la variable target. label_encoder = LabelEncoder () label_encoder = label_encoder . fit ( cancer2 . target ) encoded_y = label_encoder . transform ( cancer2 . target ) # Separando los datos en sets de entrenamiento y evaluación X_train , X_test , y_train , y_test = train_test_split ( encoded_data , encoded_y , random_state = 1 ) In [13]: # Construyo el modelo y ajusto los datos. modelo = xgb . XGBClassifier () modelo . fit ( X_train , y_train ) # Realizo las predicciones y_pred = modelo . predict ( X_train ) predicciones = [ round ( value ) for value in y_pred ] # Evalúo las predicciones precision_train = accuracy_score ( y_train , predicciones ) # Repito el proceso con datos de evaluacion y_pred = modelo . predict ( X_test ) predicciones = [ round ( value ) for value in y_pred ] # Evalúo las predicciones precision_test = accuracy_score ( y_test , predicciones ) print ( modelo ) print ( 'Precisión xgboost train/test {0:.3f} / {1:.3f} ' . format ( precision_train , precision_test )) XGBClassifier(base_score=0.5, colsample_bylevel=1, colsample_bytree=1, gamma=0, learning_rate=0.1, max_delta_step=0, max_depth=3, min_child_weight=1, missing=None, n_estimators=100, nthread=-1, objective='binary:logistic', reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=0, silent=True, subsample=1) Precisión xgboost train/test 0.879/0.694 En este ejemplo logramos una precisión de 88 % con los datos de entrenamiento y del 69 % con los datos de evaluación. Con esto termina este artículo. Espero les haya sido de utilidad y puedan explorar todo el poder predictivo de XGBoost . Asimismo, otra implementación de Gradient boosting que deberíamos tener en cuenta ya que también ha obtenido muy buenos resultados en términos de precisión y rendimiento es LightGBM , que forma parte del Distributed Machine Learning Toolkit de Microsoft. Saludos! Este post fue escrito por Raúl e. López Briega utilizando Jupyter notebook . Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Machine Learning","url":"https://relopezbriega.github.io/blog/2017/06/10/boosting-en-machine-learning-con-python/"},{"title":"Introducción a la Inteligencia Artificial","text":"Este artículo fue publicado originalmente en el sitio de capacitaciones de IAAR . Introducción El cerebro es el órgano más increíble del cuerpo humano. Establece la forma en que percibimos las imágenes, el sonido, los olores, los sabores y el tacto. Nos permite almacenar recuerdos, experimentar emociones e incluso soñar. Sin el, seríamos organismos primitivos, incapaces de otra cosa que el más simple de los reflejos. El cerebro es, en definitiva, lo que nos hace inteligentes. Durante décadas hemos soñado con construir máquinas inteligentes con cerebros como los nuestros; asistentes robotizados para limpiar nuestras casas, coches que se conducen por sí mismos, microscopios que detecten enfermedades automáticamente. Pero construir estas máquinas artificialmente inteligentes nos obliga a resolver algunos de los problemas computacionales más complejos que hemos tenido; problemas que nuestros cerebros ya pueden resolver en una fracción de segundos. La forma de atacar y resolver estos problemas, es el campo de estudio de la Inteligencia Artificial . ¿Qué es la Inteligencia Artificial? Definir el concepto de Inteligencia Artificial no es nada fácil. Una definición sumamente general sería que la IA es el estudio de la infromática centrándose en el desarrollo de software o máquinas que exhiben una inteligencia humana . Objetivos de la Inteligencia Artificial Los objetivos principales de la IA incluyen la deducción y el razonamiento, la representación del conocimiento, la planificación, el procesamiento del lenguaje natural ( NLP ), el aprendizaje, la percepción y la capacidad de manipular y mover objetos. Los objetivos a largo plazo incluyen el logro de la Creatividad, la Inteligencia Social y la Inteligencia General (a nivel Humano). Cuatro enfoques distintos Podemos distinguir cuatro enfoques distintos de abordar el problema de la Inteligencia Artificial . Sistemas que se comportan como humanos : Aquí la idea es desarrollar máquinas capaces de realizar funciones para las cuales se requeriría un humano inteligente. Dentro de este enfoque podemos encontrar la famosa Prueba de Turing . Para poder superar esta prueba, la máquina debería poseer las siguientes capacidades: Procesamiento de lenguaje natural , que le permita comunicarse satisfactoriamente. Representación del conocimiento , para almacenar lo que se conoce o se siente. Razonamiento automático , para utilizar la información almacenada para responder a preguntas y extraer nuevas conclusiones. Aprendizaje automático , para adaptarse a nuevas circunstancias y para detectar y extrapolar patrones. Visión computacional , para percibir objetos. Robótica , para manipular y mover objetos. Sistemas que piensan como humanos : Aquí la idea es hacer que las máquinas piensen como humanos en el sentido más literal; es decir, que tengan capacidades cognitivas de toma de decisiones, resolución de problemas, aprendizaje, etc. Dentro de este enfoque podemos encontrar al campo interdisciplinario de la ciencia cognitiva , en el cual convergen modelos computacionales de IA y técnicas experimentales de psicología intentando elaborar teorías precisas y verificables sobre el funcionamiento de la mente humana. Sistemas que piensan racionalmente : Aquí la idea es descubrir los cálculos que hacen posible percibir, razonar y actuar; es decir, encontrar las leyes que rigen el pensamiento racional. Dentro de este enfoque podemos encontrar a la Lógica , que intenta expresar las leyes que gobiernan la manera de operar de la mente. Sistemas que se comportan racionalmente : Aquí la idea es diseñar agentes inteligentes. Dentro de este enfoque un agente racional es aquel que actúa con la intención de alcanzar el mejor resultado o, cuando hay incertidumbre, el mejor resultado esperado. Un elemento importante a tener en cuenta es que tarde o temprano uno se dará cuenta de que obtener una racionalidad perfecta (hacer siempre lo correcto) no es del todo posible en entornos complejos. La demanda computacional que esto implica es demasiado grande, por lo que debemos conformarnos con una racionalidad limitada. Como lo que se busca en este enfoque es realizar inferencias correctas, se necesitan las mismas habilidades que para la Prueba de Turing , es decir, es necesario contar con la capacidad para representar el conocimiento y razonar basándonos en él , porque ello permitirá alcanzar decisiones correctas en una amplia gama de situaciones. Es necesario ser capaz de generar sentencias comprensibles en lenguaje natural , ya que el enunciado de tales oraciones permite a los agentes desenvolverse en una sociedad compleja. El aprendizaje no se lleva a cabo por erudición exclusivamente, sino que profundizar en el conocimiento de cómo funciona el mundo facilita la concepción de estrategias mejores para manejarse en él. Fundamentos de la Inteligencia artificial Existen varias disciplinas que han contribuido con ideas, puntos de vista y técnicas al desarrollo del campo de la Inteligencia Artificial . Ellas son: Filosofía Muchas han sido las contribuciones de la Filosofía a las ciencias. En el campo de la Inteligencia Artificial a contribuido con varios aportes entre los que se destacan los conceptos de IA débil y IA fuerte . La IA débil se define como la inteligencia artificial racional que se centra típicamente en una tarea estrecha. La inteligencia de la IA débil es limitada, no hay autoconciencia o inteligencia genuina. Siri es un buen ejemplo de una IA débil que combina varias técnicas de IA débil para funcionar. Siri puede hacer un montón de cosas por nosotros, pero a medida que intentamos tener conversaciones con el asistente virtual, nos damos cuenta de cuan limitada es. La IA fuerte es aquella inteligencia artificial que iguala o excede la inteligencia humana promedio. Este tipo de AI será capaz de realizar todas las tareas que un ser humano podría hacer. Hay mucha investigación en este campo, pero todavía no han habido grandes avances. Muchos son los debates filosóficos alrededor de la inteligencia artificial , para aquellos interesados en los aspectos filosóficos les recomiendo inscribirse en nuestro grupo de debate de IAAR Matemáticas Si de ciencias aplicadas se trata, no puede faltar el aporte de las Matemáticas . Para entender y desarrollar los principales algoritmos que se utilizan en el campo de la Inteligencia Artificial , deberíamos tener nociones de: Álgebra lineal El álgebra lineal es una rama de las matemáticas que estudia conceptos tales como vectores, matrices, tensores, sistemas de ecuaciones lineales y en su enfoque de manera más formal, espacios vectoriales y sus transformaciones lineales. Una buena comprensión del álgebra lineal es esencial para entender y trabajar con muchos algoritmos de Machine Learning , y especialmente para los algoritmos de Deep Learning . Cálculo El Cálculo es el campo de la matemática que incluye el estudio de los límites, derivadas, integrales y series infinitas, y más concretamente se puede decir que es el estudio del cambio . Particularmente para el campo de la Inteligencia Artificial algunos conceptos que se deberían conocer incluyen: Cálculo Diferencial e Integral , Derivadas Parciales , Funciones de Valores Vectoriales, y Gradientes . Optimización matemática La Optimización matemática es la herramienta matemática que nos permite optimizar decisiones, es decir, seleccionar la mejor alternativa de un conjunto de criterios disponibles. Su comprensión es fundamental para poder entender la eficiencia computacional y la escalabilidad de los principales algoritmos de Machine Learning y Deep Learning , los cuales suelen trabajar con matrices dispersas de gran tamaño. Probabilidad y estadística La Probabilidad y estadística es la rama de la matemática que trata con la incertidumbre , la aleatoriedad y la inferencia . Sus conceptos son fundamentales para cualquier algoritmo de Machine Learning o Deep Learning . Una buena introducción a cada uno de estos campos de las matemáticas que son fundamentales para la Inteligencia Artificial , la pueden encontrar en este mismo blog . Lingüística La Lingüística moderna y la Inteligencia Artificial nacieron al mismo tiempo y maduraron juntas, solapándose en un campo híbrido llamado lingüística computacional o procesamiento de lenguaje natural . El entendimiento del lenguaje requiere la comprensión de la materia bajo estudio y de su contexto, y no solamente el entendimiento de la estructura de las sentencias; lo que lo convierte en un problema bastante complejo de abordar. Neurociencias La Neurociencia es el estudio del sistema neurológico, y en especial del cerebro. La forma exacta en la que en un cerebro se genera el pensamiento es uno de los grandes misterios de la ciencia. El hecho de que una colección de simples células puede llegar a generar razonamiento, acción, y conciencia es un enigma a resolver. Cerebros y computadores realizan tareas bastante diferentes y tienen propiedades muy distintas. Según los cálculos de los expertos se estima que para el 2020 las computadoras igualaran la capacidad de procesamiento de los cerebros. Muchos modelos de IA fueron inspirados en la estructura y el funcionamiento de nuestro cerebro. Psicología La Psicología trata sobre el estudio y análisis de la conducta y los procesos mentales de los individuos y grupos humanos. La rama que más influencia ha tenido para la Inteligencia Artificial es la de la psicología cognitiva que se encarga del estudio de la cognición; es decir, de los procesos mentales implicados en el conocimiento. Tiene como objeto de estudio los mecanismos básicos y profundos por los que se elabora el conocimiento, desde la percepción, la memoria y el aprendizaje, hasta la formación de conceptos y razonamiento lógico. Las teoría descritas por esta rama han sido utilizados para desarrollar varios modelos de Inteligencia Artificial y Machine Learning Ramas de la Inteligencia artificial Dentro de la Inteligencia Artificial podemos encontrar distintas ramas, entre las que se destacan: Machine Learning El Machine Learning es el diseño y estudio de las herramientas informáticas que utilizan la experiencia pasada para tomar decisiones futuras; es el estudio de programas que pueden aprenden de los datos. El objetivo fundamental del Machine Learning es generalizar, o inducir una regla desconocida a partir de ejemplos donde esa regla es aplicada . El ejemplo más típico donde podemos ver el uso del Machine Learning es en el filtrado de los correo basura o spam. Mediante la observación de miles de correos electrónicos que han sido marcados previamente como basura, los filtros de spam aprenden a clasificar los mensajes nuevos. El Machine Learning tiene una amplia gama de aplicaciones, incluyendo motores de búsqueda, diagnósticos médicos, detección de fraude en el uso de tarjetas de crédito, análisis del mercado de valores, clasificación de secuencias de ADN, reconocimiento del habla y del lenguaje escrito, juegos y robótica. Pero para poder abordar cada uno de estos temas es crucial en primer lugar distingir los distintos tipos de problemas de Machine Learning con los que nos podemos encontrar. Aprendizaje supervisado En los problemas de aprendizaje supervisado se enseña o entrena al algoritmo a partir de datos que ya vienen etiquetados con la respuesta correcta. Cuanto mayor es el conjunto de datos, el algoritmo podrá generalizar en una forma más precisa. Una vez concluido el entrenamiento, se le brindan nuevos datos, ya sin las etiquetas de las respuestas correctas, y el algoritmo de aprendizaje utiliza la experiencia pasada que adquirió durante la etapa de entrenamiento para predecir un resultado. Aprendizaje no supervisado En los problemas de aprendizaje no supervisado , el algoritmo es entrenado usando un conjunto de datos que no tiene ninguna etiqueta; en este caso, nunca se le dice al algoritmo lo que representan los datos. La idea es que el algoritmo pueda encontrar por si solo patrones que ayuden a entender el conjunto de datos. Aprendizaje por refuerzo En los problemas de aprendizaje por refuerzo , el algoritmo aprende observando el mundo que le rodea. Su información de entrada es el feedback o retroalimentación que obtiene del mundo exterior como respuesta a sus acciones. Por lo tanto, el sistema aprende a base de ensayo-error. Un buen ejemplo de este tipo de aprendizaje lo podemos encontrar en los juegos, donde vamos probando nuevas estrategias y vamos seleccionando y perfeccionando aquellas que nos ayudan a ganar el juego. A medida que vamos adquiriendo más practica, el efecto acumulativo del refuerzo a nuestras acciones victoriosas terminará creando una estrategia ganadora. Deep Learning El Deep Learning constituye un conjunto particular de algoritmos de Machine Learning que utilizan estructuras profundas de redes neuronales para encontrar patrones en los datos. Estos tipos de algoritmos cuentan actualmente con un gran interés, ya que han demostrado ser sumamente exitosos para resolver determinados tipos de problemas; como por ejemplo, el reconocimiento de imágenes. Muchos consideran que este tipo de modelos son los que en el futuro nos llevaran a resolver definitivamente el problema de la Inteligencia Artificial . Razonamiento probabilístico El razonamiento probabilístico se encarga de lidiar con la incertidumbre inherente de todo proceso de aprendizaje. El problema para crear una Inteligencia Artificial entonces se convierte en encontrar la forma de trabajar con información ruidosa, incompleta e incluso muchas veces contradictoria. Estos algoritmos están sumamente ligados a la estadística bayesiana ; y la principal herramienta en la que se apoyan es en el teorema de Bayes . Algortimos genéticos Los algoritmos genéticos se basan en la idea de que la madre de todo aprendizaje es la selección natural . Si la Naturaleza pudo crearnos, puede crear cualquier cosa; por tal motivo lo único que deberíamos hacer para alcanzar una Inteligencia Artificial es simular sus mecanismos en una computadora. La idea de estos algoritmos es imitar a la Evolución; funcionan seleccionando individuos de una población de soluciones candidatas, y luego intentando producir nuevas generaciones de soluciones mejores que las anteriores una y otra vez hasta aproximarse a una solución perfecta. Aplicaciones de la Inteligencia artificial Las técnicas de la Inteligencia Artificial pueden ser aplicadas en una gran variedad de industrias y situaciones, como ser: Medicina Apoyándose en las herramientas que proporciona la Inteligencia Artificial , los doctores podrían realizar diagnósticos más certeros y oportunos, lo que llevaría a mejores tratamientos y más vidas salvadas. Autos autónomos Utilizando Inteligencia Artificial podríamos crear autos autónomos que aprendan de los datos y experiencias de millones de otros autos, mejorando el tráfico y haciendo mucho más segura la conducción. Bancos Utilizando técnicas de Machine Learning los bancos pueden detectar fraudes antes de que ocurran por medio de analizar los patrones de comportamiento de gastos e identificando rápidamente actividades sospechosas. Agricultura En Agricultura se podría optimizar el rendimiento de los cultivos por medio de la utilización de las técnicas de Inteligencia Artificial para analizar los datos del suelo y del clima en tiempo real, logrando producir más alimentos incluso con climas perjudiciales. Educación En la Educación se podrían utilizar las técnicas de la Inteligencia Artificial para diseñar programas de estudios personalizados basados en datos que mejoren el rendimiento y el ritmo de aprendizaje de los alumnos. La ética y los riesgos de desarrollar una Inteligencia Artificial Actualmente también ha surgido un debate ético alrededor de la Inteligencia Artificial . Algunos de los pensadores más importantes del planeta han establecido su preocupación sobre el progreso de la IA . Entre los problemas que puede traer aparejado el desarrollo de la Inteligencia Artificial , podemos encontrar los siguientes: Las personas podrían perder sus trabajos por la automatización. Las personas podrían tener demasiado (o muy poco) tiempo de ocio. Las personas podrían perder el sentido de ser únicos. Las personas podrían perder algunos de sus derechos privados. La utilización de los sistemas de IA podría llevar a la pérdida de responsabilidad. El éxito de la IA podría significar el fin de la raza humana. El debate sobre los beneficios y riesgos del desarrollo de la Inteligencia Artificial está todavía abierto. ¿Cómo iniciarse en el campo de la Inteligencia artificial? Si luego de leer esta introducción, te has quedado fascinado por el campo de la Inteligencia Artificial y quieres incursionar en el mismo, aquí te dejo algunas recomendaciones para iniciarse. IAAR IAAR es la comunidad argentina de inteligencia artificial. Agrupa a ingenieros, desarrolladores, emprendedores, investigadores, entidades gubernamentales y empresas en pos del desarrollo ético y humanitario de las tecnologías cognitivas. Para comenzar a formar parte de la comunidad pueden inscribirse en los grupos de facebook: IAAR , Debates , Proyectos , Capacitación ; y/o en el meetup . Programación Para poder trabajar en problemas relacionados al campo de la Inteligencia Artificial es necesario saber programar. Los principales lenguajes que se utilizan son Python y R . En los repositorios de Academia de IAAR van a poder encontrar material sobre estos lenguajes. Frameworks Existen varios frameworks open source que nos facilitan el trabajar con modelos de Deep Learning , entre los que se destacan: TensorFlow : TensorFlow es un frameworks desarrollado por Google. Es una librería de código libre para computación numérica usando grafos de flujo de datos que utiliza el lenguaje Python . PyTorch : PyTorch es un framework de Deep Learning que utiliza el lenguaje Python y cuenta con el apoyo de Facebook. Caffe : Caffe es un framework de Deep Learning hecho con expresión, velocidad y modularidad en mente, el cual es desarrollado por la universidad de Berkeley. CNTK : CNTK es un conjunto de herramientas, desarrolladas por Microsoft, fáciles de usar, de código abierto que entrena algoritmos de Deep Learning para aprender como el cerebro humano. Theano : Theano es una librería de Python que permite definir, optimizar y evaluar expresiones matemáticas que involucran tensores de manera eficiente. DeepLearning4j : DeepLearning4j Es una librería open source para trabajar con modelos de Deep Learning distribuidos utilizando el lenguaje Java . Bots Una de las ramas con mayor crecimiento y que más se ha beneficiado con el boom de la Inteligencia Artificial es la de los Bots . Generar pequeños Bots que puedan tener conversaciones básicas con los usuarios es bastante simple. Pueden encontrar una guía con una gran número de herramientas en el blog de capacitación de IAAR . Aquí concluye esta introducción por el fascinante campo de la Inteligencia Artificial . Recuerden anotarse en el grupo de facebook de IAAR y al meetup . Saludos! Este post fue escrito por Raúl e. López Briega utilizando Jupyter notebook . Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"IA","url":"https://relopezbriega.github.io/blog/2017/06/05/introduccion-a-la-inteligencia-artificial/"},{"title":"Introducción a la inferencia Bayesiana con Python","text":"\"Las preguntas más importantes de la vida son, en su mayor parte, nada más que problemas de probabilidad.\" Pierre-Simon Lapace Introducción: La incertidumbre y el problema de la inducción ¿Cómo se deben evaluar las hipótesis?¿Cuál es el papel de la evidencia en este proceso?¿Cuáles son los experimentos que debemos realizar para obtener la mayor información posible?. Éstas son preguntas que están en los cimientos de toda búsqueda científica. Las hipótesis científicas tienen un carácter general en relación con las observaciones empíricas que se supone deben explicar, teniendo implicaciones sobre fenómenos y acontecimientos que no podemos encontrar en ninguna evidencia real. Existe, por lo tanto, un vacío lógico entre la información derivada de la observación empírica y el contenido de nuestras teorías científicas. ¿Cómo, entonces, esta información nos da una confianza razonable en esas teorías? Éste, es el tradicional problema de la inducción . David Hume , demostró correctamente, que partiendo solamente de las evidencias del pasado y del presente, no podemos inferir con seguridad nada sobre el futuro. Que hayamos visto solamente miles de cisnes blancos, no quiere decir que todos los cisnes sean blancos; siempre existirá la posibilidad de que podamos encontrar un cisne negro que falsifique toda nuestra teoría. Este problema, hace que en esencia todas las teorías tengan un carácter probabilístico . Desde hace ya tiempo se considera que las teorías científicas se extienden más allá de cualquier dato experimental y por lo tanto no pueden ser verificadas (es decir, logicamente implicadas) por ellos. Si bien existe un consenso en que la certeza absoluta no puede ser nunca alcanzable; la mayoría de los científicos coinciden en que las teorías pueden alcanzar un estado intermedio entre la certeza absoluta y la falsificación; el cual dependerá de la calidad de las observaciones y de cómo la teoría se ve afectada por nuevas evidencias. Desde esta perspectiva, entonces la pregunta crucial pasa a ser ¿cómo adaptamos nuestras creencias a medida que vamos incorporando nuevas evidencias sobre nuestras teorías? la respuesta a esta pregunta, la podemos encontrar en el teorema de Bayes . El Teorema de Bayes Thomas Bayes fue un ministro presbiteriano y matemático inglés que estudió la relación íntima que existe entre la probabilidad , la predicción y el progreso científico. Su trabajo se centró principalmente en cómo formulamos nuestras creencias probabilísticas sobre el mundo que nos rodea cuando nos encontramos con nuevos datos o evidencias. El argumento de Bayes no es que el mundo es intrínsecamente probabilístico o incierto, ya que él era un creyente en la divina perfección; sino que aprendemos sobre el mundo a través de la aproximación, acercándonos cada vez más a la verdad a medida que recogemos más evidencias. Este argumento lo expresó matemáticamente a través de su famoso teorema : $$P(H|D) = \\frac{P(D|H)P(H)}{P(D)} $$ En donde: $P(H)$ es el a priori , la forma de introducir conocimiento previo sobre los valores que puede tomar la hipótesis. A veces cuando no sabemos demasiado se suelen usar a prioris que asignan igual probabilidad a todos los valores de la hipótesis; otras veces se puede elegir a prioris que restrinjan los valores a rangos razonables, por ejemplo solo valores positivos; y otras veces contamos con información mucho más precisa, como experimentos previos o límites impuesto por alguna teoría. $P(D|H)$ es el likelihood , la forma de incluir nuestros datos en el análisis. Es una expresión matemática que especifica la plausibilidad de los datos. A medida que la cantidad de datos aumenta, el likelihood tiene cada vez más peso en los resultados. Debemos tener en cuenta que si bien el likelihood se asemeja a una probabilidad , en realidad no lo es; el likelihood de una hipótesis $H$, dados los datos $D$ va a ser proporcional a la probabilidad de obtener $D$ dado que $H$ es verdadera. Como el likelihood no es una probabilidad tampoco tiene que respetar las leyes de las probabilidades y por lo tanto no necesariamente tiene que sumar 1. $P(H|D)$ es el a posteriori , la distribución de probabilidad final para la hipótesis. Es la consecuencia lógica de haber usado un conjunto de datos, un likelihood y un a priori . Se lo suele pensar como la versión actualizada del a priori luego de que hemos agregado los datos adicionales. $P(D)$ es el likelihood marginal o evidencia , la probabilidad de observar los datos $D$ promediado sobre todas las posibles hipótesis $H$. En general, la evidencia puede ser vista como una simple constante de normalización que en la mayoría de los problemas prácticos puede omitirse sin demasiada perdida de generalidad. Si los fundamentos filosóficos del teorema de Bayes son sorprendentemente ricos, sus matemáticas son increíblemente simples. En su forma más básica, no es más que una expresión algebraica con tres variables conocidas y una incógnita; y que trabaja con probabilidades condicionales ; nos dice la probabilidad de que una hipótesis $H$ sea verdadera si algún evento $D$ ha sucedido. El teorema de Bayes es útil porque lo que normalmente sabemos es la probabilidad de los efectos dados las causas, pero lo que queremos saber es la probabilidad de las causas dadas los efectos. Por ejemplo, podemos saber cual es el porcentaje de pacientes con gripe que tiene fiebre, pero lo que realmente queremos saber es la probabilidad de que un paciente con fiebre tenga gripe. El teorema de Bayes nos permite ir de uno a otro con suma facilidad. La inferencia Bayesiana Toda forma de inferencia que realicemos sobre el mundo que nos rodea, debe indefectiblemente lidiar con la incertidumbre . Existen por lo menos, tres tipos de incertidumbre con la que nos debemos enfrentar: Ignorancia , los límites de nuestro conocimiento nos llevan a ser ignorantes sobre muchas cosas. Aleatoriedad , es imposible negar la influencia del azar en casi todo lo que nos rodea; incluso aunque podamos saber todo sobre una moneda y la forma de lanzarla, es imposible predecir con anterioridad si va a caer cara o seca. Vaguedad , muchos de los conceptos que utilizamos en nuestro pensamiento tienen cierto grado de subjetividad en su definición. ¿cómo calificaríamos si una persona es valiente o no?. Cada uno de nosotros puede tener una apreciación diferente del concepto de valentía. La inferencia bayesiana es la filosofía que afirma que para entender la opinión humana como debe ser, limitada por la ignorancia y la incertidumbre; debemos utilizar al cálculo de probabilidad como la herramienta más importante para representar la fortaleza de nuestras creencias. En esencia, la inferencia bayesiana combina nuestra experiencia previa, en la forma de la probabilidad a priori ; con los datos observados, en la forma del likelihood ; para interpretarlos y arribar a una probabilidad a posteriori . La inferencia bayesiana no nos va a garantizar que podamos alcanzar la respuesta correcta. En su lugar, nos va a proporcionar la probabilidad de que cada una de un número de respuestas alternativas, sea verdadera. Y luego podemos utilizar esta información para encontrar la respuesta que más probablemente sea la correcta. En otras palabras, nos proporciona un mecanismo para hacer una especie de adivinación basada en información . Bayes en el diagnostico médico Para que quede más claro, ilustremos la aplicación de la inferencia bayesiana con un simple ejemplo del diagnostico médico, uno de los campos dónde más éxito ha tenido. Supongamos que nos hicimos un estudio y nos ha dado positivo para una rara enfermedad que solo el 0.3 % de la población tiene. La tasa de efectividad de este estudio es del 99 %, es decir, que solo da falsos positivos en el 1 % de los casos. ¿Cuán probable es que realmente tengamos la enfermedad?. En un principio, nos veríamos tentados a responder que hay un 99 % de probabilidad de que tengamos la enfermedad; pero en este caso nos estaríamos olvidando del concepto importante del a priori . Sabemos con anterioridad que la enfermedad es extremadamente rara (solo el 0.3 % la tiene); si incluimos esta información previa en nuestro cálculo de probabilidad y aplicamos el teorema de Bayes podemos llegar a una conclusión totalmente distinta. $$ P(\\text{ enfermedad | pos}) = \\frac{P(\\text{ pos | enfermedad})P( \\text{enfermedad})}{P(\\text{pos})}$$ In [1]: # Ejemplo simple teorema de Bayes aplicado a estimación de un sólo parámetro. a_priori = 0.003 likelihood = 0.99 evidencia = 0.01 a_posteriori = likelihood * a_priori / evidencia a_posteriori Out[1]: 0.297 Como vemos, luego de aplicar el teorema de Bayes llegamos a la conclusión de que en realidad nuestra probabilidad de estar realmente enfermo es de sólo 30 % y no de 99 %, ya que podemos ser uno de los falsos positivos del estudio y la enfermedad es realmente muy rara. Como este ejemplo demuestra, la inclusión del a priori es sumamente importante para la inferencia bayesiana , por lo cual también debemos ser sumamente cuidadosos a la hora de elegirla. Cuando nuestra a priori es fuerte, puede ser sorprendentemente resistente frente a nuevas evidencias. Redes Bayesianas El teorema de Bayes nos permite actualizar las probabilidades de variables cuyo estado no hemos observado dada una serie de nuevas observaciones. Las redes bayesianas automatizan este proceso, permitiendo que el razonamiento avance en cualquier dirección a través de la red de variables. Las redes bayesianas están constituidas por una estructura en forma de grafo , en la que cada nodo representa variables aleatorias (discretas o continuas) y cada arista representa las conexiones directas entre ellas. Estas conexiones suelen representar relaciones de causalidad. Adicionalmente, las redes bayesianas también modelan el peso cuantitativo de las conexiones entre las variables, permitiendo que las creencias probabilísticas sobre ellas se actualicen automáticamente a medida que se disponga de nueva información. Al construir una red bayesiana , los principales problemas de modelización que surgen son: ¿Cuáles son las variables? ¿Cuáles son sus valores / estados? ¿Cuál es la estructura del grafo ? ¿Cuáles son los parámetros (probabilidades)? Profundicemos un poco en cada uno de estos puntos. Nodos y variables Lo primero que debemos hacer es identificar las variables de interés. Sus valores deben ser mutuamente excluyentes y exhaustivos. Los tipos de nodos discretos más comunes son: Nodos booleanos , que representan proposiciones tomando los valores binarios Verdadero (V) y Falso (F). En el dominio del diagnóstico médico, por ejemplo, un nodo llamado \"Cáncer\" podría representar la proposición del que paciente tenga cáncer. Valores ordenados Por ejemplo, un nodo \"Contaminación\" podría representar la exposición de un paciente a la contaminación del ambiente y tomar los valores {alta, baja}. Valores enteros . Por ejemplo, un nodo llamado \"Edad\" puede representar la edad de un paciente y tener valores posibles de 1 a 120. Lo importante es elegir valores que representen el dominio de manera eficiente, pero con suficiente detalle para realizar el razonamiento requerido. Estructura La estructura o topología de la red debe captar las relaciones cualitativas entre las variables. En particular, dos nodos deben conectarse directamente si uno afecta o causa al otro, con la arista indicando la dirección del efecto. Por lo tanto, en nuestro ejemplo de diagnóstico médico, podríamos preguntarnos qué factores afectan la probabilidad de tener cáncer. Si la respuesta es \"Contaminación y Fumar\", entonces deberíamos agregar aristas desde \"Contaminación\" y desde \"Fumador\" hacia el nodo \"Cáncer\". Del mismo modo, tener cáncer afectará la respiración del paciente y las posibilidades de tener un resultado positivo de rayos X. Por lo tanto, también podemos agregar aristas de \"Cáncer\" a \"Disnea\" y \"RayosX\". Es deseable construir redes bayesianas lo más compactas posibles por tres razones. Primero, mientras más compacto es el modelo , es más fácil de manejar. Segundo, cuando las redes se vuelven demasiado densas, fallan en representar la independencia en forma explícita. Y Tercero, las redes excesivamente densas no suelen representar las dependencias causales del dominio. Probabilidades condicionales Una vez que tenemos definida la estructura de la red bayesiana , el siguiente paso es cuantificar las relaciones entre los nodos interconectados; esto se hace especificando una probabilidad condicional para cada nodo. Primero, para cada nodo necesitamos mirar todas las posibles combinaciones de valores de los nodos padres. Por ejemplo, continuando con el ejemplo del diagnostico del cáncer, si tomamos el nodo \"Cáncer\" con sus dos nodos padres \"Contaminación\" y \"Fumador\" podemos calcular los posibles valores conjuntos { (A, V), (A, F), (B, V), (B, F)}. La tabla de probabilidad condicional especifica para cada uno de estos casos podría ser la siguiente: {0,05, 0,02, 0,03, 0,001}. Con estos datos, ya estamos en condiciones de representar el grafo de la red bayesiana de nuestro ejemplo. Razonando con redes Bayesianas La tarea básica de cualquier sistema de inferencia probabilística es la de obtener la distribución a posteriori para cada conjunto de nodos . Esta tarea se llama actualización de creencia o inferencia probabilística . En el caso de las redes bayesianas , el proceso de inferencia es muy flexible, nueva evidencia puede ser introducida en cualquiera de los nodos mientras que las creencias son actualizadas en cualquiera de los otros nodos . En la práctica, la velocidad del proceso de inferencia va a depender de la estructura y complejidad de la red. Programación probabilística y PyMC3 A pesar de que las redes bayesianas y demás modelos de inferencia bayesiana son conceptualmente simples; a menudo los cálculos de sus probabilidades conducen a expresiones que no se pueden resolver en forma analítica. Durante muchos años, este fue un gran problema y fue probablemente una de las principales razones que obstaculizaron la adopción de los métodos bayesianos. La llegada de las computadoras y el desarrollo de métodos numéricos que se pueden aplicar para calcular la distribución a posteriori de casi cualquier modelo, junto con el avance en las técnicas de muestreo de los métodos de Monte-Carlo ; han transformado completamente la práctica del análisis de datos Bayesiano. La posibilidad de automatizar la inferencia probabilística ha conducido al desarrollo de la Programación probabilística , la cuál utiliza las ventajas de los lenguajes de programación modernos y nos permite realizar una clara separación entre la creación del modelo y el proceso de inferencia . En Programación probabilística , especificamos un modelo probabilístico completo escribiendo unos cuantos líneas de código y luego la inferencia se realiza en forma automática. PyMC3 PyMC3 es un paquete para Programación probabilística que utiliza el lenguaje de programación Python . PyMC3 es lo suficientemente maduro para resolver muchos de los principales problemas estadísticos. Permite crear modelos probabilísticos usando una sintaxis intuitiva y fácil de leer que es muy similar a la sintaxis usada para describir modelos probabilísticos. Veamos algunos ejemplos: El problema de la moneda Los problemas de monedas son clásicos cuando hablamos de probabilidad y estadística , nos permiten ejemplificar conceptos abstractos de forma simple. Asimismo, pueden ser muchas veces conceptualmente similares a situaciones reales , de hecho cualquier problema en donde obtengamos resultados binarios, 0/1, enfermo/sano, spam/no-spam, puede ser pensado como si estuviéramos hablando de monedas. En este caso, la idea es utilizar un modelo bayesiano para inferir si la moneda se encuentra sesgada o no. Para este ejemplo, vamos a utilizar una distribución binomial como likelihood y una distribución beta como a priori . Veamos como lo podemos modelar con PyMC3 . In [2]: Ver Código # importando modulos necesarios import matplotlib.pyplot as plt import numpy as np import pandas as pd import scipy.stats as stats import seaborn as sns import pymc3 as pm import theano.tensor as tt from sklearn import datasets from sklearn.naive_bayes import GaussianNB from sklearn.metrics import confusion_matrix from sklearn.model_selection import train_test_split np . random . seed ( 1984 ) #replicar random % matplotlib inline In [3]: # El problema de la moneda # de 100 lanzamientos 80 caras n = 100 caras = 80 In [4]: # Creación del modelo niter = 2000 with pm . Model () as modelo_moneda : # a priori p = pm . Beta ( 'p' , alpha = 2 , beta = 2 ) # likelihood y = pm . Binomial ( 'y' , n = n , p = p , observed = caras ) In [5]: # Realizando el muestreo para la inferencia with modelo_moneda : trace = pm . sample ( niter , njobs = 4 ) Auto-assigning NUTS sampler... Initializing NUTS using advi... Average ELBO = -4.656: 100%|██████████| 200000/200000 [00:19<00:00, 10433.82it/s] Finished [100%]: Average ELBO = -4.639 WARNING (theano.tensor.blas): We did not found a dynamic library into the library_dir of the library we use for blas. If you use ATLAS, make sure to compile it with dynamics library. 100%|██████████| 2000/2000 [00:08<00:00, 226.40it/s] In [6]: # Analizando los resultados pm . traceplot ( trace , varnames = [ 'p' ], lines = { 'p' : . 8 }) pass In [7]: # Información resumen. #Vemos que hay un 95% de probabilidades de que el valor de sesgo este entre # .706 y .864 pm . summary ( trace ) p: Mean SD MC Error 95% HPD interval ------------------------------------------------------------------- 0.789 0.040 0.001 [0.706, 0.864] Posterior quantiles: 2.5 25 50 75 97.5 |--------------|==============|==============|--------------| 0.701 0.764 0.791 0.816 0.861 Como vemos el modelo nos indica que la moneda parece tener un claro sesgo hacia cara. El problema de la hierba mojada Supongamos que hay dos eventos los cuales pueden causar que la hierba esté húmeda: que el rociador esté activado o que esté lloviendo. También supongamos que la lluvia tiene un efecto directo sobre el uso del rociador (usualmente cuando llueve el rociador se encuentra apagado). Entonces la situación puede ser modelada con la siguiente red bayesiana . In [8]: # Problema de la hierba mojada # https://es.wikipedia.org/wiki/Red_bayesiana#Ejemplo niter = 10000 # 10000 tune = 5000 # 5000 modelo = pm . Model () with modelo : tv = [ 1 ] lluvia = pm . Bernoulli ( 'lluvia' , 0.2 , shape = 1 , testval = tv ) rociador_p = pm . Deterministic ( 'rociador_p' , pm . math . switch ( lluvia , 0.01 , 0.40 )) rociador = pm . Bernoulli ( 'rociador' , rociador_p , shape = 1 , testval = tv ) hierba_mojada_p = pm . Deterministic ( 'hierba_mojada_p' , pm . math . switch ( lluvia , pm . math . switch ( rociador , 0.99 , 0.80 ), pm . math . switch ( rociador , 0.90 , 0.0 ))) hierba_mojada = pm . Bernoulli ( 'hierba_mojada' , hierba_mojada_p , observed = np . array ([ 1 ]), shape = 1 ) trace = pm . sample ( 20000 , step = [ pm . BinaryGibbsMetropolis ([ lluvia , rociador ])], tune = tune , random_seed = 124 ) # pm.traceplot(trace) dictionary = { 'lluvia' : [ 1 if ii [ 0 ] else 0 for ii in trace [ 'lluvia' ] . tolist () ], 'rociador' : [ 1 if ii [ 0 ] else 0 for ii in trace [ 'rociador' ] . tolist () ], 'rociador_p' : [ ii [ 0 ] for ii in trace [ 'rociador_p' ] . tolist ()], 'hierba_mojada_p' : [ ii [ 0 ] for ii in trace [ 'hierba_mojada_p' ] . tolist ()], } df = pd . DataFrame ( dictionary ) p_lluvia = df [( df [ 'lluvia' ] == 1 )] . shape [ 0 ] / df . shape [ 0 ] print ( \" \\n Probabilidad de que la hierba este mojada por la lluvia: {0} \" . format ( p_lluvia )) p_rociador = df [( df [ 'rociador' ] == 1 )] . shape [ 0 ] / df . shape [ 0 ] print ( \"Probabilidad de que la hierba este mojada por el rociador: {0} \" . format ( p_rociador )) [-----------------100%-----------------] 20000 of 20000 complete in 8.8 sec Probabilidad de que la hierba este mojada por la lluvia: 0.38355 Probabilidad de que la hierba este mojada por el rociador: 0.62105 De acuerdo a los resultados de la red bayesiana , si vemos que la hierba esta mojada, la probabilidad de que este lloviendo es alrededor del 38%. Clasificador Bayes ingenuo Uno de los clasificadores más utilizados en Machine Learning por su simplicidad y rapidez, es el Clasificador Bayes ingenuo . El cual es una técnica de clasificación supervisada basada en el teorema de Bayes que asume que existe una independencia entre los atributos . En términos simples, un Clasificador Bayes ingenuo asume que la presencia de una característica particular en una clase no está relacionada con la presencia de cualquier otra característica. Por ejemplo, una fruta puede considerarse como una manzana si es roja, redonda y de aproximadamente 9 cm de diámetro. Incluso si estas características dependen unas de otras o de la existencia de otras características, todas estas propiedades contribuyen independientemente a la probabilidad de que esta fruta sea una manzana. Se lo llama ingenuo ya que asumir independencia absoluta entre todos los atributos, no es algo que se suela dar en la realidad. El modelo Bayes ingenuo es fácil de construir y particularmente útil para conjuntos de datos muy grandes. A pesar de su simplicidad y de su irealista postulado de independencia , este clasificador se ha mostrado muy efectivo y se suele utilizar como el estándar para evaluar el rendimiento de otros modelos de Machine Learning . El Clasificador Bayes ingenuo se utiliza en múltiples escenarios de la vida real, tales como: Clasificación de texto: Es uno de los algoritmos conocidos más exitosos cuando se trata de la clasificación de documentos de texto, es decir, si un documento de texto pertenece a una o más categorías (clases). Detección de spam: Es un ejemplo de clasificación de texto. Se ha convertido en un mecanismo popular para distinguir el correo electrónico spam del correo electrónico legítimo. Análisis de sentimientos: Puede ser utilizado para analizar el tono de tweets, comentarios y revisiones, ya sean negativos, positivos o neutrales. Sistema de Recomendaciones: El algoritmo Bayes ingenuo en combinación con el filtrado colaborativo se utiliza para construir sistemas de recomendación híbridos que ayudan a predecir si un usuario desea un recurso determinado o no. Veamos un ejemplo con la ayuda de Scikit-Learn : In [9]: # Ejemplo Naive Bayes usuando iris dataset iris = datasets . load_iris () X = iris . data y = iris . target # Dividir los datos en entrenamiento y evaluación X_train , X_test , y_train , y_test = train_test_split ( X , y , test_size = 0.7 , random_state = 0 ) # inicializar el clasificador Naive Bayes bayes_ingenuo = GaussianNB () # predicción y_pred = bayes_ingenuo . fit ( X_train , y_train ) . predict ( X_test ) # Matriz de confusión cnf_matrix = confusion_matrix ( y_test , y_pred ) print ( \"Cantidad de errores de clasificación sobre un total de {0} casos: {1} \" . format ( y_test . shape [ 0 ],( y_test != y_pred ) . sum ())) print ( \"Efectividad del algoritmo: {0: .2f} \" . format ( 1 - ( y_test != y_pred ) . sum () / y_test . shape [ 0 ])) # Graficando la matriz de confusión sns . heatmap ( cnf_matrix . T , square = True , annot = True , fmt = 'd' , cbar = False ) plt . xlabel ( 'Clase verdadera' ) plt . ylabel ( 'Clase predecida' ) plt . title ( 'Matriz de Confusión' ) plt . show () Cantidad de errores de clasificación sobre un total de 105 casos: 7 Efectividad del algoritmo: 0.93 En este sencillo ejemplo, podemos ver como el Clasificador Bayes ingenuo ha clasificado correctamente la mayoría de los casos del dataset iris , obteniendo un efectividad del 93 %. Debido a que los clasificadores bayesianos ingenuos hacen suposiciones tan estrictas acerca de los datos, generalmente no funcionarán tan bien con modelos más complicados. Dicho esto, tienen varias ventajas: Son extremadamente rápidos tanto para entrenamiento como para predicción Proporcionan una predicción probabilística directa A menudo son muy fácilmente interpretables Tienen muy pocos parámetros que necesiten optimizarse. Estas ventajas significan que un clasificador bayesiano ingenuo es a menudo una buena opción como un modelo de clasificación inicial. Si obtenemos resultados satisfactorios, entonces tenemos un clasificador muy rápido, y muy fácil de interpretar. Si no funciona bien, entonces podemos comenzar a explorar modelos más sofisticados. Aquí concluye esta introducción a la inferencia bayesiana ; como vemos es una teoría sumamente fascinante con serias implicancias filosóficas. La teoría Bayesiana es mucho más que un simple teorema de probabilidad, es una lógica para razonar sobre el amplio espectro de la vida que se encuentra en las áreas grises entre la verdad absoluta y la incertidumbre total . A menudo tenemos información sobre sólo una pequeña parte de lo que nos preguntamos. Sin embargo, todos queremos predecir algo basado en nuestras experiencias pasadas; y adaptamos nuestras creencias a medida que adquirimos nueva información. La inferencia bayesiana nos proporciona una forma de pensar racionalmente sobre el mundo que nos rodea. Saludos! Este post fue escrito utilizando Jupyter notebook . Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Pobabilidad y Estadistica","url":"https://relopezbriega.github.io/blog/2017/05/21/introduccion-a-la-inferencia-bayesiana-con-python/"},{"title":"Problemas de Optimización con Python","text":"Introducción La optimización es fundamental para cualquier problema relacionado con la toma de decisiones, ya sea en ingeniería o en ciencias económicas . La tarea de tomar decisiones implica elegir entre varias alternativas. Esta opción va a estar gobernada por nuestro deseo de tomar la \"mejor\" decisión posible. Que tan buena va a ser cada una de las alternativas va a estar descripta por una función objetivo o índice de rendimiento. La teoría y los métodos de optimización nos van a ayudar a seleccionar la mejor alternativa de acuerdo a esta función objetivo dada. El área de optimización ha recibido gran atención en los últimos años, principalmente por el rápido desarrollo de las ciencias de computación , incluido el desarrollo y la disponibilidad de herramientas de software sumamente amigables, procesadores paralelos de alta velocidad, y redes neuronales artificiales . El poder de los métodos de optimización reside en la posibilidad de determinar la solución óptima sin realmente tener que probar todos los casos posibles. Para logar esto, se utiliza un nivel modesto de Matemáticas y se realizan cálculos numéricos iterativos utilizando procedimientos lógicos claramente definidos o algoritmos implementados en computadoras . ¿Qué es un problema de optimización? Un problema de optimización comienza con un conjunto de variables independientes o parámetros , y a menudo incluye condiciones o restricciones que definen los valores aceptables de estas variables. Tales restricciones se denominan las limitaciones del problema . El otro componente esencial de un problema de optimización es una medida única de \"bondad\", denominada función objetivo , la cual va a depender también de las variables independientes . La solución al problema de optimización va a estar dada por un conjunto de valores permitidos para las variables independientes , de acuerdo con las limitaciones del problema , en los que la función objetivo asume un valor óptimo . En términos matemáticos, la optimización implica normalmente maximizar o minimizar la función objetivo . Requisitos para la aplicación de los métodos de optimización Para aplicar los resultados matemáticos y técnicas numéricas de la teoría de optimización a problemas concretos, es necesario delinear claramente los límites del sistema a optimizar , definir los parámetros cuantitativos que se utilizaran como criterio en base al cual serán clasificadas las alternativas para determinar la \"mejor\" opción, seleccionar las variables que se utilizarán para caracterizar o identificar alternativas; y definir el modelo que exprese la forma en que las variables estarán relacionadas. Una buena formulación del problema es la clave para el éxito de un problema de optimización y es en alto grado un arte. Se aprende a través de la práctica y el estudio de aplicaciones exitosas y se basa en el conocimiento de las fortalezas, debilidades y particularidades de las técnicas proporcionadas por los distintos métodos de optimización . Clasificación de los problemas de optimización Un paso importante en cualquier problema de optimización es clasificar nuestro modelo, ya que los algoritmos para resolver problemas de optimización generalmente están diseñados para atacar un tipo de problema en particular. Algunas de las principales clasificaciones que vamos a poder encontrar son las siguientes: Optimización continua versus optimización discreta : Algunos modelos sólo tienen sentido si las variables toman valores de un conjunto discreto , a menudo un subconjunto de enteros, mientras que otros modelos contienen variables que pueden asumir cualquier valor real o continuo . Los modelos con variables discretas son problemas de optimización discretos ; mientras que los modelos con variables continuas son problemas de optimización continua . Los problemas de optimización continua tienden a ser más fáciles de resolver que los problemas de optimización discreta; sin embargo, las mejoras en los algoritmos junto con los avances en la tecnología informática han aumentado dramáticamente el tamaño y la complejidad de los problemas de optimización discreta que se pueden resolver eficientemente. Optimización sin restricciones versus optimización con restricciones : Otra distinción importante que podemos encontrar es entre los problemas en los que no hay restricciones sobre las variables y los problemas en los que hay restricciones sobre las variables . Los problemas de optimización sin restricciones surgen directamente en muchas aplicaciones prácticas; también surgen en la reformulación de problemas de optimización restringida en los que las restricciones son reemplazadas por un término de penalización en la función objetivo . Los problemas de optimización restringida o con restrincciones surgen de las aplicaciones en las que hay restricciones explícitas sobre las variables. Las restricciones sobre las variables pueden variar ampliamente de simples límites a sistemas de igualdades y desigualdades que modelan relaciones complejas entre las variables. Los problemas de optimización con restricciones se pueden clasificar asimismo según la naturaleza de las restricciones que poseen (por ejemplo, lineales , no lineales , convexos ) y la suavidad de las funciones (por ejemplo, diferenciables o no diferenciables ). Ninguna, una o varias funciones objetivo : La mayoría de los problemas de optimización tienen una única función objetivo , sin embargo, hay casos interesantes en los cuales los problemas de optimización no tienen una función objetivo o tienen múltiples de ellas. Los problemas de factibilidad , son problemas en los que el objetivo es encontrar valores para las variables que satisfacen las limitaciones de un modelo sin un objetivo particular de optimización . Los problemas de complementariedad surgen a menudo en ingeniería y economía; el objetivo es encontrar una solución que satisfaga las condiciones de complementariedad. Los problemas de optimización multiobjetivo surgen en muchos campos, como la ingeniería, la economía y la logística, cuando es necesario tomar decisiones óptimas en presencia de compromisos entre dos o más objetivos conflictivos. En la práctica, los problemas con objetivos múltiples a menudo se reformulan como problemas con objetivos únicos, ya sea formando una combinación ponderada de los diferentes objetivos o sustituyendo algunos de los objetivos por restricciones . Optimización determinista versus optimización estocástica : En la optimización determinista , se supone que los datos para el problema dado se conocen con exactitud. Sin embargo, para muchos problemas reales, los datos no pueden ser conocidos con precisión por una variedad de razones. La primera razón se debe a un simple error de medición. La segunda razón más fundamental es que algunos datos representan información sobre el futuro (por ejemplo, la demanda o el precio del producto para un período de tiempo futuro) y simplemente no se puede saber con certeza. En la optimización bajo incertidumbre, o optimización estocástica , la incertidumbre se incorpora en el modelo. El objetivo es encontrar una solución que sea factible para todos los datos y óptima en algún sentido. Los modelos de programación estocástica aprovechan el hecho de que las distribuciones de probabilidad que gobiernan los datos son conocidas o pueden ser estimadas; el objetivo es encontrar alguna solución que sea factible para todas (o casi todas) las posibles instancias de los datos y optimice el rendimiento esperado del modelo. Programación lineal Matemáticamente, un problema de optimización con restricciones asume la siguiente forma: $$ \\begin{array}{ll} \\min \\hspace{1cm} f_0(x)\\\\ \\mbox{sujeto a } \\ f_i (x) \\leq b_i, \\hspace{1cm} i=1, \\dots, m. \\end{array} $$ En donde el vector $x = (x_1, \\dots, x_n)$ es la variable de optimización del problema, la función $f_0: \\mathbb{R}^n \\rightarrow \\mathbb{R}$ es la función objetivo , las funciones $f_i: \\mathbb{R}^n \\rightarrow \\mathbb{R}, i=1, \\dots, m$; son las funciones de restricciones de desigualdad ; y las constantes $b_1, \\dots, m$ son los límites de las restricciones . Dentro de este marco, un caso importante es el de la programación lineal , en el cual la función objetivo y las restricciones son lineales . El objetivo de la programación lineal es determinar los valores de las variables de decisión que maximizan o minimizan una función objetivo lineal , y en donde las variables de decisión están sujetas a restricciones lineales . En general, el objetivo es encontrar un punto que minimice la función objetivo al mismo tiempo que satisface las restricciones . Para resolver un problema de programación lineal , debemos seguir los siguientes pasos: Elegir las incógnitas o variables de decisión. Escribir la función objetivo en función de los datos del problema. Escribir las restricciones en forma de sistema de inecuaciones . Averiguar el conjunto de soluciones factibles representando gráficamente las restricciones. Calcular las coordenadas de los vértices del recinto de soluciones factibles (si son pocos). Calcular el valor de la función objetivo en cada uno de los vértices para ver en cuál de ellos presenta el valor máximo o mínimo según nos pida el problema (hay que tener en cuenta aquí la posible no existencia de solución). Uno de los algoritmos más eficientes para resolver problemas de programación lineal , es el método simplex . Optimización convexa Otro caso importante de optimización que debemos destacar es el de la optimización convexa , en el cual la función objetivo y las restricciones son convexas . En realidad, la programación lineal que vimos anteriormente, no es más que un caso especial de optimización convexa . ¿qué es una función convexa? Podemos decir que un un conjunto es convexo si se puede ir de cualquier punto a cualquier otro en línea recta, sin salir del mismo. El concepto de convexidad es el opuesto de concavidad . LLevado a las funciones, podemos decir que una función es convexa en un intervalo (a,c), si para todo punto b del intervalo la recta tangente en ese punto queda por debajo de la función. Los conjuntos y funciones convexas tienen algunas propiedades que los hacen especiales para problemas de optimización , como ser: Una función convexa no tiene mínimos locales que no sean globales. Un conjunto convexo tiene un interior relativo no vacío. Un conjunto convexo está conectado y tiene direcciones factibles en cualquier punto. Una función no convexa puede ser convexificada manteniendo al mismo tiempo lo óptima de sus mínimos globales. Una función convexa es continua dentro del interior de su dominio, y tiene buenas propiedades de diferenciación . entre otras Librerías de Python para problemas de optimización Como es de esperar, en el ecosistema científico de Python podemos encontrar varias librerías que nos van a ayudar a enfrentar los problemas de optimización , entre las que podemos destacar: scipy.optimize : Este es el módulo de optimización de SciPy , en el cual vamos a poder encontrar varias rutinas numéricas para resolver problemas no lineales de optimización . CVXopt : Esta es una librería con una interface amigable para resolver problemas de optimización convexa . PuLP : Esta librería nos proporciona un lenguaje para modelar y resolver problemas de optimización utilizando programación lineal . Pyomo : Esta librería también nos va a proporcionar un lenguaje para modelar problemas de optimización en Python . Tiene una notación similar a la que utilizaríamos en la definición matemática de los problemas. Debemos destacar que tanto PuLP como Pyomo requieren la instalación adicional de diferentes solvers para poder resolver los problemas de optimización . Algunos de los solvers que soportan son: GLPK , COIN CLP/CBC , CPLEX y GURUBI , entre otros. Ejemplos con Python Bien, ahora llegó el momento de ver como podemos resolver algunos problemas de optimización con la ayuda de las librerías antes mencionadas. Mínimos cuadrados no lineales En general, podemos resolver problemas de Mínimos cuadrados utilizando un poco de álgebra lineal , pero los Mínimos cuadrados también pueden ser vistos como un problema de optimización ; ya que como su nombre lo indica, debemos minimizar la suma de los cuadrados de las diferencias entre los puntos generados por la función elegida y los correspondientes valores en los datos. scipy.optimize nos ofrece algunos métodos para resolver este tipo de problemas utilizando técnicas de optimización , como por ejemplo el algoritmo Gauss-Newton . Estos métodos pueden sernos de mucha utilizar sobre todo si la función tiene componentes no lineales . Veamos un ejemplo In [1]: Ver Código # importando modulos necesarios import matplotlib.pyplot as plt import numpy as np from scipy import optimize import cvxopt import pulp from pyomo.environ import * from pyomo.opt import SolverFactory import pyomo.environ np . random . seed ( 1984 ) #replicar random % matplotlib inline In [2]: # Ejemplo mínimos cuadrados no lineales utilizando scipy.optimize beta = ( 0.25 , 0.75 , 0.5 ) # funcion modelo def f ( x , b0 , b1 , b2 ): return b0 + b1 * np . exp ( - b2 * x ** 2 ) # datos aleatorios para simular las observaciones xdata = np . linspace ( 0 , 5 , 50 ) y = f ( xdata , * beta ) ydata = y + 0.05 * np . random . randn ( len ( xdata )) # función residual def g ( beta ): return ydata - f ( xdata , * beta ) # comenzamos la optimización beta_start = ( 1 , 1 , 1 ) beta_opt , beta_cov = optimize . leastsq ( g , beta_start ) beta_opt Out[2]: array([ 0.24022514, 0.76030423, 0.48425909]) In [3]: # graficamos fig , ax = plt . subplots ( figsize = ( 10 , 8 )) ax . scatter ( xdata , ydata ) ax . plot ( xdata , y , 'r' , lw = 2 ) ax . plot ( xdata , f ( xdata , * beta_opt ), 'b' , lw = 2 ) ax . set_xlim ( 0 , 5 ) ax . set_xlabel ( r \"$x$\" , fontsize = 18 ) ax . set_ylabel ( r \"$f(x, \\beta)$\" , fontsize = 18 ) ax . set_title ( 'Mínimos cuadrados no lineales' ) plt . show () Optimización con restricciones Las restricciones añaden otro nivel de complejidad a los problemas de optimización . En su forma más simple, simplemente consiste en poner algunos límites sobre los valores que las variables pueden alcanzar. Por ejemplo, podrías intentar resolver el siguiente problema: $$\\min \\ f(x)= (x_1 -1)^2 - (x_2 -1)^2 \\hspace{1cm} \\mbox{sujeto a } \\ 2 \\leq x_1 \\leq 3 \\mbox{ y } 0 \\leq x_2 \\leq 2 $$ Este tipo de problema se puede resolver utilizando el método L-BFGS-B que nos ofrece scipy.optimize . In [4]: # Ejemplo de optimización con restricciones scipy # defino una funcion de ayuda para subregion en el gráfico def func_X_Y_to_XY ( f , X , Y ): \"\"\" Wrapper for f(X, Y) -> f([X, Y]) \"\"\" s = np . shape ( X ) return f ( np . vstack ([ X . ravel (), Y . ravel ()])) . reshape ( * s ) # función a minimizar def f ( X ): x , y = X return ( x - 1 ) ** 2 + ( y - 1 ) ** 2 # minimizo la función si restricciones x_opt = optimize . minimize ( f , ( 1 , 1 ), method = 'BFGS' ) . x # el mínimo para las restricciones bnd_x1 , bnd_x2 = ( 2 , 3 ), ( 0 , 2 ) x_cons_opt = optimize . minimize ( f , np . array ([ 1 , 1 ]), method = 'L-BFGS-B' , bounds = [ bnd_x1 , bnd_x2 ]) . x # graficando la solución fig , ax = plt . subplots ( figsize = ( 10 , 8 )) x_ = y_ = np . linspace ( - 1 , 3 , 100 ) X , Y = np . meshgrid ( x_ , y_ ) c = ax . contour ( X , Y , func_X_Y_to_XY ( f , X , Y ), 50 ) ax . plot ( x_opt [ 0 ], x_opt [ 1 ], 'b*' , markersize = 15 ) ax . plot ( x_cons_opt [ 0 ], x_cons_opt [ 1 ], 'r*' , markersize = 15 ) bound_rect = plt . Rectangle (( bnd_x1 [ 0 ], bnd_x2 [ 0 ]), bnd_x1 [ 1 ] - bnd_x1 [ 0 ], bnd_x2 [ 1 ] - bnd_x2 [ 0 ], facecolor = \"grey\" ) ax . add_patch ( bound_rect ) ax . set_xlabel ( r \"$x_1$\" , fontsize = 18 ) ax . set_ylabel ( r \"$x_2$\" , fontsize = 18 ) plt . colorbar ( c , ax = ax ) ax . set_title ( 'Optimización con restricciones' ) plt . show () Las restricciones que se definen por igualdades o desigualdades que incluyen más de una variable son más complicadas de tratar. Sin embargo, existen técnicas generales que también podemos utilizar para este tipo de problemas. Volviendo al ejemplo anterior, cambiemos la restricción por una más compleja, como ser: $g(x) = x_1 - 1.75 - ( x_0 - 0.75 )^4 \\geq 0$. Para resolver este problema scipy.optimize nos ofrece un método llamado programación secuencial por mínimos cuadrados, o SLSQP por su abreviatura en inglés. In [5]: # Ejemplo scipy SLSQP # funcion de restriccion def g ( X ): return X [ 1 ] - 1.75 - ( X [ 0 ] - 0.75 ) ** 4 # definimos el diccionario con la restricción restriccion = dict ( type = 'ineq' , fun = g ) # resolvemos x_opt = optimize . minimize ( f , ( 0 , 0 ), method = 'BFGS' ) . x x_cons_opt = optimize . minimize ( f , ( 0 , 0 ), method = 'SLSQP' , constraints = restriccion ) . x # graficamos ig , ax = plt . subplots ( figsize = ( 10 , 8 )) x_ = y_ = np . linspace ( - 1 , 3 , 100 ) X , Y = np . meshgrid ( x_ , y_ ) c = ax . contour ( X , Y , func_X_Y_to_XY ( f , X , Y ), 50 ) ax . plot ( x_opt [ 0 ], x_opt [ 1 ], 'b*' , markersize = 15 ) ax . plot ( x_ , 1.75 + ( x_ - 0.75 ) ** 4 , 'k-' , markersize = 15 ) ax . fill_between ( x_ , 1.75 + ( x_ - 0.75 ) ** 4 , 3 , color = 'grey' ) ax . plot ( x_cons_opt [ 0 ], x_cons_opt [ 1 ], 'r*' , markersize = 15 ) ax . set_ylim ( - 1 , 3 ) ax . set_xlabel ( r \"$x_0$\" , fontsize = 18 ) ax . set_ylabel ( r \"$x_1$\" , fontsize = 18 ) plt . colorbar ( c , ax = ax ) plt . show () Programación lineal con CVXopt Ahora veamos un ejemplo de programación lineal utilizando los métodos de optimización convexa que nos ofrece la librería CVXopt . Recordemos que la programación lineal es un caso especial de la optimización convexa y el principal algoritmo que se utiliza es el método simplex . Para este pequeño ejemplo vamos a maximizar la siguiente función objetivo: $$f(x_1,x_2) = 50x_1 + 40x_2$$ con las siguientes restricciones: $$x_{1} + 1.5x_{2} \\leq 750 \\\\ 2x_1 + x_2 \\leq 1000 \\\\ x_1 \\geq 0 \\\\ x_2 \\geq 0 $$ In [6]: # Ejemplo programación lineal con CVXopt # Resolviendo el problema con cvxopt A = cvxopt . matrix ([[ - 1. , - 2. , 1. , 0. ], # columna de x1 [ - 1.5 , - 1. , 0. , 1. ]]) # columna de x2 b = cvxopt . matrix ([ 750. , 1000. , 0. , 0. ]) # resultados c = cvxopt . matrix ([ 50. , 40. ]) # funcion objetivo # resolviendo el problema sol = cvxopt . solvers . lp ( c , A , b ) pcost dcost gap pres dres k/t 0: -2.5472e+04 -3.6797e+04 5e+03 0e+00 3e-01 1e+00 1: -2.8720e+04 -2.9111e+04 1e+02 2e-16 9e-03 2e+01 2: -2.8750e+04 -2.8754e+04 1e+00 8e-17 9e-05 2e-01 3: -2.8750e+04 -2.8750e+04 1e-02 4e-16 9e-07 2e-03 4: -2.8750e+04 -2.8750e+04 1e-04 9e-17 9e-09 2e-05 Optimal solution found. In [7]: # imprimiendo la solucion. print ( ' {0:.2f} , {1:.2f} ' . format ( sol [ 'x' ][ 0 ] *- 1 , sol [ 'x' ][ 1 ] *- 1 )) 375.00, 250.00 In [8]: # Resolviendo la optimizacion graficamente. x_vals = np . linspace ( 0 , 800 , 10 ) # 10 valores entre 0 y 800 y1 = (( 750 - x_vals ) / 1.5 ) # x1 + 1.5x2 = 750 y2 = ( 1000 - 2 * x_vals ) # 2x1 + x2 = 1000 plt . figure ( figsize = ( 10 , 8 )) plt . plot ( x_vals , y1 , label = r '$x_1 + 1.5x_2 \\leq 750$' ) plt . plot ( x_vals , y2 , label = r '$2x_1 + x_2 \\leq 1000$' ) # plt . plot ( 375 , 250 , 'b*' , markersize = 15 ) # Región factible y3 = np . minimum ( y1 , y2 ) plt . fill_between ( x_vals , 0 , y3 , alpha = 0.15 , color = 'b' ) plt . axis ( ymin = 0 ) plt . title ( 'Optimización lineal' ) plt . legend () plt . show () Como podemos ver, tanto la solución utilizando CVXopt , como la solución gráfica; nos devuelven el mismo resultado $x_1=375$ y $x_2=250$. El problema de transporte El problema de transporte es un problema clásico de programación lineal en el cual se debe minimizar el costo del abastecimiento a una serie de puntos de demanda a partir de un grupo de puntos de oferta, teniendo en cuenta los distintos precios de envío de cada punto de oferta a cada punto de demanda. Por ejemplo, supongamos que tenemos que enviar cajas de cervezas de 2 cervecerías a 5 bares de acuerdo al siguiente gráfico: Asimismo, supongamos que nuestro gerente financiero nos informa que el costo de transporte por caja de cada ruta se conforma de acuerdo a la siguiente tabla: Bar 1 Bar 2 Bar 3 Bar 4 Bar 5 Cervecería A 2 4 5 2 1 Cervecería B 3 1 3 2 3 Y por último, las restricciones del problema, van a estar dadas por las capacidades de oferta y demanda de cada cervecería y cada bar, las cuales se detallan en el gráfico de más arriba. Veamos como podemos modelar este ejemplo con la ayuda de PuLP y de Pyomo . In [9]: # Ejemplo del problema de transporte de las cervezas utilizando PuLP # Creamos la variable prob que contiene los datos del problema prob = pulp . LpProblem ( \"Problema de distribución de cerveza\" , pulp . LpMinimize ) In [10]: # Creamos lista de cervecerías o nodos de oferta cervecerias = [ \"Cervecería A\" , \"Cervercería B\" ] # diccionario con la capacidad de oferta de cada cerveceria oferta = { \"Cervecería A\" : 1000 , \"Cervercería B\" : 4000 } # Creamos la lista de los bares o nodos de demanda bares = [ \"Bar 1\" , \"Bar 2\" , \"Bar 3\" , \"Bar 4\" , \"Bar 5\" ] # diccionario con la capacidad de demanda de cada bar demanda = { \"Bar 1\" : 500 , \"Bar 2\" : 900 , \"Bar 3\" : 1800 , \"Bar 4\" : 200 , \"Bar 5\" : 700 ,} # Lista con los costos de transporte de cada nodo costos = [ #Bares #1 2 3 4 5 [ 2 , 4 , 5 , 2 , 1 ], #A Cervecerías [ 3 , 1 , 3 , 2 , 3 ] #B ] # Convertimos los costos en un diccionario de PuLP costos = pulp . makeDict ([ cervecerias , bares ], costos , 0 ) # Creamos una lista de tuplas que contiene todas las posibles rutas de tranporte. rutas = [( c , b ) for c in cervecerias for b in bares ] In [11]: # creamos diccionario x que contendrá la candidad enviada en las rutas x = pulp . LpVariable . dicts ( \"ruta\" , ( cervecerias , bares ), lowBound = 0 , cat = pulp . LpInteger ) # Agregamos la función objetivo al problema prob += sum ([ x [ c ][ b ] * costos [ c ][ b ] for ( c , b ) in rutas ]), \\ \"Suma_de_costos_de_transporte\" # Agregamos la restricción de máxima oferta de cada cervecería al problema. for c in cervecerias : prob += sum ([ x [ c ][ b ] for b in bares ]) <= oferta [ c ], \\ \"Suma_de_Productos_que_salen_de_cervecerias_ %s \" % c # Agregamos la restricción de demanda mínima de cada bar for b in bares : prob += sum ([ x [ c ][ b ] for c in cervecerias ]) >= demanda [ b ], \\ \"Sum_of_Products_into_Bar %s \" % b # Los datos del problema son exportado a archivo .lp prob . writeLP ( \"problemaDeTransporte.lp\" ) # Resolviendo el problema. prob . solve () # Imprimimos el estado del problema. print ( \"Status: {} \" . format ( pulp . LpStatus [ prob . status ])) # Imprimimos cada variable con su solución óptima. for v in prob . variables (): print ( \" {0:} = {1:} \" . format ( v . name , v . varValue )) # Imprimimos el valor óptimo de la función objetivo print ( \"Costo total de transporte = {} \" . format ( prob . objective . value ())) Status: Optimal ruta_Cervecería_A_Bar_1 = 300.0 ruta_Cervecería_A_Bar_2 = 0.0 ruta_Cervecería_A_Bar_3 = 0.0 ruta_Cervecería_A_Bar_4 = 0.0 ruta_Cervecería_A_Bar_5 = 700.0 ruta_Cervercería_B_Bar_1 = 200.0 ruta_Cervercería_B_Bar_2 = 900.0 ruta_Cervercería_B_Bar_3 = 1800.0 ruta_Cervercería_B_Bar_4 = 200.0 ruta_Cervercería_B_Bar_5 = 0.0 Costo total de transporte = 8600.0 Como vemos, la solución óptima que encontramos con la ayuda de PuLP , nos dice que deberíamos enviar desde la Cervecería A, 300 cajas al Bar 1 y 700 cajas al Bar 5; y que desde la Cervecería B deberíamos enviar 200 cajas al Bar 1, 900 cajas al Bar 2, 1800 cajas al Bar 3 y 200 cajas al Bar 4. De esta forma podemos minimizar el costo de transporte a un total de 8600. Veamos ahora si podemos formular este mismo problema con Pyomo , así podemos darnos una idea de las diferencias entre las herramientas. In [12]: # Ejemplo del problema de transporte de las cervezas utilizando Pyomo # Creamos el modelo modelo = ConcreteModel () # Creamos los nodos de oferta y demanda modelo . i = Set ( initialize = [ 'Cervecería A' , 'Cervecería B' ], doc = 'Cervecerías' ) modelo . j = Set ( initialize = [ 'Bar 1' , 'Bar 2' , 'Bar 3' , 'Bar 4' , 'Bar 5' ], doc = 'Bares' ) # Definimos las capacidades de oferta y demanda modelo . a = Param ( modelo . i , initialize = { 'Cervecería A' : 1000 , 'Cervecería B' : 4000 }, doc = 'Capacidad de oferta de las cervecerías' ) modelo . b = Param ( modelo . j , initialize = { 'Bar 1' : 500 , 'Bar 2' : 900 , 'Bar 3' : 1800 , 'Bar 4' : 200 , 'Bar 5' : 700 }, doc = 'Demanda de cada bar' ) # Costo de transporte costos = { ( 'Cervecería A' , 'Bar 1' ): 2 , ( 'Cervecería A' , 'Bar 2' ): 4 , ( 'Cervecería A' , 'Bar 3' ): 5 , ( 'Cervecería A' , 'Bar 4' ): 2 , ( 'Cervecería A' , 'Bar 5' ): 1 , ( 'Cervecería B' , 'Bar 1' ): 3 , ( 'Cervecería B' , 'Bar 2' ): 1 , ( 'Cervecería B' , 'Bar 3' ): 3 , ( 'Cervecería B' , 'Bar 4' ): 2 , ( 'Cervecería B' , 'Bar 5' ): 3 } modelo . d = Param ( modelo . i , modelo . j , initialize = costos , doc = 'Costo de transporte' ) # definimos el costo de tranporte def f_costo ( modelo , i , j ): return modelo . d [ i , j ] modelo . c = Param ( modelo . i , modelo . j , initialize = f_costo , doc = 'Costo de transporte' ) # definimos variable x con las cantidades de cajas enviadas modelo . x = Var ( modelo . i , modelo . j , bounds = ( 0.0 , None ), doc = 'Cantidad de cajas' ) In [13]: ## Definimos las restricciones ## # Límite de oferta def f_oferta ( modelo , i ): return sum ( modelo . x [ i , j ] for j in modelo . j ) <= modelo . a [ i ] modelo . oferta = Constraint ( modelo . i , rule = f_oferta , doc = 'Límites oferta de cada Cervecería' ) # Límite de demanda def f_demanda ( modelo , j ): return sum ( modelo . x [ i , j ] for i in modelo . i ) >= modelo . b [ j ] modelo . demanda = Constraint ( modelo . j , rule = f_demanda , doc = 'Límites demanda de cada bar' ) In [14]: ## Definimos la función objetivo y resolvemos el problema ## # Función objetivo def f_objetivo ( modelo ): return sum ( modelo . c [ i , j ] * modelo . x [ i , j ] for i in modelo . i for j in modelo . j ) modelo . objetivo = Objective ( rule = f_objetivo , sense = minimize , doc = 'Función Objetivo' ) # resolvemos el problema e imprimimos resultados def pyomo_postprocess ( options = None , instance = None , results = None ): modelo . x . display () # utilizamos solver glpk opt = SolverFactory ( \"glpk\" ) resultados = opt . solve ( modelo ) # imprimimos resultados print ( \" \\n Solución óptima encontrada \\n \" + '-' * 80 ) pyomo_postprocess ( None , None , resultados ) Solución óptima encontrada -------------------------------------------------------------------------------- x : Cantidad de cajas Size=10, Index=x_index Key : Lower : Value : Upper : Fixed : Stale : Domain ('Cervecería A', 'Bar 1') : 0.0 : 300.0 : None : False : False : Reals ('Cervecería A', 'Bar 2') : 0.0 : 0.0 : None : False : False : Reals ('Cervecería A', 'Bar 3') : 0.0 : 0.0 : None : False : False : Reals ('Cervecería A', 'Bar 4') : 0.0 : 0.0 : None : False : False : Reals ('Cervecería A', 'Bar 5') : 0.0 : 700.0 : None : False : False : Reals ('Cervecería B', 'Bar 1') : 0.0 : 200.0 : None : False : False : Reals ('Cervecería B', 'Bar 2') : 0.0 : 900.0 : None : False : False : Reals ('Cervecería B', 'Bar 3') : 0.0 : 1800.0 : None : False : False : Reals ('Cervecería B', 'Bar 4') : 0.0 : 200.0 : None : False : False : Reals ('Cervecería B', 'Bar 5') : 0.0 : 0.0 : None : False : False : Reals Como podemos ver, arribamos a la mismo solución que utilizando PuLP . Ambas herramientas siguen un lenguaje de modelado totalmente distinto, particularmente me gusta más la sintaxis que ofrece PuLP sobre la de Pyomo . Aquí concluye este artículo, como vemos la optimización se puede aplicar a un amplio rango de problemas, por lo que es sumamente importe conocer sus principales métodos y como podemos aplicarlos con éxito. Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Matematica","url":"https://relopezbriega.github.io/blog/2017/01/18/problemas-de-optimizacion-con-python/"},{"title":"Introducción a los métodos de Monte-Carlo con Python","text":"Introducción En el cierre de mi artículo anterior , comentaba sobre la sorprendente influencia que han tenido los números aleatorios , que junto con el poder de cálculo que nos proporcionan las computadoras modernas; nos han ayudado a resolver muchos de los problemas numéricos más complejos en ciencia, ingeniería, finanzas y estadísticas . En esencia, detrás de cada una de esas soluciones encontramos una familia de métodos que se conocen bajo el nombre de Métodos de Monte-Carlo . ¿Qué son los métodos de Monte-Carlo? Los Métodos de Monte-Carlo son técnicas para analizar fenómenos por medio de algoritmos computacionales , que utilizan y dependen fundamentalmente de la generación de números aleatorios . El término Monte-Carlo, hace referencia al casino de Montecarlo , una de las capitales de los juegos de azar; y se utilizó como denominación para estás técnicas por la aleatoriedad inherente que poseen. El estudio de los Métodos de Monte-Carlo requiere un conocimiento detallado en una amplia gama de campos; por ejemplo, la probabilidad para describir los experimentos y procesos aleatorios, la estadística para analizar los datos, las ciencias de la computación para implementar eficientemente los algoritmos y la programación matemática para formular y resolver problemas de optimización . Como los Métodos de Monte-Carlo dependen en gran medida de la posibilidad de producir, con una computadora, un flujo infinito de variables aleatorias para todo tipo de distribuciones ; no podemos hablar de los Métodos de Monte-Carlo , sin antes explicar los números aleatorios y como podemos generarlos con la ayuda de una computadora. Números aleatorios y Monte-Carlo En el corazón de los Métodos de Monte-Carlo encontramos un generador de números aleatorios , es decir un procedimiento que produce un flujo infinito de variables aleatorias , que generalmente se encuentran en el intervalo (0, 1); los cuales son independientes y están uniformemente distribuidos de acuerdo a una distribución de probabilidad . La mayoría de los lenguajes de programación hoy en día contienen un generador de números aleatorios por defecto al cual simplemente debemos ingresarle un valor inicial, generalmente llamado seed o semilla, y que luego en cada invocación nos va a devolver un secuencia uniforme de variables aleatorias independientes en el intervalo (0, 1). Números pseudoaleatorios El concepto de una secuencia infinita de variables aleatorias es una abstracción matemática que puede ser imposible de implementar en una computadora. En la práctica, lo mejor que se puede hacer es producir una secuencia de números pseudoaleatorios con propiedades estadísticas que son indistinguibles de las de una verdadera secuencia de variables aleatorias . Aunque actualmente métodos de generación física basados en la radiación de fondo o la mecánica cuántica parecen ofrecer una fuente estable de números verdaderamente aleatorios , la gran mayoría de los generadores de números aleatorios que se utilizan hoy en día están basados en algoritmos simples que pueden ser fácilmente implementados en una computadora; por lo que en realidad son generadores de números pseudoaleatorios . Números aleatorios en Python En Python el módulo random nos proporciona un rápido generador de números pseudoaleatorios basado en el algoritmo Mersenne Twister ; el cual genera números con una distribución casi uniforme y un período grande, haciéndolo adecuado para una amplia gama de aplicaciones. Veamos un pequeño ejemplo. In [1]: # Utilizando random para genera números aleatorios. import random random . seed ( 1984 ) # semilla para replicar la aleatoriedad random . random () # primer llamado a random Out[1]: 0.36352835585530807 In [2]: random . random () # segundo llamado a random Out[2]: 0.49420568181919666 In [3]: for i in range ( 5 ): print ( random . random ()) # 5 números aleatorios 0.33961008717180197 0.21648780903913534 0.8626522767441037 0.8493329421213219 0.38578540884489343 In [4]: # volviendo a llamar a seed para replicar el mismo resultado aleatorio. random . seed ( 1984 ) for i in range ( 7 ): print ( random . random ()) # Mismos resultados que arriba. 0.36352835585530807 0.49420568181919666 0.33961008717180197 0.21648780903913534 0.8626522767441037 0.8493329421213219 0.38578540884489343 En este ejemplo podemos ver como la función random genera números aleatorios entre 0 y 1, también podemos ver como con el uso de seed podemos replicar el comportamiento aleatorio. Elegir un buen generador de números aleatorios Existe una gran variedad de generadores de números aleatorios que podemos elegir; pero elegir un buen generador de números aleatorios es como elegir un coche nuevo: para algunas personas o aplicaciones la velocidad es primordial, mientras que para otros la robustez y la fiabilidad son más importantes. Para la simulación de Monte-Carlo las propiedades distributivas de los generadores aleatorios es primordial, mientras que en la criptografía la imprevisibilidad es crucial. Por tal motivo, el generador que vayamos a elegir dependerá de la aplicación que le vayamos a dar. Monte-Carlo en acción Los Métodos de Monte-Carlo se basan en la analogía entre probabilidad y volumen. Las matemáticas de las medidas formalizan la noción intuitiva de probabilidad , asociando un evento con un conjunto de resultados y definiendo que la probabilidad del evento será el volumen o medida relativa del universo de posibles resultados. Monte-Carlo usa esta identidad a la inversa, calculando el volumen de un conjunto interpretando el volumen como una probabilidad . En el caso más simple, esto significa muestrear aleatoriamente un universo de resultados posibles y tomar la fracción de muestras aleatorias que caen en un conjunto dado como una estimación del volumen del conjunto. La ley de grandes números asegura que esta estimación converja al valor correcto a medida que aumenta el número de muestras. El teorema del límite central proporciona información sobre la magnitud del probable error en la estimación después de un número finito de muestras. En esencia podemos decir que el Método de Monte-Carlo consiste en calcular o aproximar ciertas expresiones a través de adivinarlas con la ayuda de dibujar una cantidad normalmente grande de números aleatorios. Veamos como funciona con un ejemplo, calculemos el área de un círculo de radio 1; lo que es lo mismo a decir que aproximemos el valor de $\\pi$ . In [5]: Ver Código # importando modulos necesarios import matplotlib.pyplot as plt import numpy as np # importando numpy import pandas as pd # importando pandas from scipy import stats np . random . seed ( 1984 ) # para poder replicar el random % matplotlib inline In [6]: # Ejemplo: Aproximando el valor de pi - área de un círculo de # radio = 1. def mc_pi_aprox ( N = 10000 ): plt . figure ( figsize = ( 8 , 8 )) # tamaño de la figura x , y = np . random . uniform ( - 1 , 1 , size = ( 2 , N )) interior = ( x ** 2 + y ** 2 ) <= 1 pi = interior . sum () * 4 / N error = abs (( pi - np . pi ) / pi ) * 100 exterior = np . invert ( interior ) plt . plot ( x [ interior ], y [ interior ], 'b.' ) plt . plot ( x [ exterior ], y [ exterior ], 'r.' ) plt . plot ( 0 , 0 , label = '$\\hat \\pi$ = {:4.4f} \\n error = {:4.4f} %' . format ( pi , error ), alpha = 0 ) plt . axis ( 'square' ) plt . legend ( frameon = True , framealpha = 0.9 , fontsize = 16 ) mc_pi_aprox () In [7]: # con 1000000 experimentos mc_pi_aprox ( N = 100000 ) Como vemos en este ejemplo, para calcular el área del círculo realizamos un gran número de experimentos aleatorios, en el primer ejemplo utilizamos 10,000 experimentos; y luego calculamos el área obteniendo una media aritmética de los valores que caen dentro de la superficie del círculo. Debemos hacer notar que incluso utilizando un gran número de experimentos aún así en el primer ejemplo no logramos obtener los primeros dos decimales correctos; recién en el segundo ejemplo, cuando utilizamos 100,000 experimentos logramos obtener los primeros dos dígitos correctos; esto demuestra que el Método de Monte-Carlo en su versión más cruda tarda bastante en converger . Técnicas de reducción de varianza Existen varias técnicas generales para la reducción de la varianza , estos métodos mejoran la precisión y la tasa de convergencia de la integración por medio del Método de Monte-Carlo sin aumentar el número de experimentos. Algunas de estas técnicas son: Muestreo de importancia : La idea principal detrás del Muestreo de importancia es simplemente encontrar una distribución para la variable aleatoria subyacente que asigne una alta probabilidad a aquellos valores que son importantes para calcular la cantidad que nos interesa determinar. Muestreo estratificado : El principal principio subyacente al Muestreo estratificado es natural: tomar una muestra de una pequeña subpoblación que refleje las propiedades del total de la población tanto como sea posible. Variantes de control : El método de las Variantes de control explota la información sobre los errores en las estimaciones de las cantidades conocidas para reducir el error de una estimación de una cantidad desconocida. Variaciones antitéticas : El método de las Variaciones antitéticas es el método de reducción de la varianza , más fácil. Se basa en la idea de combinar una selección aleatoria de puntos con una opción sistemática. Su principal principio es la reducción de la varianza , mediante la introducción de la simetría. Excede al alcance de este artículo el profundizar en cada una de estas técnicas, pueden encontrar un análisis más detallado de las mismas en el siguiente enlace (en inglés). Métodos de Monte-Carlo via cadenas de Markov El desarrollo de los métodos de Monte-Carlo via cadenas de Markov , o MCMC por sus siglas en inglés, es sin duda uno de los mayores avances en el enfoque computacional de la estadística . Muchos de los problemas que son intratables utilizando un enfoque analítico a menudo pueden ser resueltos utilizando alguna forma de MCMC , incluso aunque se trate de problemas en varias dimensiones . Las técnicas MCMC se aplican para resolver problemas de integración y optimización en grandes espacios dimensionales . Estos dos tipos de problemas desempeñan un papel fundamental en machine learning , física , estadística , econometría y el análisis de decisiones . ¿Qué es una cadena de Markov? Una cadena de Markov es un objeto matemático que consiste en una secuencia de estados y un conjunto de probabilidades que describen las transiciones entre esos estados . La característica principal que tiene esta cadena es que la probabilidad de moverse a otros estados depende solamente del estado actual . Dada una cadena, se puede realizar una caminata aleatoria eligiendo un punto de partida y moviéndose a otros estados siguiendo las probabilidades de transición . Si de alguna manera encontramos una cadena de Markov con transiciones proporcionales a la distribución que queremos probar, el muestreo se convierte simplemente en una cuestión de moverse entre los estados de esta cadena. Caminata aleatoria en un grafo Una cadena de Markov puede ser ilustrada con el siguiente grafo , el cual representa una cadena de Markov de cuatro estados. Los estados de la cadena se representan como los nodos del grafo , una arista de dirección se extiende de un nodo $x$ hacia otro nodo $y$ si la transición de $x$ hacia $y$ es posible en una iteración. Cada una de estas aristas de dirección tienen una probabilidad asociada $P_{xy}$; esta es la probabilidad de que el nodo sea elegido en la siguiente iteración cuando la cadena se encuentra en el estado $x$. La cadena comienza en algún estado, digamos $X_0$, que puede ser elegido en forma aleatoria de acuerdo con una distribución inicial o simplemente asignado en forma arbitraria. Desde allí, la cadena se mueve de un estado a otro en cada iteración según las probabilidades de transición del nodo vecino. Representación matricial Como alternativa a la representación en forma de grafo , la cadena antes descripta también puede ser representada por una matriz $P = (p_{xy})$ de las probabilidades $p_{xy}$ de transición de un estado $x$ a un estado $y$ en una iteración de la cadena. Esta matriz es llamada matriz de transición y tiene la característica que todas sus filas deben sumar 1. Por ejemplo, la matriz de transición de nuestro ejemplo sería la siguiente: $$ P = \\begin{bmatrix} p_{11} & p_{12} & 0 & p_{14} \\\\ p_{21} & 0 & p_{23} & 0 \\\\ p_{31} & 0 & 0 & p_{34} \\\\ 0 & 0 & p_{43} & p_{44} \\end{bmatrix} $$ Esta formulación de matriz es mucho más que una descripción tabular de la cadena; también es una herramienta de cálculo. Ya que si por ejemplo definimos a $p_t$ como el vector de probabilidad de una variable aleatoria $X_t$; entonces podemos calcular $p_{t + 1}$ como una multiplicación de matrices $$ p_{t + 1} = p_t \\cdot P, \\hspace{1cm} t= 1, 2, \\dots $$ La distribución invariante Una de las características generales de las cadenas de Markov es que pueden poseer una distribución invariante , tomemos por ejemplo la siguiente representación matricial de una cadena de Markov : $$ P = \\begin{bmatrix} 0.3 & 0.2 & 0.5 \\\\ 0.4 & 0.3 & 0.3 \\\\ 0.3 & 0.4 & 0.3 \\end{bmatrix} $$ Si comenzamos en el primer estado de la cadena, podemos obtener $p_1$ del siguiente modo: $$ p_1 = \\begin{bmatrix} 1 & 0 & 0 \\end{bmatrix} \\cdot \\begin{bmatrix} 0.3 & 0.2 & 0.5 \\\\ 0.4 & 0.3 & 0.3 \\\\ 0.3 & 0.4 & 0.3 \\end{bmatrix} = \\begin{bmatrix} 0.3 & 0.2 & 0.5 \\end{bmatrix} $$ Ahora que ya obtuvimos $p_1$, podemos continuar y obtener $p_2$: $$ p_2 = p_1 P = \\begin{bmatrix} 0.3 & 0.2 & 0.5 \\end{bmatrix} \\cdot \\begin{bmatrix} 0.3 & 0.2 & 0.5 \\\\ 0.4 & 0.3 & 0.3 \\\\ 0.3 & 0.4 & 0.3 \\end{bmatrix} = \\begin{bmatrix} 0.32 & 0.22 & 0.36 \\end{bmatrix} $$ Si continuamos con este proceso en forma recursiva, veremos que la distribución tiende hacía un límite , este límite es su distribución invariante . $$ p_3 = p_2 P = \\begin{bmatrix} 0.332 & 0.304 & 0.364 \\end{bmatrix}, \\\\ p_4 = p_3 P = \\begin{bmatrix} 0.3304 & 0.3032 & 0.3664 \\end{bmatrix}, \\\\ p_5 = p_4 P = \\begin{bmatrix} 0.33032 & 0.3036 & 0.36608 \\end{bmatrix}, \\\\ \\dots \\hspace{1cm} \\dots \\\\ p_{10} = p_9 P = \\begin{bmatrix} 0.330357 & 0.303571 & 0.366072 \\end{bmatrix} $$ Veamos el ejemplo con la ayuda de Python para que quede más claro. In [8]: # Ejemplo distribución invariante P = np . array ( [[ 0.3 , 0.2 , 0.5 ], [ 0.4 , 0.3 , 0.3 ], [ 0.3 , 0.4 , 0.3 ]] ) P Out[8]: array([[ 0.3, 0.2, 0.5], [ 0.4, 0.3, 0.3], [ 0.3, 0.4, 0.3]]) In [9]: p1 = np . array ( [ 1 , 0 , 0 ] ) for i in range ( 1 , 12 ): p_i = p1 @ P print ( 'p_ {0:} = {1:} ' . format ( i , p_i )) p1 = p_i p_1 = [ 0.3 0.2 0.5] p_2 = [ 0.32 0.32 0.36] p_3 = [ 0.332 0.304 0.364] p_4 = [ 0.3304 0.3032 0.3664] p_5 = [ 0.33032 0.3036 0.36608] p_6 = [ 0.33036 0.303576 0.366064] p_7 = [ 0.3303576 0.3035704 0.366072 ] p_8 = [ 0.33035704 0.30357144 0.36607152] p_9 = [ 0.33035714 0.30357145 0.36607141] p_10 = [ 0.33035714 0.30357143 0.36607143] p_11 = [ 0.33035714 0.30357143 0.36607143] Como vemos, luego de 12 iteraciones la distribución alcanza su límite y ya no cambian los resultados. Hemos alcanzado la distribución invariante ! El algoritmo Metropolis-Hastings Uno de los métodos MCMC más populares es el algoritmo Metropolis-Hastings ; de hecho la mayoría de los algoritmos de MCMC pueden ser interpretados como casos especiales de este algoritmo. El algoritmo Metropolis-Hastings esta catalogado como uno de los 10 algoritmos más importantes y más utilizados en ciencia e ingeniería en los últimos veinte años.Se encuentra en el corazón de la mayoría de los métodos de muestreo MCMC . El problema básico que intenta resolver el algoritmo Metropolis-Hastings es proporcionar un método para generar muestras de alguna distribución genérica, $P(x)$. La idea es que en muchos casos, podemos saber cómo escribir la ecuación para la distribución de probabilidad $P(x)$, pero no sabemos cómo generar muestras aleatorias de la misma. Entonces la idea básica detrás de este algoritmo es la de construir una cadena de Markov cuya distribución invariante sea la distribución de muestreo que deseamos, es decir $P(x)$. En principio, esto puede parecer bastante complicado, pero la flexibilidad inherente en la elección de las probabilidades de transición lo hacen más simple de lo que parece. ¿Cómo funciona el algoritmo? El algoritmo funciona del siguiente modo. Supongamos que el estado actual de la cadena de Markov es $x_n$, y queremos generar $x_{n + 1}$. De acuerdo con el algoritmo Metropolis-Hastings , la generación de $x_{n + 1}$ es un proceso en dos etapas . La primera etapa consiste en generar un candidato , que denominaremos $x^*$. El valor de $x^*$ se genera a partir de la distribución propuesta , denotada $Q (x^* | x_n)$, la cual depende del estado actual de la cadena de Markov , $x_n$. Existen algunas limitaciones técnicas menores sobre la distribución propuesta que podemos utilizar, pero en su mayor parte puede ser cualquier cosa que deseemos. Una forma típica de hacerlo es utilizar una distribución normal centrada en el estado actual $x_n$. Es decir, $$ x^*|x_n \\sim Normal(x_n, \\sigma^2)$$ La segunda etapa es la de aceptación-rechazo . Lo primero que debemos hacer en este paso es calcular la probabilidad de aceptación $A(x_n \\rightarrow x^*)$, la cual estará dada por: $$A(x_n \\rightarrow x^*) = \\min \\left(1, \\frac{P(x^*)}{P(x_n)} \\cdot \\frac{Q(x_n | x^*)}{Q(x^* | x_n)} \\right) $$ Muy bien. Ahora que tenemos el candidato $x^*$ y hemos calculado la probabilidad de aceptación $A(x_n \\rightarrow x^*)$, es tiempo de decidir aceptar al candidato (en cuyo caso se establecemos $x_{n + 1} = x^*$); o rechazar al candidato (en cuyo caso estableceremos $x_{n + 1} = x_n$). Para tomar esta decisión, generamos un número aleatorio (uniformemente distribuido) entre 0 y 1, que denominaremos $u$. Entonces: $$x_{n + 1} = \\left\\{ \\begin{array}{ll} x^* & \\mbox{si } u \\leq A(x_n \\rightarrow x^*)\\\\ x_n & \\mbox{si } u > A(x_n \\rightarrow x^*) \\end{array} \\right. $$ Y esto es en esencia como funciona el algoritmo Metropolis-Hastings ! Veamos un pequeño ejemplo en Python : In [10]: # Ejemplo algoritmo metropolis def metropolis ( func , steps = 10000 ): \"\"\"A very simple Metropolis implementation\"\"\" muestras = np . zeros ( steps ) old_x = func . mean () old_prob = func . pdf ( old_x ) for i in range ( steps ): new_x = old_x + np . random . normal ( 0 , 0.5 ) new_prob = func . pdf ( new_x ) aceptacion = new_prob / old_prob if aceptacion >= np . random . random (): muestras [ i ] = new_x old_x = new_x old_prob = new_prob else : muestras [ i ] = old_x return muestras In [11]: # distribución beta func = stats . beta ( 0.4 , 2 ) samples = metropolis ( func = func , steps = 100000 ) x = np . linspace ( 0.01 , . 99 , 100 ) y = func . pdf ( x ) plt . figure ( figsize = ( 8 , 8 )) plt . xlim ( 0 , 1 ) plt . plot ( x , y , 'r-' , lw = 3 , label = 'Distribución verdadera' ) plt . hist ( samples , bins = 30 , normed = True , label = 'Distribución estimada con MCMC' ) plt . xlabel ( '$x$' , fontsize = 14 ) plt . ylabel ( '$pdf(x)$' , fontsize = 14 ) plt . legend ( fontsize = 14 ) plt . show () In [12]: # distribución normal func = stats . norm ( 0.4 , 2 ) samples = metropolis ( func = func ) x = np . linspace ( - 6 , 10 , 100 ) y = func . pdf ( x ) plt . figure ( figsize = ( 8 , 8 )) plt . xlim ( - 6 , 6 ) plt . plot ( x , y , 'r-' , lw = 3 , label = 'Distribución verdadera' ) plt . hist ( samples , bins = 30 , normed = True , label = 'Distribución estimada con MCMC' ) plt . xlabel ( '$x$' , fontsize = 14 ) plt . ylabel ( '$pdf(x)$' , fontsize = 14 ) plt . legend ( fontsize = 14 ) plt . show () como vemos, las distribuciones estimadas utilizando MCMC se acercan bastante a las distribuciones reales. Otros métodos MCMC Además del algoritmo Metropolis-Hastings existen otros algoritmos de muestreo que utilizan los métodos MCMC . Algunos de ellos son: Muestreo de Gibbs , el cual es un caso especial del algoritmo Metropolis-Hastings . Monte-Carlo Hamiltoniano o híbrido , el cual reduce la correlación entre los sucesivos estados de muestreo usando una evolución Hamiltoniana . Muestreo de rebanada o Slice sampler , este método se basa en la observación de que para muestrear una variable aleatoria se pueden tomar muestras en forma uniforme de la región debajo del gráfico de su función de densidad . NUTS o No U turn sampler , el cual es una extensión del algoritmo híbrido de Monte-Carlo que logra incluso mayor eficiencia. Con esto concluye este paseo por los Métodos de Monte-Carlo y la estadística computacional, espero que les haya parecido interesante y les sea de utilidad en sus proyectos. Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Pobabilidad y Estadistica","url":"https://relopezbriega.github.io/blog/2017/01/10/introduccion-a-los-metodos-de-monte-carlo-con-python/"},{"title":"Introducción a la teoría de probabilidad con Python","text":"\"En el fondo, la teoría de probabilidades es sólo sentido común expresado con números\" Pierre Simon de Laplace Introducción: probabilidad y sentido común La incertidumbre constituye una pieza fundamental del mundo en que vivimos, en parte hace la vida mucho más interesante, ya que sería muy aburrido si todo fuera perfectamente predecible. Aun así, una parte de nosotros quisiera predecir el futuro y que las cosas sean mucho más predecibles. Para poder lidiar con la incertidumbre que nos rodea, solemos aplicar lo que llamamos nuestro \" sentido común \". Por ejemplo, si al levantarnos por la mañana vemos que el día se encuentra nublado, este hecho no nos da la certeza de que comenzará a llover más tarde; sin embargo, nuestro sentido común puede inducirnos a cambiar nuestros planes y a actuar como si creyéramos que fuera a llover si las nubes son los suficientemente oscuras o si escuchamos truenos, ya que nuestra experiencia nos dice que estos signos indicarían una mayor posibilidad de que el hecho de que fuera a llover más tarde realmente ocurra. Nuestro sentido común es algo tan arraigado en nuestro pensamiento, que lo utilizamos automáticamente sin siquiera ponernos a pensar en ello; pero muchas veces, el sentido común también nos puede jugar una mala pasada y hacernos elegir una respuesta incorrecta. Tomemos por ejemplo alguna de las siguiente situaciones... Situación 1 - La coincidencia de cumpleaños: Vamos a una fiesta a la que concurren un total de 50 personas. Allí un amigo nos desafía afirmando que en la fiesta debe haber por lo menos 2 personas que cumplen años el mismo día y nos apuesta 100 pesos a que está en lo correcto. Es decir, que si él acierta deberíamos pagarle los 100 pesos; o en caso contrario, el nos pagará los 100 pesos. ¿Deberíamos aceptar la apuesta? Situación 2 - ¿Que puerta elegir?: Estamos participando en un concurso en el cual se nos ofrece la posibilidad de elegir una entre tres puertas. Tras una de ellas se encuentra una ferrari ultimo modelo, y detrás de las otras dos hay una cabra; luego de elegir una puerta, el presentador del concurso abre una de las puertas restantes y muestra que hay una cabra (el presentador sabe que hay detrás de cada puerta). Luego de hacer esto, el presentador nos ofrece la posibilidad de cambiar nuestra elección inicial y quedarnos con la otra puerta que no habíamos elegido inicialmente. ¿Deberíamos cambiar o confiar en nuestra elección inicial? ¿Qué les diría su sentido común que deberían hacer en cada una de estas situaciones? Para poder responder éstas y otras preguntas de una manera más rigurosa, primero deberíamos de alguna forma modelar matemáticamente nuestro sentido común , es aquí, como lo expresa la frase del comienzo del artículo, como surge la teoría de probabilidad . ¿Qué es la teoría de probabilidad? La teoría de probabilidad es la rama de las matemáticas que se ocupa de los fenómenos aleatorios y de la incertidumbre. Existen muchos eventos que no se pueden predecir con certeza; ya que su observación repetida bajo un mismo conjunto específico de condiciones puede arrojar resultados distintos, mostrando un comportamiento errático e impredecible. En estas situaciones, la teoría de probabilidad proporciona los métodos para cuantificar las posibilidades, o probabilidades , asociadas con los diversos resultados. Su estudio ha atraído a un gran número de gente, ya sea por su interés intrínseco como por su aplicación con éxito en las ciencias físicas, biológicas y sociales, así como también en áreas de la ingeniería y en el mundo de los negocios. Cuantificando la incertidumbre Ahora bien, en la definición de arriba dijimos que la teoría de probabilidad , nos proporciona las herramientas para poder cuantificar la incertidumbre, pero ¿cómo podemos realmente cuantificar estos eventos aleatorios y hacer inferencias sobre ellos? La respuesta a esta pregunta es, a su vez, intuitiva y simple; la podemos encontrar en el concepto del espacio de muestreo . El Espacio de muestreo El espacio de muestreo hace referencia a la idea de que los posibles resultados de un proceso aleatorio pueden ser pensados como puntos en el espacio. En los casos más simples, este espacio puede consistir en sólo algunos puntos, pero en casos más complejos puede estar representado por un continuo , como el espacio en que vivimos. El espacio de muestreo , en general se expresa con la letra $S$, y consiste en el conjunto de todos los resultados posibles de un experimento . Si el experimento consiste en el lanzamiento de una moneda, entonces el espacio de muestreo será $S = \\{cara, seca \\}$, ya que estas dos alternativas representan a todos los resultados posibles del experimento . En definitiva el espacio de muestreo no es más que una simple enumeración de todos los resultados posibles, aunque las cosas nunca suelen ser tan simples como aparentan. Si en lugar de considerar el lanzamiento de una moneda, lanzamos dos monedas; uno podría pensar que el espacio de muestreo para este caso será $S = \\{\\text{ 2 caras}, \\text{2 secas}, \\text{cara y seca} \\}$; es decir que de acuerdo con este espacio de muestreo la probabilidad de que obtengamos dos caras es 1 en 3; pero la verdadera probabilidad de obtener dos caras, confirmada por la experimentación, es 1 en 4; la cual se hace evidente si definimos correctamente el espacio de muestreo , que será el siguiente: $S = \\{\\text{ 2 caras}, \\text{2 secas}, \\text{cara y seca}, \\text{seca y cara} \\}$. Como este simple ejemplo nos enseña, debemos ser muy cuidadosos al definir el espacio de muestreo , ya que una mala definición del mismo, puede inducir a cálculos totalmente errados de la probabilidad . Independencia, la ley de grandes números y el teorema del límite central Una de las cosas más fascinantes sobre el estudio de la teoría de probabilidad es que si bien el comportamiento de un evento individual es totalmente impredecible, el comportamiento de una cantidad suficientemente grande de eventos se puede predecir con un alto grado de certeza!. Si tomamos el caso clásico del lanzamiento de una moneda, no podemos predecir con exactitud cuantas caras podemos obtener luego de 10 tiradas, tal vez el azar haga que obtengamos 7, 10, o 3 caras, dependiendo de con cuanta suerte nos encontremos; pero si repetimos el lanzamiento un millón de veces, casi con seguridad que la cantidad de caras se aproximará a la verdadera probabilidad subyacente del experimento, es decir, al 50% de los lanzamientos. Este comportamiento es lo que en la teoría de probabilidad se conoce con el nombre de ley de grandes números ; pero antes de poder definir esta ley, primero debemos describir otro concepto también muy importante, la independencia de los eventos . El concepto de independencia En teoría de probabilidad , podemos decir que dos eventos son independientes cuando la probabilidad de cada uno de ellos no se ve afecta porque el otro evento ocurra, es decir que no existe ninguna relación entre los eventos . En el lanzamiento de la moneda; la moneda no sabe, ni le interesa saber si el resultado del lanzamiento anterior fue cara; cada lanzamiento es un suceso totalmente aislado el uno del otro y la probabilidad del resultado va a ser siempre 50% en cada lanzamiento. Definiendo la ley de grandes números Ahora que ya conocemos el concepto de independencia , estamos en condiciones de dar una definición más formal de la ley de grandes números , que junto con el Teorema del límite central , constituyen los cimientos de la teoría de probabilidad . Podemos formular esta ley de la siguiente manera: si se repite un experimento aleatorio , bajo las mismas condiciones, un número ilimitado de veces; y si estas repeticiones son independientes la una de la otra, entonces la frecuencia de veces que un evento $A$ ocurra, convergerá con probabilidad 1 a un número que es igual a la probabilidad de que $A$ ocurra en una sola repetición del experimento. Lo que esta ley nos enseña, es que la probabilidad subyacente de cualquier suceso aleatorio puede ser aprendido por medio de la experimentación, simplemente tendríamos que repetirlo una cantidad suficientemente grande de veces!. Un error que la gente suele cometer y asociar a esta ley, es la idea de que un evento tiene más posibilidades de ocurrir porque ha o no ha ocurrido recientemente. Esta idea de que las chances de un evento con una probabilidad fija, aumentan o disminuyen dependiendo de las ocurrencias recientes del evento, es un error que se conoce bajo el nombre de la falacia del apostador . Para entender mejor la ley de grandes números , experimentemos con algunos ejemplos en Python . Utilicemos nuevamente el ejemplo del lanzamiento de la moneda, en el primer ejemplo, la moneda va a tener la misma posibilidad de caer en cara o seca; mientras que en el segundo ejemplo, vamos a modificar la probabilidad de la moneda para que caiga cara solo en 1 de 6 veces. In [1]: Ver Código # importando modulos necesarios import matplotlib.pyplot as plt import numpy as np # importando numpy import pandas as pd # importando pandas np . random . seed ( 2131982 ) # para poder replicar el random % matplotlib inline In [2]: # Ejemplo ley de grandes números # moneda p=1/2 cara=1 seca=0 resultados = [] for lanzamientos in range ( 1 , 10000 ): lanzamientos = np . random . choice ([ 0 , 1 ], lanzamientos ) caras = lanzamientos . mean () resultados . append ( caras ) # graficamente df = pd . DataFrame ({ 'lanzamientos' : resultados }) df . plot ( title = 'Ley de grandes números' , color = 'r' , figsize = ( 8 , 6 )) plt . axhline ( 0.5 ) plt . xlabel ( \"Número de lanzamientos\" ) plt . ylabel ( \"frecuencia caras\" ) plt . show () In [3]: # moneda p=1/6 cara=1 seca=0 resultados = [] for lanzamientos in range ( 1 , 10000 ): lanzamientos = np . random . choice ([ 0 , 1 ], lanzamientos , p = [ 5 / 6 , 1 / 6 ]) caras = lanzamientos . mean () resultados . append ( caras ) # graficamente df = pd . DataFrame ({ 'lanzamientos' : resultados }) df . plot ( title = 'Ley de grandes números' , color = 'r' , figsize = ( 8 , 6 )) plt . axhline ( 1 / 6 ) plt . xlabel ( \"Número de lanzamientos\" ) plt . ylabel ( \"frecuencia caras\" ) plt . show () Como estos ejemplos nos muestran, al comienzo, la frecuencia en que vamos obteniendo caras va variando considerablemente, pero a medida que aumentamos el número de repeticiones, la frecuencia de caras se va estabilizando en la probabilidad subyacente el evento, 1 en 2 para el primer caso y 1 en 6 para el segundo ejemplo. En los gráficos podemos ver claramente el comportamiento de la ley. El Teorema del límite central El otro gran teorema de la teoría de probabilidad es el Teorema del límite central . Este teorema establece que la suma o el promedio de casi cualquier conjunto de variables independientes generadas al azar se aproximan a la Distribución Normal . El Teorema del límite central explica por qué la Distribución Normal surge tan comúnmente y por qué es generalmente una aproximación excelente para la media de casi cualquier colección de datos. Este notable hallazgo se mantiene verdadero sin importar la forma que adopte la distribución de datos que tomemos. Para ilustrar también este teorema, recurramos a un poco más de Python . In [4]: # Ejemplo teorema del límite central muestra_binomial = [] muestra_exp = [] muestra_possion = [] muestra_geometric = [] mu = . 9 lam = 1.0 size = 1000 for i in range ( 1 , 20000 ): muestra = np . random . binomial ( 1 , mu , size = size ) muestra_binomial . append ( muestra . mean ()) muestra = np . random . exponential ( scale = 2.0 , size = size ) muestra_exp . append ( muestra . mean ()) muestra = np . random . geometric ( p =. 5 , size = size ) muestra_geometric . append ( muestra . mean ()) muestra = np . random . poisson ( lam = lam , size = size ) muestra_possion . append ( muestra . mean ()) df = pd . DataFrame ({ 'binomial' : muestra_binomial , 'poission' : muestra_possion , 'geometrica' : muestra_geometric , 'exponencial' : muestra_exp }) fig , axes = plt . subplots ( nrows = 2 , ncols = 2 , figsize = ( 10 , 10 )) df . binomial . hist ( ax = axes [ 0 , 0 ], alpha = 0.9 , bins = 1000 ) df . exponencial . hist ( ax = axes [ 0 , 1 ], bins = 1000 ) df . poission . hist ( ax = axes [ 1 , 0 ], bins = 1000 ) df . geometrica . hist ( ax = axes [ 1 , 1 ], bins = 1000 ) axes [ 0 , 0 ] . set_title ( 'Binomial' ) axes [ 0 , 1 ] . set_title ( 'Poisson' ) axes [ 1 , 0 ] . set_title ( 'Geométrica' ) axes [ 1 , 1 ] . set_title ( 'Exponencial' ) plt . show () Como nos muestra este ejemplo, al graficar la distribución de las medias de las distribuciones Binomial , Poisson , Geométrica y Exponencial ; vemos que todas ellas responden a la famosa forma de campana de la Distribución Normal !. Algo realmente sorprendente! Calculando probabilidades Saber calcular la probabilidad de que un evento o varios eventos ocurran puede ser una habilidad valiosa al tomar decisiones, ya sea en la vida real o jugando juegos de azar. Cómo calcular la probabilidad , sin embargo, cambia dependiendo del tipo de evento que se está observando. Por ejemplo, no calcularíamos nuestras posibilidades de ganar la lotería de la misma manera que calcularíamos nuestras posibilidades de obtener una generala servida en un juego de dados. Sin embargo, una vez que determinamos si los eventos son independientes , condicionales o mutuamente excluyentes, calcular su probabilidad es relativamente simple. Propiedades básicas de la probabilidad Antes de poder calcular las probabilidades , primero debemos conocer sus 3 propiedades fundamentales, ellas son: La probabilidad se expresa como un ratio que será un valor positivo menor o igual a 1. $ 0 \\le p(A) \\le 1$ La probabilidad de un evento del que tenemos total certeza es 1. $ p(S) = 1 $ Si el evento $A$ y el evento $B$ son mutuamente excluyentes , entonces: $ p(A \\cup B ) = p(A) + p(B) $ A partir de estas propiedades básicas, se pueden derivar muchas otras propiedades. Teoría de conjuntos y probabilidades En mi artículo sobre conjuntos comentaba que la teoría de conjuntos se ha convertido en un pilar fundamental de las matemáticas, casi cualquier rama de las matemáticas puede ser definida utilizando conjuntos ; y la teoría de probabilidad no es la excepción. Antes de poder calcular probabilidades , primero debemos discutir como se relacionan los eventos en términos de la teoría de conjuntos . Las relaciones que podemos encontrar son: Unión: La unión de varios eventos simples crea un evento compuesto que ocurre si uno o más de los eventos ocurren. La unión de $E$ y $F$ se escribe $E \\cup F$ y significa \"Ya sea $E$ o $F$, o ambos $E$ y $F$.\" Intersección: La intersección de dos o más eventos simples crea un evento compuesto que ocurre sólo si ocurren todos los eventos simples. La intersección de $E$ y $F$ se escribe $E \\cap F$ y significa \"$E$ y $F$.\" Complemento: El complemento de un evento significa todo en el espacio de muestreo que no es ese evento. El complemento del evento $E$ se escribe varias veces como $\\sim{E}$, $E^c$, o $\\overline{E}$, y se lee como \"no $E$\" o \"complemento $E$\". Exclusión mutua: Si los eventos no pueden ocurrir juntos, son mutuamente excluyentes . Siguiendo la misma línea de razonamiento, si dos conjuntos no tienen ningún evento en común, son mutuamente excluyentes. Calculando la probabilidad de múltiples eventos Ahora sí, ya podemos calcular las probabilidades de los eventos. Recordemos que la probabilidad de un solo evento se expresa como un ratio entre el número de resultados favorables sobre el número de los posibles resultados. Pero ¿qué pasa cuando tenemos múltiples eventos? Unión de eventos mutuamente excluyentes Si los eventos son mutuamente excluyentes entonces para calcular la probabilidad de su unión, simplemente sumamos sus probabilidades individuales. $p(E \\cup F) = p(E) + p(F)$ Unión de eventos que no son mutuamente excluyentes Si los eventos no son mutuamente excluyentes entonces debemos corregir la fórmula anterior para incluir el efecto de la superposición de los eventos. Esta superposición se da en el lugar de la intersección de los eventos; por lo tanto la formula para calcular la probabilidad de estos eventos es: $p(E \\cup F) = p(E) + p(F) - p(E \\cap F)$ Intersección de eventos independientes Para calcular la probabilidad de que ocurran varios eventos (la intersección de varios eventos), se multiplican sus probabilidades individuales. La fórmula específica utilizada dependerá de si los eventos son independientes o no. Si son independientes , la probabilidad de $E$ y $F$ se calcula como: $p(E \\cap F) = p(E) \\times p(F)$ Intersección de eventos no independientes Si dos eventos no son independientes , debemos conocer su probabilidad condicional para poder calcular la probabilidad de que ambos se produzcan. La fórmula en este caso es: $p(E \\cap F) = p(E) \\times p(F|E)$ La probabilidad condicional Con frecuencia queremos conocer la probabilidad de algún evento, dado que otro evento ha ocurrido. Esto se expresa simbólicamente como $p(E | F)$ y se lee como \"la probabilidad de $E$ dado $F$\". El segundo evento se conoce como la condición y el proceso se refiere a veces como \"condicionamiento en F\". La probabilidad condicional es un concepto importante de estadística, porque a menudo estamos tratando de establecer que un factor tiene una relación con un resultado, como por ejemplo, que las personas que fuman cigarrillos tienen más probabilidades de desarrollar cáncer de pulmón. La probabilidad condicional también se puede usar para definir la independencia . Dos variables se dice que son independientes si la siguiente relación se cumple: $p(E | F) = p(E)$ Calculando la probabilidad condicional Para calcular la probabilidad del evento $E$ dada la información de que el evento $F$ ha ocurrido utilizamos la siguiente formula: $p(E | F) = \\frac{p(E \\cap F)}{p(F)}$ Jugando con Probabilidades y Python Bien, ahora que ya sabemos como calcular probabilidades , llegó finalmente el momento de ponerse a resolver las situaciones planteadas en el comienzo, para eso vamos a utilizar nuevamente un poco de Python . Resolviendo la situación 1 - La coincidencia de cumpleaños La paradoja del cumpleaños es un problema muy conocido en el campo de la teoría de probabilidad . Plantea las siguientes interesantes preguntas: ¿Cuál es la probabilidad de que, en un grupo de personas elegidas al azar, al menos dos de ellas habrán nacido el mismo día del año? ¿Cuántas personas son necesarias para asegurar una probabilidad mayor al 50%?. Excluyendo el 29 de febrero de nuestros cálculos y asumiendo que los restantes 365 días de posibles cumpleaños son igualmente probables, nos sorprendería darnos cuenta de que, en un grupo de sólo 23 personas, la probabilidad de que dos personas compartan la misma fecha de cumpleaños es mayor al 50%!. Esto ya nos dice algo respecto a nuestras chances en la apuesta con nuestro amigo, pero de todas formas calculemos la probabilidad en un grupo de 50 personas. Calcular la probabilidad de un cumpleaños duplicado puede parecer una tarea desalentadora. Pero ¿qué pasa con calcular la probabilidad de que no haya un cumpleaños duplicado? Esto es realmente una tarea más fácil. Especialmente si simplificamos el problema a un grupo muy pequeño. Supongamos que el grupo sólo tiene una persona, en ese caso, hay una probabilidad del 100% que esta persona no comparte un cumpleaños puesto que no hay nadie más quien compartir. Pero ahora podemos añadir una segunda persona al grupo. ¿Cuáles son las posibilidades de que tenga un cumpleaños diferente de esa persona? De hecho esto es bastante fácil, hay 364 otros días en el año, así que las posibilidades son 364/365. ¿Qué tal si agregamos una tercera persona al grupo? Ahora hay 363/365 días. Para obtener la probabilidad general de que no hay cumpleaños compartidos simplemente multiplicamos las probabilidades individuales. Si utilizamos este procedimiento, con la ayuda de Python podemos calcular fácilmente las probabilidades de un cumpleaños compartido en un grupo de 50 personas. In [5]: # Ejemplo situación 2 La coincidencia de cumpleaños prob = 1.0 asistentes = 50 for i in range ( asistentes ): prob = prob * ( 365 - i ) / 365 print ( \"Probabilidad de que compartan una misma fecha de cumpleaños es {0:.2f} \" . format ( 1 - prob )) Probabilidad de que compartan una misma fecha de cumpleaños es 0.97 Como vemos, la apuesta de nuestro amigo es casi una apuesta segura para él. Se ve que conoce bastante bien la teoría de probabilidad y quiere disfrutar de la fiesta a consta nuestra! Resolviendo la situación 2 - ¿Que puerta elegir? Este problema, más conocido con el nombre de Monty Hall , también es un problema muy popular dentro de la teoría de probabilidad ; y se destaca por su solución que a simple vista parece totalmente anti-intuitiva. Intuitivamente, es bastante sencillo que nuestra elección original (cuando hay tres puertas para elegir) tiene una probabilidad de 1/3 de ganar el concurso. Las cosas sin embargo se complican, cuando se descarta una puerta. Muchos dirían que ahora tenemos una probabilidad de 1/2 de ganar, seleccionando cualquiera de las dos puertas; pero este no es el caso. Un aspecto crítico del problema es darse cuenta de que la elección de la puerta a descartar por el presentador, no es una decisión al azar. El presentador puede descartar una puerta porque él sabe (a) qué puerta hemos seleccionado y (b) qué puerta tiene la ferrari. De hecho, en muchos casos, el presentador debe quitar una puerta específica. Por ejemplo, si seleccionamos la puerta 1 y el premio está detrás de la puerta 3, el presentador no tiene otra opción que retirar la puerta 2. Es decir, que la elección de la puerta a descartar está condicionada tanto por la puerta con el premio como por la puerta que seleccionamos inicialmente. Este hecho, cambia totalmente la naturaleza del juego, y hace que las probabilidades de ganar sean 2/3 si cambiamos de puerta!. Si aun no están convencidos, simulemos los resultados del concurso con la ayuda de Python . In [6]: Ver Código # Ejemplo situación 2 ¿Que puerta elegir? (el problema Monty Hall) def elegir_puerta (): \"\"\" Función para elegir una puerta. Devuelve 1, 2, o 3 en forma aleatoria. \"\"\" return np . random . randint ( 1 , 4 ) class MontyHall : \"\"\" Clase para modelar el problema de Monty Hall. \"\"\" def __init__ ( self ): \"\"\" Crea la instancia del problema. \"\"\" # Elige una puerta en forma aleatoria. self . puerta_ganadora = elegir_puerta () # variables para la puerta elegida y la puerta descartada self . puerta_elegida = None self . puerta_descartada = None def selecciona_puerta ( self ): \"\"\" Selecciona la puerta del concursante en forma aleatoria. \"\"\" self . puerta_elegida = elegir_puerta () def descarta_puerta ( self ): \"\"\" Con este método el presentador descarta una de la puertas. \"\"\" # elegir puerta en forma aleatoria . d = elegir_puerta () # Si es al puerta ganadora o la del concursante, volver a elegir. while d == self . puerta_ganadora or d == self . puerta_elegida : d = elegir_puerta () # Asignar el valor a puerta_descartada. self . puerta_descartada = d def cambiar_puerta ( self ): \"\"\" Cambia la puerta del concursante una vez que se elimino una puerta. \"\"\" # 1+2+3=6. Solo existe una puerta para elegir. self . puerta_elegida = 6 - self . puerta_elegida - self . puerta_descartada def gana_concursante ( self ): \"\"\" Determina si el concursante gana. Devuelve True si gana, False si pierde. \"\"\" return self . puerta_elegida == self . puerta_ganadora def jugar ( self , cambiar = True ): \"\"\" Una vez que la clase se inicio, jugar el concurso. 'cambiar' determina si el concursante cambia su elección. \"\"\" # El concursante elige una puerta. self . selecciona_puerta () # El presentador elimina una puerta. self . descarta_puerta () # El concursante cambia su elección. if cambiar : self . cambiar_puerta () # Determinar si el concursante ha ganado. return self . gana_concursante () In [7]: # Ahora, jugamos el concurso. primero nos vamos a quedar con nuestra elección # inicial. Vamos a ejecutar el experimiento 10.000 veces. gana , pierde = 0 , 0 for i in range ( 10000 ): # Crear la instancia del problema. s2 = MontyHall () # ejecutar el concurso sin cambiar de puerta.. if s2 . jugar ( cambiar = False ): # si devuelve True significa que gana. gana += 1 else : # si devuelve False significa que pierde. pierde += 1 # veamos la fecuencia de victorias del concursante. porc_gana = 100.0 * gana / ( gana + pierde ) print ( \" \\n 10.000 concursos sin cambiar de puerta:\" ) print ( \" gana: {0:} concursos\" . format ( gana )) print ( \" pierde: {0:} concursos\" . format ( pierde )) print ( \" probabilidad: {0:.2f} procentaje de victorias\" . format ( porc_gana )) 10.000 concursos sin cambiar de puerta: gana: 3311 concursos pierde: 6689 concursos probabilidad: 33.11 procentaje de victorias In [8]: # Ahora, jugamos el concurso siempre cambiando la elección inicial # Vamos a ejecutar el experimiento 10.000 veces. gana , pierde = 0 , 0 for i in range ( 10000 ): # Crear la instancia del problema. s2 = MontyHall () # ejecutar el concurso sin cambiar de puerta.. if s2 . jugar ( cambiar = True ): # si devuelve True significa que gana. gana += 1 else : # si devuelve False significa que pierde. pierde += 1 # veamos la fecuencia de victorias del concursante. porc_gana = 100.0 * gana / ( gana + pierde ) print ( \" \\n 10.000 concursos cambiando de puerta:\" ) print ( \" gana: {0:} concursos\" . format ( gana )) print ( \" pierde: {0:} concursos\" . format ( pierde )) print ( \" probabilidad: {0:.2f} procentaje de victorias\" . format ( porc_gana )) 10.000 concursos cambiando de puerta: gana: 6591 concursos pierde: 3409 concursos probabilidad: 65.91 procentaje de victorias Como esta simulación lo demuestra, si utilizamos la estrategia de siempre cambiar de puerta, podemos ganar el concurso un 66% de las veces; mientras que si nos quedamos con nuestra elección inicial, solo ganaríamos el 33% de las veces. Distintas interpretaciones de la probabilidad Las probabilidades pueden ser interpretadas generalmente de dos maneras distintas. La interpretación frecuentista u objetivista de la probabilidad es una perspectiva en la que las probabilidades se consideran frecuencias relativas constantes a largo plazo. Este es el enfoque clásico de la teoría de probabilidad . La interpretación Bayesiana o subjetivista de la probabilidad es una perspectiva en la que las probabilidades son consideradas como medidas de creencia que pueden cambiar con el tiempo para reflejar nueva información. El enfoque clásico sostiene que los métodos bayesianos sufren de falta de objetividad, ya que diferentes individuos son libres de asignar diferentes probabilidades al mismo evento según sus propias opiniones personales. Los bayesianos se oponen a los clásicos sosteniendo que la interpretación frecuentista de la probabilidad tiene ya de por sí una subjetividad incorporada (por ejemplo, mediante la elección y el diseño del procedimiento de muestreo utilizado) y que la ventaja del enfoque bayesiano es que ya hace explícita esta subjetividad. En la actualidad, la mayoría de los problemas son abordados siguiendo un enfoque mixto entre ambas interpretaciones de la probabilidad . El poder de los números aleatorios Uno podría pensar que un comportamiento aleatorio es caótico y totalmente opuesto a la razón, que sería una forma de renunciar a un problema, un último recurso. Pero lejos de esto, el sorprendente y cada vez más importante rol que viene desempeñando lo aleatorio en las ciencias de la computación nos enseña que el hacer un uso deliberado de lo aleatorio puede ser una forma muy efectiva de abordar los problemas más difíciles; incluso en algunos casos, puede ser el único camino viable. Los Algoritmos probabilísticos como el método Miller-Rabin para encontrar números primos y el método de Monte Carlo , nos demuestran lo poderoso que puede ser utilizar la aleatoriedad para resolver problemas. Muchas veces, la mejor solución a un problema, puede ser simplemente dejarlo al azar en lugar de tratar de razonar totalmente su solución! Aquí concluye este artículo. Espero les haya resultado útil y encuentren tan fascinante como yo a la teoría de probabilidad ; después de todo, la incertidumbre esta en todo lo que nos rodea y la casualidad es un concepto más fundamental que la causalidad! Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Pobabilidad y Estadistica","url":"https://relopezbriega.github.io/blog/2016/11/26/introduccion-a-la-teoria-de-probabilidad-con-python/"},{"title":"Series de tiempo con Python","text":"Introducción Los datos obtenidos a partir de observaciones recogidas a lo largo del tiempo son extremadamente comunes. En los negocios, observamos las tasas de interés de la semana, los precios de cierre de las acciones diarios, los índices de precios mensuales, las cifras de ventas anuales, y así sucesivamente. En meteorología, observamos las temperaturas máximas y mínimas diarias, los índices anuales de precipitación y de sequía, y las velocidades del viento por hora. En la agricultura, registramos las cifras anuales de producción agrícola y ganadera, la erosión del suelo, y las ventas de exportación. En las ciencias biológicas, observamos la actividad eléctrica del corazón en intervalos de milisegundos. La lista de las áreas en las que se estudian las series de tiempo es prácticamente interminable. ¿Qué es una serie de tiempo? Una serie de tiempo o serie temporal es una secuencia de datos, observaciones o valores, medidos en determinados momentos y ordenados cronológicamente. Los datos pueden estar espaciados a intervalos iguales o desiguales. Una vez que se captura una serie de tiempo , a menudo se realiza un análisis sobre ella para identificar patrones en los datos, en esencia, lo que se busca es entender que suceda a medida que el tiempo va avanzando. Ser capaz de procesar datos de series de tiempo es una habilidad esencial en el mundo moderno. Uno de los usos más habituales de las series de tiempo es su análisis para predicción y pronóstico (así se hace por ejemplo con los datos climáticos, las acciones de bolsa, o las series de datos demográficos). Resulta difícil imaginar una rama de las ciencias en la que no aparezcan datos que puedan ser considerados como series de tiempo . ¿Qué las hace especiales? Las características que hacen a las series de tiempo especiales y las diferencia de, por ejemplo, un problema de regresión son las siguientes: Son dependientes del tiempo ; por lo tanto el supuesto básico de los modelos de regresión de que las observaciones son independientes no se sostiene en este caso. Suelen tener una tendencia ; la mayoría de las series de tiempo suelen tener algún tipo de tendencia de estacionalidad, es decir, las variaciones propias de un período de tiempo determinado. Suelen estar autocorrelacionadas ; la mayoría de los procesos físicos presentan una inercia y no cambian tan rápidamente. Esto, combinado con la frecuencia del muestreo, a menudo hace que las observaciones consecutivas estén correlacionadas . Esta correlación entre observaciones consecutivas se llama autocorrelación . Cuando los datos están autocorrelacionados , la mayoría de los métodos estadísticos estándares basados en la suposición de observaciones independientes pueden arrojar resultados engañosos o incluso ser inútiles. Series de tiempo estacionarias Un tipo muy importante de series de tiempo son las series de tiempo estacionarias . Una series de tiempo se dice que es estrictamente estacionaria si sus propiedades no son afectadas por los cambios a lo largo del tiempo. Es decir, que se deberían cumplir tres criterios básicos para poder considerar a una series de tiempo como estacionaria : La media de la serie no debe ser una función de tiempo ; sino que debe ser constante. La siguiente imagen muestra una serie que cumple con esta condición y otra que no la cumple. La varianza de la serie no debe ser una función del tiempo . El siguiente gráfico representa una serie cuya varianza no esta afectada por el tiempo (es estacionaria ) y otra que no cumple con esa condición. La covarianza de la serie no debe ser una función del tiempo . En el gráfico de la derecha, se puede observar que la propagación de la serie se va encogiendo a medida que aumenta el tiempo. Por lo tanto, la covarianza no es constante en el tiempo para la serie roja . ¿Por qué son importantes las series de tiempo estacionarias? La razón por la que estas series son importantes es que la mayoría de los modelos de series de tiempo funcionan bajo el supuesto de que la serie es estacionaria . Intuitivamente, podemos suponer que si una serie tiene un comportamiento particular en el tiempo, hay una probabilidad muy alta de que se comportamiento continúe en el futuro. Además, las teorías relacionadas con las series estacionarias son más maduras y más fáciles de implementar en comparación con series no estacionarias. A pesar de que el supuesto de que la serie es estacionaria se utiliza en muchos modelos, casi ninguna de las series de tiempo que encontramos en la práctica son estacionarias . Por tal motivo la estadística tuvo que desarrollar varias técnicas para hacer estacionaria , o lo más cercano posible a estacionaria , a una serie . Series de tiempo con Python Las principales librerías que nos ofrece Python para trabajar con series de tiempo son: Statsmodels : Esta librería contiene muchos objetos y funciones de suma utilidad para el análisis de series de tiempo . Algunos de los modelos que están cubiertos por Statsmodels incluyen: el modelo autorregresivo (AR); el modelo autorregresivo de vectores (VAR); y el modelo autorregresivo de media móvil (ARMA). También incluye funciones de estadística descriptiva de series de tiempo , como por ejemplo la autocorrelación , así como las correspondientes propiedades teóricas de ARMA o procesos relacionados. Por último, también ofrece las pruebas estadísticas relacionadas y algunas funciones auxiliares muy útiles. Pandas : Pandas proporciona un amplio soporte para trabajar con datos de series de tiempo . Generalmente cuando trabajamos con series de tiempo realizamos un amplio abanico de tareas, como: convertir fechas, estandarizar el tiempo de acuerdo a la zona horaria, crear secuencias a determinados intervalos o frecuencias, identificar datos faltantes, desplazar las fechas hacia atrás o hacia adelante por un determinado valor, calcular resúmenes agregados de valores a medida que el tiempo cambia, etc. Pandas nos brinda las herramientas para poder realizar estas y muchas otras tareas en forma muy sencilla. Veamos algunos ejemplos de como podemos manipular y analizar series de tiempo con la ayuda de Python . En este caso, vamos a jugar un poco con la información de los precios de las acciones de Weatherford ( WFT ) de este año. Manipulando la serie de tiempo con pandas In [1]: Ver Código # importando modulos necesarios import numpy as np import pandas as pd import pandas.io.data as web import datetime as dt from pydataset import data import statsmodels.api as sm # librerías de visualizaciones import seaborn as sns import matplotlib.pyplot as plt # graficos incrustados % matplotlib inline # pandas solo 4 decimales pd . set_option ( 'precision' , 4 ) In [2]: # Ejemplo serie de tiempo con Pandas # Creando una serie de tiempo de las acciones de WFT desde yahoo finance wft = web . DataReader ( \"WFT\" , 'yahoo' , '2016-1-1' , '2016-9-30' ) wft . head ( 5 ) Out[2]: Open High Low Close Volume Adj Close Date 2016-01-04 8.40 8.70 8.29 8.64 10719400 8.64 2016-01-05 8.67 8.80 8.13 8.26 9109100 8.26 2016-01-06 7.94 8.16 7.84 7.91 13203200 7.91 2016-01-07 7.69 7.83 7.34 7.34 12633800 7.34 2016-01-08 7.48 7.55 6.86 6.97 18547500 6.97 In [3]: # filtrando sólo del 2016-02-04 al 2016-02-18 wft [ '2016-02-04' : '2016-02-18' ] Out[3]: Open High Low Close Volume Adj Close Date 2016-02-04 7.10 7.82 6.99 7.39 34474500 7.39 2016-02-05 7.37 7.52 6.87 6.94 27775700 6.94 2016-02-08 6.68 6.79 6.41 6.74 17611300 6.74 2016-02-09 6.60 6.72 6.07 6.34 13741100 6.34 2016-02-10 6.28 6.59 6.11 6.24 8623900 6.24 2016-02-11 6.02 6.27 5.74 6.06 17133900 6.06 2016-02-12 6.14 6.66 6.06 6.47 13498600 6.47 2016-02-16 6.66 6.74 6.33 6.62 11453500 6.62 2016-02-17 6.70 7.13 6.55 6.72 29061300 6.72 2016-02-18 6.95 6.96 6.22 6.51 13587900 6.51 In [4]: # valores al 2016-02-16 wft . loc [ '2016-2-16' ] Out[4]: Open 6.6600e+00 High 6.7400e+00 Low 6.3300e+00 Close 6.6200e+00 Volume 1.1454e+07 Adj Close 6.6200e+00 Name: 2016-02-16 00:00:00, dtype: float64 In [5]: # valor de la columna Adj Close al 2016-2-16 wft [ 'Adj Close' ][ '2016-2-16' ] Out[5]: 6.6200000000000001 In [6]: # filtrando todo febrero de 2016 wft [ '2016-2' ] Out[6]: Open High Low Close Volume Adj Close Date 2016-02-01 6.51 6.67 5.90 6.33 36665900 6.33 2016-02-02 6.12 6.16 5.82 5.97 21091100 5.97 2016-02-03 6.04 6.40 5.60 6.27 24870400 6.27 2016-02-04 7.10 7.82 6.99 7.39 34474500 7.39 2016-02-05 7.37 7.52 6.87 6.94 27775700 6.94 2016-02-08 6.68 6.79 6.41 6.74 17611300 6.74 2016-02-09 6.60 6.72 6.07 6.34 13741100 6.34 2016-02-10 6.28 6.59 6.11 6.24 8623900 6.24 2016-02-11 6.02 6.27 5.74 6.06 17133900 6.06 2016-02-12 6.14 6.66 6.06 6.47 13498600 6.47 2016-02-16 6.66 6.74 6.33 6.62 11453500 6.62 2016-02-17 6.70 7.13 6.55 6.72 29061300 6.72 2016-02-18 6.95 6.96 6.22 6.51 13587900 6.51 2016-02-19 6.47 6.51 5.97 6.20 14541500 6.20 2016-02-22 6.20 6.92 6.20 6.75 11878300 6.75 2016-02-23 6.61 6.68 6.07 6.12 9486500 6.12 2016-02-24 5.93 6.14 5.77 6.08 8333800 6.08 2016-02-25 6.07 6.13 5.68 5.93 8972000 5.93 2016-02-26 6.13 6.55 6.07 6.43 12288100 6.43 2016-02-29 6.43 6.62 6.33 6.40 14120300 6.40 In [7]: # Valores al cierre de cada mes. wft . asfreq ( 'M' , method = 'ffill' ) Out[7]: Open High Low Close Volume Adj Close Date 2016-01-31 6.26 6.77 6.20 6.74 17661000 6.74 2016-02-29 6.43 6.62 6.33 6.40 14120300 6.40 2016-03-31 7.62 7.86 7.55 7.78 13224600 7.78 2016-04-30 8.10 8.34 7.88 8.13 21137000 8.13 2016-05-31 5.61 5.74 5.55 5.61 8481400 5.61 2016-06-30 5.50 5.58 5.36 5.55 14896000 5.55 2016-07-31 5.64 5.81 5.59 5.68 19153500 5.68 2016-08-31 5.48 5.59 5.37 5.47 11293500 5.47 In [8]: # Valores al cierre de cada mes (días laborales). wft . asfreq ( 'BM' ) Out[8]: Open High Low Close Volume Adj Close Date 2016-01-29 6.26 6.77 6.20 6.74 17661000 6.74 2016-02-29 6.43 6.62 6.33 6.40 14120300 6.40 2016-03-31 7.62 7.86 7.55 7.78 13224600 7.78 2016-04-29 8.10 8.34 7.88 8.13 21137000 8.13 2016-05-31 5.61 5.74 5.55 5.61 8481400 5.61 2016-06-30 5.50 5.58 5.36 5.55 14896000 5.55 2016-07-29 5.64 5.81 5.59 5.68 19153500 5.68 2016-08-31 5.48 5.59 5.37 5.47 11293500 5.47 In [9]: # valores al cierre de cada trimestre wft . asfreq ( 'BQ' ) Out[9]: Open High Low Close Volume Adj Close Date 2016-03-31 7.62 7.86 7.55 7.78 13224600 7.78 2016-06-30 5.50 5.58 5.36 5.55 14896000 5.55 Desplazando los valores de la serie Una operación común en los datos de series de tiempo es desplazar los valores hacia atrás y adelante en el tiempo, como por ejemplo para calcular el cambio porcentual de una muestra a otra. En Pandas podemos utilizar el método .shift() . In [10]: # desplazando el 1 dia el valor de cierre desplazado = wft [ 'Adj Close' ] . shift ( 1 ) desplazado [: 5 ] Out[10]: Date 2016-01-04 NaN 2016-01-05 8.64 2016-01-06 8.26 2016-01-07 7.91 2016-01-08 7.34 Name: Adj Close, dtype: float64 In [11]: # calculando el porcentaje de variación del día. variacion_diaria = wft [ 'Adj Close' ] / wft [ 'Adj Close' ] . shift ( 1 ) - 1 wft [ 'var_diaria' ] = variacion_diaria wft [ 'var_diaria' ][: 5 ] Out[11]: Date 2016-01-04 NaN 2016-01-05 -0.0440 2016-01-06 -0.0424 2016-01-07 -0.0721 2016-01-08 -0.0504 Name: var_diaria, dtype: float64 In [12]: # mismo resultado utilizando pct_change() wft [ 'Adj Close' ] . pct_change ()[: 5 ] Out[12]: Date 2016-01-04 NaN 2016-01-05 -0.0440 2016-01-06 -0.0424 2016-01-07 -0.0721 2016-01-08 -0.0504 Name: Adj Close, dtype: float64 In [13]: # calculando rendimiento acumulado diario rendimiento_diario = ( 1 + wft [ 'Adj Close' ] . pct_change ()) . cumprod () wft [ 'rend_diario' ] = rendimiento_diario wft [ 'rend_diario' ][: 5 ] Out[13]: Date 2016-01-04 NaN 2016-01-05 0.9560 2016-01-06 0.9155 2016-01-07 0.8495 2016-01-08 0.8067 Name: rend_diario, dtype: float64 Visualizando las series de tiempo Una operación fundamental para entender el comportamiento de una serie de tiempo y poder determinar si se trata de una serie estacionaria o no; es realizar gráficos de la misma. En Pandas esto lo podemos realizar en forma muy sencilla con el método .plot() . In [14]: # graficando Adj Close plot = wft [ 'Adj Close' ] . plot ( figsize = ( 10 , 8 )) In [15]: # Aplicando el filtro Hodrick-Prescott para separar en tendencia y # componente ciclico. wft_ciclo , wft_tend = sm . tsa . filters . hpfilter ( wft [ 'Adj Close' ]) wft [ 'tend' ] = wft_tend In [16]: # graficando la variacion del precio real con la tendencia. wft [[ 'Adj Close' , 'tend' ]] . plot ( figsize = ( 10 , 8 ), fontsize = 12 ); legend = plt . legend () legend . prop . set_size ( 14 ); In [17]: # graficando rendimiento diario plot = wft [ 'var_diaria' ] . plot ( figsize = ( 10 , 8 )) Promedios móviles y descomposición Pandas también nos ofrece una serie de funciones para calcular estadísticas móviles, en ellas la función estadística se calcula sobre una ventana de datos representados por un determinado período de tiempo y luego se desplaza la ventana de datos por un intervalo especificado, calculando continuamente la estadística, siempre y cuando la ventana este dentro de las fechas de la serie de tiempo . El ejemplo más utilizado es el de media móvil , que se usa comúnmente en el análisis de series de tiempo financieras para suavizar las fluctuaciones a corto plazo y poner de relieve las tendencias a largo plazo en los datos. Otra técnica interesante que podemos intentar también es la descomposición. Esta es una técnica que trata de descomponer una serie de tiempo en su tendencia, su estacionalidad y sus factores residuales. Statsmodels viene con una función de descomposición que nos facilita en sobremanera el trabajo. Veamos unos ejemplos. In [18]: # Calculando promedios móviles cada 5 días wft_ma = pd . rolling_mean ( wft [ 'Adj Close' ], 5 ) wft [ 'prod_mov' ] = wft_ma plot = wft [[ 'Adj Close' , 'prod_mov' ]] . plot ( figsize = ( 10 , 8 ), fontsize = 12 ) In [19]: # Ejemplo de descomposición de serie de tiempo descomposicion = sm . tsa . seasonal_decompose ( wft [ 'Adj Close' ], model = 'additive' , freq = 30 ) fig = descomposicion . plot () Pronosticando la serie con ARIMA Como podemos observar en los gráficos que realizamos anteriormente, el comportamiento de la serie de tiempo con la que estamos trabajando parece ser totalmente aleatorio y las medidas móviles que calculamos tampoco parecen ser de mucha utilidad para acercar la serie a un comportamiento estacionario . De todas formas podemos intentar aplicar un modelo ARIMA sobre la serie y ver que tan bien nos va con el pronostico del modelo. El modelo ARIMA es similar a una regresión estadística pero aplicando los conceptos de las series de tiempo ; por tanto, los pronósticos del modelo vienen explicadas por los datos del pasado y no por variables independientes. In [20]: # Modelo ARIMA sobre el valor de cierre de la acción. modelo = sm . tsa . ARIMA ( wft [ 'Adj Close' ] . iloc [ 1 :], order = ( 1 , 0 , 0 )) resultados = modelo . fit ( disp =- 1 ) wft [ 'pronostico' ] = resultados . fittedvalues plot = wft [[ 'Adj Close' , 'pronostico' ]] . plot ( figsize = ( 10 , 8 )) Aquí el modelo parece ser bastante efectivo, las líneas en el gráfico son muy similares. Pero para armar el modelo hemos utilizado el valor de cierre de la acción, y la variación de precio en el día a día es muy pequeña en comparación al precio absoluto. Lo que realmente nos interesa predecir es la variación diaria del precio de la acción, por lo tanto deberíamos armar el modelo utilizando la columna de variación diaria que calculamos previamente. In [21]: # modelo ARIMA sobre variación diaria modelo = sm . tsa . ARIMA ( wft [ 'var_diaria' ] . iloc [ 1 :], order = ( 1 , 0 , 0 )) resultados = modelo . fit ( disp =- 1 ) wft [ 'pronostico' ] = resultados . fittedvalues plot = wft [[ 'var_diaria' , 'pronostico' ]] . plot ( figsize = ( 10 , 8 )) En este gráfico podemos ver que es bastante obvio que el pronóstico esta muy lejos. Nuestro modelo predice variaciones muy pequeñas en comparación con lo que ocurre en la realidad del día a día. Este era un resultado esperado ya que solo aplicamos un modelo sencillo de promedios móviles a una serie no estacionaria ; después de todo, si fuera tan fácil predecir el movimiento del mercado, todos seríamos millonarios!. No hay suficiente información en los días anteriores para poder predecir con exactitud lo que va a pasar al día siguiente. Aquí concluye este paseo por el mundo de las series de tiempo ; como vimos, Pandas y Statsmodels pueden ser de mucha ayuda para trabajar con ellas. Espero que lo hayan encontrado útil. Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Finanzas","url":"https://relopezbriega.github.io/blog/2016/09/26/series-de-tiempo-con-python/"},{"title":"Visualizaciones de datos con Python","text":"Introducción Las visualizaciones son una herramienta fundamental para entender y compartir ideas sobre los datos. La visualización correcta puede ayudar a expresar una idea central, o abrir un espacio para una más profunda investigación; con ella se puede conseguir que todo el mundo hable sobre un conjunto de datos , o compartir una visión sobre lo que los datos nos quieren decir. Una buena visualización puede dar a quien la observa un sentido rico y amplio de un conjunto de datos . Puede comunicar los datos de manera precisa a la vez que expone los lugares en dónde se necesita más información o dónde una hipótesis no se sostiene. Por otra parte, la visualización nos proporciona un lienzo para aplicar nuestras propias ideas, experiencias y conocimientos cuando observamos y analizamos datos, permitiendo realizar múltiples interpretaciones. Si como dice el dicho \"una imagen vale más que mil palabras\" , un gráfico interactivo bien elegido entonces podría valer cientos de pruebas estadísticas . Librerías para visualizar datos en Python Como bien sabemos, la comunidad de Python es muy grande, por lo tanto vamos a poder encontrar un gran número de librerías para visualizar datos. Al tener tanta variedad de opciones, a veces se hace realmente difícil determinar cuando utilizar cada una de ellas. En este artículo yo voy a presentar solo cuatro que creo que cubren un gran abanico de casos: Matplotlib : Que es la más antigua y se convirtió en la librería por defecto para visualizaciones de datos; muchas otras están basadas en ella. Es extremadamente potente, pero con ese poder viene aparejada la complejidad. Se puede hacer prácticamente de todo con Matplotlib pero no siempre es tan fácil de averiguar como hacerlo. Los que siguen el blog me habrán visto utilizarla en varios artículos. Bokeh : Una de las más jóvenes librerías de visualizaciones, pero no por ello menos potente. Bokeh es una librería para visualizaciones interactivas diseñada para funcionar en los navegadores web modernos. Su objetivo es proporcionar una construcción elegante y concisa de gráficos modernos al estilo de D3.js , y para ampliar esta capacidad con la interactividad y buen rendimiento sobre grandes volúmenes de datos. Bokeh puede ayudar a cualquier persona a crear en forma rápida y sencilla gráficos interactivos, dashboards y aplicaciones de datos. Puede crear tanto gráficos estáticos como gráficos interactivos en el servidor de Bokeh . Seaborn : Si de gráficos estadísticos se trata, Seaborn es la librería que deberíamos utilizar, con ella podemos crear gráficos estadísticos informativos y atractivos de forma muy sencilla. Es una de las tantas librerías que se basan en Matplotlib pero nos ofrece varias características interesantes tales como temas, paletas de colores, funciones y herramientas para visualizar distribuciones de una o varias variables aleatorias , regresiones lineales , series de tiempo , entre muchas otras. Con ella podemos construir visualizaciones complejas en forma sencilla. Folium : Si lo que necesitamos es visualizar datos de geolocalización en mapas interactivos, entonces Folium es una muy buena opción. Esta librería de Python es una herramienta sumamente poderosa para realizar mapas al estilo leaflet.js . El hecho de que los resultados de Folium son interactivos hace que esta librería sea útil para la construcción de dashboards . ¿Cómo elegir la visualización adecuada? Una de las primeras preguntas que nos debemos realizar al explorar datos es ¿qué método de visualización es más efectivo?. Para intentar responder esta pregunta podemos utilizar la siguiente guía: Como podemos ver, la guía se divide en cuatro categorías principales y luego se clasifican los distintos métodos de visualización que mejor representan cada una de esas categorías. Veamos un poco más en detalle cada una de ellas: Distribuciones : En esta categoría intentamos comprender como los datos se distribuyen. Se suelen utilizar en el comienzo de la etapa de exploración de datos, cuando queremos comprender las variables. Aquí también nos vamos a encontrar con variables de dos tipos cuantitativas y categóricas . Dependiendo del tipo y cantidad de variables, el método de visualización que vamos a utilizar. Comparaciones : En esta categoría el objetivo es comparar valores a través de diferentes categorías y con el tiempo (tendencia). Los tipos de gráficos más comunes en esta categoría son los diagramas de barras para cuando estamos comparando elementos o categorías y los diagramas de puntos y líneas cuando comparamos variables cuantitativas . Relaciones : Aquí el objetivo es comprender la relación entre dos o más variables. La visualización más utilizada en esta categoría es el gráfico de dispersión . Composiciones : En esta categoría el objetivo es comprender como esta compuesta o distribuida una variable; ya sea a través del tiempo o en forma estática. Las visualizaciones más comunes aquí son los diagramas de barras y los gráficos de tortas . Ejemplos en Python Luego de esta introducción es hora de ensuciarse las manos y ponerse a jugar con algunos ejemplos en el uso de cada una de estas 4 librerías que nos ofrece Python para visualización de datos. Obviamente los ejemplos van a ser sencillos ya que un tutorial exhaustivo sobre cada herramienta requeriría mucho más espacio. Matplotlib Comencemos con Matplotlib ; como les comentaba, es tal vez la librería más utilizada para gráficos en 2d. El objeto pyplot nos proporciona la interfase principal sobre la que podemos crear las visualizaciones de datos con esta librería. In [1]: Ver Código # importando modulos necesarios import numpy as np import pandas as pd from pydataset import data import re # librerías de visualizaciones import seaborn as sns import matplotlib.pyplot as plt from bokeh.io import output_notebook , show from bokeh.charts import Histogram , Scatter import folium # graficos incrustados % matplotlib inline output_notebook () Loading BokehJS ... var element = $('#f8182847-48e8-4f91-8354-408ca14975fc'); (function(global) { function now() { return new Date(); } var force = \"1\"; if (typeof (window._bokeh_onload_callbacks) === \"undefined\" || force !== \"\") { window._bokeh_onload_callbacks = []; window._bokeh_is_loading = undefined; } if (typeof (window._bokeh_timeout) === \"undefined\" || force !== \"\") { window._bokeh_timeout = Date.now() + 5000; window._bokeh_failed_load = false; } var NB_LOAD_WARNING = {'data': {'text/html': \"<div style='background-color: #fdd'>\\n\"+ \"<p>\\n\"+ \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+ \"may be due to a slow or bad network connection. Possible fixes:\\n\"+ \"</p>\\n\"+ \"<ul>\\n\"+ \"<li>re-rerun `output_notebook()` to attempt to load from CDN again, or</li>\\n\"+ \"<li>use INLINE resources instead, as so:</li>\\n\"+ \"</ul>\\n\"+ \"<code>\\n\"+ \"from bokeh.resources import INLINE\\n\"+ \"output_notebook(resources=INLINE)\\n\"+ \"</code>\\n\"+ \"</div>\"}}; function display_loaded() { if (window.Bokeh !== undefined) { Bokeh.$(\"#0f67c312-b93b-41ef-9215-63bfa5199b1f\").text(\"BokehJS successfully loaded.\"); } else if (Date.now() < window._bokeh_timeout) { setTimeout(display_loaded, 100) } } function run_callbacks() { window._bokeh_onload_callbacks.forEach(function(callback) { callback() }); delete window._bokeh_onload_callbacks console.info(\"Bokeh: all callbacks have finished\"); } function load_libs(js_urls, callback) { window._bokeh_onload_callbacks.push(callback); if (window._bokeh_is_loading > 0) { console.log(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now()); return null; } if (js_urls == null || js_urls.length === 0) { run_callbacks(); return null; } console.log(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now()); window._bokeh_is_loading = js_urls.length; for (var i = 0; i < js_urls.length; i++) { var url = js_urls[i]; var s = document.createElement('script'); s.src = url; s.async = false; s.onreadystatechange = s.onload = function() { window._bokeh_is_loading--; if (window._bokeh_is_loading === 0) { console.log(\"Bokeh: all BokehJS libraries loaded\"); run_callbacks() } }; s.onerror = function() { console.warn(\"failed to load library \" + url); }; console.log(\"Bokeh: injecting script tag for BokehJS library: \", url); document.getElementsByTagName(\"head\")[0].appendChild(s); } };var element = document.getElementById(\"0f67c312-b93b-41ef-9215-63bfa5199b1f\"); if (element == null) { console.log(\"Bokeh: ERROR: autoload.js configured with elementid '0f67c312-b93b-41ef-9215-63bfa5199b1f' but no matching script tag was found. \") return false; } var js_urls = ['https://cdn.pydata.org/bokeh/release/bokeh-0.12.2.min.js', 'https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.2.min.js', 'https://cdn.pydata.org/bokeh/release/bokeh-compiler-0.12.2.min.js']; var inline_js = [ function(Bokeh) { Bokeh.set_log_level(\"info\"); }, function(Bokeh) { Bokeh.$(\"#0f67c312-b93b-41ef-9215-63bfa5199b1f\").text(\"BokehJS is loading...\"); }, function(Bokeh) { console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-0.12.2.min.css\"); Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-0.12.2.min.css\"); console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.2.min.css\"); Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.2.min.css\"); } ]; function run_inline_js() { if ((window.Bokeh !== undefined) || (force === \"1\")) { for (var i = 0; i < inline_js.length; i++) { inline_js[i](window.Bokeh); }if (force === \"1\") { display_loaded(); }} else if (Date.now() < window._bokeh_timeout) { setTimeout(run_inline_js, 100); } else if (!window._bokeh_failed_load) { console.log(\"Bokeh: BokehJS failed to load within specified timeout.\"); window._bokeh_failed_load = true; } else if (!force) { var cell = $(\"#0f67c312-b93b-41ef-9215-63bfa5199b1f\").parents('.cell').data().cell; cell.output_area.append_execute_result(NB_LOAD_WARNING) } } if (window._bokeh_is_loading === 0) { console.log(\"Bokeh: BokehJS loaded, going straight to plotting\"); run_inline_js(); } else { load_libs(js_urls, function() { console.log(\"Bokeh: BokehJS plotting callback run at\", now()); run_inline_js(); }); } }(this)); In [2]: # Cargamos algunos datasets de ejemplo iris = data ( 'iris' ) tips = data ( 'tips' ) In [3]: # Ejemplo matplotlib # graficanco funciones seno y coseno X = np . linspace ( - np . pi , np . pi , 256 , endpoint = True ) C , S = np . cos ( X ), np . sin ( X ) # configurando el tamaño de la figura plt . figure ( figsize = ( 8 , 6 )) # dibujando las curvas plt . plot ( X , C , color = \"blue\" , linewidth = 2.5 , linestyle = \"-\" , label = \"coseno\" ) plt . plot ( X , S , color = \"red\" , linewidth = 2.5 , linestyle = \"-\" , label = \"seno\" ) # personalizando los valores de los ejes plt . xticks ([ - np . pi , - np . pi / 2 , 0 , np . pi / 2 , np . pi ], [ r '$-\\pi$' , r '$-\\pi/2$' , r '$0$' , r '$+\\pi/2$' , r '$+\\pi$' ]) plt . yticks ([ - 1 , 0 , + 1 ], [ r '$-1$' , r '$0$' , r '$+1$' ]) # agregando la leyenda plt . legend ( loc = 'upper left' ) # moviendo los ejes de coordenadas ax = plt . gca () # get current axis ax . spines [ 'right' ] . set_color ( 'none' ) ax . spines [ 'top' ] . set_color ( 'none' ) ax . xaxis . set_ticks_position ( 'bottom' ) ax . spines [ 'bottom' ] . set_position (( 'data' , 0 )) ax . yaxis . set_ticks_position ( 'left' ) ax . spines [ 'left' ] . set_position (( 'data' , 0 )) # mostrando el resultado plt . show () En este primer ejemplo vemos como podemos acceder a la API de Matplotlib desde el objeto pyplot e ir dando forma al gráfico. Veamos ahora unos ejemplos con el dataset iris. In [4]: # Ejemplo con iris # histograma de Petal.Length iris . head () Out[4]: Sepal.Length Sepal.Width Petal.Length Petal.Width Species 1 5.1 3.5 1.4 0.2 setosa 2 4.9 3.0 1.4 0.2 setosa 3 4.7 3.2 1.3 0.2 setosa 4 4.6 3.1 1.5 0.2 setosa 5 5.0 3.6 1.4 0.2 setosa In [5]: # separo en especies setosa = iris [ iris . Species == 'setosa' ] versicolor = iris [ iris . Species == 'versicolor' ] virginica = iris [ iris . Species == 'virginica' ] In [6]: # crear histograma plt . figure ( figsize = ( 10 , 8 )) n , bins , patches = plt . hist ( setosa [ 'Petal.Length' ], 12 , facecolor = 'red' , label = 'setosa' ) n , bins , patches = plt . hist ( versicolor [ 'Petal.Length' ], 12 , facecolor = 'green' , label = 'versicolor' ) n , bins , patches = plt . hist ( virginica [ 'Petal.Length' ], 12 , facecolor = 'blue' , label = 'virginica' ) plt . legend ( loc = 'top_right' ) plt . title ( 'Histograma largo del pétalo' ) plt . xlabel ( 'largo del pétalo' ) plt . ylabel ( 'cuenta largo del pétalo' ) plt . show () In [7]: # Ejemplo diagrama de dispersion entre Petal.Length y Petal.Width plt . figure ( figsize = ( 10 , 8 )) plt . scatter ( setosa [ 'Petal.Length' ], setosa [ 'Petal.Width' ], c = 'red' , label = 'setosa' ) plt . scatter ( versicolor [ 'Petal.Length' ], versicolor [ 'Petal.Width' ], c = 'green' , label = 'versicolor' ) plt . scatter ( virginica [ 'Petal.Length' ], virginica [ 'Petal.Width' ], c = 'blue' , label = 'virginica' ) plt . title ( 'Tamaño del pétalo' ) plt . xlabel ( 'Largo del pétalo (cm)' ) plt . ylabel ( 'Ancho del pétalo (cm)' ) plt . legend ( loc = 'top_left' ) plt . show () Bokeh Bokeh además de generar unos hermosos gráficos interactivos nos permite realizar gráficos complejos en forma muy sencilla. La interfase de alto nivel con la que vamos a trabajar principalmente para generar visualizaciones con esta librería es bokeh.charts . Repitamos los ejemplos que realizamos anteriormente sobre el dataset iris y veamos que sencillo que es realizarlos con Bokeh . In [8]: # Ejemplo de histograma de Petal.Length # solo 2 lineas de código hist = Histogram ( iris , values = \"Petal.Length\" , color = \"Species\" , legend = \"top_right\" , bins = 12 ) show ( hist ) (function(global) { function now() { return new Date(); } var force = \"\"; if (typeof (window._bokeh_onload_callbacks) === \"undefined\" || force !== \"\") { window._bokeh_onload_callbacks = []; window._bokeh_is_loading = undefined; } if (typeof (window._bokeh_timeout) === \"undefined\" || force !== \"\") { window._bokeh_timeout = Date.now() + 0; window._bokeh_failed_load = false; } var NB_LOAD_WARNING = {'data': {'text/html': \"<div style='background-color: #fdd'>\\n\"+ \"<p>\\n\"+ \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+ \"may be due to a slow or bad network connection. Possible fixes:\\n\"+ \"</p>\\n\"+ \"<ul>\\n\"+ \"<li>re-rerun `output_notebook()` to attempt to load from CDN again, or</li>\\n\"+ \"<li>use INLINE resources instead, as so:</li>\\n\"+ \"</ul>\\n\"+ \"<code>\\n\"+ \"from bokeh.resources import INLINE\\n\"+ \"output_notebook(resources=INLINE)\\n\"+ \"</code>\\n\"+ \"</div>\"}}; function display_loaded() { if (window.Bokeh !== undefined) { Bokeh.$(\"#c195cf80-93de-4663-b5d3-f106a1051aec\").text(\"BokehJS successfully loaded.\"); } else if (Date.now() < window._bokeh_timeout) { setTimeout(display_loaded, 100) } } function run_callbacks() { window._bokeh_onload_callbacks.forEach(function(callback) { callback() }); delete window._bokeh_onload_callbacks console.info(\"Bokeh: all callbacks have finished\"); } function load_libs(js_urls, callback) { window._bokeh_onload_callbacks.push(callback); if (window._bokeh_is_loading > 0) { console.log(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now()); return null; } if (js_urls == null || js_urls.length === 0) { run_callbacks(); return null; } console.log(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now()); window._bokeh_is_loading = js_urls.length; for (var i = 0; i < js_urls.length; i++) { var url = js_urls[i]; var s = document.createElement('script'); s.src = url; s.async = false; s.onreadystatechange = s.onload = function() { window._bokeh_is_loading--; if (window._bokeh_is_loading === 0) { console.log(\"Bokeh: all BokehJS libraries loaded\"); run_callbacks() } }; s.onerror = function() { console.warn(\"failed to load library \" + url); }; console.log(\"Bokeh: injecting script tag for BokehJS library: \", url); document.getElementsByTagName(\"head\")[0].appendChild(s); } };var element = document.getElementById(\"c195cf80-93de-4663-b5d3-f106a1051aec\"); if (element == null) { console.log(\"Bokeh: ERROR: autoload.js configured with elementid 'c195cf80-93de-4663-b5d3-f106a1051aec' but no matching script tag was found. \") return false; } var js_urls = []; var inline_js = [ function(Bokeh) { Bokeh.$(function() { var docs_json = {\"9a7ef9a1-1421-4082-99ad-6e5085ca7300\":{\"roots\":{\"references\":[{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(6.7, 6.9]\"],\"color\":[\"#407ee7\"],\"fill_alpha\":[0.6],\"height\":[1.0],\"label\":[\"(6.7, 6.9]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.1999999999999993],\"x\":[\"6.800000000000001\"],\"y\":[0.5]}},\"id\":\"f0919d08-3312-4eb1-9203-d799f245e6ac\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"axis_label\":\"Count( Petal.Length )\",\"formatter\":{\"id\":\"c3c1ce28-d2d4-44e7-ad49-3561d2c59f93\",\"type\":\"BasicTickFormatter\"},\"plot\":{\"id\":\"11045096-f9bf-453f-ab37-ae3b16279d10\",\"subtype\":\"Chart\",\"type\":\"Plot\"},\"ticker\":{\"id\":\"8b75d8f4-692d-46b5-8161-191c87ac53d0\",\"type\":\"BasicTicker\"}},\"id\":\"0a7db6c1-eb28-4900-9bdf-1d93ba9cb5e5\",\"type\":\"LinearAxis\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"d8b4a2de-5be2-42de-9bb0-550541660d68\",\"type\":\"Rect\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"de23b9c7-1fa0-4bde-950f-dd7a950cddc1\",\"type\":\"Rect\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(1.5, 1.6]\"],\"color\":[\"#f22c40\"],\"fill_alpha\":[0.6],\"height\":[0.0],\"label\":[\"(1.5, 1.6]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.07500000000000018],\"x\":[\"1.55\"],\"y\":[0.0]}},\"id\":\"7d34c7ce-0b4d-46b1-b52e-5060ef3e7fc8\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"below\":[{\"id\":\"65c588cf-89f1-43bc-93f4-1ab62f536b61\",\"type\":\"LinearAxis\"}],\"left\":[{\"id\":\"0a7db6c1-eb28-4900-9bdf-1d93ba9cb5e5\",\"type\":\"LinearAxis\"}],\"renderers\":[{\"id\":\"65b3ec7f-68f8-4773-bc4f-fc7249739ad4\",\"type\":\"BoxAnnotation\"},{\"id\":\"36511e12-214f-4304-a6d6-29bd5a432678\",\"type\":\"GlyphRenderer\"},{\"id\":\"d50b7377-77b1-4d59-b5d4-3596eabedf1b\",\"type\":\"GlyphRenderer\"},{\"id\":\"1adfccc6-6cd0-428d-b448-55ad3556b10b\",\"type\":\"GlyphRenderer\"},{\"id\":\"6584ad63-c9cd-4c40-86c6-1089a9b60d0f\",\"type\":\"GlyphRenderer\"},{\"id\":\"a9ac49d3-5728-40e2-9b96-cc8e87459f11\",\"type\":\"GlyphRenderer\"},{\"id\":\"3953359b-7cbf-4f92-ac48-d7e946f5da8f\",\"type\":\"GlyphRenderer\"},{\"id\":\"1c22c604-377c-43df-9911-72a7c1c0fcdc\",\"type\":\"GlyphRenderer\"},{\"id\":\"57407940-9fcc-40f2-a7be-93b285aae9e3\",\"type\":\"GlyphRenderer\"},{\"id\":\"5f2942c5-803d-4c89-b331-f25318d170a9\",\"type\":\"GlyphRenderer\"},{\"id\":\"d23a806d-8cf1-44d1-a75f-2f214751f842\",\"type\":\"GlyphRenderer\"},{\"id\":\"0d30e117-2f9d-4731-b823-66dfd38e639c\",\"type\":\"GlyphRenderer\"},{\"id\":\"b420e134-cf91-427c-b267-bfb8d03b9c86\",\"type\":\"GlyphRenderer\"},{\"id\":\"d32373e1-c6f4-4f9b-a126-a2b6b1e6015c\",\"type\":\"GlyphRenderer\"},{\"id\":\"99021687-61c6-4ef6-9f5b-9e8c33093b4d\",\"type\":\"GlyphRenderer\"},{\"id\":\"a2c440a9-b3e4-4ccf-95b2-b0d01cae766b\",\"type\":\"GlyphRenderer\"},{\"id\":\"74ad0c86-2d16-4f7a-9a52-9bf87ee943dc\",\"type\":\"GlyphRenderer\"},{\"id\":\"4d5c02ae-1ef9-4dc7-854d-da02e7bfae05\",\"type\":\"GlyphRenderer\"},{\"id\":\"2bb1c3ce-0233-42f3-b87e-70e089d0a0fe\",\"type\":\"GlyphRenderer\"},{\"id\":\"00b0bc04-4582-44b4-b685-3fecdcd5405a\",\"type\":\"GlyphRenderer\"},{\"id\":\"54dbde3c-1354-4de1-bd7a-63cf83fa0ffa\",\"type\":\"GlyphRenderer\"},{\"id\":\"d94405dd-7d4e-43f6-9f40-3f9d037bcd34\",\"type\":\"GlyphRenderer\"},{\"id\":\"76fdaea6-4520-4acd-a374-3898b7087165\",\"type\":\"GlyphRenderer\"},{\"id\":\"4f214efb-7718-46f8-ad1d-db22eb046cd9\",\"type\":\"GlyphRenderer\"},{\"id\":\"6a916ded-3ce1-43ff-a8f8-6bd97ede2ac5\",\"type\":\"GlyphRenderer\"},{\"id\":\"91998099-89a6-4938-a8b2-3f06cbf18308\",\"type\":\"GlyphRenderer\"},{\"id\":\"e8ee0648-accf-4eec-9d42-ee61ca622048\",\"type\":\"GlyphRenderer\"},{\"id\":\"453ec58b-0d01-4a9f-945b-a19c5464a7c1\",\"type\":\"GlyphRenderer\"},{\"id\":\"14dc7813-2970-41f3-ba9c-6f6cda3970c9\",\"type\":\"GlyphRenderer\"},{\"id\":\"13782731-bbbb-4240-88a7-35929bc0c496\",\"type\":\"GlyphRenderer\"},{\"id\":\"74dc2f94-589b-408e-afa0-e9957b2cdca1\",\"type\":\"GlyphRenderer\"},{\"id\":\"db55a27a-d062-4fdd-a78d-bf702fc14f91\",\"type\":\"GlyphRenderer\"},{\"id\":\"a99be2a3-d708-4868-8123-b3025ac7a9eb\",\"type\":\"GlyphRenderer\"},{\"id\":\"18dbee68-9d0a-4476-b264-a21c7ff6c0fc\",\"type\":\"GlyphRenderer\"},{\"id\":\"a8bffd51-3e3c-4b99-a636-44f92fd7821a\",\"type\":\"GlyphRenderer\"},{\"id\":\"7684a062-ce50-4e22-89ac-a24647378a6b\",\"type\":\"GlyphRenderer\"},{\"id\":\"b7f5cce1-f6dd-47d2-80fd-bc12ae18401b\",\"type\":\"GlyphRenderer\"},{\"id\":\"ebd307bb-49ce-42a0-9983-7551927b1dd8\",\"type\":\"Legend\"},{\"id\":\"65c588cf-89f1-43bc-93f4-1ab62f536b61\",\"type\":\"LinearAxis\"},{\"id\":\"0a7db6c1-eb28-4900-9bdf-1d93ba9cb5e5\",\"type\":\"LinearAxis\"},{\"id\":\"4343e9e9-a240-4174-9829-7ec76f67ec39\",\"type\":\"Grid\"}],\"title\":{\"id\":\"26780f07-f264-4ebc-a91d-c436d5e85dc6\",\"type\":\"Title\"},\"tool_events\":{\"id\":\"9bf3c871-b086-4c1d-b7e3-cd60db2e64bb\",\"type\":\"ToolEvents\"},\"toolbar\":{\"id\":\"7cde5e44-4da9-432b-847e-6472ebb9e0c0\",\"type\":\"Toolbar\"},\"x_mapper_type\":\"auto\",\"x_range\":{\"id\":\"c0c01d28-e83a-4a5b-aa15-a683b132b950\",\"type\":\"Range1d\"},\"y_mapper_type\":\"auto\",\"y_range\":{\"id\":\"dc51d7a0-661b-4b48-95c7-b7cea750f377\",\"type\":\"Range1d\"}},\"id\":\"11045096-f9bf-453f-ab37-ae3b16279d10\",\"subtype\":\"Chart\",\"type\":\"Plot\"},{\"attributes\":{},\"id\":\"f6784e6e-1643-4bdc-b6b4-213cf3fabb8c\",\"type\":\"BasicTicker\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"c49a4520-6132-483a-bbfe-7efa69605cf4\",\"type\":\"Rect\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(3.4, 3.5]\"],\"color\":[\"#5ab738\"],\"fill_alpha\":[0.6],\"height\":[2.0],\"label\":[\"(3.4, 3.5]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.17499999999999982],\"x\":[\"3.45\"],\"y\":[1.0]}},\"id\":\"f7b1ad74-75f0-4188-a294-00817e70d879\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(4.7, 4.9]\"],\"color\":[\"#407ee7\"],\"fill_alpha\":[0.6],\"height\":[2.0],\"label\":[\"(4.7, 4.9]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.20000000000000018],\"x\":[\"4.800000000000001\"],\"y\":[1.0]}},\"id\":\"c610f030-60ea-42d7-a6ec-4fe4ea68cee4\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(4.8, 4.9]\"],\"color\":[\"#5ab738\"],\"fill_alpha\":[0.6],\"height\":[4.0],\"label\":[\"(4.8, 4.9]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.17499999999999982],\"x\":[\"4.85\"],\"y\":[2.0]}},\"id\":\"a144c79a-986b-48f5-acb7-1d11f88ddb98\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"active_drag\":\"auto\",\"active_scroll\":\"auto\",\"active_tap\":\"auto\",\"tools\":[{\"id\":\"01767a08-0689-45ab-bb04-0e494141694c\",\"type\":\"PanTool\"},{\"id\":\"33b9928a-6f78-43d3-9703-8ba925f70680\",\"type\":\"WheelZoomTool\"},{\"id\":\"04948cd0-b032-42ef-9507-cccf60ac14e3\",\"type\":\"BoxZoomTool\"},{\"id\":\"8d91f9ba-606f-43d2-922a-4b4a89232e66\",\"type\":\"SaveTool\"},{\"id\":\"28d55f5f-9ea8-4891-88b6-2cc48662a398\",\"type\":\"ResetTool\"},{\"id\":\"f1471099-0de8-4b4a-8664-314236e91d91\",\"type\":\"HelpTool\"}]},\"id\":\"7cde5e44-4da9-432b-847e-6472ebb9e0c0\",\"type\":\"Toolbar\"},{\"attributes\":{\"data_source\":{\"id\":\"0fb9b06c-0bc4-412d-8e8d-b76767245486\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"f080a166-8ce2-495f-8396-7a2a36d34d6d\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"18dbee68-9d0a-4476-b264-a21c7ff6c0fc\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(5.5, 5.7]\"],\"color\":[\"#407ee7\"],\"fill_alpha\":[0.6],\"height\":[6.0],\"label\":[\"(5.5, 5.7]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.20000000000000018],\"x\":[\"5.6\"],\"y\":[3.0]}},\"id\":\"8d3e00f4-599b-4b89-a37f-bbd5e20b3a3a\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(4.4, 4.6]\"],\"color\":[\"#5ab738\"],\"fill_alpha\":[0.6],\"height\":[11.0],\"label\":[\"(4.4, 4.6]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.17499999999999982],\"x\":[\"4.5\"],\"y\":[5.5]}},\"id\":\"6997341d-616a-441c-a92f-bf944506ea15\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"data_source\":{\"id\":\"f7b1ad74-75f0-4188-a294-00817e70d879\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"8351723e-ed98-42c1-96e4-c659fb25805d\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"a2c440a9-b3e4-4ccf-95b2-b0d01cae766b\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(1.8, 1.8]\"],\"color\":[\"#f22c40\"],\"fill_alpha\":[0.6],\"height\":[0.0],\"label\":[\"(1.8, 1.8]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.07499999999999996],\"x\":[\"1.8\"],\"y\":[0.0]}},\"id\":\"2d3b8826-5904-4d7b-b066-f14a5d6575ba\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"plot\":{\"id\":\"11045096-f9bf-453f-ab37-ae3b16279d10\",\"subtype\":\"Chart\",\"type\":\"Plot\"}},\"id\":\"f1471099-0de8-4b4a-8664-314236e91d91\",\"type\":\"HelpTool\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"99f89837-6c58-43fe-a7e7-68f956bfc265\",\"type\":\"Rect\"},{\"attributes\":{},\"id\":\"8b75d8f4-692d-46b5-8161-191c87ac53d0\",\"type\":\"BasicTicker\"},{\"attributes\":{\"data_source\":{\"id\":\"7e7e3616-0f52-4aea-b0d5-f35fd6014e76\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"4ead34d7-1c73-41b8-ba8e-39143a9da986\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"91998099-89a6-4938-a8b2-3f06cbf18308\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"9c12aba0-bb79-40f7-9ed0-e56564ce3795\",\"type\":\"Rect\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"[4.5, 4.7]\"],\"color\":[\"#407ee7\"],\"fill_alpha\":[0.6],\"height\":[1.0],\"label\":[\"[4.5, 4.7]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.20000000000000018],\"x\":[\"4.6\"],\"y\":[0.5]}},\"id\":\"7e7e3616-0f52-4aea-b0d5-f35fd6014e76\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(6.3, 6.5]\"],\"color\":[\"#407ee7\"],\"fill_alpha\":[0.6],\"height\":[1.0],\"label\":[\"(6.3, 6.5]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.1999999999999993],\"x\":[\"6.4\"],\"y\":[0.5]}},\"id\":\"b8caa1af-0a95-441e-87a8-75299dd912d7\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"data_source\":{\"id\":\"2d3b8826-5904-4d7b-b066-f14a5d6575ba\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"652ddbec-0873-40db-a0ae-66f832788023\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"0d30e117-2f9d-4731-b823-66dfd38e639c\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"data_source\":{\"id\":\"8d3e00f4-599b-4b89-a37f-bbd5e20b3a3a\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"86f5a87e-9113-4062-9be9-71482851fbf4\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"74dc2f94-589b-408e-afa0-e9957b2cdca1\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"data_source\":{\"id\":\"6f74eb57-be16-4e39-9c55-a5357bf06e8c\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"de23b9c7-1fa0-4bde-950f-dd7a950cddc1\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"d32373e1-c6f4-4f9b-a126-a2b6b1e6015c\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"data_source\":{\"id\":\"f50c290c-9a0d-46c1-b06d-02432e3ed4d7\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"1bb2865b-928e-4d4b-b9e1-6c5850c034b8\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"db55a27a-d062-4fdd-a78d-bf702fc14f91\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"data_source\":{\"id\":\"7526af5b-d39b-42d4-9f2b-c652c313dab2\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"35e46d23-f3f7-4e0c-9128-15dcfbdafe77\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"3953359b-7cbf-4f92-ac48-d7e946f5da8f\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"26c90387-b300-4bf0-95c9-7412b64db554\",\"type\":\"Rect\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(6.5, 6.7]\"],\"color\":[\"#407ee7\"],\"fill_alpha\":[0.6],\"height\":[3.0],\"label\":[\"(6.5, 6.7]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.20000000000000107],\"x\":[\"6.6\"],\"y\":[1.5]}},\"id\":\"9eb3dfd4-bc18-46d2-942e-7c84d93900c2\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"data_source\":{\"id\":\"b9eee02f-7a94-4e05-b27d-8f54fc6f10b0\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"d8b4a2de-5be2-42de-9bb0-550541660d68\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"00b0bc04-4582-44b4-b685-3fecdcd5405a\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"callback\":null,\"end\":7.097812500000001,\"start\":0.8146875000000001},\"id\":\"c0c01d28-e83a-4a5b-aa15-a683b132b950\",\"type\":\"Range1d\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"86f5a87e-9113-4062-9be9-71482851fbf4\",\"type\":\"Rect\"},{\"attributes\":{\"data_source\":{\"id\":\"c2e1fe56-41c4-4a24-be85-01a0a088a228\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"9c12aba0-bb79-40f7-9ed0-e56564ce3795\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"54dbde3c-1354-4de1-bd7a-63cf83fa0ffa\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(1.4, 1.5]\"],\"color\":[\"#f22c40\"],\"fill_alpha\":[0.6],\"height\":[13.0],\"label\":[\"(1.4, 1.5]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.07499999999999996],\"x\":[\"1.45\"],\"y\":[6.5]}},\"id\":\"2cf4059b-f475-4e7a-ad98-62b6a916e262\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"a6d20e7d-b9c8-404d-ab8d-8c230ab70714\",\"type\":\"Rect\"},{\"attributes\":{\"data_source\":{\"id\":\"99070434-8683-414b-acc7-2154b21380c3\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"71b1fd33-c505-43b4-92fe-e40258d933fd\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"36511e12-214f-4304-a6d6-29bd5a432678\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"data_source\":{\"id\":\"f44df727-4cac-4374-96d8-ef0851413fa6\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"0680f1d7-a780-40c3-a7dd-fbb5bdf6209e\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"13782731-bbbb-4240-88a7-35929bc0c496\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"76846e18-e4cb-4f33-9f4c-58f4f2703a56\",\"type\":\"Rect\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(1.2, 1.3]\"],\"color\":[\"#f22c40\"],\"fill_alpha\":[0.6],\"height\":[0.0],\"label\":[\"(1.2, 1.3]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.07499999999999996],\"x\":[\"1.25\"],\"y\":[0.0]}},\"id\":\"2b13a05d-9444-464f-b00d-72bbba77fde5\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"c555888d-6099-4f08-a212-2801b85d632e\",\"type\":\"Rect\"},{\"attributes\":{\"data_source\":{\"id\":\"a952c612-c391-418c-9b12-65bb7c1323f3\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"34da3089-0c20-43ba-b380-58674d133744\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"4d5c02ae-1ef9-4dc7-854d-da02e7bfae05\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"4e1a0571-a9b8-4f63-8801-698fa1f61372\",\"type\":\"Rect\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"0680f1d7-a780-40c3-a7dd-fbb5bdf6209e\",\"type\":\"Rect\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"f932210c-c7f5-4a26-ad41-b3f024c3ef6d\",\"type\":\"Rect\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(5.7, 5.9]\"],\"color\":[\"#407ee7\"],\"fill_alpha\":[0.6],\"height\":[6.0],\"label\":[\"(5.7, 5.9]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.20000000000000018],\"x\":[\"5.800000000000001\"],\"y\":[3.0]}},\"id\":\"f50c290c-9a0d-46c1-b06d-02432e3ed4d7\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(1.1, 1.2]\"],\"color\":[\"#f22c40\"],\"fill_alpha\":[0.6],\"height\":[2.0],\"label\":[\"(1.1, 1.2]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.07500000000000018],\"x\":[\"1.15\"],\"y\":[1.0]}},\"id\":\"29ff4a76-00e2-4dc4-a886-d6beb2e374d5\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"71b1fd33-c505-43b4-92fe-e40258d933fd\",\"type\":\"Rect\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"16a90b24-083d-4317-9520-6d9fdb1d59da\",\"type\":\"Rect\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(4.9, 5.1]\"],\"color\":[\"#5ab738\"],\"fill_alpha\":[0.6],\"height\":[2.0],\"label\":[\"(4.9, 5.1]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.17499999999999982],\"x\":[\"5.0\"],\"y\":[1.0]}},\"id\":\"122ae1dd-4006-4254-b2a1-790acf1fdeef\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(4.0, 4.2]\"],\"color\":[\"#5ab738\"],\"fill_alpha\":[0.6],\"height\":[7.0],\"label\":[\"(4.0, 4.2]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.17499999999999982],\"x\":[\"4.1\"],\"y\":[3.5]}},\"id\":\"b9eee02f-7a94-4e05-b27d-8f54fc6f10b0\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(5.9, 6.1]\"],\"color\":[\"#407ee7\"],\"fill_alpha\":[0.6],\"height\":[7.0],\"label\":[\"(5.9, 6.1]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.20000000000000018],\"x\":[\"6.0\"],\"y\":[3.5]}},\"id\":\"b68ba484-3287-4881-8e6b-25e29bf58249\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"652ddbec-0873-40db-a0ae-66f832788023\",\"type\":\"Rect\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"8d5eec3d-9456-45f0-9229-db8d6e680a31\",\"type\":\"Rect\"},{\"attributes\":{\"legends\":[[\"setosa\",[{\"id\":\"36511e12-214f-4304-a6d6-29bd5a432678\",\"type\":\"GlyphRenderer\"},{\"id\":\"d50b7377-77b1-4d59-b5d4-3596eabedf1b\",\"type\":\"GlyphRenderer\"},{\"id\":\"1adfccc6-6cd0-428d-b448-55ad3556b10b\",\"type\":\"GlyphRenderer\"},{\"id\":\"6584ad63-c9cd-4c40-86c6-1089a9b60d0f\",\"type\":\"GlyphRenderer\"},{\"id\":\"a9ac49d3-5728-40e2-9b96-cc8e87459f11\",\"type\":\"GlyphRenderer\"},{\"id\":\"3953359b-7cbf-4f92-ac48-d7e946f5da8f\",\"type\":\"GlyphRenderer\"},{\"id\":\"1c22c604-377c-43df-9911-72a7c1c0fcdc\",\"type\":\"GlyphRenderer\"},{\"id\":\"57407940-9fcc-40f2-a7be-93b285aae9e3\",\"type\":\"GlyphRenderer\"},{\"id\":\"5f2942c5-803d-4c89-b331-f25318d170a9\",\"type\":\"GlyphRenderer\"},{\"id\":\"d23a806d-8cf1-44d1-a75f-2f214751f842\",\"type\":\"GlyphRenderer\"},{\"id\":\"0d30e117-2f9d-4731-b823-66dfd38e639c\",\"type\":\"GlyphRenderer\"},{\"id\":\"b420e134-cf91-427c-b267-bfb8d03b9c86\",\"type\":\"GlyphRenderer\"}]],[\"versicolor\",[{\"id\":\"d32373e1-c6f4-4f9b-a126-a2b6b1e6015c\",\"type\":\"GlyphRenderer\"},{\"id\":\"99021687-61c6-4ef6-9f5b-9e8c33093b4d\",\"type\":\"GlyphRenderer\"},{\"id\":\"a2c440a9-b3e4-4ccf-95b2-b0d01cae766b\",\"type\":\"GlyphRenderer\"},{\"id\":\"74ad0c86-2d16-4f7a-9a52-9bf87ee943dc\",\"type\":\"GlyphRenderer\"},{\"id\":\"4d5c02ae-1ef9-4dc7-854d-da02e7bfae05\",\"type\":\"GlyphRenderer\"},{\"id\":\"2bb1c3ce-0233-42f3-b87e-70e089d0a0fe\",\"type\":\"GlyphRenderer\"},{\"id\":\"00b0bc04-4582-44b4-b685-3fecdcd5405a\",\"type\":\"GlyphRenderer\"},{\"id\":\"54dbde3c-1354-4de1-bd7a-63cf83fa0ffa\",\"type\":\"GlyphRenderer\"},{\"id\":\"d94405dd-7d4e-43f6-9f40-3f9d037bcd34\",\"type\":\"GlyphRenderer\"},{\"id\":\"76fdaea6-4520-4acd-a374-3898b7087165\",\"type\":\"GlyphRenderer\"},{\"id\":\"4f214efb-7718-46f8-ad1d-db22eb046cd9\",\"type\":\"GlyphRenderer\"},{\"id\":\"6a916ded-3ce1-43ff-a8f8-6bd97ede2ac5\",\"type\":\"GlyphRenderer\"}]],[\"virginica\",[{\"id\":\"91998099-89a6-4938-a8b2-3f06cbf18308\",\"type\":\"GlyphRenderer\"},{\"id\":\"e8ee0648-accf-4eec-9d42-ee61ca622048\",\"type\":\"GlyphRenderer\"},{\"id\":\"453ec58b-0d01-4a9f-945b-a19c5464a7c1\",\"type\":\"GlyphRenderer\"},{\"id\":\"14dc7813-2970-41f3-ba9c-6f6cda3970c9\",\"type\":\"GlyphRenderer\"},{\"id\":\"13782731-bbbb-4240-88a7-35929bc0c496\",\"type\":\"GlyphRenderer\"},{\"id\":\"74dc2f94-589b-408e-afa0-e9957b2cdca1\",\"type\":\"GlyphRenderer\"},{\"id\":\"db55a27a-d062-4fdd-a78d-bf702fc14f91\",\"type\":\"GlyphRenderer\"},{\"id\":\"a99be2a3-d708-4868-8123-b3025ac7a9eb\",\"type\":\"GlyphRenderer\"},{\"id\":\"18dbee68-9d0a-4476-b264-a21c7ff6c0fc\",\"type\":\"GlyphRenderer\"},{\"id\":\"a8bffd51-3e3c-4b99-a636-44f92fd7821a\",\"type\":\"GlyphRenderer\"},{\"id\":\"7684a062-ce50-4e22-89ac-a24647378a6b\",\"type\":\"GlyphRenderer\"},{\"id\":\"b7f5cce1-f6dd-47d2-80fd-bc12ae18401b\",\"type\":\"GlyphRenderer\"}]]],\"plot\":{\"id\":\"11045096-f9bf-453f-ab37-ae3b16279d10\",\"subtype\":\"Chart\",\"type\":\"Plot\"}},\"id\":\"ebd307bb-49ce-42a0-9983-7551927b1dd8\",\"type\":\"Legend\"},{\"attributes\":{\"bottom_units\":\"screen\",\"fill_alpha\":{\"value\":0.5},\"fill_color\":{\"value\":\"lightgrey\"},\"left_units\":\"screen\",\"level\":\"overlay\",\"line_alpha\":{\"value\":1.0},\"line_color\":{\"value\":\"black\"},\"line_dash\":[4,4],\"line_width\":{\"value\":2},\"plot\":null,\"render_mode\":\"css\",\"right_units\":\"screen\",\"top_units\":\"screen\"},\"id\":\"65b3ec7f-68f8-4773-bc4f-fc7249739ad4\",\"type\":\"BoxAnnotation\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"8260b0c1-d1d4-4ea6-b5c0-cede9d9b148b\",\"type\":\"Rect\"},{\"attributes\":{\"plot\":{\"id\":\"11045096-f9bf-453f-ab37-ae3b16279d10\",\"subtype\":\"Chart\",\"type\":\"Plot\"}},\"id\":\"01767a08-0689-45ab-bb04-0e494141694c\",\"type\":\"PanTool\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"f080a166-8ce2-495f-8396-7a2a36d34d6d\",\"type\":\"Rect\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(1.6, 1.7]\"],\"color\":[\"#f22c40\"],\"fill_alpha\":[0.6],\"height\":[7.0],\"label\":[\"(1.6, 1.7]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.07499999999999973],\"x\":[\"1.65\"],\"y\":[3.5]}},\"id\":\"74e11ba5-53d0-4bad-9aa3-380a3cf30f62\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"b7cc61ce-3be9-42d4-b7a7-f157df14105d\",\"type\":\"Rect\"},{\"attributes\":{},\"id\":\"41f1fafd-e3dd-4cbe-ae3f-c63382b1b03c\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{\"data_source\":{\"id\":\"b68ba484-3287-4881-8e6b-25e29bf58249\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"76846e18-e4cb-4f33-9f4c-58f4f2703a56\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"a99be2a3-d708-4868-8123-b3025ac7a9eb\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"data_source\":{\"id\":\"2cf4059b-f475-4e7a-ad98-62b6a916e262\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"16a90b24-083d-4317-9520-6d9fdb1d59da\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"1c22c604-377c-43df-9911-72a7c1c0fcdc\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"954264d8-6012-4c7d-b0ac-4425e2cbce75\",\"type\":\"Rect\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(1.1, 1.1]\"],\"color\":[\"#f22c40\"],\"fill_alpha\":[0.6],\"height\":[1.0],\"label\":[\"(1.1, 1.1]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.07499999999999996],\"x\":[\"1.1\"],\"y\":[0.5]}},\"id\":\"31e98faf-0be6-4bfc-8595-b5f76e6d6116\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(3.2, 3.4]\"],\"color\":[\"#5ab738\"],\"fill_alpha\":[0.6],\"height\":[2.0],\"label\":[\"(3.2, 3.4]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.17500000000000027],\"x\":[\"3.3\"],\"y\":[1.0]}},\"id\":\"3706bae4-a773-47f0-a96e-f2c07adbd601\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"[3.0, 3.2]\"],\"color\":[\"#5ab738\"],\"fill_alpha\":[0.6],\"height\":[1.0],\"label\":[\"[3.0, 3.2]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.17499999999999982],\"x\":[\"3.1\"],\"y\":[0.5]}},\"id\":\"6f74eb57-be16-4e39-9c55-a5357bf06e8c\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"data_source\":{\"id\":\"9da9aea0-c0fd-4ecf-8f37-50d46903dd4e\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"c555888d-6099-4f08-a212-2801b85d632e\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"d23a806d-8cf1-44d1-a75f-2f214751f842\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"data_source\":{\"id\":\"6997341d-616a-441c-a92f-bf944506ea15\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"13690294-4149-4694-b35c-e861baab8952\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"d94405dd-7d4e-43f6-9f40-3f9d037bcd34\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"71836376-11cb-44e4-8dd2-2baae9fd5d7a\",\"type\":\"Rect\"},{\"attributes\":{\"data_source\":{\"id\":\"41ebf089-64d1-49a4-bce8-68f7436952a1\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"71836376-11cb-44e4-8dd2-2baae9fd5d7a\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"a9ac49d3-5728-40e2-9b96-cc8e87459f11\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"axis_label\":\"Petal.Length\",\"formatter\":{\"id\":\"41f1fafd-e3dd-4cbe-ae3f-c63382b1b03c\",\"type\":\"BasicTickFormatter\"},\"plot\":{\"id\":\"11045096-f9bf-453f-ab37-ae3b16279d10\",\"subtype\":\"Chart\",\"type\":\"Plot\"},\"ticker\":{\"id\":\"f6784e6e-1643-4bdc-b6b4-213cf3fabb8c\",\"type\":\"BasicTicker\"}},\"id\":\"65c588cf-89f1-43bc-93f4-1ab62f536b61\",\"type\":\"LinearAxis\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(3.9, 4.0]\"],\"color\":[\"#5ab738\"],\"fill_alpha\":[0.6],\"height\":[8.0],\"label\":[\"(3.9, 4.0]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.17499999999999982],\"x\":[\"3.95\"],\"y\":[4.0]}},\"id\":\"1ca15bdf-f407-4282-9a89-3bb23c6b9809\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"13690294-4149-4694-b35c-e861baab8952\",\"type\":\"Rect\"},{\"attributes\":{\"data_source\":{\"id\":\"122ae1dd-4006-4254-b2a1-790acf1fdeef\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"c49a4520-6132-483a-bbfe-7efa69605cf4\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"6a916ded-3ce1-43ff-a8f8-6bd97ede2ac5\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(4.6, 4.8]\"],\"color\":[\"#5ab738\"],\"fill_alpha\":[0.6],\"height\":[8.0],\"label\":[\"(4.6, 4.8]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.1750000000000007],\"x\":[\"4.699999999999999\"],\"y\":[4.0]}},\"id\":\"85fdd08b-006e-491b-89df-ea5277a32035\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"data_source\":{\"id\":\"b8caa1af-0a95-441e-87a8-75299dd912d7\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"f665bceb-bc09-4ea3-bcd0-4f40c54bb0aa\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"a8bffd51-3e3c-4b99-a636-44f92fd7821a\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"d0feb6f4-2b2e-4050-b574-6296e0e162a7\",\"type\":\"Rect\"},{\"attributes\":{\"data_source\":{\"id\":\"cacbfcb9-1efc-4e64-a1d2-98e09ba01027\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"4e1a0571-a9b8-4f63-8801-698fa1f61372\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"453ec58b-0d01-4a9f-945b-a19c5464a7c1\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"4ead34d7-1c73-41b8-ba8e-39143a9da986\",\"type\":\"Rect\"},{\"attributes\":{},\"id\":\"9bf3c871-b086-4c1d-b7e3-cd60db2e64bb\",\"type\":\"ToolEvents\"},{\"attributes\":{\"data_source\":{\"id\":\"9eb3dfd4-bc18-46d2-942e-7c84d93900c2\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"f932210c-c7f5-4a26-ad41-b3f024c3ef6d\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"7684a062-ce50-4e22-89ac-a24647378a6b\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"overlay\":{\"id\":\"65b3ec7f-68f8-4773-bc4f-fc7249739ad4\",\"type\":\"BoxAnnotation\"},\"plot\":{\"id\":\"11045096-f9bf-453f-ab37-ae3b16279d10\",\"subtype\":\"Chart\",\"type\":\"Plot\"}},\"id\":\"04948cd0-b032-42ef-9507-cccf60ac14e3\",\"type\":\"BoxZoomTool\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(1.3, 1.4]\"],\"color\":[\"#f22c40\"],\"fill_alpha\":[0.6],\"height\":[7.0],\"label\":[\"(1.3, 1.4]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.07499999999999996],\"x\":[\"1.35\"],\"y\":[3.5]}},\"id\":\"41ebf089-64d1-49a4-bce8-68f7436952a1\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"66ab54dc-c349-43fc-be95-13ac22b1cf16\",\"type\":\"Rect\"},{\"attributes\":{\"data_source\":{\"id\":\"31e98faf-0be6-4bfc-8595-b5f76e6d6116\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"c0d2e5af-b2aa-491d-8aa3-ff52dc596b91\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"d50b7377-77b1-4d59-b5d4-3596eabedf1b\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"plot\":{\"id\":\"11045096-f9bf-453f-ab37-ae3b16279d10\",\"subtype\":\"Chart\",\"type\":\"Plot\"}},\"id\":\"33b9928a-6f78-43d3-9703-8ba925f70680\",\"type\":\"WheelZoomTool\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"1bb2865b-928e-4d4b-b9e1-6c5850c034b8\",\"type\":\"Rect\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"c0d2e5af-b2aa-491d-8aa3-ff52dc596b91\",\"type\":\"Rect\"},{\"attributes\":{\"callback\":null,\"end\":14.3},\"id\":\"dc51d7a0-661b-4b48-95c7-b7cea750f377\",\"type\":\"Range1d\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(5.1, 5.3]\"],\"color\":[\"#407ee7\"],\"fill_alpha\":[0.6],\"height\":[4.0],\"label\":[\"(5.1, 5.3]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.20000000000000018],\"x\":[\"5.199999999999999\"],\"y\":[2.0]}},\"id\":\"a46bc75e-5dff-4c2e-9be9-579f4e9351df\",\"type\":\"ColumnDataSource\"},{\"attributes\":{},\"id\":\"c3c1ce28-d2d4-44e7-ad49-3561d2c59f93\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(4.9, 5.1]\"],\"color\":[\"#407ee7\"],\"fill_alpha\":[0.6],\"height\":[13.0],\"label\":[\"(4.9, 5.1]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.1999999999999993],\"x\":[\"5.0\"],\"y\":[6.5]}},\"id\":\"cacbfcb9-1efc-4e64-a1d2-98e09ba01027\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(3.7, 3.9]\"],\"color\":[\"#5ab738\"],\"fill_alpha\":[0.6],\"height\":[2.0],\"label\":[\"(3.7, 3.9]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.17500000000000027],\"x\":[\"3.8\"],\"y\":[1.0]}},\"id\":\"a952c612-c391-418c-9b12-65bb7c1323f3\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"dimension\":1,\"plot\":{\"id\":\"11045096-f9bf-453f-ab37-ae3b16279d10\",\"subtype\":\"Chart\",\"type\":\"Plot\"},\"ticker\":{\"id\":\"8b75d8f4-692d-46b5-8161-191c87ac53d0\",\"type\":\"BasicTicker\"}},\"id\":\"4343e9e9-a240-4174-9829-7ec76f67ec39\",\"type\":\"Grid\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"f665bceb-bc09-4ea3-bcd0-4f40c54bb0aa\",\"type\":\"Rect\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"35e46d23-f3f7-4e0c-9128-15dcfbdafe77\",\"type\":\"Rect\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(5.3, 5.5]\"],\"color\":[\"#407ee7\"],\"fill_alpha\":[0.6],\"height\":[5.0],\"label\":[\"(5.3, 5.5]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.20000000000000018],\"x\":[\"5.4\"],\"y\":[2.5]}},\"id\":\"f44df727-4cac-4374-96d8-ef0851413fa6\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"data_source\":{\"id\":\"b0bf96e6-5dd5-4f8c-8d9d-30840bc1ee49\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"d0feb6f4-2b2e-4050-b574-6296e0e162a7\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"b420e134-cf91-427c-b267-bfb8d03b9c86\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"data_source\":{\"id\":\"0e943dfb-b301-4af6-a2e9-d05397655d44\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"c7c713be-38c6-4df1-a87d-9baa9e551955\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"74ad0c86-2d16-4f7a-9a52-9bf87ee943dc\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"[1.0, 1.1]\"],\"color\":[\"#f22c40\"],\"fill_alpha\":[0.6],\"height\":[1.0],\"label\":[\"[1.0, 1.1]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.07499999999999996],\"x\":[\"1.05\"],\"y\":[0.5]}},\"id\":\"99070434-8683-414b-acc7-2154b21380c3\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"data_source\":{\"id\":\"1ca15bdf-f407-4282-9a89-3bb23c6b9809\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"6a4fdb98-e275-49c2-a53f-4920f1e243ab\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"2bb1c3ce-0233-42f3-b87e-70e089d0a0fe\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"plot\":null,\"text\":null},\"id\":\"26780f07-f264-4ebc-a91d-c436d5e85dc6\",\"type\":\"Title\"},{\"attributes\":{\"data_source\":{\"id\":\"7d34c7ce-0b4d-46b1-b52e-5060ef3e7fc8\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"05cca864-9908-4c5d-aff8-32be1d0c8a03\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"57407940-9fcc-40f2-a7be-93b285aae9e3\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"data_source\":{\"id\":\"c610f030-60ea-42d7-a6ec-4fe4ea68cee4\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"99f89837-6c58-43fe-a7e7-68f956bfc265\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"e8ee0648-accf-4eec-9d42-ee61ca622048\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(6.1, 6.3]\"],\"color\":[\"#407ee7\"],\"fill_alpha\":[0.6],\"height\":[1.0],\"label\":[\"(6.1, 6.3]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.20000000000000018],\"x\":[\"6.199999999999999\"],\"y\":[0.5]}},\"id\":\"0fb9b06c-0bc4-412d-8e8d-b76767245486\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"c7c713be-38c6-4df1-a87d-9baa9e551955\",\"type\":\"Rect\"},{\"attributes\":{\"data_source\":{\"id\":\"3706bae4-a773-47f0-a96e-f2c07adbd601\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"954264d8-6012-4c7d-b0ac-4425e2cbce75\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"99021687-61c6-4ef6-9f5b-9e8c33093b4d\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(3.5, 3.7]\"],\"color\":[\"#5ab738\"],\"fill_alpha\":[0.6],\"height\":[1.0],\"label\":[\"(3.5, 3.7]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.17499999999999982],\"x\":[\"3.6\"],\"y\":[0.5]}},\"id\":\"0e943dfb-b301-4af6-a2e9-d05397655d44\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"data_source\":{\"id\":\"f0919d08-3312-4eb1-9203-d799f245e6ac\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"b7cc61ce-3be9-42d4-b7a7-f157df14105d\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"b7f5cce1-f6dd-47d2-80fd-bc12ae18401b\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"plot\":{\"id\":\"11045096-f9bf-453f-ab37-ae3b16279d10\",\"subtype\":\"Chart\",\"type\":\"Plot\"}},\"id\":\"8d91f9ba-606f-43d2-922a-4b4a89232e66\",\"type\":\"SaveTool\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(1.8, 1.9]\"],\"color\":[\"#f22c40\"],\"fill_alpha\":[0.6],\"height\":[2.0],\"label\":[\"(1.8, 1.9]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.07499999999999996],\"x\":[\"1.85\"],\"y\":[1.0]}},\"id\":\"b0bf96e6-5dd5-4f8c-8d9d-30840bc1ee49\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(4.2, 4.4]\"],\"color\":[\"#5ab738\"],\"fill_alpha\":[0.6],\"height\":[2.0],\"label\":[\"(4.2, 4.4]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.17499999999999982],\"x\":[\"4.300000000000001\"],\"y\":[1.0]}},\"id\":\"c2e1fe56-41c4-4a24-be85-01a0a088a228\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"data_source\":{\"id\":\"29ff4a76-00e2-4dc4-a886-d6beb2e374d5\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"8d5eec3d-9456-45f0-9229-db8d6e680a31\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"1adfccc6-6cd0-428d-b448-55ad3556b10b\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"34da3089-0c20-43ba-b380-58674d133744\",\"type\":\"Rect\"},{\"attributes\":{\"data_source\":{\"id\":\"2b13a05d-9444-464f-b00d-72bbba77fde5\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"66ab54dc-c349-43fc-be95-13ac22b1cf16\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"6584ad63-c9cd-4c40-86c6-1089a9b60d0f\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"data_source\":{\"id\":\"a46bc75e-5dff-4c2e-9be9-579f4e9351df\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"26c90387-b300-4bf0-95c9-7412b64db554\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"14dc7813-2970-41f3-ba9c-6f6cda3970c9\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"data_source\":{\"id\":\"85fdd08b-006e-491b-89df-ea5277a32035\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"8260b0c1-d1d4-4ea6-b5c0-cede9d9b148b\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"76fdaea6-4520-4acd-a374-3898b7087165\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(1.4, 1.4]\"],\"color\":[\"#f22c40\"],\"fill_alpha\":[0.6],\"height\":[13.0],\"label\":[\"(1.4, 1.4]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.07499999999999996],\"x\":[\"1.4\"],\"y\":[6.5]}},\"id\":\"7526af5b-d39b-42d4-9f2b-c652c313dab2\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"data_source\":{\"id\":\"74e11ba5-53d0-4bad-9aa3-380a3cf30f62\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"95219816-4281-446c-a402-62a6bb83995b\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"5f2942c5-803d-4c89-b331-f25318d170a9\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"95219816-4281-446c-a402-62a6bb83995b\",\"type\":\"Rect\"},{\"attributes\":{\"data_source\":{\"id\":\"a144c79a-986b-48f5-acb7-1d11f88ddb98\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"a6d20e7d-b9c8-404d-ab8d-8c230ab70714\",\"type\":\"Rect\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"4f214efb-7718-46f8-ad1d-db22eb046cd9\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"8351723e-ed98-42c1-96e4-c659fb25805d\",\"type\":\"Rect\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"05cca864-9908-4c5d-aff8-32be1d0c8a03\",\"type\":\"Rect\"},{\"attributes\":{\"fill_alpha\":{\"field\":\"fill_alpha\"},\"fill_color\":{\"field\":\"color\"},\"height\":{\"field\":\"height\",\"units\":\"data\"},\"line_color\":{\"field\":\"line_color\"},\"width\":{\"field\":\"width\",\"units\":\"data\"},\"x\":{\"field\":\"x\"},\"y\":{\"field\":\"y\"}},\"id\":\"6a4fdb98-e275-49c2-a53f-4920f1e243ab\",\"type\":\"Rect\"},{\"attributes\":{\"plot\":{\"id\":\"11045096-f9bf-453f-ab37-ae3b16279d10\",\"subtype\":\"Chart\",\"type\":\"Plot\"}},\"id\":\"28d55f5f-9ea8-4891-88b6-2cc48662a398\",\"type\":\"ResetTool\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"color\",\"label\",\"x\",\"line_color\",\"width\",\"height\",\"line_alpha\",\"fill_alpha\",\"y\"],\"data\":{\"chart_index\":[\"(1.7, 1.8]\"],\"color\":[\"#f22c40\"],\"fill_alpha\":[0.6],\"height\":[4.0],\"label\":[\"(1.7, 1.8]\"],\"line_alpha\":[1.0],\"line_color\":[\"black\"],\"width\":[0.07500000000000018],\"x\":[\"1.75\"],\"y\":[2.0]}},\"id\":\"9da9aea0-c0fd-4ecf-8f37-50d46903dd4e\",\"type\":\"ColumnDataSource\"}],\"root_ids\":[\"11045096-f9bf-453f-ab37-ae3b16279d10\"]},\"title\":\"Bokeh Application\",\"version\":\"0.12.2\"}}; var render_items = [{\"docid\":\"9a7ef9a1-1421-4082-99ad-6e5085ca7300\",\"elementid\":\"c195cf80-93de-4663-b5d3-f106a1051aec\",\"modelid\":\"11045096-f9bf-453f-ab37-ae3b16279d10\"}]; Bokeh.embed.embed_items(docs_json, render_items); }); }, function(Bokeh) { } ]; function run_inline_js() { if ((window.Bokeh !== undefined) || (force === \"1\")) { for (var i = 0; i < inline_js.length; i++) { inline_js[i](window.Bokeh); }if (force === \"1\") { display_loaded(); }} else if (Date.now() < window._bokeh_timeout) { setTimeout(run_inline_js, 100); } else if (!window._bokeh_failed_load) { console.log(\"Bokeh: BokehJS failed to load within specified timeout.\"); window._bokeh_failed_load = true; } else if (!force) { var cell = $(\"#c195cf80-93de-4663-b5d3-f106a1051aec\").parents('.cell').data().cell; cell.output_area.append_execute_result(NB_LOAD_WARNING) } } if (window._bokeh_is_loading === 0) { console.log(\"Bokeh: BokehJS loaded, going straight to plotting\"); run_inline_js(); } else { load_libs(js_urls, function() { console.log(\"Bokeh: BokehJS plotting callback run at\", now()); run_inline_js(); }); } }(this)); In [9]: # Ejemplo diagrama de dispersion entre Petal.Length y Petal.Width # solo 2 lineas de código disp = Scatter ( iris , x = 'Petal.Length' , y = 'Petal.Width' , color = 'Species' , legend = 'top_left' , marker = 'Species' , title = \"Tamaño del petalo\" ) show ( disp ) (function(global) { function now() { return new Date(); } var force = \"\"; if (typeof (window._bokeh_onload_callbacks) === \"undefined\" || force !== \"\") { window._bokeh_onload_callbacks = []; window._bokeh_is_loading = undefined; } if (typeof (window._bokeh_timeout) === \"undefined\" || force !== \"\") { window._bokeh_timeout = Date.now() + 0; window._bokeh_failed_load = false; } var NB_LOAD_WARNING = {'data': {'text/html': \"<div style='background-color: #fdd'>\\n\"+ \"<p>\\n\"+ \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+ \"may be due to a slow or bad network connection. Possible fixes:\\n\"+ \"</p>\\n\"+ \"<ul>\\n\"+ \"<li>re-rerun `output_notebook()` to attempt to load from CDN again, or</li>\\n\"+ \"<li>use INLINE resources instead, as so:</li>\\n\"+ \"</ul>\\n\"+ \"<code>\\n\"+ \"from bokeh.resources import INLINE\\n\"+ \"output_notebook(resources=INLINE)\\n\"+ \"</code>\\n\"+ \"</div>\"}}; function display_loaded() { if (window.Bokeh !== undefined) { Bokeh.$(\"#ab950219-9790-4c01-8a06-67fa0b5f89e7\").text(\"BokehJS successfully loaded.\"); } else if (Date.now() < window._bokeh_timeout) { setTimeout(display_loaded, 100) } } function run_callbacks() { window._bokeh_onload_callbacks.forEach(function(callback) { callback() }); delete window._bokeh_onload_callbacks console.info(\"Bokeh: all callbacks have finished\"); } function load_libs(js_urls, callback) { window._bokeh_onload_callbacks.push(callback); if (window._bokeh_is_loading > 0) { console.log(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now()); return null; } if (js_urls == null || js_urls.length === 0) { run_callbacks(); return null; } console.log(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now()); window._bokeh_is_loading = js_urls.length; for (var i = 0; i < js_urls.length; i++) { var url = js_urls[i]; var s = document.createElement('script'); s.src = url; s.async = false; s.onreadystatechange = s.onload = function() { window._bokeh_is_loading--; if (window._bokeh_is_loading === 0) { console.log(\"Bokeh: all BokehJS libraries loaded\"); run_callbacks() } }; s.onerror = function() { console.warn(\"failed to load library \" + url); }; console.log(\"Bokeh: injecting script tag for BokehJS library: \", url); document.getElementsByTagName(\"head\")[0].appendChild(s); } };var element = document.getElementById(\"ab950219-9790-4c01-8a06-67fa0b5f89e7\"); if (element == null) { console.log(\"Bokeh: ERROR: autoload.js configured with elementid 'ab950219-9790-4c01-8a06-67fa0b5f89e7' but no matching script tag was found. \") return false; } var js_urls = []; var inline_js = [ function(Bokeh) { Bokeh.$(function() { var docs_json = {\"759303cb-f221-4e8e-8eee-555189b6cd91\":{\"roots\":{\"references\":[{\"attributes\":{\"plot\":{\"id\":\"ba86df0a-4ca1-46f5-b426-d8c77b0de1c5\",\"subtype\":\"Chart\",\"type\":\"Plot\"}},\"id\":\"a533ef92-e4f9-4bce-890a-609515944a02\",\"type\":\"ResetTool\"},{\"attributes\":{\"axis_label\":\"Petal.Width\",\"formatter\":{\"id\":\"92489249-e75e-4958-b198-0a9b2e7d50ec\",\"type\":\"BasicTickFormatter\"},\"plot\":{\"id\":\"ba86df0a-4ca1-46f5-b426-d8c77b0de1c5\",\"subtype\":\"Chart\",\"type\":\"Plot\"},\"ticker\":{\"id\":\"323f04d2-87d9-4a08-83da-edb5d413b9c1\",\"type\":\"BasicTicker\"}},\"id\":\"55a820ac-900a-4faf-a84c-4553f36ae46b\",\"type\":\"LinearAxis\"},{\"attributes\":{\"active_drag\":\"auto\",\"active_scroll\":\"auto\",\"active_tap\":\"auto\",\"tools\":[{\"id\":\"9e8e7715-81bd-4ddc-b910-0813b5ae8e15\",\"type\":\"PanTool\"},{\"id\":\"99cd1fc0-415a-46cd-9288-7ffd0765ee72\",\"type\":\"WheelZoomTool\"},{\"id\":\"23f70c92-cd53-46eb-8f92-058e7a2a82d9\",\"type\":\"BoxZoomTool\"},{\"id\":\"87804e55-93dc-4211-9a10-3c498e5fdefa\",\"type\":\"SaveTool\"},{\"id\":\"a533ef92-e4f9-4bce-890a-609515944a02\",\"type\":\"ResetTool\"},{\"id\":\"c56fbf00-fa8a-4f2b-9469-d3f0455c1b34\",\"type\":\"HelpTool\"}]},\"id\":\"dd9b0607-d230-4e42-9619-71e758f69c95\",\"type\":\"Toolbar\"},{\"attributes\":{\"fill_alpha\":{\"value\":0.7},\"fill_color\":{\"value\":\"#5ab738\"},\"line_color\":{\"value\":\"#5ab738\"},\"size\":{\"units\":\"screen\",\"value\":8},\"x\":{\"field\":\"x_values\"},\"y\":{\"field\":\"y_values\"}},\"id\":\"491b68ae-f79e-4d5e-a703-35872def6516\",\"type\":\"Square\"},{\"attributes\":{\"data_source\":{\"id\":\"30098c0b-f052-45ff-b52d-38088ee33c6a\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"491b68ae-f79e-4d5e-a703-35872def6516\",\"type\":\"Square\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"3d1734f2-150d-4be1-858c-ea278bb06948\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"below\":[{\"id\":\"2f67b147-7fb4-4ee9-8d7f-3279e9fce20b\",\"type\":\"LinearAxis\"}],\"left\":[{\"id\":\"55a820ac-900a-4faf-a84c-4553f36ae46b\",\"type\":\"LinearAxis\"}],\"renderers\":[{\"id\":\"4e35114f-ab56-4e77-9fa2-066e58a4be2d\",\"type\":\"BoxAnnotation\"},{\"id\":\"55ea4a08-e14d-4986-a3c5-65bc3fe6184d\",\"type\":\"GlyphRenderer\"},{\"id\":\"3d1734f2-150d-4be1-858c-ea278bb06948\",\"type\":\"GlyphRenderer\"},{\"id\":\"da012400-a2ac-4c0c-b102-7bbd17e3104a\",\"type\":\"GlyphRenderer\"},{\"id\":\"caf01d4d-f6e4-41f9-9532-b3e14065bfc8\",\"type\":\"Legend\"},{\"id\":\"2f67b147-7fb4-4ee9-8d7f-3279e9fce20b\",\"type\":\"LinearAxis\"},{\"id\":\"55a820ac-900a-4faf-a84c-4553f36ae46b\",\"type\":\"LinearAxis\"},{\"id\":\"f249f8df-6378-4708-92c5-fc18f4d9ab0d\",\"type\":\"Grid\"},{\"id\":\"f7059893-dae5-48c5-8148-bcd6af6fbca1\",\"type\":\"Grid\"}],\"title\":{\"id\":\"85fccbff-a31c-4584-8d3d-3558c773eb3c\",\"type\":\"Title\"},\"tool_events\":{\"id\":\"9390d8c4-2b02-4dfb-8a1a-b1b1d19fbc4a\",\"type\":\"ToolEvents\"},\"toolbar\":{\"id\":\"dd9b0607-d230-4e42-9619-71e758f69c95\",\"type\":\"Toolbar\"},\"x_mapper_type\":\"auto\",\"x_range\":{\"id\":\"53cec0e4-d0da-4c18-bda6-63879ea04b5a\",\"type\":\"Range1d\"},\"y_mapper_type\":\"auto\",\"y_range\":{\"id\":\"0ad5466e-d73b-4137-9a97-b9993f242fcc\",\"type\":\"Range1d\"}},\"id\":\"ba86df0a-4ca1-46f5-b426-d8c77b0de1c5\",\"subtype\":\"Chart\",\"type\":\"Plot\"},{\"attributes\":{\"fill_alpha\":{\"value\":0.7},\"fill_color\":{\"value\":\"#407ee7\"},\"line_color\":{\"value\":\"#407ee7\"},\"size\":{\"units\":\"screen\",\"value\":8},\"x\":{\"field\":\"x_values\"},\"y\":{\"field\":\"y_values\"}},\"id\":\"0a5d9d12-b4c5-46f8-b0c9-7a31deba66b9\",\"type\":\"Triangle\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"x_values\",\"y_values\"],\"data\":{\"Species\":[\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\",\"virginica\"],\"chart_index\":[{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"},{\"Species\":\"virginica\"}],\"x_values\":[6.0,5.1,5.9,5.6,5.8,6.6,4.5,6.3,5.8,6.1,5.1,5.3,5.5,5.0,5.1,5.3,5.5,6.7,6.9,5.0,5.7,4.9,6.7,4.9,5.7,6.0,4.8,4.9,5.6,5.8,6.1,6.4,5.6,5.1,5.6,6.1,5.6,5.5,4.8,5.4,5.6,5.1,5.1,5.9,5.7,5.2,5.0,5.2,5.4,5.1],\"y_values\":[2.5,1.9,2.1,1.8,2.2,2.1,1.7,1.8,1.8,2.5,2.0,1.9,2.1,2.0,2.4,2.3,1.8,2.2,2.3,1.5,2.3,2.0,2.0,1.8,2.1,1.8,1.8,1.8,2.1,1.6,1.9,2.0,2.2,1.5,1.4,2.3,2.4,1.8,1.8,2.1,2.4,2.3,1.9,2.3,2.5,2.3,1.9,2.0,2.3,1.8]}},\"id\":\"4f398212-e76d-4540-8a8f-92df566049cd\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"x_values\",\"y_values\"],\"data\":{\"Species\":[\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\",\"setosa\"],\"chart_index\":[{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"},{\"Species\":\"setosa\"}],\"x_values\":[1.4,1.4,1.3,1.5,1.4,1.7,1.4,1.5,1.4,1.5,1.5,1.6,1.4,1.1,1.2,1.5,1.3,1.4,1.7,1.5,1.7,1.5,1.0,1.7,1.9,1.6,1.6,1.5,1.4,1.6,1.6,1.5,1.5,1.4,1.5,1.2,1.3,1.4,1.3,1.5,1.3,1.3,1.3,1.6,1.9,1.4,1.6,1.4,1.5,1.4],\"y_values\":[0.2,0.2,0.2,0.2,0.2,0.4,0.3,0.2,0.2,0.1,0.2,0.2,0.1,0.1,0.2,0.4,0.4,0.3,0.3,0.3,0.2,0.4,0.2,0.5,0.2,0.2,0.4,0.2,0.2,0.2,0.2,0.4,0.1,0.2,0.2,0.2,0.2,0.1,0.2,0.2,0.3,0.3,0.2,0.6,0.4,0.3,0.2,0.2,0.2,0.2]}},\"id\":\"a349c093-1e2c-406d-8164-6bec9f4d1db3\",\"type\":\"ColumnDataSource\"},{\"attributes\":{\"data_source\":{\"id\":\"a349c093-1e2c-406d-8164-6bec9f4d1db3\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"81044132-8f4c-4f2f-98c0-8cb4b96d8325\",\"type\":\"Circle\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"55ea4a08-e14d-4986-a3c5-65bc3fe6184d\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"bottom_units\":\"screen\",\"fill_alpha\":{\"value\":0.5},\"fill_color\":{\"value\":\"lightgrey\"},\"left_units\":\"screen\",\"level\":\"overlay\",\"line_alpha\":{\"value\":1.0},\"line_color\":{\"value\":\"black\"},\"line_dash\":[4,4],\"line_width\":{\"value\":2},\"plot\":null,\"render_mode\":\"css\",\"right_units\":\"screen\",\"top_units\":\"screen\"},\"id\":\"4e35114f-ab56-4e77-9fa2-066e58a4be2d\",\"type\":\"BoxAnnotation\"},{\"attributes\":{\"plot\":{\"id\":\"ba86df0a-4ca1-46f5-b426-d8c77b0de1c5\",\"subtype\":\"Chart\",\"type\":\"Plot\"}},\"id\":\"9e8e7715-81bd-4ddc-b910-0813b5ae8e15\",\"type\":\"PanTool\"},{\"attributes\":{\"plot\":null,\"text\":\"Tama\\u00f1o del petalo\"},\"id\":\"85fccbff-a31c-4584-8d3d-3558c773eb3c\",\"type\":\"Title\"},{\"attributes\":{\"fill_alpha\":{\"value\":0.7},\"fill_color\":{\"value\":\"#f22c40\"},\"line_color\":{\"value\":\"#f22c40\"},\"size\":{\"units\":\"screen\",\"value\":8},\"x\":{\"field\":\"x_values\"},\"y\":{\"field\":\"y_values\"}},\"id\":\"81044132-8f4c-4f2f-98c0-8cb4b96d8325\",\"type\":\"Circle\"},{\"attributes\":{},\"id\":\"323f04d2-87d9-4a08-83da-edb5d413b9c1\",\"type\":\"BasicTicker\"},{\"attributes\":{\"overlay\":{\"id\":\"4e35114f-ab56-4e77-9fa2-066e58a4be2d\",\"type\":\"BoxAnnotation\"},\"plot\":{\"id\":\"ba86df0a-4ca1-46f5-b426-d8c77b0de1c5\",\"subtype\":\"Chart\",\"type\":\"Plot\"}},\"id\":\"23f70c92-cd53-46eb-8f92-058e7a2a82d9\",\"type\":\"BoxZoomTool\"},{\"attributes\":{\"callback\":null,\"end\":2.74,\"start\":-0.13999999999999999},\"id\":\"0ad5466e-d73b-4137-9a97-b9993f242fcc\",\"type\":\"Range1d\"},{\"attributes\":{\"callback\":null,\"column_names\":[\"x_values\",\"y_values\"],\"data\":{\"Species\":[\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\",\"versicolor\"],\"chart_index\":[{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"},{\"Species\":\"versicolor\"}],\"x_values\":[4.7,4.5,4.9,4.0,4.6,4.5,4.7,3.3,4.6,3.9,3.5,4.2,4.0,4.7,3.6,4.4,4.5,4.1,4.5,3.9,4.8,4.0,4.9,4.7,4.3,4.4,4.8,5.0,4.5,3.5,3.8,3.7,3.9,5.1,4.5,4.5,4.7,4.4,4.1,4.0,4.4,4.6,4.0,3.3,4.2,4.2,4.2,4.3,3.0,4.1],\"y_values\":[1.4,1.5,1.5,1.3,1.5,1.3,1.6,1.0,1.3,1.4,1.0,1.5,1.0,1.4,1.3,1.4,1.5,1.0,1.5,1.1,1.8,1.3,1.5,1.2,1.3,1.4,1.4,1.7,1.5,1.0,1.1,1.0,1.2,1.6,1.5,1.6,1.5,1.3,1.3,1.3,1.2,1.4,1.2,1.0,1.3,1.2,1.3,1.3,1.1,1.3]}},\"id\":\"30098c0b-f052-45ff-b52d-38088ee33c6a\",\"type\":\"ColumnDataSource\"},{\"attributes\":{},\"id\":\"c36cace6-8b07-4c31-b641-01cd9060fd10\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{\"plot\":{\"id\":\"ba86df0a-4ca1-46f5-b426-d8c77b0de1c5\",\"subtype\":\"Chart\",\"type\":\"Plot\"}},\"id\":\"c56fbf00-fa8a-4f2b-9469-d3f0455c1b34\",\"type\":\"HelpTool\"},{\"attributes\":{\"plot\":{\"id\":\"ba86df0a-4ca1-46f5-b426-d8c77b0de1c5\",\"subtype\":\"Chart\",\"type\":\"Plot\"},\"ticker\":{\"id\":\"7f586ebc-3e90-45c3-91f3-18145932fa14\",\"type\":\"BasicTicker\"}},\"id\":\"f249f8df-6378-4708-92c5-fc18f4d9ab0d\",\"type\":\"Grid\"},{\"attributes\":{\"data_source\":{\"id\":\"4f398212-e76d-4540-8a8f-92df566049cd\",\"type\":\"ColumnDataSource\"},\"glyph\":{\"id\":\"0a5d9d12-b4c5-46f8-b0c9-7a31deba66b9\",\"type\":\"Triangle\"},\"hover_glyph\":null,\"nonselection_glyph\":null,\"selection_glyph\":null},\"id\":\"da012400-a2ac-4c0c-b102-7bbd17e3104a\",\"type\":\"GlyphRenderer\"},{\"attributes\":{\"callback\":null,\"end\":7.49,\"start\":0.4099999999999999},\"id\":\"53cec0e4-d0da-4c18-bda6-63879ea04b5a\",\"type\":\"Range1d\"},{\"attributes\":{},\"id\":\"9390d8c4-2b02-4dfb-8a1a-b1b1d19fbc4a\",\"type\":\"ToolEvents\"},{\"attributes\":{},\"id\":\"92489249-e75e-4958-b198-0a9b2e7d50ec\",\"type\":\"BasicTickFormatter\"},{\"attributes\":{\"plot\":{\"id\":\"ba86df0a-4ca1-46f5-b426-d8c77b0de1c5\",\"subtype\":\"Chart\",\"type\":\"Plot\"}},\"id\":\"99cd1fc0-415a-46cd-9288-7ffd0765ee72\",\"type\":\"WheelZoomTool\"},{\"attributes\":{\"legends\":[[\"setosa\",[{\"id\":\"55ea4a08-e14d-4986-a3c5-65bc3fe6184d\",\"type\":\"GlyphRenderer\"}]],[\"versicolor\",[{\"id\":\"3d1734f2-150d-4be1-858c-ea278bb06948\",\"type\":\"GlyphRenderer\"}]],[\"virginica\",[{\"id\":\"da012400-a2ac-4c0c-b102-7bbd17e3104a\",\"type\":\"GlyphRenderer\"}]]],\"location\":\"top_left\",\"plot\":{\"id\":\"ba86df0a-4ca1-46f5-b426-d8c77b0de1c5\",\"subtype\":\"Chart\",\"type\":\"Plot\"}},\"id\":\"caf01d4d-f6e4-41f9-9532-b3e14065bfc8\",\"type\":\"Legend\"},{\"attributes\":{\"plot\":{\"id\":\"ba86df0a-4ca1-46f5-b426-d8c77b0de1c5\",\"subtype\":\"Chart\",\"type\":\"Plot\"}},\"id\":\"87804e55-93dc-4211-9a10-3c498e5fdefa\",\"type\":\"SaveTool\"},{\"attributes\":{\"dimension\":1,\"plot\":{\"id\":\"ba86df0a-4ca1-46f5-b426-d8c77b0de1c5\",\"subtype\":\"Chart\",\"type\":\"Plot\"},\"ticker\":{\"id\":\"323f04d2-87d9-4a08-83da-edb5d413b9c1\",\"type\":\"BasicTicker\"}},\"id\":\"f7059893-dae5-48c5-8148-bcd6af6fbca1\",\"type\":\"Grid\"},{\"attributes\":{\"axis_label\":\"Petal.Length\",\"formatter\":{\"id\":\"c36cace6-8b07-4c31-b641-01cd9060fd10\",\"type\":\"BasicTickFormatter\"},\"plot\":{\"id\":\"ba86df0a-4ca1-46f5-b426-d8c77b0de1c5\",\"subtype\":\"Chart\",\"type\":\"Plot\"},\"ticker\":{\"id\":\"7f586ebc-3e90-45c3-91f3-18145932fa14\",\"type\":\"BasicTicker\"}},\"id\":\"2f67b147-7fb4-4ee9-8d7f-3279e9fce20b\",\"type\":\"LinearAxis\"},{\"attributes\":{},\"id\":\"7f586ebc-3e90-45c3-91f3-18145932fa14\",\"type\":\"BasicTicker\"}],\"root_ids\":[\"ba86df0a-4ca1-46f5-b426-d8c77b0de1c5\"]},\"title\":\"Bokeh Application\",\"version\":\"0.12.2\"}}; var render_items = [{\"docid\":\"759303cb-f221-4e8e-8eee-555189b6cd91\",\"elementid\":\"ab950219-9790-4c01-8a06-67fa0b5f89e7\",\"modelid\":\"ba86df0a-4ca1-46f5-b426-d8c77b0de1c5\"}]; Bokeh.embed.embed_items(docs_json, render_items); }); }, function(Bokeh) { } ]; function run_inline_js() { if ((window.Bokeh !== undefined) || (force === \"1\")) { for (var i = 0; i < inline_js.length; i++) { inline_js[i](window.Bokeh); }if (force === \"1\") { display_loaded(); }} else if (Date.now() < window._bokeh_timeout) { setTimeout(run_inline_js, 100); } else if (!window._bokeh_failed_load) { console.log(\"Bokeh: BokehJS failed to load within specified timeout.\"); window._bokeh_failed_load = true; } else if (!force) { var cell = $(\"#ab950219-9790-4c01-8a06-67fa0b5f89e7\").parents('.cell').data().cell; cell.output_area.append_execute_result(NB_LOAD_WARNING) } } if (window._bokeh_is_loading === 0) { console.log(\"Bokeh: BokehJS loaded, going straight to plotting\"); run_inline_js(); } else { load_libs(js_urls, function() { console.log(\"Bokeh: BokehJS plotting callback run at\", now()); run_inline_js(); }); } }(this)); Seaborn Seaborn tiene su énfasis en los gráficos estadísticos. Nos permite realizar fácilmente gráficos de regresión y de las principales distribuciones; pero donde realmente brilla Seaborn es en su capacidad de visualizar muchas características diferentes a la vez. Veamos algunos ejemplos In [10]: # Ejemplo gráfico de distribuciones x = np . random . normal ( size = 100 ) dist = sns . distplot ( x ) In [11]: # Ejemplo gráfico regresión con tips dataset tips . head () Out[11]: total_bill tip sex smoker day time size 1 16.99 1.01 Female No Sun Dinner 2 2 10.34 1.66 Male No Sun Dinner 3 3 21.01 3.50 Male No Sun Dinner 3 4 23.68 3.31 Male No Sun Dinner 2 5 24.59 3.61 Female No Sun Dinner 4 In [12]: reg = sns . regplot ( x = \"total_bill\" , y = \"tip\" , data = tips ) In [13]: # Ejemplo pairplot con datase iris g = sns . pairplot ( iris , hue = \"Species\" , diag_kind = \"hist\" ) In [14]: # Ejemplo FacetGrid con iris g = sns . FacetGrid ( iris , col = \"Species\" ) g = g . map ( plt . scatter , \"Petal.Length\" , \"Petal.Width\" ) Folium Por último, veamos un ejemplo de como utilizar Folium . Ya que yo soy adepto al uso de la bicicleta para moverme por la ciudad, y muchas veces se hace difícil encontrar una bicicletería en donde poder encontrar repuestos o reparar la bicicleta; en este ejemplo vamos a crear un mapa interactivo del barrio de Palermo en donde vamos a marcar la ubicación de los negocios de bicicleterías. Esta información la podemos extraer del padrón que ofrece el gobierno de la Ciudad de Buenos Aires en su portal de datos . In [15]: # dataset de bicicleterías de Ciudad de Buenos Aires # descargado desde https://data.buenosaires.gob.ar/dataset/bicicleterias bici = pd . read_csv ( 'data/bicicleterias.csv' , sep = ';' ) bici . head () Out[15]: WKT ID NOMBRE DIRECCION TELEFONO EMAIL WEB MECANICA_S HORARIO_DE CALLE ALTURA DIRECCION_ BARRIO COMUNA 0 POINT (-58.466041249451614 -34.557060215984805) 52 11 A FONDO CONGRESO 2757 45421835 [email protected] https://WWW.11AFONDO.COM NO LUN A VIE DE 10 A 14 Y DE 16 A 20/SAB DE 10 A 14 CONGRESO AV. 2757 CONGRESO AV. 2757 NUÑEZ COMUNA 13 1 POINT (-58.41279876038783 -34.591915372813645) 32 AMERICAN BIKE AV. CNEL. DIAZ 1664 48220889 [email protected] https://WWW.AMERICANBIKE.COM.AR/ SI LUN A VIER DE 10 A 14 Y DE 15 A 20.30 / SAB DE 10 A 14 Y DE 15 A 20 DIAZ, CNEL. AV. 1664 DIAZ, CNEL. AV. 1664 PALERMO COMUNA 14 2 POINT (-58.425646989945932 -34.580365554062418) 30 ANDINO BIKES GUEMES 4818 47753677 [email protected] https://WWW.ANDINOBIKE.COM.AR/ NO LUN A VIER DE 9 A 19 / SAB DE 10:00 - 17:00 GUEMES 4818 GUEMES 4818 PALERMO COMUNA 14 3 POINT (-58.437608880680997 -34.6045094278806) 107 BABE BIKES WARNES 10 48549862 [email protected] NaN NO NaN WARNES 10 WARNES AV. 10 VILLA CRESPO COMUNA 15 4 POINT (-58.439598908303168 -34.58547499220991) 118 BELGRAVIA TAILOR MADE BICYLES BONPLAND 1459 1544291001 [email protected] WWW.FACEBOOK.COM/BELGRAVIABIKES NO NaN BONPLAND 1459 BONPLAND 1459 PALERMO COMUNA 14 In [16]: # corregimos el campo de coordenadas del dataset. def coord ( c ): coor = re . findall ( r '-?\\d+\\.\\d {7} ' , c ) coords = [ float ( s ) for s in coor ] return coords [:: - 1 ] bici [ 'WKT' ] = bici [ 'WKT' ] . apply ( coord ) # filtramos solo las bicicleterías de palermo bici_palermo = bici [ bici . BARRIO == 'PALERMO' ][[ 'WKT' , 'NOMBRE' ]] In [17]: # creamos el mapa con folium mapa = folium . Map ( location = [ - 34.588889 , - 58.430556 ], zoom_start = 13 ) In [18]: # agregamos los markers con el nombre de cada bicicletería. for index , row in bici_palermo . iterrows (): mapa . simple_marker ( row [ 'WKT' ], popup = row [ 'NOMBRE' ], marker_color = 'red' , marker_icon = 'info-sign' ) In [19]: # visualizamos el mapa con los markers mapa Out[19]: Aquí concluye este artículo, ya no hay excusas para graficar sus datos, como vimos Python cuenta con herramientas que son fáciles de usar y muy poderosas. A divertirse! Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Analisis de datos","url":"https://relopezbriega.github.io/blog/2016/09/18/visualizaciones-de-datos-con-python/"},{"title":"Factorización de Matrices con Python","text":"Introducción Cuando trabajamos en problemas de Machine Learning , muchas veces nos vamos a encontrar con enormes conjuntos de datos , con cientos o miles de características o features . Una forma simple de reducir las dimensiones de estas características es aplicar alguna técnica de Factorización de matrices . La Factorización de matrices tiene enormes aplicaciones en todo tipo de problemas relacionados a la inteligencia artificial , ya que la reducción de dimensionalidad es la esencia de la cognición . Asimismo, la Factorización de matrices es también un tema unificador dentro del álgebra lineal numérica . Una amplia variedad de algoritmos se han desarrollado a lo largo de muchas décadas, proporcionando una plataforma numérica para operaciones de matrices tales como, la resolución de sistemas de ecuaciones lineales , la descomposición espectral , y la identificación de subespacios vectoriales . Algunos de estos algoritmos también han demostrado ser de utilidad en problemas de análisis estadístico de datos , como es el caso de la descomposición en valores singulares o SVD , por sus siglas en inglés, que es la base del análisis de componentes principales o PCA , que es una técnica muy utilizada para reducir el tamaño de los datos. Muchas investigaciones actuales en Machine Learning han centrados sus esfuerzos en el uso de la Factorización de matrices para mejorar el rendimiento de los sistemas de aprendizaje. Principalmente en el estudio de la factorización de matrices no negativas (NMF) , la cual se centra en el análisis de matrices de datos cuyos elementos son positivos (no negativos), una ocurrencia muy común en los conjuntos de datos derivados de textos e imágenes. ¿Qué es la factorización de matrices? En matemáticas , la factorización es una técnica que consiste en la descomposición de una expresión matemática (que puede ser un número, una matriz , un tensor , etc.) en forma de producto. Existen distintos métodos de factorización, dependiendo de los objetos matemáticos estudiados; el objetivo es simplificar una expresión o reescribirla en términos de «bloques fundamentales», que reciben el nombre de factores . Así por ejemplo, el número 6 se puede descomponer en el producto de 3 y 2. Si extendemos este concepto al mundo de las matrices , entonces podemos decir que la Factorización de matrices consiste en encontrar dos o más matrices de manera tal que cuando se multipliquen nos devuelvan la matriz original. Por ejemplo: $$\\left(\\begin{matrix}3 & 4 & 5\\\\6 & 8 & 10 \\end{matrix}\\right) = \\left(\\begin{matrix}1\\\\2 \\end{matrix}\\right) \\cdot \\left(\\begin{matrix}3 & 4 & 5 \\end{matrix}\\right) $$ Los métodos de Factorización de matrices han ganado popularidad últimamente al haber sido aplicados con éxito en sistemas de recomendación para descubrir las características latentes que subyacen a las interacciones entre dos tipos de entidades, como por ejemplo usuarios y películas. El algoritmo que ganó el desafío de Netflix fue un sistema basado en métodos de Factorización de matrices . Factorización de matrices en sistemas de ecuaciones lineales Dos de las Factorización de matrices más utilizadas y que tal vez mucha gente las haya escuchado nombrar alguna vez son la factorización LU y la factorización QR ; las cuales se utilizan a menudo para resolver sistemas de ecuaciones lineales . Factorización LU $$A = LU$$ En álgebra lineal , la factorización o descomposición LU (del inglés Lower-Upper) es una forma de factorización de una matriz como el producto de una matriz triangular inferior y una superior. La factorización LU expresa el método de Gauss en forma matricial. Así por ejemplo, tenemos que $PA = LU$ donde $P$ es una matriz de permutación . Una condición suficiente para que exista la factorización es que la matriz $A$ sea una matriz no singular . En Python podemos encontrar la descomposición LU con la ayuda de SciPy de la siguiente forma: In [1]: Ver Código # importando modulos necesarios import matplotlib.pyplot as plt import numpy as np import scipy.sparse as sp import scipy.linalg as la import nimfa from sklearn.decomposition import NMF , PCA from sklearn.preprocessing import StandardScaler import seaborn as sns import pandas as pd # graficos incrustados % matplotlib inline # parámetros de estilo sns . set ( style = 'darkgrid' , palette = 'muted' ) pd . set_option ( 'display.mpl_style' , 'default' ) pd . set_option ( 'display.notebook_repr_html' , True ) plt . rcParams [ 'figure.figsize' ] = 8 , 6 In [2]: # Ejemplo factorización LU A = np . array ([[ 7 , 3 , - 1 , 2 ] ,[ 3 , 8 , 1 , - 4 ] ,[ - 1 , 1 , 4 , - 1 ] ,[ 2 , - 4 , - 1 , 6 ]]) P , L , U = la . lu ( A ) In [3]: # Matriz A A Out[3]: array([[ 7, 3, -1, 2], [ 3, 8, 1, -4], [-1, 1, 4, -1], [ 2, -4, -1, 6]]) In [4]: # Matriz de permutación P Out[4]: array([[ 1., 0., 0., 0.], [ 0., 1., 0., 0.], [ 0., 0., 1., 0.], [ 0., 0., 0., 1.]]) In [5]: # Matriz triangular inferior L Out[5]: array([[ 1. , 0. , 0. , 0. ], [ 0.42857143, 1. , 0. , 0. ], [-0.14285714, 0.21276596, 1. , 0. ], [ 0.28571429, -0.72340426, 0.08982036, 1. ]]) In [6]: # Matriz triangular superior U Out[6]: array([[ 7. , 3. , -1. , 2. ], [ 0. , 6.71428571, 1.42857143, -4.85714286], [ 0. , 0. , 3.55319149, 0.31914894], [ 0. , 0. , 0. , 1.88622754]]) In [7]: # A = LU L @ U Out[7]: array([[ 7., 3., -1., 2.], [ 3., 8., 1., -4.], [-1., 1., 4., -1.], [ 2., -4., -1., 6.]]) Factorización QR $$A = QR$$ La descomposición o factorización QR consiste en la descomposición de una matriz como producto de una matriz ortogonal ($Q^T \\cdot Q = I$) por una matriz triangular superior . la factorización QR es ampliamente utilizada en las finanzas cuantitativas como base para la solución del problema de los mínimos cuadrados lineales , que a su vez se utiliza para el análisis de regresión estadística . En Python podemos encontrar la descomposición QR con la ayuda de SciPy de la siguiente forma: In [8]: # Ejemplo factorización QR A = np . array ([[ 12 , - 51 , 4 ], [ 6 , 167 , - 68 ], [ - 4 , 24 , - 41 ]]) Q , R = la . qr ( A ) In [9]: # Matriz A A Out[9]: array([[ 12, -51, 4], [ 6, 167, -68], [ -4, 24, -41]]) In [10]: # Matriz ortogonal Q Q Out[10]: array([[-0.85714286, 0.39428571, 0.33142857], [-0.42857143, -0.90285714, -0.03428571], [ 0.28571429, -0.17142857, 0.94285714]]) In [11]: # Matriz triangular superior R R Out[11]: array([[ -14., -21., 14.], [ 0., -175., 70.], [ 0., 0., -35.]]) In [12]: # A = QR Q @ R Out[12]: array([[ 12., -51., 4.], [ 6., 167., -68.], [ -4., 24., -41.]]) Matrices dispersas y no negativas En muchas ocasiones cuando trabajamos con matrices para representar el mundo físico, nos vamos a encontrar con que muchas de estas matrices están dominadas por mayoría de elementos que son cero. Este tipo de matrices son llamadas matrices dispersas , es decir, matrices de gran tamaño en que la mayor parte de sus elementos son cero. Como sería ineficiente almacenar en la memoria de la computadora todos los elementos en cero, en las matrices dispersas solo vamos a almacenar los valores que no son cero y alguna información adicional acerca de su ubicación. Asimismo muchos datos del mundo real son no negativos y los componentes ocultos correspondientes tienen un sentido físico solamente cuando son no negativos. En la práctica, ambas características, ser dispersas y no negativas son a menudo deseable o necesario cuando los componentes subyacentes tienen una interpretación física. Por ejemplo, en el procesamiento de imágenes y visión artificial , las variables involucradas y los parámetros pueden corresponder a píxeles, y la factorización de matrices dispersas y no negativas está relacionada con la extracción de las partes más relevantes de las imágenes. La representación dispersa de los datos por un número limitado de componentes es un problema importante en la investigación. En Machine Learning , las matrices dispersas está estrechamente relacionada con la selección de atributos y ciertas generalizaciones en algoritmos de aprendizaje; mientras que la no negatividad se relaciona a las distribuciones de probabilidad . En Python , podemos representar a las matrices dispersas con la ayuda de scipy.sparse . In [13]: # Ejemplo matriz dispersa con scipy A = sp . diags ([ 1 , - 2 , 1 ], [ 1 , 0 , - 1 ], shape = [ 10 , 10 ], format = 'csc' ) A Out[13]: <10x10 sparse matrix of type '<class 'numpy.float64'>' with 28 stored elements in Compressed Sparse Column format> In [14]: A . data Out[14]: array([-2., 1., 1., -2., 1., 1., -2., 1., 1., -2., 1., 1., -2., 1., 1., -2., 1., 1., -2., 1., 1., -2., 1., 1., -2., 1., 1., -2.]) In [15]: A . indices Out[15]: array([0, 1, 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, 9], dtype=int32) In [16]: A . indptr Out[16]: array([ 0, 2, 5, 8, 11, 14, 17, 20, 23, 26, 28], dtype=int32) In [17]: A . todense () Out[17]: matrix([[-2., 1., 0., 0., 0., 0., 0., 0., 0., 0.], [ 1., -2., 1., 0., 0., 0., 0., 0., 0., 0.], [ 0., 1., -2., 1., 0., 0., 0., 0., 0., 0.], [ 0., 0., 1., -2., 1., 0., 0., 0., 0., 0.], [ 0., 0., 0., 1., -2., 1., 0., 0., 0., 0.], [ 0., 0., 0., 0., 1., -2., 1., 0., 0., 0.], [ 0., 0., 0., 0., 0., 1., -2., 1., 0., 0.], [ 0., 0., 0., 0., 0., 0., 1., -2., 1., 0.], [ 0., 0., 0., 0., 0., 0., 0., 1., -2., 1.], [ 0., 0., 0., 0., 0., 0., 0., 0., 1., -2.]]) La descomposición en valores singulares (SVD) y el análisis de componentes principales (PCA) El SVD y el PCA son herramientas ampliamente utilizadas, por ejemplo, en el análisis de imágenes médicas para la reducción de dimensionalidad , la construcción de modelos, y la comprensión y exploración de datos. Tienen aplicaciones en prácticamente todas las áreas de la ciencia, machine learning , procesamiento de imágenes , ingeniería , genética , computación cognitiva , química , meteorología , y redes neuronales , sólo por nombrar algunas; en dónde nos encontramos con grandes conjuntos de datos . El propósito del análisis de componentes principales PCA es derivar un número relativamente pequeño de combinaciones lineales no correlacionadas (componentes principales) de una conjunto de variables aleatorias de media cero mientras que conserva la mayor cantidad de información de las variables originales como sea posible. Entre los objetivos del PCA podemos encontrar los siguientes: Reducción de dimensionalidad . Determinación de combinaciones lineales de variables. Selección de características o features : la elección de las variables más útiles. Visualización de datos multidimensionales. Identificación de las variables subyacentes. Identificación grupos de objetos o de valores atípicos . Veamos algunos ejemplos con Python In [18]: # Ejemplo SVD con scipy.linalg.svd # Matriz A a factorizar A = np . array ([[ 2 , 4 ] ,[ 1 , 3 ] ,[ 0 , 0 ] ,[ 0 , 0 ]]) A Out[18]: array([[2, 4], [1, 3], [0, 0], [0, 0]]) In [19]: # Factorización con svd # svd factoriza la matriz A en dos matrices unitarias U y Vh, y una # matriz s de valores singulares (reales, no negativo) de tal manera que # A == U * S * Vh, donde S es una matriz con s como principal diagonal y ceros U , s , Vh = la . svd ( A ) U . shape , Vh . shape , s . shape Out[19]: ((4, 4), (2, 2), (2,)) In [20]: # Matriz unitaria U Out[20]: array([[-0.81741556, -0.57604844, 0. , 0. ], [-0.57604844, 0.81741556, 0. , 0. ], [ 0. , 0. , 1. , 0. ], [ 0. , 0. , 0. , 1. ]]) In [21]: # Valores singulares s Out[21]: array([ 5.4649857 , 0.36596619]) In [22]: # Matriz unitaria Vh Out[22]: array([[-0.40455358, -0.9145143 ], [-0.9145143 , 0.40455358]]) In [23]: # Generando S S = la . diagsvd ( s , 4 , 2 ) S Out[23]: array([[ 5.4649857 , 0. ], [ 0. , 0.36596619], [ 0. , 0. ], [ 0. , 0. ]]) In [24]: # Reconstruyendo la Matriz A. U @ S @ Vh Out[24]: array([[ 2., 4.], [ 1., 3.], [ 0., 0.], [ 0., 0.]]) Ejemplo de SVD y PCA con el dataset Iris El dataset iris contiene mediciones de 150 flores de iris de tres especies diferentes. Las tres clases en el dataset son: setosa (n = 50). versicolor (n = 50). virginica (n = 50). Las cuales están representadas por cuatro características: longitud en cm del sépalo . ancho en cm del sépalo . longitud en cm del pétalo . ancho en cm del pétalo . In [25]: # Ejemplo svd con iris dataset iris = sns . load_dataset ( \"iris\" ) print ( iris . shape ) iris . head () (150, 5) Out[25]: sepal_length sepal_width petal_length petal_width species 0 5.1 3.5 1.4 0.2 setosa 1 4.9 3.0 1.4 0.2 setosa 2 4.7 3.2 1.3 0.2 setosa 3 4.6 3.1 1.5 0.2 setosa 4 5.0 3.6 1.4 0.2 setosa In [26]: # Aplicando svd U , s , Vh = sp . linalg . svds (( iris - iris . mean ()) . iloc [:,: - 1 ], 2 ) In [27]: # Creando los componentes principales pc = U @ np . diag ( s ) pc = pc [:,:: - 1 ] # graficando el dataset reducido a 2 componenetes iris_svd = pd . concat (( pd . DataFrame ( pc , index = iris . index , columns = ( 'c0' , 'c1' )), iris . loc [:, 'species' ]), 1 ) g = sns . lmplot ( 'c0' , 'c1' , iris_svd , hue = 'species' , fit_reg = False , size = 8 , scatter_kws = { 'alpha' : 0.7 , 's' : 60 }) In [28]: # Ejemplo de PCA con Scikit-Learn e Iris dataset # Divido el dataset en datos y clases X = iris . ix [:, 0 : 4 ] . values y = iris . ix [:, 4 ] . values # Estandarizo los datos X_std = StandardScaler () . fit_transform ( X ) pca = PCA ( n_components = 2 ) Y_pca = pca . fit_transform ( X_std ) # Visualizo el resultado for lab , col in zip (( 'setosa' , 'versicolor' , 'virginica' ), ( 'blue' , 'red' , 'green' )): plt . scatter ( Y_pca [ y == lab , 0 ], Y_pca [ y == lab , 1 ], label = lab , c = col ) plt . xlabel ( 'Componente 1' ) plt . ylabel ( 'Componente 2' ) plt . legend ( loc = 'lower center' ) plt . tight_layout () plt . title ( 'Ejemplo PCA' ) plt . show () Factorización de matrices en sistemas de recomendación En un sistemas de recomendación como Netflix o MovieLens , hay un grupo de usuarios y un conjunto de elementos (películas en los dos sistemas anteriores). Teniendo en cuenta que cada usuario ha valorado algunos elementos en el sistema, nos gustaría predecir cómo los usuarios calificarían los artículos que aún no se han valorado, de tal manera que podemos hacer recomendaciones a los usuarios. En este caso, toda la información que tenemos sobre las calificaciones existentes pueden ser representados en una matriz . Supongamos ahora que tenemos 5 usuarios y 4 películas y las calificaciones son números enteros de 1 a 5, la matriz resultante puede ser algo como la siguiente (el guión significa que el usuario aún no ha calificado la película): P1 P2 P3 P4 U1 5 3 - 1 U2 4 - - 1 U3 1 1 - 5 U4 1 - - 4 U5 - 1 5 4 Por lo tanto, la tarea de predecir las calificaciones que faltan se puede considerar como un problema de llenar los espacios en blanco (los guiones en la matriz) de tal manera que los valores resultantes serían consistentes con las calificaciones existentes en la matriz . La intuición detrás de usar Factorización de matrices para resolver este problema es que deberían existir algunas características latentes que determinen cómo un usuario califica una película. Por ejemplo, dos usuarios darían una alta calificación a una cierta película si a ambos les gusta los actores / actrices de la película, o si la película es una película de acción, que es un género preferido por ambos usuarios. Por lo tanto, si podemos descubrir estas características latentes, deberíamos ser capaces de predecir una calificación con respecto a un determinado usuario y una cierta película, porque las características asociadas con el usuario deben coincidir con las características asociadas con la película. Al tratar de descubrir las diferentes características latentes, estamos suponiendo implícitamente que el número de características va a ser menor que el número de usuarios y el número de elementos. No debería ser difícil de entender este supuesto ya que no sería razonable que cada usuario está asociado con una característica única (aunque esto no es imposible). Y de todos modos, si este es el caso, no tendría sentido hacer recomendaciones, porque ninguno de estos usuarios estarían interesados en los artículos calificados por otros usuarios. Si llevamos este ejemplo sencillo a Python , podríamos modelarlo utilizando Scikit-learn NMF o Nimfa . In [29]: # Ejemplo en python # Matriz de ratings de los usuarios R = np . array ([[ 5 , 3 , 0 , 1 ] ,[ 4 , 0 , 0 , 1 ] ,[ 1 , 1 , 0 , 5 ] ,[ 1 , 0 , 0 , 4 ] ,[ 0 , 1 , 5 , 4 ]]) In [30]: # Armado del modelo modelo = NMF ( n_components = 2 , init = 'random' , random_state = 1982 , alpha = 0.0002 , beta = 0.02 , max_iter = 5000 ) U = modelo . fit_transform ( R ) V = modelo . components_ In [31]: # Reconstrucción de la matriz U @ V Out[31]: array([[ 5.25534386, 1.99295604, 0. , 1.45508737], [ 3.50398031, 1.32879578, 0. , 0.97017391], [ 1.31288534, 0.94413861, 1.9495384 , 3.94596646], [ 0.98125417, 0.7217855 , 1.52757221, 3.07874315], [ 0. , 0.65011225, 2.84008987, 5.21892884]]) In [32]: # Error del modelo modelo . reconstruction_err_ Out[32]: 4.276529876124528 In [33]: # Ejemplo utilizando nimfa snmf = nimfa . Snmf ( R , seed = \"random_vcol\" , rank = 2 , max_iter = 30 , version = 'r' , eta = 1. , beta = 1e-4 , i_conv = 10 , w_min_change = 0 ) In [34]: # Armando el modelo fit = snmf () U = fit . basis () V = fit . coef () In [35]: # Reconstruyendo la matriz np . around ( U @ V , decimals = 2 ) Out[35]: array([[ 5.18, 1.96, 0. , 1.44], [ 3.45, 1.31, 0. , 0.96], [ 1.32, 0.93, 1.91, 3.87], [ 0.98, 0.71, 1.5 , 3.02], [ 0. , 0.63, 2.79, 5.11]]) Librerías de Python para factorización de matrices Para concluir, podemos enumerar algunas de las librerías de Python que nos pueden ser de mucha utilidad a la hora de trabajar con Factorización de matrices , como ser: NumPy : El popular paquete matemático de Python , nos va a permitir crear vectores , matrices y tensores ; y poder manipularlos y realizar operaciones sobre ellos con mucha facilidad. SciPy : El paquete que agrupa un gran número de herramientas científicas en Python . Nos va a permitir crear matrices dispersas y realizar factorizaciones con facilidad. Scikit-learn : Es una librería especializada en algoritmos para data mining y machine learning . Principalmente el submódulo de Descomposiciones en dónde vamos a encontrar las herramientas para reducciones de dimensionalidad . SymPy : Esta librería nos permite trabajar con matemática simbólica, convierte a Python en un sistema algebraico computacional . Nos va a permitir trabajar con las factorizaciones en forma analítica. Nimfa : Nimfa es una librería para factorización de matrices no negativas . Incluye las implementaciones de varios métodos de factorización , enfoques de inicialización y calificación de calidad. Soporta representaciones tanto de matrices densas como dispersas . Se distribuye bajo licencia BSD . Con esto termina este artículo, espero les haya parecido interesante y les sea de utilidad en sus proyectos. Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Algebra","url":"https://relopezbriega.github.io/blog/2016/09/13/factorizacion-de-matrices-con-python/"},{"title":"Redes neuronales convolucionales con TensorFlow","text":"Introducción De más esta decir que el sentido de la visión es uno de los grandes prodigios de la Naturaleza. En fracciones de segundos, podemos identificar objetos dentro de nuestro campo de visión, sin siquiera detenernos a pensar en ello. Pero no sólo podemos nombrar estos objetos que observamos, sino que también podemos percibir su profundidad, distinguir perfectamente sus contornos, y separarlos de sus fondos. De alguna manera los ojos captan datos de píxeles , pero el cerebro transforma esa información en características más significativas - líneas, curvas y formas - que podrían indicar, por ejemplo, que estamos mirando a una persona. Gracias a que el área del cerebro responsable de la visión es una de las zonas más estudiadas y que más conocemos; sabemos que la corteza visual contiene una disposición jerárquica compleja de neuronas . Por ejemplo, la información visual es introducida en la corteza a través del área visual primaria, llamada V1. Las neuronas de V1 se ocupan de características visuales de bajo nivel, tales como pequeños segmentos de contorno, componentes de pequeña escala del movimiento, disparidad binocular , e información básica de contraste y color. V1 luego alimenta de información a otras áreas, como V2, V4 y V5. Cada una de estas áreas se ocupa de los aspectos más específicos o abstractas de la información. Por ejemplo, las neuronas en V4 se ocupan de objetos de mediana complejidad, tales como formas de estrellas en diferentes colores. La corteza visual de los animales es el más potente sistema de procesamiento visual que conocemos, por lo que suena lógico inspirarse en ella para crear una variante de redes neuronales artificiales que ayude a identificar imágenes; es así como surgen las redes neuronales convolucionales . ¿Qué son las Redes Neuronales Convolucionales? Las redes neuronales convolucionales son muy similares a las redes neuronales ordinarias como el perceptron multicapa que vimos en el artículo anterior ; se componen de neuronas que tienen pesos y sesgos que pueden aprender. Cada neurona recibe algunas entradas, realiza un producto escalar y luego aplica una función de activación. Al igual que en el perceptron multicapa también vamos a tener una función de pérdida o costo (por ejemplo SVM / Softmax) sobre la última capa, la cual estará totalmente conectada. Lo que diferencia a las redes neuronales convolucionales es que suponen explícitamente que las entradas son imágenes, lo que nos permite codificar ciertas propiedades en la arquitectura; permitiendo ganar en eficiencia y reducir la cantidad de parámetros en la red. Las redes neuronales convolucionales vienen a solucionar el problema de que las redes neuronales ordinarias no escalan bien para imágenes de mucha definición; por ejemplo en el problema de MNIST , las imágenes son de 28x28; por lo que una sola neurona plenamente conectado en una primera capa oculta de una red neuronal ordinaria tendría 28 x 28 = 784 pesos. Esta cantidad todavía parece manejable, pero es evidente que esta estructura totalmente conectado no funciona bien con imágenes más grandes. Si tomamos el caso de una imagen de mayor tamaño, por ejemplo de 200x200 con colores RGB, daría lugar a neuronas que tienen 200 x 200 x 3 = 120.000 pesos. Por otra parte, el contar con tantos parámetros, también sería un desperdicio de recursos y conduciría rápidamente a sobreajuste . Las redes neuronales convolucionales trabajan modelando de forma consecutiva pequeñas piezas de información, y luego combinando esta información en las capas más profundas de la red. Una manera de entenderlas es que la primera capa intentará detectar los bordes y establecer patrones de detección de bordes. Luego, las capas posteriores trataran de combinarlos en formas más simples y, finalmente, en patrones de las diferentes posiciones de los objetos, iluminación, escalas, etc. Las capas finales intentarán hacer coincidir una imagen de entrada con todas los patrones y arribar a una predicción final como una suma ponderada de todos ellos. De esta forma las redes neuronales convolucionales son capaces de modelar complejas variaciones y comportamientos dando predicciones bastantes precisas. Estructura de las Redes Neuronales Convolucionales En general, las redes neuronales convolucionales van a estar construidas con una estructura que contendrá 3 tipos distintos de capas: Una capa convolucional, que es la que le da le nombre a la red. Una capa de reducción o de pooling , la cual va a reducir la cantidad de parámetros al quedarse con las características más comunes. Una capa clasificadora totalmente conectada, la cual nos va dar el resultado final de la red. Profundicemos un poco en cada una de ellas. Capa convolucional Como dijimos anteriormente, lo que distingue a las redes neuronales convolucionales de cualquier otra red neuronal es utilizan un operación llamada convolución en alguna de sus capas; en lugar de utilizar la multiplicación de matrices que se aplica generalmente. La operación de convolución recibe como entrada o input la imagen y luego aplica sobre ella un filtro o kernel que nos devuelve un mapa de las características de la imagen original, de esta forma logramos reducir el tamaño de los parámetros. La convolución aprovecha tres ideas importantes que pueden ayudar a mejorar cualquier sistema de machine learning , ellas son: interacciones dispersas , ya que al aplicar un filtro de menor tamaño sobre la entrada original podemos reducir drásticamente la cantidad de parámetros y cálculos; los parámetros compartidos , que hace referencia a compartir los parámetros entre los distintos tipos de filtros, ayudando también a mejorar la eficiencia del sistema; y las representaciones equivariante , que indican que si las entradas cambian, las salidas van a cambiar también en forma similar. Por otra parte, la convolución proporciona un medio para trabajar con entradas de tamaño variable, lo que puede ser también muy conveniente. Capa de reducción o pooling La capa de reducción o pooling se coloca generalmente después de la capa convolucional . Su utilidad principal radica en la reducción de las dimensiones espaciales (ancho x alto) del volumen de entrada para la siguiente capa convolucional . No afecta a la dimensión de profundidad del volumen. La operación realizada por esta capa también se llama reducción de muestreo , ya que la reducción de tamaño conduce también a la pérdida de información. Sin embargo, una pérdida de este tipo puede ser beneficioso para la red por dos razones: la disminución en el tamaño conduce a una menor sobrecarga de cálculo para las próximas capas de la red; también trabaja para reducir el sobreajuste . La operación que se suele utilizar en esta capa es max-pooling , que divide a la imagen de entrada en un conjunto de rectángulos y, respecto de cada subregión, se va quedando con el máximo valor. Capa clasificadora totalmente conectada Al final de las capas convolucional y de pooling , las redes utilizan generalmente capas completamente conectados en la que cada pixel se considera como una neurona separada al igual que en una red neuronal regular. Esta última capa clasificadora tendrá tantas neuronas como el número de clases que se debe predecir. Ejemplo con TensorFlow y el dataset CIFAR-10 Luego de toda esta introducción teórica es tiempo de pasar a la acción y ver como podemos aplicar todo lo que hemos aprendimos para crear una red neuronal convolucional con la ayuda de TensorFlow . En este ejemplo vamos a intentar clasificar imágenes en 10 categorías distintas; la idea es entrenar la red para que pueda distinguir entre imágenes de aviones, automóviles, pájaros, gatos, ciervos, perros, ranas, caballos, barcos y camiones. Para esto vamos a utilizar el conjunto de datos CIFAR-10 . El conjunto de datos CIFAR-10 El conjunto de datos CIFAR-10 consta de 60.000 imágenes a color de 32x32 que están divididas en 10 clases distintas; con 6.000 imágenes por cada clase. Tiene 50.000 imágenes de entrenamiento y 10.000 imágenes de prueba. CIFAR-10 se divide en cinco lotes de entrenamiento y un lote de prueba, cada uno con 10.000 imágenes. El lote de prueba contiene exactamente 1.000 imágenes seleccionadas al azar de cada clase. Los lotes de entrenamiento contienen las imágenes restantes en orden aleatorio, pero algunos lotes de entrenamiento pueden tener más imágenes de una clase que otro. Entre todos ellos, los lotes de entrenamiento contienen exactamente 5.000 imágenes de cada clase. Las clases son completamente mutuamente excluyentes. No existe solapamiento entre automóviles y camiones. Explorando CIFAR-10 Antes de pasar al armado de la red, exploremos un poco este conjunto de datos . In [1]: Ver Código # importamos la libreria import tensorflow as tf from tensorflow.python import control_flow_ops import time , os import cifar10_input cifar10_input . maybe_download_and_extract () # importamos librerías adicionales import numpy as np import matplotlib.pyplot as plt import cPickle % matplotlib inline # funciones de ayuda def unpickle ( file ): fo = open ( file , 'rb' ) dict = cPickle . load ( fo ) fo . close () data = dict [ 'data' ] imgs = np . transpose ( np . reshape ( data ,( - 1 , 32 , 32 , 3 ), order = 'F' ), axes = ( 0 , 2 , 1 , 3 )) #order batch,x,y,color y = np . asarray ( dict [ 'labels' ], dtype = 'uint8' ) return y , imgs clases = [ 'avion' , 'auto' , 'pajaro' , 'gato' , 'ciervo' , 'perro' , 'rana' , 'caballo' , 'barco' , 'camion' ] In [2]: # cargar los datos labels , data = unpickle ( 'cifar-10-batches-py/test_batch' ) In [3]: # forma del dataset print 'forma dataset prueba: {} ' . format ( data . shape ) forma dataset prueba: (10000, 32, 32, 3) In [4]: # dibujar imagenes fig = plt . figure ( figsize = ( 7 , 7 )) for i in range ( 25 ): fig . add_subplot ( 5 , 5 ,( i + 1 ), xticks = [], yticks = []) plt . title ( \" {} \" . format ( clases [ labels [ i ]])) plt . imshow ( data [ i ]) plt . savefig ( 'cifar.jpg' ) In [5]: # dibujar 200 imagenes conj = np . zeros (( 320 , 640 , 3 ), dtype = 'uint8' ) for i in range ( 10 ): idx = np . where ( labels == i )[ 0 ] for j in range ( 20 ): conj [ 32 * i : 32 * ( i + 1 ), 32 * j : 32 * ( j + 1 ),:] = data [ idx [ j ]] fig = plt . figure ( figsize = ( 10 , 20 )) imgs = plt . imshow ( conj ) Como podemos ver, contamos con 5 lotes de entrenamientos de 10.000 imágenes cada uno; cada imagen esta representa por un tensor de forma 32x32x3; siendo 32x32 el largo y ancho de la imagen y la profundidad de 3 se la da los valores de los colores RGB . También podemos ver que las imágenes de cada clase son bastante variadas y en distintas posiciones, por lo que la red deberá aprender las características fundamentas de cada una de ellas para poder clasificarlas correctamente. Construyendo la red neuronal convolucional Ahora que ya conocemos el dataset con el que vamos a trabajar, es tiempo de pasar al armado del modelo. Enseñemos a la computadora a ver! In [6]: # armado de la red # Parametros learning_rate = 0.01 training_epochs = 15 batch_size = 128 display_step = 500 NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN = 50000 def inputs ( eval_data = True ): data_dir = os . path . join ( 'data/cifar10_data' , 'cifar-10-batches-bin' ) return cifar10_input . inputs ( eval_data = eval_data , data_dir = data_dir , batch_size = batch_size ) def distorted_inputs (): data_dir = os . path . join ( 'data/cifar10_data' , 'cifar-10-batches-bin' ) return cifar10_input . distorted_inputs ( data_dir = data_dir , batch_size = batch_size ) def filter_summary ( V , weight_shape ): ix = weight_shape [ 0 ] iy = weight_shape [ 1 ] cx , cy = 8 , 8 V_T = tf . transpose ( V , ( 3 , 0 , 1 , 2 )) tf . image_summary ( \"filters\" , V_T , max_images = 64 ) def max_pool ( input , k = 2 ): return tf . nn . max_pool ( input , ksize = [ 1 , k , k , 1 ], strides = [ 1 , k , k , 1 ], padding = 'SAME' ) def loss ( output , y ): xentropy = tf . nn . sparse_softmax_cross_entropy_with_logits ( output , tf . cast ( y , tf . int64 )) loss = tf . reduce_mean ( xentropy ) return loss def conv_batch_norm ( x , n_out , phase_train ): beta_init = tf . constant_initializer ( value = 0.0 , dtype = tf . float32 ) gamma_init = tf . constant_initializer ( value = 1.0 , dtype = tf . float32 ) beta = tf . get_variable ( \"beta\" , [ n_out ], initializer = beta_init ) gamma = tf . get_variable ( \"gamma\" , [ n_out ], initializer = gamma_init ) batch_mean , batch_var = tf . nn . moments ( x , [ 0 , 1 , 2 ], name = 'moments' ) ema = tf . train . ExponentialMovingAverage ( decay = 0.9 ) ema_apply_op = ema . apply ([ batch_mean , batch_var ]) ema_mean , ema_var = ema . average ( batch_mean ), ema . average ( batch_var ) def mean_var_with_update (): with tf . control_dependencies ([ ema_apply_op ]): return tf . identity ( batch_mean ), tf . identity ( batch_var ) mean , var = control_flow_ops . cond ( phase_train , mean_var_with_update , lambda : ( ema_mean , ema_var )) normed = tf . nn . batch_norm_with_global_normalization ( x , mean , var , beta , gamma , 1e-3 , True ) return normed def layer_batch_norm ( x , n_out , phase_train ): beta_init = tf . constant_initializer ( value = 0.0 , dtype = tf . float32 ) gamma_init = tf . constant_initializer ( value = 1.0 , dtype = tf . float32 ) beta = tf . get_variable ( \"beta\" , [ n_out ], initializer = beta_init ) gamma = tf . get_variable ( \"gamma\" , [ n_out ], initializer = gamma_init ) batch_mean , batch_var = tf . nn . moments ( x , [ 0 ], name = 'moments' ) ema = tf . train . ExponentialMovingAverage ( decay = 0.9 ) ema_apply_op = ema . apply ([ batch_mean , batch_var ]) ema_mean , ema_var = ema . average ( batch_mean ), ema . average ( batch_var ) def mean_var_with_update (): with tf . control_dependencies ([ ema_apply_op ]): return tf . identity ( batch_mean ), tf . identity ( batch_var ) mean , var = control_flow_ops . cond ( phase_train , mean_var_with_update , lambda : ( ema_mean , ema_var )) reshaped_x = tf . reshape ( x , [ - 1 , 1 , 1 , n_out ]) normed = tf . nn . batch_norm_with_global_normalization ( reshaped_x , mean , var , beta , gamma , 1e-3 , True ) return tf . reshape ( normed , [ - 1 , n_out ]) def conv2d ( input , weight_shape , bias_shape , phase_train , visualize = False ): incoming = weight_shape [ 0 ] * weight_shape [ 1 ] * weight_shape [ 2 ] weight_init = tf . random_normal_initializer ( stddev = ( 2.0 / incoming ) ** 0.5 ) W = tf . get_variable ( \"W\" , weight_shape , initializer = weight_init ) if visualize : filter_summary ( W , weight_shape ) bias_init = tf . constant_initializer ( value = 0 ) b = tf . get_variable ( \"b\" , bias_shape , initializer = bias_init ) logits = tf . nn . bias_add ( tf . nn . conv2d ( input , W , strides = [ 1 , 1 , 1 , 1 ], padding = 'SAME' ), b ) return tf . nn . relu ( conv_batch_norm ( logits , weight_shape [ 3 ], phase_train )) def layer ( input , weight_shape , bias_shape , phase_train ): weight_init = tf . random_normal_initializer ( stddev = ( 2.0 / weight_shape [ 0 ]) ** 0.5 ) bias_init = tf . constant_initializer ( value = 0 ) W = tf . get_variable ( \"W\" , weight_shape , initializer = weight_init ) b = tf . get_variable ( \"b\" , bias_shape , initializer = bias_init ) logits = tf . matmul ( input , W ) + b return tf . nn . relu ( layer_batch_norm ( logits , weight_shape [ 1 ], phase_train )) def inference ( x , keep_prob , phase_train ): with tf . variable_scope ( \"conv_1\" ): conv_1 = conv2d ( x , [ 5 , 5 , 3 , 64 ], [ 64 ], phase_train , visualize = True ) pool_1 = max_pool ( conv_1 ) with tf . variable_scope ( \"conv_2\" ): conv_2 = conv2d ( pool_1 , [ 5 , 5 , 64 , 64 ], [ 64 ], phase_train ) pool_2 = max_pool ( conv_2 ) with tf . variable_scope ( \"fc_1\" ): dim = 1 for d in pool_2 . get_shape ()[ 1 :] . as_list (): dim *= d pool_2_flat = tf . reshape ( pool_2 , [ - 1 , dim ]) fc_1 = layer ( pool_2_flat , [ dim , 384 ], [ 384 ], phase_train ) # apply dropout fc_1_drop = tf . nn . dropout ( fc_1 , keep_prob ) with tf . variable_scope ( \"fc_2\" ): fc_2 = layer ( fc_1_drop , [ 384 , 192 ], [ 192 ], phase_train ) # apply dropout fc_2_drop = tf . nn . dropout ( fc_2 , keep_prob ) with tf . variable_scope ( \"output\" ): output = layer ( fc_2_drop , [ 192 , 10 ], [ 10 ], phase_train ) return output def evaluate ( output , y ): correct_prediction = tf . equal ( tf . cast ( tf . argmax ( output , 1 ), dtype = tf . int32 ), y ) accuracy = tf . reduce_mean ( tf . cast ( correct_prediction , tf . float32 )) tf . scalar_summary ( \"validation error\" , ( 1.0 - accuracy )) return accuracy def training ( cost , global_step ): tf . scalar_summary ( \"cost\" , cost ) optimizer = tf . train . AdamOptimizer ( learning_rate ) train_op = optimizer . minimize ( cost , global_step = global_step ) return train_op En este bloque de código comenzamos definiendo los parámetros del modelo y algunas funciones de ayuda. Las funciones inputs y distorted_inputs las vamos a utilizar para cargar los datos de entrada y reducir su tamaño a 24x24 para no sobrecargar al equipo. La función filter_summary nos va a ayudar a visualizar la evolución de los filtros de las capas convolucionales . La función max_pool es la que vamos a utilizar para las capas de pooling . La función loss es la que va a medir la perdida o costo del modelo en cada iteración para luego poder ir optimizando. Las funciones evaluate y training son las que vamos a utilizar para evaluar y entrenar el modelo. La función conv2d nos va a permitir crear la capa convolucional con una forma particular. La función layer nos va a ayudar a construir cada capa. Las funciones conv_batch_norm y layer_batch_norm nos van a ayudar a normalizar cada uno de los lotes con los que vamos a nutrir a la red. Por último, la función inference es la que va a tener la estructura general del modelo y terminar realizando las predicciones. Ahora solo resta lanzar y entrenar el modelo. In [ ]: # Lanzando el modelo with tf . Graph () . as_default (): with tf . variable_scope ( \"cifar_Conv_model\" ): x = tf . placeholder ( \"float\" , [ None , 24 , 24 , 3 ]) y = tf . placeholder ( \"int32\" , [ None ]) keep_prob = tf . placeholder ( tf . float32 ) # dropout probability phase_train = tf . placeholder ( tf . bool ) # training or testing distorted_images , distorted_labels = distorted_inputs () val_images , val_labels = inputs () output = inference ( x , keep_prob , phase_train ) cost = loss ( output , y ) global_step = tf . Variable ( 0 , name = 'global_step' , trainable = False ) train_op = training ( cost , global_step ) eval_op = evaluate ( output , y ) summary_op = tf . merge_all_summaries () saver = tf . train . Saver () sess = tf . Session () summary_writer = tf . train . SummaryWriter ( \"conv_cifar_bn_logs/\" , graph = sess . graph ) init_op = tf . initialize_all_variables () sess . run ( init_op ) tf . train . start_queue_runners ( sess = sess ) # Training cycle for epoch in range ( training_epochs ): avg_cost = 0. total_batch = int ( NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN / batch_size ) # Loop over all batches for i in range ( total_batch ): # Fit training using batch data train_x , train_y = sess . run ([ distorted_images , distorted_labels ]) _ , new_cost = sess . run ([ train_op , cost ], feed_dict = { x : train_x , y : train_y , keep_prob : 1 , phase_train : True }) # Compute average loss avg_cost += new_cost / total_batch #print \"Epoch %d, minibatch %d of %d. Cost = %0.4f.\" %(epoch, # i, total_batch, new_cost) # Display logs per epoch step if epoch % display_step == 0 : print \"Epoch:\" , ' %04d ' % ( epoch + 1 ), \"cost =\" , \" {:.9f} \" . format ( avg_cost ) val_x , val_y = sess . run ([ val_images , val_labels ]) accuracy = sess . run ( eval_op , feed_dict = { x : val_x , y : val_y , keep_prob : 1 , phase_train : False }) print \"Validation Error:\" , ( 1 - accuracy ) summary_str = sess . run ( summary_op , feed_dict = { x : train_x , y : train_y , keep_prob : 1 , phase_train : False }) summary_writer . add_summary ( summary_str , sess . run ( global_step )) saver . save ( sess , \"conv_cifar_bn_logs/model-checkpoint\" , global_step = global_step ) print \"Optimization Finished!\" # validation first 256 imgs val_x , val_y = sess . run ([ val_images , val_labels ]) accuracy = sess . run ( eval_op , feed_dict = { x : val_x [: 256 ], y : val_y [: 256 ], keep_prob : 1 , phase_train : False }) print \"Test Accuracy:\" , accuracy Filling queue with 20000 CIFAR images before starting to train. This will take a few minutes. Epoch: 0001 cost = 1.521742517 Validation Error: Como podemos ver, logramos una precisión del 82% entrenando a la red en tan solo 15 épocas, una precisión bastante alta, dada la complejidad del problema y tan poco entrenamiento. Aquí termina el artículo, espero que les haya resultado interesante y los motive a explorar el fascinante mundo de las redes neuronales . Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer . In [ ]: % load_ext version_information % version_information numpy, Cython, matplotlib, tensorflow","tags":"Redes Neuronales","url":"https://relopezbriega.github.io/blog/2016/08/02/redes-neuronales-convolucionales-con-tensorflow/"},{"title":"Distribuciones de probabilidad con Python","text":"Introducción Las variables aleatorias han llegado a desempeñar un papel importante en casi todos los campos de estudio: en la Física , la Química y la Ingeniería ; y especialmente en las ciencias biológicas y sociales. Estas variables aleatorias son medidas y analizadas en términos de sus propiedades estadísticas y probabilísticas , de las cuales una característica subyacente es su función de distribución . A pesar de que el número potencial de distribuciones puede ser muy grande, en la práctica, un número relativamente pequeño se utilizan; ya sea porque tienen características matemáticas que las hace fáciles de usar o porque se asemejan bastante bien a una porción de la realidad, o por ambas razones combinadas. ¿Por qué es importante conocer las distribuciones? Muchos resultados en las ciencias se basan en conclusiones que se extraen sobre una población general a partir del estudio de una muestra de esta población. Este proceso se conoce como inferencia estadística ; y este tipo de inferencia con frecuencia se basa en hacer suposiciones acerca de la forma en que los datos se distribuyen, o requiere realizar alguna transformación de los datos para que se ajusten mejor a alguna de las distribuciones conocidas y estudiadas en profundidad. Las distribuciones de probabilidad teóricas son útiles en la inferencia estadística porque sus propiedades y características son conocidas. Si la distribución real de un conjunto de datos dado es razonablemente cercana a la de una distribución de probabilidad teórica, muchos de los cálculos se pueden realizar en los datos reales utilizando hipótesis extraídas de la distribución teórica. Graficando distribuciones Histogramas Una de las mejores maneras de describir una variable es representar los valores que aparecen en el conjunto de datos y el número de veces que aparece cada valor. La representación más común de una distribución es un histograma , que es un gráfico que muestra la frecuencia de cada valor. En Python , podemos graficar fácilmente un histograma con la ayuda de la función hist de matplotlib , simplemente debemos pasarle los datos y la cantidad de contenedores en los que queremos dividirlos. Por ejemplo, podríamos graficar el histograma de una distribución normal del siguiente modo. In [1]: Ver Código # importando modulos necesarios % matplotlib inline import matplotlib.pyplot as plt import numpy as np from scipy import stats import seaborn as sns np . random . seed ( 2016 ) # replicar random # parametros esteticos de seaborn sns . set_palette ( \"deep\" , desat =. 6 ) sns . set_context ( rc = { \"figure.figsize\" : ( 8 , 4 )}) In [2]: # Graficando histograma mu , sigma = 0 , 0.2 # media y desvio estandar datos = np . random . normal ( mu , sigma , 1000 ) #creando muestra de datos # histograma de distribución normal. cuenta , cajas , ignorar = plt . hist ( datos , 20 ) plt . ylabel ( 'frequencia' ) plt . xlabel ( 'valores' ) plt . title ( 'Histograma' ) plt . show () Función de Masa de Probabilidad Otra forma de representar a las distribuciones discretas es utilizando su Función de Masa de Probabilidad o FMP , la cual relaciona cada valor con su probabilidad en lugar de su frecuencia como vimos anteriormente. Esta función es normalizada de forma tal que el valor total de probabilidad sea 1. La ventaja que nos ofrece utilizar la FMP es que podemos comparar dos distribuciones sin necesidad de ser confundidos por las diferencias en el tamaño de las muestras . También debemos tener en cuenta que FMP funciona bien si el número de valores es pequeño; pero a medida que el número de valores aumenta, la probabilidad asociada a cada valor se hace cada vez más pequeña y el efecto del ruido aleatorio aumenta. Veamos un ejemplo con Python . In [3]: # Graficando FMP n , p = 30 , 0.4 # parametros de forma de la distribución binomial n_1 , p_1 = 20 , 0.3 # parametros de forma de la distribución binomial x = np . arange ( stats . binom . ppf ( 0.01 , n , p ), stats . binom . ppf ( 0.99 , n , p )) x_1 = np . arange ( stats . binom . ppf ( 0.01 , n_1 , p_1 ), stats . binom . ppf ( 0.99 , n_1 , p_1 )) fmp = stats . binom . pmf ( x , n , p ) # Función de Masa de Probabilidad fmp_1 = stats . binom . pmf ( x_1 , n_1 , p_1 ) # Función de Masa de Probabilidad plt . plot ( x , fmp , '--' ) plt . plot ( x_1 , fmp_1 ) plt . vlines ( x , 0 , fmp , colors = 'b' , lw = 5 , alpha = 0.5 ) plt . vlines ( x_1 , 0 , fmp_1 , colors = 'g' , lw = 5 , alpha = 0.5 ) plt . title ( 'Función de Masa de Probabilidad' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () Función de Distribución Acumulada Si queremos evitar los problemas que se generan con FMP cuando el número de valores es muy grande, podemos recurrir a utilizar la Función de Distribución Acumulada o FDA , para representar a nuestras distribuciones , tanto discretas como continuas . Esta función relaciona los valores con su correspondiente percentil ; es decir que va a describir la probabilidad de que una variable aleatoria X sujeta a cierta ley de distribución de probabilidad se sitúe en la zona de valores menores o iguales a x. In [4]: # Graficando Función de Distribución Acumulada con Python x_1 = np . linspace ( stats . norm ( 10 , 1.2 ) . ppf ( 0.01 ), stats . norm ( 10 , 1.2 ) . ppf ( 0.99 ), 100 ) fda_binom = stats . binom . cdf ( x , n , p ) # Función de Distribución Acumulada fda_normal = stats . norm ( 10 , 1.2 ) . cdf ( x_1 ) # Función de Distribución Acumulada plt . plot ( x , fda_binom , '--' , label = 'FDA binomial' ) plt . plot ( x_1 , fda_normal , label = 'FDA nomal' ) plt . title ( 'Función de Distribución Acumulada' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . legend ( loc = 4 ) plt . show () Función de Densidad de Probabilidad Por último, el equivalente a la FMP para distribuciones continuas es la Función de Densidad de Probabilidad o FDP . Esta función es la derivada de la Función de Distribución Acumulada . Por ejemplo, para la distribución normal que graficamos anteriormente, su FDP es la siguiente. La típica forma de campana que caracteriza a esta distribución . In [5]: # Graficando Función de Densidad de Probibilidad con Python FDP_normal = stats . norm ( 10 , 1.2 ) . pdf ( x_1 ) # FDP plt . plot ( x_1 , FDP_normal , label = 'FDP nomal' ) plt . title ( 'Función de Densidad de Probabilidad' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () Distribuciones Ahora que ya conocemos como podemos hacer para representar a las distribuciones ; pasemos a analizar cada una de ellas en más detalle para conocer su forma, sus principales aplicaciones y sus propiedades. Comencemos por las distribuciones discretas . Distribuciones Discretas Las distribuciones discretas son aquellas en las que la variable puede tomar solo algunos valores determinados. Los principales exponentes de este grupo son las siguientes: Distribución Poisson La Distribución Poisson esta dada por la formula: $$p(r; \\mu) = \\frac{\\mu^r e^{-\\mu}}{r!}$$ En dónde $r$ es un entero ($r \\ge 0$) y $\\mu$ es un número real positivo. La Distribución Poisson describe la probabilidad de encontrar exactamente $r$ eventos en un lapso de tiempo si los acontecimientos se producen de forma independiente a una velocidad constante $\\mu$. Es una de las distribuciones más utilizadas en estadística con varias aplicaciones; como por ejemplo describir el número de fallos en un lote de materiales o la cantidad de llegadas por hora a un centro de servicios. En Python la podemos generar fácilmente con la ayuda de scipy.stats , paquete que utilizaremos para representar a todas las restantes distribuciones a lo largo de todo el artículo. In [6]: # Graficando Poisson mu = 3.6 # parametro de forma poisson = stats . poisson ( mu ) # Distribución x = np . arange ( poisson . ppf ( 0.01 ), poisson . ppf ( 0.99 )) fmp = poisson . pmf ( x ) # Función de Masa de Probabilidad plt . plot ( x , fmp , '--' ) plt . vlines ( x , 0 , fmp , colors = 'b' , lw = 5 , alpha = 0.5 ) plt . title ( 'Distribución Poisson' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () In [7]: # histograma aleatorios = poisson . rvs ( 1000 ) # genera aleatorios cuenta , cajas , ignorar = plt . hist ( aleatorios , 20 ) plt . ylabel ( 'frequencia' ) plt . xlabel ( 'valores' ) plt . title ( 'Histograma Poisson' ) plt . show () Distribución Binomial La Distribución Binomial esta dada por la formula: $$p(r; N, p) = \\left(\\begin{array}{c} N \\\\ r \\end{array}\\right) p^r(1 - p)^{N - r} $$ En dónde $r$ con la condición $0 \\le r \\le N$ y el parámetro $N$ ($N > 0$) son enteros ; y el parámetro $p$ ($0 \\le p \\le 1$) es un número real . La Distribución Binomial describe la probabilidad de exactamente $r$ éxitos en $N$ pruebas si la probabilidad de éxito en una sola prueba es $p$. In [8]: # Graficando Binomial N , p = 30 , 0.4 # parametros de forma binomial = stats . binom ( N , p ) # Distribución x = np . arange ( binomial . ppf ( 0.01 ), binomial . ppf ( 0.99 )) fmp = binomial . pmf ( x ) # Función de Masa de Probabilidad plt . plot ( x , fmp , '--' ) plt . vlines ( x , 0 , fmp , colors = 'b' , lw = 5 , alpha = 0.5 ) plt . title ( 'Distribución Binomial' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () In [9]: # histograma aleatorios = binomial . rvs ( 1000 ) # genera aleatorios cuenta , cajas , ignorar = plt . hist ( aleatorios , 20 ) plt . ylabel ( 'frequencia' ) plt . xlabel ( 'valores' ) plt . title ( 'Histograma Binomial' ) plt . show () Distribución Geométrica La Distribución Geométrica esta dada por la formula: $$p(r; p) = p(1- p)^{r-1} $$ En dónde $r \\ge 1$ y el parámetro $p$ ($0 \\le p \\le 1$) es un número real . La Distribución Geométrica expresa la probabilidad de tener que esperar exactamente $r$ pruebas hasta encontrar el primer éxito si la probabilidad de éxito en una sola prueba es $p$. Por ejemplo, en un proceso de selección, podría definir el número de entrevistas que deberíamos realizar antes de encontrar al primer candidato aceptable. In [10]: # Graficando Geométrica p = 0.3 # parametro de forma geometrica = stats . geom ( p ) # Distribución x = np . arange ( geometrica . ppf ( 0.01 ), geometrica . ppf ( 0.99 )) fmp = geometrica . pmf ( x ) # Función de Masa de Probabilidad plt . plot ( x , fmp , '--' ) plt . vlines ( x , 0 , fmp , colors = 'b' , lw = 5 , alpha = 0.5 ) plt . title ( 'Distribución Geométrica' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () In [11]: # histograma aleatorios = geometrica . rvs ( 1000 ) # genera aleatorios cuenta , cajas , ignorar = plt . hist ( aleatorios , 20 ) plt . ylabel ( 'frequencia' ) plt . xlabel ( 'valores' ) plt . title ( 'Histograma Geométrica' ) plt . show () Distribución Hipergeométrica La Distribución Hipergeométrica esta dada por la formula: $$p(r; n, N, M) = \\frac{\\left(\\begin{array}{c} M \\\\ r \\end{array}\\right)\\left(\\begin{array}{c} N - M\\\\ n -r \\end{array}\\right)}{\\left(\\begin{array}{c} N \\\\ n \\end{array}\\right)} $$ En dónde el valor de $r$ esta limitado por $\\max(0, n - N + M)$ y $\\min(n, M)$ inclusive; y los parámetros $n$ ($1 \\le n \\le N$), $N$ ($N \\ge 1$) y $M$ ($M \\ge 1$) son todos números enteros . La Distribución Hipergeométrica describe experimentos en donde se seleccionan los elementos al azar sin reemplazo (se evita seleccionar el mismo elemento más de una vez). Más precisamente, supongamos que tenemos $N$ elementos de los cuales $M$ tienen un cierto atributo (y $N - M$ no tiene). Si escogemos $n$ elementos al azar sin reemplazo , $p(r)$ es la probabilidad de que exactamente $r$ de los elementos seleccionados provienen del grupo con el atributo. In [12]: # Graficando Hipergeométrica M , n , N = 30 , 10 , 12 # parametros de forma hipergeometrica = stats . hypergeom ( M , n , N ) # Distribución x = np . arange ( 0 , n + 1 ) fmp = hipergeometrica . pmf ( x ) # Función de Masa de Probabilidad plt . plot ( x , fmp , '--' ) plt . vlines ( x , 0 , fmp , colors = 'b' , lw = 5 , alpha = 0.5 ) plt . title ( 'Distribución Hipergeométrica' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () In [13]: # histograma aleatorios = hipergeometrica . rvs ( 1000 ) # genera aleatorios cuenta , cajas , ignorar = plt . hist ( aleatorios , 20 ) plt . ylabel ( 'frequencia' ) plt . xlabel ( 'valores' ) plt . title ( 'Histograma Hipergeométrica' ) plt . show () Distribución de Bernoulli La Distribución de Bernoulli esta dada por la formula: $$p(r;p) = \\left\\{ \\begin{array}{ll} 1 - p = q & \\mbox{si } r = 0 \\ \\mbox{(fracaso)}\\\\ p & \\mbox{si } r = 1 \\ \\mbox{(éxito)} \\end{array} \\right.$$ En dónde el parámetro $p$ es la probabilidad de éxito en un solo ensayo, la probabilidad de fracaso por lo tanto va a ser $1 - p$ (muchas veces expresada como $q$). Tanto $p$ como $q$ van a estar limitados al intervalo de cero a uno. La Distribución de Bernoulli describe un experimento probabilístico en donde el ensayo tiene dos posibles resultados, éxito o fracaso. Desde esta distribución se pueden deducir varias Funciones de Densidad de Probabilidad de otras distribuciones que se basen en una serie de ensayos independientes. In [14]: # Graficando Bernoulli p = 0.5 # parametro de forma bernoulli = stats . bernoulli ( p ) x = np . arange ( - 1 , 3 ) fmp = bernoulli . pmf ( x ) # Función de Masa de Probabilidad fig , ax = plt . subplots () ax . plot ( x , fmp , 'bo' ) ax . vlines ( x , 0 , fmp , colors = 'b' , lw = 5 , alpha = 0.5 ) ax . set_yticks ([ 0. , 0.2 , 0.4 , 0.6 ]) plt . title ( 'Distribución Bernoulli' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () In [15]: # histograma aleatorios = bernoulli . rvs ( 1000 ) # genera aleatorios cuenta , cajas , ignorar = plt . hist ( aleatorios , 20 ) plt . ylabel ( 'frequencia' ) plt . xlabel ( 'valores' ) plt . title ( 'Histograma Bernoulli' ) plt . show () Distribuciones continuas Ahora que ya conocemos las principales distribuciones discretas , podemos pasar a describir a las distribuciones continuas ; en ellas a diferencia de lo que veíamos antes, la variable puede tomar cualquier valor dentro de un intervalo específico. Dentro de este grupo vamos a encontrar a las siguientes: Distribución de Normal La Distribución Normal , o también llamada Distribución de Gauss , es aplicable a un amplio rango de problemas, lo que la convierte en la distribución más utilizada en estadística ; esta dada por la formula: $$p(x;\\mu, \\sigma^2) = \\frac{1}{\\sigma \\sqrt{2 \\pi}} e^{\\frac{-1}{2}\\left(\\frac{x - \\mu}{\\sigma} \\right)^2} $$ En dónde $\\mu$ es el parámetro de ubicación, y va a ser igual a la media aritmética y $\\sigma^2$ es el desvío estándar . Algunos ejemplos de variables asociadas a fenómenos naturales que siguen el modelo de la Distribución Normal son: características morfológicas de individuos, como la estatura; características sociológicas, como el consumo de cierto producto por un mismo grupo de individuos; características psicológicas, como el cociente intelectual; nivel de ruido en telecomunicaciones; errores cometidos al medir ciertas magnitudes; etc. In [16]: # Graficando Normal mu , sigma = 0 , 0.2 # media y desvio estandar normal = stats . norm ( mu , sigma ) x = np . linspace ( normal . ppf ( 0.01 ), normal . ppf ( 0.99 ), 100 ) fp = normal . pdf ( x ) # Función de Probabilidad plt . plot ( x , fp ) plt . title ( 'Distribución Normal' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () In [17]: # histograma aleatorios = normal . rvs ( 1000 ) # genera aleatorios cuenta , cajas , ignorar = plt . hist ( aleatorios , 20 ) plt . ylabel ( 'frequencia' ) plt . xlabel ( 'valores' ) plt . title ( 'Histograma Normal' ) plt . show () Distribución Uniforme La Distribución Uniforme es un caso muy simple expresada por la función: $$f(x; a, b) = \\frac{1}{b -a} \\ \\mbox{para} \\ a \\le x \\le b $$ Su función de distribución esta entonces dada por: $$ p(x;a, b) = \\left\\{ \\begin{array}{ll} 0 & \\mbox{si } x \\le a \\\\ \\frac{x-a}{b-a} & \\mbox{si } a \\le x \\le b \\\\ 1 & \\mbox{si } b \\le x \\end{array} \\right. $$ Todos los valore tienen prácticamente la misma probabilidad. In [18]: # Graficando Uniforme uniforme = stats . uniform () x = np . linspace ( uniforme . ppf ( 0.01 ), uniforme . ppf ( 0.99 ), 100 ) fp = uniforme . pdf ( x ) # Función de Probabilidad fig , ax = plt . subplots () ax . plot ( x , fp , '--' ) ax . vlines ( x , 0 , fp , colors = 'b' , lw = 5 , alpha = 0.5 ) ax . set_yticks ([ 0. , 0.2 , 0.4 , 0.6 , 0.8 , 1. , 1.2 ]) plt . title ( 'Distribución Uniforme' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () In [19]: # histograma aleatorios = uniforme . rvs ( 1000 ) # genera aleatorios cuenta , cajas , ignorar = plt . hist ( aleatorios , 20 ) plt . ylabel ( 'frequencia' ) plt . xlabel ( 'valores' ) plt . title ( 'Histograma Uniforme' ) plt . show () Distribución de Log-normal La Distribución Log-normal esta dada por la formula: $$p(x;\\mu, \\sigma) = \\frac{1}{ x \\sigma \\sqrt{2 \\pi}} e^{\\frac{-1}{2}\\left(\\frac{\\ln x - \\mu}{\\sigma} \\right)^2} $$ En dónde la variable $x > 0$ y los parámetros $\\mu$ y $\\sigma > 0$ son todos números reales . La Distribución Log-normal es aplicable a variables aleatorias que están limitadas por cero, pero tienen pocos valores grandes. Es una distribución con asimetría positiva . Algunos de los ejemplos en que la solemos encontrar son: El peso de los adultos. La concentración de los minerales en depósitos. Duración de licencia por enfermedad. Distribución de riqueza Tiempos muertos de maquinarias. In [20]: # Graficando Log-Normal sigma = 0.6 # parametro lognormal = stats . lognorm ( sigma ) x = np . linspace ( lognormal . ppf ( 0.01 ), lognormal . ppf ( 0.99 ), 100 ) fp = lognormal . pdf ( x ) # Función de Probabilidad plt . plot ( x , fp ) plt . title ( 'Distribución Log-normal' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () In [21]: # histograma aleatorios = lognormal . rvs ( 1000 ) # genera aleatorios cuenta , cajas , ignorar = plt . hist ( aleatorios , 20 ) plt . ylabel ( 'frequencia' ) plt . xlabel ( 'valores' ) plt . title ( 'Histograma Log-normal' ) plt . show () Distribución de Exponencial La Distribución Exponencial esta dada por la formula: $$p(x;\\alpha) = \\frac{1}{ \\alpha} e^{\\frac{-x}{\\alpha}} $$ En dónde tanto la variable $x$ como el parámetro $\\alpha$ son números reales positivos. La Distribución Exponencial tiene bastantes aplicaciones, tales como la desintegración de un átomo radioactivo o el tiempo entre eventos en un proceso de Poisson donde los acontecimientos suceden a una velocidad constante. In [22]: # Graficando Exponencial exponencial = stats . expon () x = np . linspace ( exponencial . ppf ( 0.01 ), exponencial . ppf ( 0.99 ), 100 ) fp = exponencial . pdf ( x ) # Función de Probabilidad plt . plot ( x , fp ) plt . title ( 'Distribución Exponencial' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () In [23]: # histograma aleatorios = exponencial . rvs ( 1000 ) # genera aleatorios cuenta , cajas , ignorar = plt . hist ( aleatorios , 20 ) plt . ylabel ( 'frequencia' ) plt . xlabel ( 'valores' ) plt . title ( 'Histograma Exponencial' ) plt . show () Distribución Gamma La Distribución Gamma esta dada por la formula: $$p(x;a, b) = \\frac{a(a x)^{b -1} e^{-ax}}{\\Gamma(b)} $$ En dónde los parámetros $a$ y $b$ y la variable $x$ son números reales positivos y $\\Gamma(b)$ es la función gamma . La Distribución Gamma comienza en el origen de coordenadas y tiene una forma bastante flexible. Otras distribuciones son casos especiales de ella. In [24]: # Graficando Gamma a = 2.6 # parametro de forma. gamma = stats . gamma ( a ) x = np . linspace ( gamma . ppf ( 0.01 ), gamma . ppf ( 0.99 ), 100 ) fp = gamma . pdf ( x ) # Función de Probabilidad plt . plot ( x , fp ) plt . title ( 'Distribución Gamma' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () In [25]: # histograma aleatorios = gamma . rvs ( 1000 ) # genera aleatorios cuenta , cajas , ignorar = plt . hist ( aleatorios , 20 ) plt . ylabel ( 'frequencia' ) plt . xlabel ( 'valores' ) plt . title ( 'Histograma Gamma' ) plt . show () Distribución Beta La Distribución Beta esta dada por la formula: $$p(x;p, q) = \\frac{1}{B(p, q)} x^{p-1}(1 - x)^{q-1} $$ En dónde los parámetros $p$ y $q$ son números reales positivos, la variable $x$ satisface la condición $0 \\le x \\le 1$ y $B(p, q)$ es la función beta . Las aplicaciones de la Distribución Beta incluyen el modelado de variables aleatorias que tienen un rango finito de $a$ hasta $b$. Un ejemplo de ello es la distribución de los tiempos de actividad en las redes de proyectos. La Distribución Beta se utiliza también con frecuencia como una probabilidad a priori para proporciones binomiales en el análisis bayesiano . In [26]: # Graficando Beta a , b = 2.3 , 0.6 # parametros de forma. beta = stats . beta ( a , b ) x = np . linspace ( beta . ppf ( 0.01 ), beta . ppf ( 0.99 ), 100 ) fp = beta . pdf ( x ) # Función de Probabilidad plt . plot ( x , fp ) plt . title ( 'Distribución Beta' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () In [27]: # histograma aleatorios = beta . rvs ( 1000 ) # genera aleatorios cuenta , cajas , ignorar = plt . hist ( aleatorios , 20 ) plt . ylabel ( 'frequencia' ) plt . xlabel ( 'valores' ) plt . title ( 'Histograma Beta' ) plt . show () Distribución Chi cuadrado La Distribución Chi cuadrado esta dada por la función: $$p(x; n) = \\frac{\\left(\\frac{x}{2}\\right)^{\\frac{n}{2}-1} e^{\\frac{-x}{2}}}{2\\Gamma \\left(\\frac{n}{2}\\right)} $$ En dónde la variable $x \\ge 0$ y el parámetro $n$, el número de grados de libertad, es un número entero positivo. Una importante aplicación de la Distribución Chi cuadrado es que cuando un conjunto de datos es representado por un modelo teórico, esta distribución puede ser utilizada para controlar cuan bien se ajustan los valores predichos por el modelo, y los datos realmente observados. In [28]: # Graficando Chi cuadrado df = 34 # parametro de forma. chi2 = stats . chi2 ( df ) x = np . linspace ( chi2 . ppf ( 0.01 ), chi2 . ppf ( 0.99 ), 100 ) fp = chi2 . pdf ( x ) # Función de Probabilidad plt . plot ( x , fp ) plt . title ( 'Distribución Chi cuadrado' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () In [29]: # histograma aleatorios = chi2 . rvs ( 1000 ) # genera aleatorios cuenta , cajas , ignorar = plt . hist ( aleatorios , 20 ) plt . ylabel ( 'frequencia' ) plt . xlabel ( 'valores' ) plt . title ( 'Histograma Chi cuadrado' ) plt . show () Distribución T de Student La Distribución t de Student esta dada por la función: $$p(t; n) = \\frac{\\Gamma(\\frac{n+1}{2})}{\\sqrt{n\\pi}\\Gamma(\\frac{n}{2})} \\left( 1 + \\frac{t^2}{2} \\right)^{-\\frac{n+1}{2}} $$ En dónde la variable $t$ es un número real y el parámetro $n$ es un número entero positivo. La Distribución t de Student es utilizada para probar si la diferencia entre las medias de dos muestras de observaciones es estadísticamente significativa. Por ejemplo, las alturas de una muestra aleatoria de los jugadores de baloncesto podría compararse con las alturas de una muestra aleatoria de jugadores de fútbol; esta distribución nos podría ayudar a determinar si un grupo es significativamente más alto que el otro. In [30]: # Graficando t de Student df = 50 # parametro de forma. t = stats . t ( df ) x = np . linspace ( t . ppf ( 0.01 ), t . ppf ( 0.99 ), 100 ) fp = t . pdf ( x ) # Función de Probabilidad plt . plot ( x , fp ) plt . title ( 'Distribución t de Student' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () In [31]: # histograma aleatorios = t . rvs ( 1000 ) # genera aleatorios cuenta , cajas , ignorar = plt . hist ( aleatorios , 20 ) plt . ylabel ( 'frequencia' ) plt . xlabel ( 'valores' ) plt . title ( 'Histograma t de Student' ) plt . show () Distribución de Pareto La Distribución de Pareto esta dada por la función: $$p(x; \\alpha, k) = \\frac{\\alpha k^{\\alpha}}{x^{\\alpha + 1}} $$ En dónde la variable $x \\ge k$ y el parámetro $\\alpha > 0$ son números reales . Esta distribución fue introducida por su inventor, Vilfredo Pareto , con el fin de explicar la distribución de los salarios en la sociedad. La Distribución de Pareto se describe a menudo como la base de la regla 80/20 . Por ejemplo, el 80% de las quejas de los clientes con respecto al funcionamiento de su vehículo por lo general surgen del 20% de los componentes. In [32]: # Graficando Pareto k = 2.3 # parametro de forma. pareto = stats . pareto ( k ) x = np . linspace ( pareto . ppf ( 0.01 ), pareto . ppf ( 0.99 ), 100 ) fp = pareto . pdf ( x ) # Función de Probabilidad plt . plot ( x , fp ) plt . title ( 'Distribución de Pareto' ) plt . ylabel ( 'probabilidad' ) plt . xlabel ( 'valores' ) plt . show () In [33]: # histograma aleatorios = pareto . rvs ( 1000 ) # genera aleatorios cuenta , cajas , ignorar = plt . hist ( aleatorios , 20 ) plt . ylabel ( 'frequencia' ) plt . xlabel ( 'valores' ) plt . title ( 'Histograma de Pareto' ) plt . show () ¿Cómo elegir la distribución que mejor se ajusta a mis datos? Ahora ya tenemos un conocimiento general de las principales distribuciones con que nos podemos encontrar; pero ¿cómo determinamos que distribución debemos utilizar? Un modelo que podemos seguir cuando nos encontramos con datos que necesitamos ajustar a una distribución , es comenzar con los datos sin procesar y responder a cuatro preguntas básicas acerca de los mismos, que nos pueden ayudar a caracterizarlos. La primer pregunta se refiere a si los datos pueden tomar valores discretos o continuos . La segunda pregunta que nos debemos hacer, hace referencia a la simetría de los datos y si hay asimetría, en qué dirección se encuentra; en otras palabras, son los valores atípicos positivos y negativos igualmente probables o es uno más probable que el otro. La tercer pregunta abarca los límites superiores e inferiores en los datos ; hay algunos datos, como los ingresos, que no pueden ser inferiores a cero, mientras que hay otros, como los márgenes de operación que no puede exceder de un valor (100%). La última pregunta se refiere a la posibilidad de observar valores extremos en la distribución ; en algunos casos, los valores extremos ocurren con muy poca frecuencia, mientras que en otros, se producen con mayor frecuencia. Este proceso, lo podemos resumir en el siguiente gráfico: Con la ayuda de estas preguntas fundamentales, más el conocimiento de las distintas distribuciones deberíamos estar en condiciones de poder caracterizar cualquier conjunto de datos . Con esto concluyo este tour por las principales distribuciones utilizadas en estadística . Para más información también pueden visitar mi artículo Probabilidad y Estadística con Python o la categoría estadística del blog. Espero les resulte útil. Saludos! Este post fue escrito utilizando Jupyter notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Pobabilidad y Estadistica","url":"https://relopezbriega.github.io/blog/2016/06/29/distribuciones-de-probabilidad-con-python/"},{"title":"TensorFlow y Redes Neuronales","text":"Sobre TensoFlow TensorFlow es una biblioteca open source desarrollada por Google que nos permite realizar cálculos numéricos usando diagramas de flujo de datos. Los nodos del grafo representan operaciones matemáticas, mientras que los arcos del grafo representan los arreglos de datos multidimensionales ( tensores ) comunicados entre ellos. Esta arquitectura flexible nos permite realizar los cálculos en más de un CPU o GPU utilizando la misma API. ¿Qué es un diagrama de flujo de datos? Los diagramas de flujo de datos describen cálculos matemáticos con un grafo de nodos y arcos . Los nodos normalmente implementan operaciones matemáticas, pero también pueden representar los puntos para alimentarse de datos, devolver resultados, o leer / escribir variables persistentes. Los arcos o aristas describen las relaciones de entrada / salida entre los nodos . Estos arcos están representados por los arreglos de datos multidimensionales o tensores . El flujo de los tensores a través del grafo es de donde TensorFlow recibe su nombre. Los nodos se asignan a los dispositivos computacionales y se ejecutan de forma asincrónica y en paralelo una vez que todos los tensores en los arcos de entrada están disponibles. Introducción a TensorFlow Para poder utilizar TensorFlow primero es necesario entender cómo la librería: Representa cálculos en forma de grafos . Ejecuta los grafos en el contexto de Sesiones. Representa los datos como tensores . Mantiene el estado con variables. Se alimenta de datos y devuelve los resultados de cada operación. Funcionamiento general TensorFlow es un sistema de programación en el que representamos cálculos en forma de grafos . Los nodos en el grafo se llaman ops (abreviatura de operaciones). Una op tiene cero o más tensores , realiza algún cálculo, y produce cero o más tensores . Un grafo de TensorFlow es una descripción de cálculos. Para calcular cualquier cosa dentro de TensorFlow , el grafo debe ser lanzado dentro de una sesión . La Sesión coloca las operaciones del grafo en los diferentes dispositivos , tales como CPU o GPU, y proporciona métodos para ejecutarlas. Creando un Grafo Para construir un grafo simple, podemos comenzar con ops que no necesitan ningún dato de entrada, como son las constantes y luego le pasamos su salida a ops que realizan cálculos. In [1]: # importamos la libreria import tensorflow as tf # importamos librerías adicionales import numpy as np import matplotlib.pyplot as plt import matplotlib.cm as cm import pandas as pd % matplotlib inline Constantes Podemos construir ops de constantes utilizando constant , su API es bastante simple: constant(value, dtype=None, shape=None, name='Const') Le debemos pasar un valor, el cual puede ser cualquier tipo de tensor (un escalar, un vector, una matriz, etc) y luego opcionalmente le podemos pasar el tipo de datos, la forma y un nombre. In [2]: # Creación de Constantes # El valor que retorna el constructor es el valor de la constante. # creamos constantes a=2 y b=3 a = tf . constant ( 2 ) b = tf . constant ( 3 ) # creamos matrices de 3x3 matriz1 = tf . constant ([[ 1 , 3 , 2 ], [ 1 , 0 , 0 ], [ 1 , 2 , 2 ]]) matriz2 = tf . constant ([[ 1 , 0 , 5 ], [ 7 , 5 , 0 ], [ 2 , 1 , 1 ]]) In [3]: # Realizamos algunos cálculos con estas constantes suma = tf . add ( a , b ) mult = tf . mul ( a , b ) cubo_a = a ** 3 # suma de matrices suma_mat = tf . add ( matriz1 , matriz2 ) # producto de matrices mult_mat = tf . matmul ( matriz1 , matriz2 ) Sesiones Ahora que ya definimos algunas ops constantes y algunos cálculos con ellas, debemos lanzar el grafo dentro de una Sesión . Para realizar esto utilizamos el objeto Session . Este objeto va a encapsular el ambiente en el que las operaciones que definimos en el grafo van a ser ejecutadas y los tensores son evaluados. In [4]: # Todo en TensorFlow ocurre dentro de una Sesión # creamos la sesion y realizamos algunas operaciones con las constantes # y lanzamos la sesión with tf . Session () as sess : print ( \"Suma de las constantes: {} \" . format ( sess . run ( suma ))) print ( \"Multiplicación de las constantes: {} \" . format ( sess . run ( mult ))) print ( \"Constante elevada al cubo: {} \" . format ( sess . run ( cubo_a ))) print ( \"Suma de matrices: \\n {} \" . format ( sess . run ( suma_mat ))) print ( \"Producto de matrices: \\n {} \" . format ( sess . run ( mult_mat ))) Suma de las constantes: 5 Multiplicación de las constantes: 6 Constante elevada al cubo: 8 Suma de matrices: [[2 3 7] [8 5 0] [3 3 3]] Producto de matrices: [[26 17 7] [ 1 0 5] [19 12 7]] Las Sesiones deben ser cerradas para liberar los recursos, por lo que es una buena práctica incluir la Sesión dentro de un bloque \"with\" que la cierra automáticamente cuando el bloque termina de ejecutar. Para ejecutar las operaciones y evaluar los tensores utilizamos Session.run() . Variables persistentes Las Variables mantienen el estado a través de las ejecuciones del grafo . Son buffers en memoria que contienen tensores . Se deben inicializar explícitamente y se pueden guardar en el disco para luego restaurar su estado de necesitarlo. Se crean utilizando el objeto Variable . In [5]: # Creamos una variable y la inicializamos con 0 estado = tf . Variable ( 0 , name = \"contador\" ) # Creamos la op que le va a sumar uno a la Variable `estado`. uno = tf . constant ( 1 ) nuevo_valor = tf . add ( estado , uno ) actualizar = tf . assign ( estado , nuevo_valor ) # Las Variables deben ser inicializadas por la operación `init` luego de # lanzar el grafo. Debemos agregar la op `init` a nuestro grafo. init = tf . initialize_all_variables () # Lanzamos la sesion y ejecutamos las operaciones with tf . Session () as sess : # Ejecutamos la op `init` sess . run ( init ) # imprimir el valor de la Variable estado. print ( sess . run ( estado )) # ejecutamos la op que va a actualizar a `estado`. for _ in range ( 3 ): sess . run ( actualizar ) print ( sess . run ( estado )) 0 1 2 3 Variables simbólicas (contenedores) Las Variables simbólicas o Contenedores nos van a permitir alimentar a las operaciones con los datos durante la ejecución del grafo . Estos contenedores deben ser alimentados antes de ser evaluados en la sesión, sino obtendremos un error. In [6]: # Ejemplo variables simbólicas en los grafos # El valor que devuelve el constructor representa la salida de la # variable (la entrada de la variable se define en la sesion) # Creamos un contenedor del tipo float. Un tensor de 4x4. x = tf . placeholder ( tf . float32 , shape = ( 4 , 4 )) y = tf . matmul ( x , x ) with tf . Session () as sess : # print(sess.run(y)) # ERROR: va a fallar porque no alimentamos a x. rand_array = np . random . rand ( 4 , 4 ) print ( sess . run ( y , feed_dict = { x : rand_array })) # ahora esta correcto. [[ 2.27301431 2.39163661 1.22738445 1.87839973] [ 2.66718912 2.76533985 1.08909523 1.96862805] [ 2.38245845 2.37843513 1.0873785 1.67218387] [ 1.68678236 1.77147484 1.0363127 1.36901033]] Ahora ya conocemos en líneas generales como es la mecánica detrás del funcionamiento de TensorFlow y como deberíamos proceder para crear las operaciones dentro de los grafos . Veamos si podemos implementar modelos de neuronas simples con la ayuda de esta librería. Ejemplo de neuronas simples Una neurona simple, va a tener una forma similar al siguiente diagrama: En donde sus componentes son: $x_1, x_2, \\dots, x_n$: son los datos de entrada en la neurona, los cuales también puede ser que sean producto de la salida de otra neurona de la red. $x_0$: Es la unidad de sesgo; un valor constante que se le suma a la entrada de la función de activación de la neurona. Generalmente tiene el valor 1. Este valor va a permitir cambiar la función de activación hacia la derecha o izquierda, otorgándole más flexibilidad para aprender a la neurona. $w_0, w_1, w_2, \\dots, w_n$: Los pesos relativos de cada entrada. Tener en cuenta que incluso la unidad de sesgo tiene un peso. a: La salida de la neurona. Que va a ser calculada de la siguiente forma: $$a = f\\left(\\sum_{i=0}^n w_i \\cdot x_i \\right)$$ Aquí $f$ es la función de activación de la neurona. Esta función es la que le otorga tanta flexibilidad a las redes neuronales y le permite estimar complejas relaciones no lineales en los datos. Puede ser tanto una función lineal , una función logística , hiperbólica , etc. Ahora que ya conocemos como se construye una neurona tratemos de implementar con este modelo las funciones lógicas AND, OR y XNOR . Podemos pensar a estas funciones como un problema de clasificación en el que la salida va a ser 0 o 1, de acuerdo a la combinación de las diferentes entradas. Las podemos modelar linealmente con la siguiente función de activación: $$f(x) = \\left\\{ \\begin{array}{ll} 0 & \\mbox{si } x < 0 \\\\ 1 & \\mbox{si } x \\ge 0 \\end{array} \\right.$$ Neurona AND La neurona AND puede ser modelada con el siguiente esquema: La salida de esta neurona entonces va a ser: $$a = f(-1.5 + x_1 + x_2)$$ Veamos como la podemos implementar en TensorFlow . In [7]: # Neurona con TensorFlow # Defino las entradas entradas = tf . placeholder ( \"float\" , name = 'Entradas' ) datos = np . array ([[ 0 , 0 ] ,[ 1 , 0 ] ,[ 0 , 1 ] ,[ 1 , 1 ]]) # Defino las salidas uno = lambda : tf . constant ( 1.0 ) cero = lambda : tf . constant ( 0.0 ) with tf . name_scope ( 'Pesos' ): # Definiendo pesos y sesgo pesos = tf . placeholder ( \"float\" , name = 'Pesos' ) sesgo = tf . placeholder ( \"float\" , name = 'Sesgo' ) with tf . name_scope ( 'Activacion' ): # Función de activación activacion = tf . reduce_sum ( tf . add ( tf . matmul ( entradas , pesos ), sesgo )) with tf . name_scope ( 'Neurona' ): # Defino la neurona def neurona (): return tf . case ([( tf . less ( activacion , 0.0 ), cero )], default = uno ) # Salida a = neurona () # path de logs logs_path = '/tmp/tensorflow_logs/neurona' In [8]: # Lanzar la Sesion with tf . Session () as sess : # para armar el grafo summary_writer = tf . train . SummaryWriter ( logs_path , graph = sess . graph ) # para armar tabla de verdad x_1 = [] x_2 = [] out = [] act = [] for i in range ( len ( datos )): t = datos [ i ] . reshape ( 1 , 2 ) salida , activ = sess . run ([ a , activacion ], feed_dict = { entradas : t , pesos : np . array ([[ 1. ],[ 1. ]]), sesgo : - 1.5 }) # armar tabla de verdad en DataFrame x_1 . append ( t [ 0 ][ 0 ]) x_2 . append ( t [ 0 ][ 1 ]) out . append ( salida ) act . append ( activ ) tabla_info = np . array ([ x_1 , x_2 , act , out ]) . transpose () tabla = pd . DataFrame ( tabla_info , columns = [ 'x1' , 'x2' , 'f(x)' , 'x1 AND x2' ]) tabla Out[8]: x1 x2 f(x) x1 AND x2 0 0.0 0.0 -1.5 0.0 1 1.0 0.0 -0.5 0.0 2 0.0 1.0 -0.5 0.0 3 1.0 1.0 0.5 1.0 Aquí podemos ver los datos de entrada de $x_1$ y $x_2$, el resultado de la función de activación y la decisión final que toma la neurona de acuerdo este último resultado. Como podemos ver en la tabla de verdad, la neurona nos dice que $x_1$ and $x_2$ solo es verdad cuando ambos son verdaderos, lo que es correcto. Neurona OR La neurona OR puede ser modelada con el siguiente esquema: La salida de esta neurona entonces va a ser: $$a = f(-0.5 + x_1 + x_2)$$ Como se puede ver a simple vista, el modelo de esta neurona es similar a la de la neurona AND, con el único cambio en el valor del sesgo , por lo tanto solo tendríamos que cambiar ese valor en nuestro modelo anterior para crear esta nueva neurona. In [9]: # Neurona OR, solo cambiamos el valor del sesgo with tf . Session () as sess : # para armar el grafo summary_writer = tf . train . SummaryWriter ( logs_path , graph = sess . graph ) # para armar tabla de verdad x_1 = [] x_2 = [] out = [] act = [] for i in range ( len ( datos )): t = datos [ i ] . reshape ( 1 , 2 ) salida , activ = sess . run ([ a , activacion ], feed_dict = { entradas : t , pesos : np . array ([[ 1. ],[ 1. ]]), sesgo : - 0.5 }) # sesgo ahora -0.5 # armar tabla de verdad en DataFrame x_1 . append ( t [ 0 ][ 0 ]) x_2 . append ( t [ 0 ][ 1 ]) out . append ( salida ) act . append ( activ ) tabla_info = np . array ([ x_1 , x_2 , act , out ]) . transpose () tabla = pd . DataFrame ( tabla_info , columns = [ 'x1' , 'x2' , 'f(x)' , 'x1 OR x2' ]) tabla Out[9]: x1 x2 f(x) x1 OR x2 0 0.0 0.0 -0.5 0.0 1 1.0 0.0 0.5 1.0 2 0.0 1.0 0.5 1.0 3 1.0 1.0 1.5 1.0 Como vemos, cambiando simplemente el peso del sesgo , convertimos a nuestra neurona AND en una neurona OR. Como muestra la tabla de verdad, el único caso en que $x_1$ OR $x_2$ es falso es cuando ambos son falsos. Red Neuronal XNOR El caso de la función XNOR, ya es más complicado y no puede modelarse utilizando una sola neurona como hicimos con los ejemplos anteriores. $x_1$ XNOR $x_2$ va a ser verdadero cuando ambos son verdaderos o ambos son falsos, para implementar esta función lógica debemos crear una red con dos capas, la primer capa tendrá dos neuronas cuya salida servirá de entrada para una nueva neurona que nos dará el resultado final. Esta red la podemos modelar de acuerdo al siguiente esquema: Veamos entonces si podemos implementar este modelo en TensorFlow . In [10]: # Red Neuronal XNOR con TensorFlow # Defino las entradas entradas = tf . placeholder ( \"float\" , name = 'Entradas' ) datos = np . array ([[ 0 , 0 ] ,[ 1 , 0 ] ,[ 0 , 1 ] ,[ 1 , 1 ]]) # Defino las salidas uno = lambda : tf . constant ( 1.0 ) cero = lambda : tf . constant ( 0.0 ) with tf . name_scope ( 'Pesos' ): # Definiendo pesos y sesgo pesos = { 'a1' : tf . constant ([[ - 1.0 ], [ - 1.0 ]], name = 'peso_a1' ), 'a2' : tf . constant ([[ 1.0 ], [ 1.0 ]], name = 'peso_a2' ), 'a3' : tf . constant ([[ 1.0 ], [ 1.0 ]], name = 'peso_a3' ) } sesgo = { 'a1' : tf . constant ( 0.5 , name = 'sesgo_a1' ), 'a2' : tf . constant ( - 1.5 , name = 'sesgo_a2' ), 'a3' : tf . constant ( - 0.5 , name = 'sesgo_a3' ) } with tf . name_scope ( 'Red_neuronal' ): # Defino las capas def capa1 ( entradas , pesos , sesgo ): # activacion a1 a1 = tf . reduce_sum ( tf . add ( tf . matmul ( entradas , pesos [ 'a1' ]), sesgo [ 'a1' ])) a1 = tf . case ([( tf . less ( a1 , 0.0 ), cero )], default = uno ) # activacion a2 a2 = tf . reduce_sum ( tf . add ( tf . matmul ( entradas , pesos [ 'a2' ]), sesgo [ 'a2' ])) a2 = tf . case ([( tf . less ( a2 , 0.0 ), cero )], default = uno ) return a1 , a2 def capa2 ( entradas , pesos , sesgo ): # activacion a3 a3 = tf . reduce_sum ( tf . add ( tf . matmul ( entradas , pesos [ 'a3' ]), sesgo [ 'a3' ])) a3 = tf . case ([( tf . less ( a3 , 0.0 ), cero )], default = uno ) return a3 # path de logs logs_path = '/tmp/tensorflow_logs/redXNOR' In [11]: # Sesion red neuronal XNOR with tf . Session () as sess : # para armar el grafo summary_writer = tf . train . SummaryWriter ( logs_path , graph = sess . graph ) # para armar tabla de verdad x_1 = [] x_2 = [] out = [] for i in range ( len ( datos )): t = datos [ i ] . reshape ( 1 , 2 ) # obtenos resultados 1ra capa a1 , a2 = sess . run ( capa1 ( entradas , pesos , sesgo ), feed_dict = { entradas : t }) # pasamos resultados a la 2da capa ent_a3 = np . array ([[ a1 , a2 ]]) salida = sess . run ( capa2 ( ent_a3 , pesos , sesgo )) # armar tabla de verdad en DataFrame x_1 . append ( t [ 0 ][ 0 ]) x_2 . append ( t [ 0 ][ 1 ]) out . append ( salida ) tabla_info = np . array ([ x_1 , x_2 , out ]) . transpose () tabla = pd . DataFrame ( tabla_info , columns = [ 'x1' , 'x2' , 'x1 XNOR x2' ]) tabla Out[11]: x1 x2 x1 XNOR x2 0 0.0 0.0 1.0 1 1.0 0.0 0.0 2 0.0 1.0 0.0 3 1.0 1.0 1.0 Como vemos, la red neuronal nos da el resultado correcto para la función lógica XNOR, solo es verdadera si ambos valores son verdaderos, o ambos son falsos. Hasta aquí implementamos simples neuronas y les pasamos los valores de sus pesos y sesgo a mano; esto es sencillo para los ejemplos; pero en la vida real, si queremos utilizar redes neuronales necesitamos implementar un procesos que vaya actualizando los pesos a medida que la red vaya aprendiendo con el entrenamiento. Este proceso se conoce con el nombre de propagación hacia atrás o backpropagation . Propagación hacia atrás La propagación hacia atrás o backpropagation es un algoritmo que funciona mediante la determinación de la pérdida (o error) en la salida y luego propagándolo de nuevo hacia atrás en la red. De esta forma los pesos se van actualizando para minimizar el error resultante de cada neurona. Este algoritmo es lo que les permite a las redes neuronales aprender. Veamos un ejemplo de como podemos implementar una red neuronal que pueda aprender por sí sola con la ayuda de TensorFlow . Ejemplo de Perceptron multicapa para reconocer dígitos escritos En este ejemplo vamos a construir un peceptron multicapa para clasificar dígitos escritos. Antes de pasar a la construcción del modelo, exploremos un poco el conjunto de datos con el que vamos a trabajar en la clasificación. MNIST dataset MNIST es un simple conjunto de datos para reconocimiento de imágenes por computadora. Se compone de imágenes de dígitos escritos a mano como los siguientes: Para más información sobre el dataset pueden visitar el siguiente enlace , en donde hacen un análisis detallado del mismo. In [12]: # importando el dataset from tensorflow.examples.tutorials.mnist import input_data mnist = input_data . read_data_sets ( \"MNIST_data/\" , one_hot = True ) Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes. Extracting MNIST_data/train-images-idx3-ubyte.gz Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes. Extracting MNIST_data/train-labels-idx1-ubyte.gz Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes. Extracting MNIST_data/t10k-images-idx3-ubyte.gz Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes. Extracting MNIST_data/t10k-labels-idx1-ubyte.gz Explorando MNIST dataset In [13]: # forma del dataset 55000 imagenes mnist . train . images . shape Out[13]: (55000, 784) In [14]: # cada imagen es un array de 28x28 con cada pixel # definido como escala de grises. digito1 = mnist . train . images [ 0 ] . reshape (( 28 , 28 )) In [15]: # visualizando el primer digito plt . imshow ( digito1 , cmap = cm . Greys ) plt . show () In [16]: # valor correcto mnist . train . labels [ 0 ] . nonzero ()[ 0 ][ 0 ] Out[16]: 7 In [17]: # visualizando imagenes de 5 en 5 def visualizar_imagenes ( dataset , cant_img ): img_linea = 5 lineas = int ( cant_img / img_linea ) imagenes = [] for i in range ( lineas ): datos = [] for img in dataset [ img_linea * i : img_linea * ( i + 1 )]: datos . append ( img . reshape (( 28 , 28 ))) imgs = np . hstack ( datos ) imagenes . append ( imgs ) data = np . vstack ( imagenes ) plt . imshow ( data , cmap = cm . Greys ) plt . show () In [18]: # visualizando los primeros 30 dígitos plt . figure ( figsize = ( 8 , 8 )) visualizar_imagenes ( mnist . train . images , 30 ) Construyendo el perceptron multicapa Ahora que ya conocemos los datos con los que vamos a trabajar, ya estamos en condiciones de construir el modelo. Vamos a construir un peceptron multicapa que es una de las redes neuronales más simples. El modelo va a tener dos capas ocultas, que se van a activar con la función de activación ReLU y vamos a optimizar los pesos reduciendo la entropía cruzada utilizando el algoritmo Adam que es un método para optimización estocástica . In [19]: # Parametros tasa_aprendizaje = 0.001 epocas = 15 lote = 100 display_step = 1 logs_path = \"/tmp/tensorflow_logs/perceptron\" # Parametros de la red n_oculta_1 = 256 # 1ra capa de atributos n_oculta_2 = 256 # 2ra capa de atributos n_entradas = 784 # datos de MNIST(forma img: 28*28) n_clases = 10 # Total de clases a clasificar (0-9 digitos) # input para los grafos x = tf . placeholder ( \"float\" , [ None , n_entradas ], name = 'DatosEntrada' ) y = tf . placeholder ( \"float\" , [ None , n_clases ], name = 'Clases' ) In [20]: # Creamos el modelo def perceptron_multicapa ( x , pesos , sesgo ): # Función de activación de la capa escondida capa_1 = tf . add ( tf . matmul ( x , pesos [ 'h1' ]), sesgo [ 'b1' ]) # activacion relu capa_1 = tf . nn . relu ( capa_1 ) # Función de activación de la capa escondida capa_2 = tf . add ( tf . matmul ( capa_1 , pesos [ 'h2' ]), sesgo [ 'b2' ]) # activación relu capa_2 = tf . nn . relu ( capa_2 ) # Salida con activación lineal salida = tf . matmul ( capa_2 , pesos [ 'out' ]) + sesgo [ 'out' ] return salida In [21]: # Definimos los pesos y sesgo de cada capa. pesos = { 'h1' : tf . Variable ( tf . random_normal ([ n_entradas , n_oculta_1 ])), 'h2' : tf . Variable ( tf . random_normal ([ n_oculta_1 , n_oculta_2 ])), 'out' : tf . Variable ( tf . random_normal ([ n_oculta_2 , n_clases ])) } sesgo = { 'b1' : tf . Variable ( tf . random_normal ([ n_oculta_1 ])), 'b2' : tf . Variable ( tf . random_normal ([ n_oculta_2 ])), 'out' : tf . Variable ( tf . random_normal ([ n_clases ])) } with tf . name_scope ( 'Modelo' ): # Construimos el modelo pred = perceptron_multicapa ( x , pesos , sesgo ) with tf . name_scope ( 'Costo' ): # Definimos la funcion de costo costo = tf . reduce_mean ( tf . nn . softmax_cross_entropy_with_logits ( pred , y )) with tf . name_scope ( 'optimizador' ): # Algoritmo de optimización optimizar = tf . train . AdamOptimizer ( learning_rate = tasa_aprendizaje ) . minimize ( costo ) with tf . name_scope ( 'Precision' ): # Evaluar el modelo pred_correcta = tf . equal ( tf . argmax ( pred , 1 ), tf . argmax ( y , 1 )) # Calcular la precisión Precision = tf . reduce_mean ( tf . cast ( pred_correcta , \"float\" )) # Inicializamos todas las variables init = tf . initialize_all_variables () # Crear sumarización para controlar el costo tf . scalar_summary ( \"Costo\" , costo ) # Crear sumarización para controlar la precisión tf . scalar_summary ( \"Precision\" , Precision ) # Juntar los resumenes en una sola operación merged_summary_op = tf . merge_all_summaries () In [22]: # Lanzamos la sesión with tf . Session () as sess : sess . run ( init ) # op to write logs to Tensorboard summary_writer = tf . train . SummaryWriter ( logs_path , graph = tf . get_default_graph ()) # Entrenamiento for epoca in range ( epocas ): avg_cost = 0. lote_total = int ( mnist . train . num_examples / lote ) for i in range ( lote_total ): lote_x , lote_y = mnist . train . next_batch ( lote ) # Optimización por backprop y funcion de costo _ , c , summary = sess . run ([ optimizar , costo , merged_summary_op ], feed_dict = { x : lote_x , y : lote_y }) # escribir logs en cada iteracion summary_writer . add_summary ( summary , epoca * lote_total + i ) # perdida promedio avg_cost += c / lote_total # imprimir información de entrenamiento if epoca % display_step == 0 : print ( \"Iteración: {0: 04d} costo = {1:.9f} \" . format ( epoca + 1 , avg_cost )) print ( \"Optimización Terminada! \\n \" ) print ( \"Precisión: {0:.2f} \" . format ( Precision . eval ({ x : mnist . test . images , y : mnist . test . labels }))) print ( \"Ejecutar el comando: \\n \" , \"--> tensorboard --logdir=/tmp/tensorflow_logs \" , \" \\n Luego abir https://0.0.0.0:6006/ en el navegador\" ) Iteración: 001 costo = 190.739139247 Iteración: 002 costo = 42.639138275 Iteración: 003 costo = 26.239370855 Iteración: 004 costo = 18.236157751 Iteración: 005 costo = 13.129509245 Iteración: 006 costo = 9.765473726 Iteración: 007 costo = 7.159448563 Iteración: 008 costo = 5.309303818 Iteración: 009 costo = 3.940411947 Iteración: 010 costo = 2.904317733 Iteración: 011 costo = 2.179349244 Iteración: 012 costo = 1.597618810 Iteración: 013 costo = 1.215200688 Iteración: 014 costo = 0.875238173 Iteración: 015 costo = 0.760177279 Optimización Terminada! Precisión: 0.94 Ejecutar el comando: --> tensorboard --logdir=/tmp/tensorflow_logs Luego abir https://0.0.0.0:6006/ en el navegador Como vemos TensorFlow nos da mucha flexibilidad para construir el modelo, modificando muy pocas líneas podríamos cambiar el algoritmo de optimización o el calculo del error y obtener otros resultados; de esta forma vamos a poder personalizar el modelo para alcanzar mayores niveles de precisión. TensorBoard Otra gran herramienta que nos proporciona TensorFlow es TensorBoard que nos permite visualizar nuestros grafos y nos ayudan a alcanzar un mayor entendimiento del flujo de cálculos que ocurre en nuestro modelo. Para crear la información de la que se va a nutrir el TensorBoard , podemos definir algunos scopes utilizando tf.name_scope ; también podemos incluir algunos gráficos sumarizados con tf.scalar_summary y luego llamamos a la función tf.train.SummaryWriter dentro de una Sesión . Luego podemos iniciar el board con el comando tensorboard --logdir=logpath como se puede ver en la salida del último ejemplo. Los grafos de los casos que vimos por ejemplo, se ven así. Los invito a explorar la herramienta y adentrarse en el fascinante mundo de las redes neuronales . Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Redes Neuronales","url":"https://relopezbriega.github.io/blog/2016/06/05/tensorflow-y-redes-neuronales/"},{"title":"Machine Learning con Python - Sobreajuste","text":"Introducción Uno de los conceptos más importantes en Machine Learning es el overfitting o sobreajuste del modelo. Comprender como un modelo se ajusta a los datos es muy importante para entender las causas de baja precisión en las predicciones. Un modelo va a estar sobreajustado cuando vemos que se desempeña bien con los datos de entrenamiento, pero su precisión es notablemente más baja con los datos de evaluación; esto se debe a que el modelo ha memorizado los datos que ha visto y no pudo generalizar las reglas para predecir los datos que no ha visto. De aquí también la importancia de siempre contar con dos conjuntos de datos distintos, uno para entrenar el modelo y otro para evaluar su precisión; ya que si utilizamos el mismo dataset para las dos tareas, no tendríamos forma de determinar como el modelo se comporta con datos que nunca ha visto. ¿Cómo reconocer el sobreajuste? En líneas generales el sobreajuste va a estar relacionado con la complejidad del modelo, mientras más complejidad le agreguemos, mayor va a ser la tendencia a sobreajustarse a los datos, ya que va a contar con mayor flexibilidad para realizar las predicciones y puede ser que los patrones que encuentre estén relacionados con el ruido (pequeños errores aleatorios) en los datos y no con la verdadera señal o relación subyacente. No existe una regla general para establecer cual es el nivel ideal de complejidad que le podemos otorgar a nuestro modelo sin caer en el sobreajuste ; pero podemos valernos de algunas herramientas analíticas para intentar entender como el modelo se ajusta a los datos y reconocer el sobreajuste . Veamos un ejemplo. Árboles de Decisión y sobreajuste Los Árboles de Decisión pueden ser muchas veces una herramienta muy precisa, pero también con mucha tendencia al sobreajuste . Para construir estos modelos aplicamos un procedimiento recursivo para encontrar los atributos que nos proporcionan más información sobre distintos subconjuntos de datos, cada vez más pequeños. Si aplicamos este procedimiento en forma reiterada, eventualmente podemos llegar a un árbol en el que cada hoja tenga una sola instancia de nuestra variable objetivo a clasificar. En este caso extremo, el Árbol de Decisión va a tener una pobre generalización y estar bastante sobreajustado ; ya que cada instancia de los datos de entrenamiento va a encontrar el camino que lo lleve eventualmente a la hoja que lo contiene, alcanzando así una precisión del 100% con los datos de entrenamiento. Veamos un ejemplo sencillo con la ayuda de Python . In [1]: Ver Código # Importando las librerías que vamos a utilizar import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from sklearn.cross_validation import train_test_split from sklearn.datasets import make_classification from sklearn.svm import SVC from sklearn.tree import DecisionTreeClassifier import random ; random . seed ( 1982 ) # graficos incrustados % matplotlib inline # parametros esteticos de seaborn sns . set_palette ( \"deep\" , desat =. 6 ) sns . set_context ( rc = { \"figure.figsize\" : ( 8 , 4 )}) In [2]: # Ejemplo en python - árboles de decisión # dummy data con 100 atributos y 2 clases X , y = make_classification ( 10000 , 100 , n_informative = 3 , n_classes = 2 , random_state = 1982 ) # separ los datos en train y eval x_train , x_eval , y_train , y_eval = train_test_split ( X , y , test_size = 0.35 , train_size = 0.65 , random_state = 1982 ) # creando el modelo sin control de profundidad, va a continuar hasta # que todas las hojas sean puras arbol = DecisionTreeClassifier ( criterion = 'entropy' ) # Ajustando el modelo arbol . fit ( x_train , y_train ) Out[2]: DecisionTreeClassifier(class_weight=None, criterion='entropy', max_depth=None, max_features=None, max_leaf_nodes=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, presort=False, random_state=None, splitter='best') In [3]: # precisión del modelo en datos de entrenamiento. print ( \"precisión entranamiento: {0: .2f} \" . format ( arbol . score ( x_train , y_train ))) precisión entranamiento: 1.00 Logramos una precisión del 100 %, increíble, este modelo no se equivoca! deberíamos utilizarlo para jugar a la lotería y ver si ganamos algunos millones; o tal vez, no?. Veamos como se comporta con los datos de evaluación. In [4]: # precisión del modelo en datos de evaluación. print ( \"precisión evaluación: {0: .2f} \" . format ( arbol . score ( x_eval , y_eval ))) precisión evaluación: 0.87 Ah, ahora nuestro modelo ya no se muestra tan preciso, esto se debe a que seguramente esta sobreajustado , ya que dejamos crecer el árbol hasta que cada hoja estuviera pura (es decir que solo contenga datos de una sola de las clases a predecir). Una alternativa para reducir el sobreajuste y ver si podemos lograr que generalice mejor y por tanto tenga más precisión para datos nunca vistos, es tratar de reducir la complejidad del modelo por medio de controlar la profundidad que puede alcanzar el Árbol de Decisión . In [5]: # profundidad del arbol de decisión. arbol . tree_ . max_depth Out[5]: 22 Este caso nuestro modelo tiene una profundidad de 22 nodos; veamos si reduciendo esa cantidad podemos mejorar la precisión en los datos de evaluación. Por ejemplo, pongamos un máximo de profundidad de tan solo 5 nodos. In [6]: # modelo dos, con control de profundiad de 5 nodos arbol2 = DecisionTreeClassifier ( criterion = 'entropy' , max_depth = 5 ) # Ajustando el modelo arbol2 . fit ( x_train , y_train ) Out[6]: DecisionTreeClassifier(class_weight=None, criterion='entropy', max_depth=5, max_features=None, max_leaf_nodes=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, presort=False, random_state=None, splitter='best') In [7]: # precisión del modelo en datos de entrenamiento. print ( \"precisión entranamiento: {0: .2f} \" . format ( arbol2 . score ( x_train , y_train ))) precisión entranamiento: 0.92 Ahora podemos ver que ya no tenemos un modelo con 100% de precisión en los datos de entrenamiento, sino que la precisión es bastante inferior, 92%, sin embargo si ahora medimos la precisión con los datos de evaluación vemos que la precisión es del 90%, 3 puntos por arriba de lo que habíamos conseguido con el primer modelo que nunca se equivocaba en los datos de entrenamiento. In [9]: # precisión del modelo en datos de evaluación. print ( \"precisión evaluación: {0: .2f} \" . format ( arbol2 . score ( x_eval , y_eval ))) precisión evaluación: 0.90 Esta diferencia se debe a que reducimos la complejidad del modelo para intentar ganar en generalización. También debemos tener en cuenta que si seguimos reduciendo la complejidad, podemos crear un modelo demasiado simple que en vez de estar sobreajustado puede tener un desempeño muy por debajo del que podría tener; podríamos decir que el modelo estaría infraajustado y tendría un alto nivel de sesgo . Para ayudarnos a encontrar el término medio entre la complejidad del modelo y su ajuste a los datos, podemos ayudarnos de herramientas gráficas. Por ejemplo podríamos crear diferentes modelos, con distintos grados de complejidad y luego graficar la precisión en función de la complejidad. In [10]: # Grafico de ajuste del árbol de decisión train_prec = [] eval_prec = [] max_deep_list = list ( range ( 3 , 23 )) for deep in max_deep_list : arbol3 = DecisionTreeClassifier ( criterion = 'entropy' , max_depth = deep ) arbol3 . fit ( x_train , y_train ) train_prec . append ( arbol3 . score ( x_train , y_train )) eval_prec . append ( arbol3 . score ( x_eval , y_eval )) # graficar los resultados. plt . plot ( max_deep_list , train_prec , color = 'r' , label = 'entrenamiento' ) plt . plot ( max_deep_list , eval_prec , color = 'b' , label = 'evaluacion' ) plt . title ( 'Grafico de ajuste arbol de decision' ) plt . legend () plt . ylabel ( 'precision' ) plt . xlabel ( 'cant de nodos' ) plt . show () El gráfico que acabamos de construir se llama gráfico de ajuste y muestra la precisión del modelo en función de su complejidad. En nuestro ejemplo, podemos ver que el punto con mayor precisión, en los datos de evaluación, lo obtenemos con un nivel de profundidad de aproximadamente 5 nodos; a partir de allí el modelo pierde en generalización y comienza a estar sobreajustado . También podemos crear un gráfico similar con la ayuda de Scikit-learn , utilizando validation_curve . In [11]: # utilizando validation curve de sklearn from sklearn.learning_curve import validation_curve train_prec , eval_prec = validation_curve ( estimator = arbol , X = x_train , y = y_train , param_name = 'max_depth' , param_range = max_deep_list , cv = 5 ) train_mean = np . mean ( train_prec , axis = 1 ) train_std = np . std ( train_prec , axis = 1 ) test_mean = np . mean ( eval_prec , axis = 1 ) test_std = np . std ( eval_prec , axis = 1 ) In [12]: # graficando las curvas plt . plot ( max_deep_list , train_mean , color = 'r' , marker = 'o' , markersize = 5 , label = 'entrenamiento' ) plt . fill_between ( max_deep_list , train_mean + train_std , train_mean - train_std , alpha = 0.15 , color = 'r' ) plt . plot ( max_deep_list , test_mean , color = 'b' , linestyle = '--' , marker = 's' , markersize = 5 , label = 'evaluacion' ) plt . fill_between ( max_deep_list , test_mean + test_std , test_mean - test_std , alpha = 0.15 , color = 'b' ) plt . grid () plt . legend ( loc = 'center right' ) plt . xlabel ( 'Cant de nodos' ) plt . ylabel ( 'Precision' ) plt . show () En este gráfico, también podemos ver que nuestro modelo tiene bastante varianza , representada por el área esfumada. Métodos para reducir el Sobreajuste Algunas de las técnicas que podemos utilizar para reducir el Sobreajuste , son: Utilizar validación cruzada . Recolectar más datos. Introducir una penalización a la complejidad con alguna técnica de regularización. Optimizar los parámetros del modelo con grid search . Reducir la dimensión de los datos. Aplicar técnicas de selección de atributos . Utilizar modelos ensamblados . Veamos algunos ejemplos. Validación cruzada La validación cruzada se inicia mediante el fraccionamiento de un conjunto de datos en un número $k$ de particiones (generalmente entre 5 y 10) llamadas pliegues . La validación cruzada luego itera entre los datos de evaluación y entrenamiento $k$ veces, de un modo particular. En cada iteración de la validación cruzada , un pliegue diferente se elige como los datos de evaluación . En esta iteración, los otros pliegues $k-1$ se combinan para formar los datos de entrenamiento . Por lo tanto, en cada iteración tenemos $(k-1) / k$ de los datos utilizados para el entrenamiento y $1 / k$ utilizado para la evaluación . Cada iteración produce un modelo, y por lo tanto una estimación del rendimiento de la generalización , por ejemplo, una estimación de la precisión. Una vez finalizada la validación cruzada , todos los ejemplos se han utilizado sólo una vez para evaluar pero $k -1$ veces para entrenar . En este punto tenemos estimaciones de rendimiento de todos los pliegues y podemos calcular la media y la desviación estándar de la precisión del modelo. Veamos un ejemplo In [13]: # Ejemplo cross-validation from sklearn import cross_validation # creando pliegues kpliegues = cross_validation . StratifiedKFold ( y = y_train , n_folds = 10 , random_state = 2016 ) # iterando entre los plieges precision = [] for k , ( train , test ) in enumerate ( kpliegues ): arbol2 . fit ( x_train [ train ], y_train [ train ]) score = arbol2 . score ( x_train [ test ], y_train [ test ]) precision . append ( score ) print ( 'Pliegue: {0:} , Dist Clase: {1:} , Prec: {2:.3f} ' . format ( k + 1 , np . bincount ( y_train [ train ]), score )) # imprimir promedio y desvio estandar print ( 'Precision promedio: {0: .3f} +/- {1: .3f} ' . format ( np . mean ( precision ), np . std ( precision ))) Pliegue: 1, Dist Clase: [2918 2931], Prec: 0.909 Pliegue: 2, Dist Clase: [2918 2931], Prec: 0.896 Pliegue: 3, Dist Clase: [2918 2931], Prec: 0.897 Pliegue: 4, Dist Clase: [2919 2931], Prec: 0.920 Pliegue: 5, Dist Clase: [2919 2931], Prec: 0.895 Pliegue: 6, Dist Clase: [2919 2931], Prec: 0.912 Pliegue: 7, Dist Clase: [2919 2931], Prec: 0.871 Pliegue: 8, Dist Clase: [2919 2932], Prec: 0.906 Pliegue: 9, Dist Clase: [2919 2932], Prec: 0.884 Pliegue: 10, Dist Clase: [2919 2932], Prec: 0.891 Precision promedio: 0.898 +/- 0.014 En este ejemplo, utilizamos el iterador StratifiedKFold que nos proporciona Scikit-learn . Este iterador es una versión mejorada de la validación cruzada , ya que cada pliegue va a estar estratificado para mantener las proporciones entre las clases del conjunto de datos original, lo que suele dar mejores estimaciones del sesgo y la varianza del modelo. También podríamos utilizar cross_val_score que ya nos proporciona los resultados de la precisión que tuvo el modelo en cada pliegue . In [14]: # Ejemplo con cross_val_score precision = cross_validation . cross_val_score ( estimator = arbol2 , X = x_train , y = y_train , cv = 10 , n_jobs =- 1 ) print ( 'precisiones: {} ' . format ( precision )) print ( 'Precision promedio: {0: .3f} +/- {1: .3f} ' . format ( np . mean ( precision ), np . std ( precision ))) precisiones: [ 0.906298 0.89708141 0.89708141 0.91846154 0.89538462 0.91230769 0.87076923 0.90755008 0.8844376 0.89060092] Precision promedio: 0.898 +/- 0.013 Más datos y curvas de aprendizaje Muchas veces, reducir el Sobreajuste es tan fácil como conseguir más datos, dame más datos y te predeciré el futuro!. Aunque en la vida real nunca es una tarea tan sencilla conseguir más datos. Otra herramienta analítica que nos ayuda a entender como reducimos el Sobreajuste con la ayuda de más datos, son las curvas de aprendizaje , las cuales grafican la precisión en función del tamaño de los datos de entrenamiento. Veamos como podemos graficarlas con la ayuda de Python . In [15]: # Ejemplo Curvas de aprendizaje from sklearn.learning_curve import learning_curve train_sizes , train_scores , test_scores = learning_curve ( estimator = arbol2 , X = x_train , y = y_train , train_sizes = np . linspace ( 0.1 , 1.0 , 10 ), cv = 10 , n_jobs =- 1 ) train_mean = np . mean ( train_scores , axis = 1 ) train_std = np . std ( train_scores , axis = 1 ) test_mean = np . mean ( test_scores , axis = 1 ) test_std = np . std ( test_scores , axis = 1 ) In [16]: # graficando las curvas plt . plot ( train_sizes , train_mean , color = 'r' , marker = 'o' , markersize = 5 , label = 'entrenamiento' ) plt . fill_between ( train_sizes , train_mean + train_std , train_mean - train_std , alpha = 0.15 , color = 'r' ) plt . plot ( train_sizes , test_mean , color = 'b' , linestyle = '--' , marker = 's' , markersize = 5 , label = 'evaluacion' ) plt . fill_between ( train_sizes , test_mean + test_std , test_mean - test_std , alpha = 0.15 , color = 'b' ) plt . grid () plt . title ( 'Curva de aprendizaje' ) plt . legend ( loc = 'upper right' ) plt . xlabel ( 'Cant de ejemplos de entrenamiento' ) plt . ylabel ( 'Precision' ) plt . show () En este gráfico podemos ver claramente como con pocos datos la precisión entre los datos de entrenamiento y los de evaluación son muy distintas y luego a medida que la cantidad de datos va aumentando, el modelo puede generalizar mucho mejor y las precisiones se comienzan a emparejar. Este gráfico también puede ser importante a la hora de decidir invertir en la obtención de más datos, ya que por ejemplo nos indica que a partir las 2500 muestras, el modelo ya no gana mucha más precisión a pesar de obtener más datos. Optimización de parámetros con Grid Search La mayoría de los modelos de Machine Learning cuentan con varios parámetros para ajustar su comportamiento, por lo tanto otra alternativa que tenemos para reducir el Sobreajuste es optimizar estos parámetros por medio de un proceso conocido como grid search e intentar encontrar la combinación ideal que nos proporcione mayor precisión. El enfoque que utiliza grid search es bastante simple, se trata de una búsqueda exhaustiva por el paradigma de fuerza bruta en el que se especifica una lista de valores para diferentes parámetros, y la computadora evalúa el rendimiento del modelo para cada combinación de éstos parámetros para obtener el conjunto óptimo que nos brinda el mayor rendimiento. Veamos un ejemplo utilizando un modelo de SVM o Máquinas de vectores de soporte , la idea va a ser optimizar los parámetros gamma y C de este modelo. El parámetro gamma define cuan lejos llega la influencia de un solo ejemplo de entrenamiento, con valores bajos que significan \"lejos\" y los valores altos significan \"cerca\". El parámetro C es el que establece la penalización por error en la clasificación un valor bajo de este parámetro hace que la superficie de decisión sea más lisa, mientras que un valor alto tiene como objetivo que todos los ejemplos se clasifiquen correctamente, dándole más libertad al modelo para elegir más ejemplos como vectores de soporte. Tengan en cuenta que como todo proceso por fuerza bruta, puede tomar bastante tiempo según la cantidad de parámetros que utilicemos para la optimización. In [17]: # Ejemplo de grid search con SVM. from sklearn.grid_search import GridSearchCV # creación del modelo svm = SVC ( random_state = 1982 ) # rango de parametros rango_C = np . logspace ( - 2 , 10 , 10 ) rango_gamma = np . logspace ( - 9 , 3 , 10 ) param_grid = dict ( gamma = rango_gamma , C = rango_C ) # crear grid search gs = GridSearchCV ( estimator = svm , param_grid = param_grid , scoring = 'accuracy' , cv = 5 , n_jobs =- 1 ) # comenzar el ajuste gs = gs . fit ( x_train , y_train ) In [18]: # imprimir resultados print ( gs . best_score_ ) print ( gs . best_params_ ) 0.870461538462 {'C': 4.6415888336127775, 'gamma': 0.0046415888336127729} In [21]: # utilizando el mejor modelo mejor_modelo = gs . best_estimator_ mejor_modelo . fit ( x_train , y_train ) print ( 'Precisión: {0:.3f} ' . format ( mejor_modelo . score ( x_eval , y_eval ))) Precisión: 0.864 En este ejemplo, primero utilizamos el objeto GridSearchCV que nos permite realizar grid search junto con validación cruzada , luego comenzamos a ajustar el modelo con las diferentes combinaciones de los valores de los parámetros gamma y C . Finalmente imprimimos el mejor resultado de precisión y los valores de los parámetros que utilizamos para obtenerlos; por último utilizamos este mejor modelo para realizar las predicciones con los datos de evaluación . Podemos ver que la precisión que obtuvimos con los datos de evaluación es casi idéntica a la que nos indicó grid search , lo que indica que el modelo generaliza muy bien. Aquí termina este artículo, sobre la selección de atributos , pueden visitar el artículo que dedique a ese tema en este link ; en cuando a modelos ensamblados y reducción de dimensiones de los datos, espero escribir sobre esos temas en artículos futuros, no se los pierdan! Gracias por visitar el blog y saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Machine Learning","url":"https://relopezbriega.github.io/blog/2016/05/29/machine-learning-con-python-sobreajuste/"},{"title":"Ejemplo de Machine Learning con Python - Selección de atributos","text":"Introducción Continuando donde nos quedamos en el artículo anterior Ejemplo de Machine Learning - preprocesamiento y exploración ; ahora es tiempo de ingresar en el terreno de la selección de atributos . ¿En qué consiste la selección de atributos? La selección de atributos es el proceso por el cual seleccionamos un subconjunto de atributos (representados por cada una de las columnas en un dataset de forma tabular) que son más relevantes para la construcción del modelo predictivo sobre el que estamos trabajando. Este proceso, no se debe confundir con el de reducción de dimensiones ; si bien ambos procesos buscan reducir el número de atributos en nuestro dataset ; este último lo hace por medio de la creación de nuevos atributos que son combinaciones de los anteriores; mientras que en el proceso de selección de atributos , intentamos incluir y excluir los atributos prácticamente sin modificarlos. El proceso de selección de atributos es tanto un arte como una ciencia, en donde el conocimiento sobre el problema y la intuición son sumamente importantes. El objetivo de la selección de atributos es triple: mejorar la capacidad predictiva de nuestro modelo, proporcionando modelos predictivos más rápidos y eficientes, y proporcionar una mejor comprensión del proceso subyacente que generó los datos. Los métodos de selección de atributos se pueden utilizar para identificar y eliminar los atributos innecesarios, irrelevantes y redundantes que no contribuyen a la exactitud del modelo predictivo o incluso puedan disminuir su precisión. Beneficios de la selección de atributos Uno de los principales beneficios de la selección de atributos esta plasmado por la famosa frase \"Menos es más\" del arquitecto Ludwig Mies van der Rohe , precursor del minimalismo . Menos atributos son deseables ya que reduce la complejidad del modelo, y un modelo más simple es más fácil de entender y explicar. Otros beneficios adicionales que nos proporciona una buena selección de atributos antes de comenzar con el armado del modelo, son: Reduce el sobreentrenamiento : Menos datos redundantes significan menos oportunidades para tomar decisiones sobre la base de ruido. Mejora la precisión : Menos datos engañosos se convierten en una mejora en la exactitud del modelo. Reduce el tiempo de entrenamiento : Menos datos significa que los algoritmos aprenden más rápidamente. Selección de atributos univariante o multivariante Una cosa que no debemos pasar por alto en el proceso de selección de atributos , es la relación que puede existir entre ellos. Es decir que debemos considerar seleccionar o eliminar un atributo en forma individual ( univariante ) o un un grupo de atributos en forma conjunta ( multivariante ). Esto también va a depender del problema con el que estemos tratando y del modelo que elijamos. Por ejemplo si elegimos como modelo un clasificador bayesiano ingenuo , el modelo asume que cada atributo es independiente del resto, por lo tanto, podríamos utilizar un enfoque univariante sin problemas; en cambio si elegimos como modelo una red neuronal , este último no asume la independencia de los atributos, sino que utiliza todas la que dispone; por lo tanto aquí deberíamos seguir un enfoque multivariante para seleccionar los atributos. Algoritmos para selección de atributos Podemos encontrar dos clases generales de algoritmos de selección de atributos : los métodos de filtrado, y los métodos empaquetados. Métodos de filtrado Estos métodos aplican una medida estadística para asignar una puntuación a cada atributo. Los atributos luego son clasificados de acuerdo a su puntuación y son, o bien seleccionados para su conservación o eliminados del conjunto de datos. Los métodos de filtrado son a menudo univariantes y consideran a cada atributo en forma independiente, o con respecto a la variable dependiente. Ejemplos de estos métodos son: prueba de Chi cuadrado , prueba F de Fisher , ratio de ganancia de información y los coeficientes de correlación . Métodos empaquetados Estos métodos consideran la selección de un conjunto de atributos como un problema de búsqueda, en donde las diferentes combinaciones son evaluadas y comparadas. Para hacer estas evaluaciones se utiliza un modelo predictivo y luego se asigna una puntuación a cada combinación basada en la precisión del modelo. Un ejemplo de este método es el algoritmo de eliminación recursiva de atributos. Ejemplo Pasamos ahora a ver como podemos aplicar todo esto al caso en el que veníamos trabajando en el el artículo anterior . Pero antes, terminemos con algunas tareas de preprocesamiento adicionales. In [1]: Ver Código # Importando las librerías que vamos a utilizar import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from sklearn.cross_validation import train_test_split from sklearn.feature_selection import SelectKBest from sklearn.feature_selection import f_classif from sklearn.feature_selection import RFE from sklearn.ensemble import ExtraTreesClassifier # graficos incrustados % matplotlib inline # parametros esteticos de seaborn sns . set_palette ( \"deep\" , desat =. 6 ) sns . set_context ( rc = { \"figure.figsize\" : ( 8 , 4 )}) In [2]: # importando el dataset preprocesado. ONG_data = pd . read_csv ( 'LEARNING_procesado.csv' , header = 0 ) # Agregando la columna AGE2 AGE2 = pd . cut ( ONG_data [ 'AGE' ], range ( 0 , 100 , 10 )) ONG_data [ 'AGE2' ] = AGE2 In [3]: # Eliminar columnas con donaciones superiores a 60 (atípicos) ONG_data = ONG_data [ ONG_data . DONOR_AMOUNT < 60 ] In [4]: # Convertir datos categoricos a numericos tipos = ONG_data . columns . to_series () . groupby ( ONG_data . dtypes ) . groups ctext = tipos [ np . dtype ( 'object' )] for c in ctext : ONG_data [ c ], _ = pd . factorize ( ONG_data [ c ]) ONG_data [ 'AGE2' ], _ = pd . factorize ( ONG_data [ 'AGE2' ]) Con estas manipulaciones lo que hicimos es cargar en memoria el dataset que prepocesamos anteriormente, le agregamos la nueva columna AGE2, ya que es mejor tener la edad agrupada en rangos en lugar de individualmente, luego eliminamos los valores atípicos que habíamos detectado; y por último, reemplazamos con su equivalente numérico a todas las variables categóricas ; ya que para los algoritmos de Scikit-learn es mucho más eficiente trabajar con variables numéricas. Ahora sí, ya estamos en condiciones de poder comenzar a aplicar algunos de los algoritmos de selección de atributos , comencemos con un simple algoritmo univariante que aplica el método de filtrado. Para esto vamos a utilizar los objetos SelectKBest y f_classif del paquete sklearn.feature_selection . In [5]: # Separamos las columnas objetivo donor_flag = ONG_data [ 'DONOR_FLAG' ] donor_amount = ONG_data [ 'DONOR_AMOUNT' ] indice = ONG_data [ 'IDX' ] In [6]: # Aplicando el algoritmo univariante de prueba F. k = 15 # número de atributos a seleccionar entrenar = ONG_data . drop ([ 'DONOR_FLAG' , 'DONOR_AMOUNT' , 'IDX' ], axis = 1 ) columnas = list ( entrenar . columns . values ) seleccionadas = SelectKBest ( f_classif , k = k ) . fit ( entrenar , donor_flag ) atrib = seleccionadas . get_support () atributos = [ columnas [ i ] for i in list ( atrib . nonzero ()[ 0 ])] atributos Out[6]: ['ODATEDW', 'PEPSTRFL', 'HVP3', 'CARDPROM', 'NUMPROM', 'RAMNT_8', 'RAMNT_16', 'NGIFTALL', 'CARDGIFT', 'LASTGIFT', 'LASTDATE', 'FISTDATE', 'AVGGIFT', 'RFA_2F', 'RFA_2A'] Como podemos ver, el algoritmo nos seleccionó la cantidad de atributos que le indicamos; en este ejemplo decidimos seleccionar solo 15; obviamente, cuando armemos nuestro modelo final vamos a tomar un número mayor de atributos. ¿Cómo funciona? Este algoritmo selecciona a los mejores atributos basándose en una prueba estadística univariante . Al objeto SelectKBest le pasamos la prueba estadística que vamos a a aplicar, en este caso una prueba F definida por el objeto f_classif , junto con el número de atributos a seleccionar. El algoritmo va a aplicar la prueba a todos los atributos y va a seleccionar los que mejor resultado obtuvieron. Ahora veamos como funciona el algoritmo de Eliminación Recursiva de atributos . Para este caso, vamos a utilizar como nuestro modelo predictivo el algoritmo ExtraTreesClassifier . In [7]: # Algoritmo de Eliminación Recursiva de atributos con ExtraTrees modelo = ExtraTreesClassifier () era = RFE ( modelo , 15 ) # número de atributos a seleccionar era = era . fit ( entrenar , donor_flag ) In [8]: # imprimir resultados atrib = era . support_ atributos = [ columnas [ i ] for i in list ( atrib . nonzero ()[ 0 ])] atributos Out[8]: ['OSOURCE', 'ZIP', 'VIETVETS', 'WWIIVETS', 'POP901', 'HV1', 'PEC2', 'TPE13', 'EIC4', 'EIC14', 'VC2', 'CARDPROM', 'MINRDATE', 'MAXRDATE', 'TIMELAG'] ¿Cómo funciona? En este algoritmo, dado un modelo predictivo que asigna un coeficiente de importancia a cada atributo (como ExtraTreesClassifier), el objetivo de la Eliminación Recursiva de atributos es ir seleccionado en forma recursiva un número cada vez más pequeño de atributos. Primero comienza con todos los atributos del dataset y luego en cada pasada va eliminando aquellos que tenga el menor coeficiente de importancia hasta alcanzar el número de atributos deseado. Si vemos los 15 atributos seleccionados por este otro algoritmo, existen muchas diferencias con los que selecciono el modelo anterior; en general, la Eliminación Recursiva de atributos suele ser mucho más precisa, pero también consume mucho más tiempo y recursos, ya que requiere que entrenemos a un modelo predictivo para poder obtener sus resultados. Por último, también podríamos utilizar ese coeficiente de importancia que nos proporciona el modelo como una guía adicional para refinar nuestra selección de atributos . In [9]: # Importancia de atributos. modelo . fit ( entrenar , donor_flag ) modelo . feature_importances_ [: 15 ] Out[9]: array([ 2.59616058e-03, 3.03252110e-03, 2.71464477e-03, 2.51952243e-03, 2.34433609e-03, 6.01722535e-04, 4.82782938e-04, 2.24732045e-03, 2.07872966e-03, 1.29249803e-03, 1.17189225e-03, 9.77218420e-06, 6.12421074e-04, 5.79137133e-05, 2.40594405e-03]) In [10]: # 15 coeficientes más altos np . sort ( modelo . feature_importances_ )[:: - 1 ][: 15 ] Out[10]: array([ 0.00369165, 0.00353173, 0.00348298, 0.00336095, 0.00331946, 0.00331828, 0.00331345, 0.00326505, 0.00316758, 0.00316587, 0.00313431, 0.00311334, 0.00310065, 0.00307309, 0.00304518]) Analicemos algunos de estos atributos en forma individual para tener una idea de cuanto puede ser que aporten a la exactitud del modelo. Podríamos comparar por ejemplo, el promedio de donaciones que podríamos obtener con este atributo contra el promedio de todo el dataset . Tomemos por ejemplo al atributo LASTGIFT que representa el importe de la última donación que realizó cada persona incluida en el conjunto de datos. En principio parece lógico que este atributo sea significativo para el modelo, ya que si donó en el pasado, hay bastantes posibilidades de que vuelva a donar en esta oportunidad. In [11]: # Probabilidad de ser donante de todo el dataset. prob_gral = ( ONG_data [ ONG_data . DONOR_AMOUNT > 0 ][ 'DONOR_AMOUNT' ] . count () \\ / ONG_data [ 'DONOR_AMOUNT' ] . count ()) * 100.0 prob_gral Out[11]: 5.0377358490566033 In [12]: # Probabilidad de realizar donanción con LASTGIFT <= 10 lastgift10 = ( ONG_data [( ONG_data . DONOR_AMOUNT > 0 ) & ( ONG_data . LASTGIFT <= 10 )][ 'DONOR_AMOUNT' ] . count () \\ / ONG_data [ ONG_data . LASTGIFT <= 10 ][ 'DONOR_AMOUNT' ] . count ()) * 100.0 lastgift10 Out[12]: 6.9347104389524157 In [13]: # graficando los resultados lastgift = pd . Series ({ 'promedio gral' : prob_gral , 'lastgift<=10' : lastgift10 }) plot = lastgift . plot ( kind = 'barh' , color = [ 'blue' , 'green' ]) . set_title ( 'Pobabilidad de donar' ) Este último gráfico nos muestra claramente que con un valor del atributo LASTGIFT menor o igual a 10 las probabilidades de que esa persona realice una donación mejoran, pero veamos que pasa con el importe de la donación. In [14]: # importe promedio de donación general donacion_prom = ONG_data [ ONG_data . DONOR_AMOUNT > 0 ][ 'DONOR_AMOUNT' ] . mean () donacion_prom Out[14]: 14.889109446525177 In [15]: # importe promedio de donación lastgift <= 10 lastgift10_imp = ONG_data [( ONG_data . DONOR_AMOUNT > 0 ) & ( ONG_data . LASTGIFT <= 10 )][ 'DONOR_AMOUNT' ] . mean () lastgift10_imp Out[15]: 8.7553191489361701 In [16]: # graficando los resultados lastgift = pd . Series ({ 'imp promedio gral' : donacion_prom , 'lastgift<=10' : lastgift10_imp }) plot = lastgift . plot ( kind = 'barh' , color = [ 'blue' , 'green' ] ) . set_title ( 'importe promedio de donación' ) Aquí vemos, que si bien las probabilidades de que sea un donador mejoran, el importe que se dona esta por debajo del promedio. En el caso de este atributo podemos ver que existe una correlación inversa entre el importe de donación y la probabilidad de hacer una donación a la ONG. Hasta aquí llegamos en este artículo, la idea es que luego, cuando tengamos armado el modelo, podamos jugar con distintas combinaciones de atributos y ver como se comporta el modelo hasta alcanzar la combinación ideal de atributos. No se pierdan los próximos artículos! Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Machine Learning","url":"https://relopezbriega.github.io/blog/2016/04/15/ejemplo-de-machine-learning-con-python-seleccion-de-atributos/"},{"title":"Ejemplo de Machine Learning con Python - Preprocesamiento y exploración","text":"Introducción En mi artículo Machine Learning con Python , hice una breve introducción a los principales conceptos que debemos conocer de Machine Learning . En este artículo, la idea es profundizar un poco más en ellos y presentar algunos conceptos nuevos con la ayuda de un ejemplo práctico. Descripción del ejemplo En el ejemplo que vamos a utilizar, vamos a imaginarnos que una organización sin fines de lucro soporta su operación mediante la organización periódica de una campaña para recaudar fondos por correo. Esta organización ha creado una base de datos con más de 40 mil personas que por lo menos una vez en el pasado ha sido donante. La campaña de recaudación de fondos se realiza mediante el envío a una lista de correo (o un subconjunto de ella) de un regalo simbólico y la solicitud de una donación. Una vez que se planifica la campaña, el costo total de la misma se conoce de forma automática: [número de potenciales donantes a contactar] x ([costo de regalo] + [costo de correo]) Sin embargo, el resultado de la recaudación de fondos depende tanto del número de donantes que responde a la campaña, como del importe medio de dinero que es donado. La idea es que, utilizando las técnicas de Machine Learning sobre la base de datos de esta organización, podamos ayudarla a maximizar los beneficios de la campaña de recaudación, esto es, lograr el máximo importe posible de dinero recaudado, minimizando lo más que se pueda el costo total de la campaña. Debemos tener en cuenta que un miembro de la organización le enviará el correo a un potencial donante, siempre que el rendimiento esperado del pedido excede el costo del correo con la solicitud de donación. Para nuestro ejemplo, el costo por donante de la campaña va a ser igual al [costo de regalo] + [costo de correo] , y esto va a ser igual a \\$ 0.75 por correo enviado. Los ingresos netos de la campaña se calculan como la suma (importe de donación real - \\$ 0.75) sobre todos los donantes a los que se ha enviado el correo. Nuestro objetivo es ayudar a esta organización sin fines de lucro a seleccionar de su lista de correo los donantes a los que debe abordar a los efectos de maximizar los beneficios de la campaña de recaudación. El Dataset El dataset que vamos a utilizar, consiste en la base de datos de la organización sin fines de lucro con la lista de correo de los donantes de sus campañas anteriores. El mismo, ya lo hemos dividido en un dataset de aprendizaje que se pueden descargar del siguiente enlace ; y un dataset que vamos a utilizar para realizar las predicciones, el cual se lo pueden descargar desde este otro enlace . Algunos otros datos a tener en cuenta, son los siguientes: El dataset de aprendizaje contiene 47720 registros y 481 columnas. La primera fila / cabecera del mismo contiene los nombres de cada campo. El dataset de validación contiene 47692 registros y 479 columnas. Al igual que en el caso anterior, la primera fila contiene los nombres de cada campo. Los registros del dataset de validación son idénticos a los registros del dataset de aprendizaje, excepto que los valores para nuestros campos objetivo que necesitamos para el aprendizaje, no existen(es decir, las columnas DONOR_FLAG y DONOR_AMOUNT no están incluidas en el dataset de validación). Los espacios en blanco en los campos de tipo texto y los puntos en los campos de tipo numérico corresponden a valores faltantes o perdidos. Cada registro tiene un identificador único de registro o índice (campo IDX). Para cada registro, hay dos variables objetivo (campos DONOR_FLAG y DONOR_AMOUNT). DONOR_FLAG es una variable binaria que indica si ese registro fue donante o no; mientras que DONOR_AMOUNT contiene el importe de la donación para los casos que fueron donantes. Algunos de los valores en el dataset pueden contener errores de formato o de ingreso. Por lo que se deberían corregir o limpiar. Una descripción detallada del significado de cada columna del dataset , la pueden encontrar en el siguiente enlace . Análisis exploratorio y preprocesamiento El primer paso que deberíamos emprender, es realizar un pequeño análisis exploratorio de nuestro dataset ; es decir, valernos de algunos herramientas de la estadística , junto con algunas visualizaciones para entender un poco más los datos de los que disponemos. Veamos como podemos hacer esto. In [1]: Ver Código # Importando las librerías que vamos a utilizar import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from sklearn.preprocessing import LabelEncoder # graficos incrustados % matplotlib inline # parametros esteticos de seaborn sns . set_palette ( \"deep\" , desat =. 6 ) sns . set_context ( rc = { \"figure.figsize\" : ( 8 , 4 )}) In [2]: # importando el dataset a un Dataframe de Pandas ONG_data = pd . read_csv ( 'LEARNING.csv' , header = 0 ) In [3]: # Examinando las primeras 10 filas y 10 columnas del dataset ONG_data . ix [: 10 , : 10 ] Out[3]: ODATEDW OSOURCE TCODE STATE ZIP MAILCODE PVASTATE DOB NOEXCH RECINHSE 0 8901 GRI 0 IL 61081 3712 0 1 9401 NWN 0 LA 70611 0 0 2 9401 MSD 1 TN 37127- 3211 0 3 8901 ENQ 0 MN 56475 2603 0 4 9201 HCC 1 LA 70791 0 0 X 5 9301 USB 1 UT 84720 2709 0 6 9401 FRC 1 CA 90056 0 0 7 8801 PCH 2 IL 62376 5201 0 8 8601 AMB 28 FL 32810 B 3601 0 9 9501 L15 1 NC 27850 0 0 10 8701 BBK 2 MN 55125 3601 0 In [4]: # Controlando la cantidad de registros ONG_data [ 'DONOR_AMOUNT' ] . count () Out[4]: 47720 Como podemos ver, utilizando simples expresiones de Python , podemos cargar la base de datos de la ONG en un Dataframe de Pandas ; lo que nos va a permitir manipular los datos con suma facilidad. Comenzemos a explorar un poco más en detalle este dataset ! En primer lugar, lo que deberíamos hacer es controlar si existen valores faltantes o nulos; esto lo podemos realizar utilizando el método isnull() del siguiente modo: In [5]: # Controlando valores nulos ONG_data . isnull () . any () . any () Out[5]: True Como podemos ver, el método nos devuelve el valor \"True\", lo que indica que existen valores nulos en nuestro dataset . Estos valores pueden tener una influencia significativa en nuestro modelo predictivo, por lo que siempre es una decisión importante determinar la forma en que los vamos a manejar. Las alternativas que tenemos son: Dejarlos como están, lo que a la larga nos va a traer bastantes dolores de cabeza ya que en general los algoritmos no los suelen procesar correctamente y provocan errores. Eliminarlos, lo que es una alternativa viable aunque, dependiendo la cantidad de valores nulos, puede afectar significativamente el resultado final de nuestro modelo predictivo. Inferir su valor. En este caso, lo que podemos hacer es tratar de inferir el valor faltante y reemplazarlo por el valor inferido. Esta suele ser generalmente la mejor alternativa a seguir. En este ejemplo, yo voy a utilizar la última alternativa. Vamos a inferir los valores faltantes utilizando la media aritmética para los datos cuantitativos y la moda para los datos categóricos . Como vamos a utilizar dos métodos distintos para reemplazar a los valores faltantes, dependiendo de si son numéricos o categóricos , el primer paso que debemos realizar es tratar de identificar que columnas de nuestro dataset corresponde a cada tipo de datos; para realizar esto vamos a utilizar el atributo dtypes del Dataframe de Pandas . In [6]: # Agrupando columnas por tipo de datos tipos = ONG_data . columns . to_series () . groupby ( ONG_data . dtypes ) . groups # Armando lista de columnas categóricas ctext = tipos [ np . dtype ( 'object' )] len ( ctext ) # cantidad de columnas con datos categóricos. Out[6]: 68 In [7]: # Armando lista de columnas numéricas columnas = ONG_data . columns # lista de todas las columnas cnum = list ( set ( columnas ) - set ( ctext )) len ( cnum ) Out[7]: 413 Ahora ya logramos separar a las 481 columnas que tiene nuestro dataset . 68 columnas contienen datos categóricos y 413 contienen datos cuantitativos . Procedamos a inferir los valores faltantes. In [8]: # Completando valores faltantas datos cuantititavos for c in cnum : mean = ONG_data [ c ] . mean () ONG_data [ c ] = ONG_data [ c ] . fillna ( mean ) In [9]: # Completando valores faltantas datos categóricos for c in ctext : mode = ONG_data [ c ] . mode ()[ 0 ] ONG_data [ c ] = ONG_data [ c ] . fillna ( mode ) In [10]: # Controlando que no hayan valores faltantes ONG_data . isnull () . any () . any () Out[10]: False In [11]: # Guardando el dataset preprocesado # Save transform datasets ONG_data . to_csv ( \"LEARNING_procesado.csv\" , index = False ) Perfecto! Ahora tenemos un dataset limpio de valores faltantes. Ya estamos listos para comenzar a explorar los datos, comencemos por determinar el porcentaje de personas que alguna vez fue donante de la ONG y están incluidos en la base de datos con la que estamos trabajando. In [12]: # Calculando el porcentaje de donantes sobre toda la base de datos porcent_donantes = ( ONG_data [ ONG_data . DONOR_AMOUNT > 0 ][ 'DONOR_AMOUNT' ] . count () * 1.0 / ONG_data [ 'DONOR_AMOUNT' ] . count ()) * 100.0 print ( \"El procentaje de donantes de la base de datos es {0:.2f} %\" . format ( porcent_donantes )) El procentaje de donantes de la base de datos es 5.08% In [13]: # Grafico de totas del porcentaje de donantes # Agrupando por DONOR_FLAG donantes = ONG_data . groupby ( 'DONOR_FLAG' ) . IDX . count () # Creando las leyendas del grafico. labels = [ 'Donante \\n ' + str ( round ( x * 1.0 / donantes . sum () * 100.0 , 2 )) + '%' for x in donantes ] labels [ 0 ] = 'No ' + labels [ 0 ] plt . pie ( donantes , labels = labels ) plt . title ( 'Porcion de donantes' ) plt . show () In [14]: # Creando subset con solo los donates ONG_donantes = ONG_data [ ONG_data . DONOR_AMOUNT > 0 ] # cantidad de donantes len ( ONG_donantes ) Out[14]: 2423 Aquí podemos ver que el porcentaje de personas que fueron donantes en el pasado es realmente muy bajo, solo un 5 % del total de la base de datos (2423 personas). Este es un dato importante a tener en cuenta ya que al existir tanta diferencia entre las clases a clasificar, esto puede afectar considerablemente a nuestro algoritmo de aprendizaje. Exploremos también un poco más en detalle a este grupo pequeño de personas que fueron donantes; veamos por ejemplo como se dividen de acuerdo a la cantidad de dinero donado. In [15]: # Analizando el importe de donanciones # Creando un segmentos de importes imp_segm = pd . cut ( ONG_donantes [ 'DONOR_AMOUNT' ], [ 0 , 10 , 20 , 30 , 40 , 50 , 60 , 100 , 200 ]) # Creando el grafico de barras desde pandas plot = pd . value_counts ( imp_segm ) . plot ( kind = 'bar' , title = 'Importes de donacion' ) plot . set_ylabel ( 'Cant de donantes' ) plot . set_xlabel ( 'Rango de importes' ) plt . show () In [16]: # Agrupación por segmento segun importe donado. pd . value_counts ( imp_segm ) Out[16]: (0, 10] 1026 (10, 20] 921 (20, 30] 358 (30, 40] 53 (40, 50] 43 (60, 100] 15 (50, 60] 4 (100, 200] 3 dtype: int64 In [17]: # importe de donación promedio ONG_donantes [ 'DONOR_AMOUNT' ] . mean () Out[17]: 15.598237721832438 In [18]: # Gráfico de cajas del importe de donación sns . boxplot ( list ( ONG_donantes [ 'DONOR_AMOUNT' ])) plt . title ( 'importe de donación' ) plt . show () Este análisis nos muestra que la mayor cantidad de donaciones caen en un rango de importes entre 0 y 30, siendo la donación promedio 15.60. También podemos ver que donaciones que superen un importe de 50 son casos realmente poco frecuentes, por lo que constituyen valores atípicos y sería prudente eliminar estos casos al entrenar nuestro modelo para que no distorsionen los resultados. Otra exploración interesante que podríamos realizar sobre nuestro dataset relacionado con los donantes, es ver como se divide este grupo en términos de género y edad. Comencemos con el género! In [19]: # Grafico del género de los donantes ONG_donantes . groupby ( 'GENDER' ) . size () . plot ( kind = 'bar' ) plt . title ( 'Distribución por género' ) plt . show () In [20]: # Donaciones segun el género ONG_donantes [( ONG_donantes . DONOR_AMOUNT <= 50 ) & ( ONG_donantes . GENDER . isin ([ 'F' , 'M' ]) )][[ 'DONOR_AMOUNT' , 'GENDER' ]] . boxplot ( by = 'GENDER' ) plt . title ( 'Donantes segun sexo' ) plt . show () In [21]: # Media de impote donado por mujeres ONG_donantes [ ONG_donantes . GENDER == 'F' ][[ 'DONOR_AMOUNT' ]] . mean () Out[21]: DONOR_AMOUNT 14.610311 dtype: float64 In [22]: # Media de impote donado por hombres ONG_donantes [ ONG_donantes . GENDER == 'M' ][[ 'DONOR_AMOUNT' ]] . mean () Out[22]: DONOR_AMOUNT 16.81989 dtype: float64 Aquí vemos que las mujeres suelen estar más propensas a donar, aunque donan un importe promedio menor (14.61) al que donan los hombres (16.82). Veamos ahora como se comportan las donaciones respecto a la edad. In [23]: # Distribución de la edad de los donantes ONG_donantes [ 'AGE' ] . hist () . set_title ( 'Distribución de donantes segun edad' ) plt . show () In [24]: # Agrupando la edad por rango de a 10 AGE2 = pd . cut ( ONG_donantes [ 'AGE' ], range ( 0 , 100 , 10 )) ONG_donantes [ 'AGE2' ] = AGE2 # Gráfico de barras de donaciones por edad pd . value_counts ( AGE2 ) . plot ( kind = 'bar' , title = 'Donaciones por edad' ) plt . show () In [25]: # Importes de donación por grango de edad ONG_donantes [ ONG_donantes . DONOR_AMOUNT <= 50 ][[ 'DONOR_AMOUNT' , 'AGE2' ]] . boxplot ( by = 'AGE2' ) plt . title ( 'Importe de donación por edad' ) plt . show () En este último análisis podemos ver que la mayor cantidad de los donantes son personas de entre 60 y 70 años, aunque la media de importe donado más alta la tienen las personas que van desde los 30 a los 60 años. Con esto concluyo este análisis; en próximos artículos voy a continuar con el ejemplo completando los restantes pasos que incluye un proyecto de Machine Learning hasta concluir el modelo y poder utilizarlo para realizar predicciones (selección de atributos - armado de modelo - entrenamiento - evaluación - métricas - predicción). Espero lo hayan disfrutado tanto como yo disfrute al escribirlo! Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Machine Learning","url":"https://relopezbriega.github.io/blog/2016/04/08/ejemplo-de-machine-learning-con-python-preprocesamiento-y-exploracion/"},{"title":"Análisis de datos cuantitativos con Python","text":"Introducción En mi artículo anterior, Análisis de datos categóricos con Python , mencione la importancia de reconocer los distintos tipos de datos con que nos podemos encontrar al realizar análisis estadísticos y vimos también como podemos trabajar con los datos categóricos . En esta oportunidad, vamos a ver como podemos manipular, interpretar y obtener información de los datos cuantitativos . Recordemos que las variables cuantitativas son variables medidas en una escala numérica. Altura, peso, tiempo de respuesta, la calificación subjetiva del dolor, la temperatura, y la puntuación en un examen, son ejemplos de variables cuantitativas . Las variables cuantitativas se distinguen de las variables categóricas (también llamadas cualitativas) como el color favorito, religión, ciudad de nacimiento, y el deporte favorito; en las que no hay un orden o medida involucrados. Analizando datos cuantitativos con Python Para los ejemplos de este artículo, vamos a trabajar con el dataset faithful , el cual consiste en una colección de observaciones sobre las erupciones del géiser Old Faithful en el parque nacional Yellowstone de los Estados Unidos. La información que contiene este dataset es la siguiente: In [1]: Ver Código # importando modulos necesarios % matplotlib inline import matplotlib.pyplot as plt import numpy as np from scipy import stats import pandas as pd import seaborn as sns from pydataset import data # parametros esteticos de seaborn sns . set_palette ( \"deep\" , desat =. 6 ) sns . set_context ( rc = { \"figure.figsize\" : ( 8 , 4 )}) In [2]: faithful = data ( 'faithful' ) faithful . head ( 10 ) Out[2]: eruptions waiting 1 3.600 79 2 1.800 54 3 3.333 74 4 2.283 62 5 4.533 85 6 2.883 55 7 4.700 88 8 3.600 85 9 1.950 51 10 4.350 85 Como podemos ver, faithful es un dataset bastante simple que solo contiene observaciones de dos variables; la primera, que se llama eruptions , contiene la información de la duración de la erupción del géiser ; mientras que la segunda, se llama waiting y contiene la información sobre el tiempo de espera para la siguiente erupción del géiser . Al igual de como comentábamos cuando analizamos datos categóricos , lo primero que deberíamos intentar hacer es crear una imagen que represente de la mejor manera posible a nuestros datos , ya que nuestro cerebro tiende a procesar mejor la información visual. Para el caso de las variables cuantitativas , un buen candidato para comenzar a hacernos una imagen de lo que nuestros datos representan, son los histogramas . Histogramas Para las variables cuantitativas , a diferencia de lo que pasaba con las variables categóricas , no existe una forma obvia de agrupar los datos; por tal motivo lo que se suele hacer es, dividir los posibles valores en diferentes contenedores del mismo tamaño y luego contar el número de casos que cae dentro de cada uno de los contenedores . Estos contenedores junto con sus recuentos, nos proporcionan una imagen de la distribución de la variable cuantitativa y constituyen la base para poder graficar el histograma . Para construir el gráfico, simplemente debemos representar a los recuentos como barras y graficarlas contra los valores de cada uno de los contenedores . Con Python podemos representar fácilmente el histograma de la variable eruptions utilizando el método hist del DataFrame de Pandas del siguiente modo: In [3]: # histograma duración de erupciones con 8 barras faithful [ 'eruptions' ] . hist ( bins = 8 ) plt . xlabel ( \"Duración en minutos\" ) plt . ylabel ( \"Frecuencia\" ) plt . show () Como podemos ver con este gráfico, la duración más frecuente de las erupciones del géiser ronda en alrededor de cuatro minutos y medio. Una cosa que debemos hacer notar es que en los histogramas , los contenedores dividen a todos los valores de la variable cuantitativa , por lo que no deberíamos encontrar espacios entre las barras (a diferencia de lo que pasaba con los gráficos de barras que vimos en el artículo anterior ). Cualquier espacio entre las barras es una brecha en los datos , que nos indica un región para la que no existen valores. Distribución de frecuencia Un tema íntimamente relacionado con los histogramas son las tablas de distribución de frecuencia , en definitiva los histogramas no son más que gráficos de tablas de distribución de frecuencia . La distribución de frecuencia de una variable cuantitativa consiste en un resumen de la ocurrencia de un dato dentro de una colección de categorías que no se superponen. Estas categorías las vamos a poder armar según nuestra conveniencia y lo que queramos analizar. Por ejemplo si quisiéramos armar la distribución de frecuencia de la variable eruptions podríamos realizar las siguiente manipulaciones con Pandas : In [4]: # Distribución de frecuencia. # 1ro creamos un rango para las categorías. contenedores = np . arange ( 1.5 , 6. , 0.5 ) # luego cortamos los datos en cada contenedor frec = pd . cut ( faithful [ 'eruptions' ], contenedores ) # por último hacemos el recuento de los contenedores # para armar la tabla de frecuencia. tabla_frec = pd . value_counts ( frec ) tabla_frec Out[4]: (4, 4.5] 75 (1.5, 2] 55 (4.5, 5] 54 (2, 2.5] 37 (3.5, 4] 34 (3, 3.5] 9 (2.5, 3] 5 (5, 5.5] 3 dtype: int64 Como nos nuestra esta tabla de distribución de frecuencia , la duración que más veces ocurre para las erupciones, se encuentran en el rango de 4 a 4.5 minutos. Diagrama de tallos y hojas Los histogramas nos permiten apreciar la distribución de los datos de una forma sencilla, pero los mismos no nos muestran los valores del dataset en sí mismos. Para solucionar esto, existe el diagrama de tallos y hojas , el cual es similar al histograma pero nos muestra los valores individuales de nuestro dataset . Para que quede más claro, vemos un ejemplo sencillo. Supongamos que tenemos una muestra sobre el ritmo cardíaco de 24 mujeres, las observaciones son las siguientes: In [5]: pulso = [ 88 , 80 , 76 , 72 , 68 , 56 , 64 , 60 , 64 , 68 , 64 , 68 , 72 , 76 , 80 , 84 , 68 , 80 , 76 , 72 , 84 , 80 , 72 , 76 ] Podríamos graficar el histograma de estas observaciones del siguiente modo: In [6]: plt . hist ( pulso , bins = 7 ) plt . xlabel ( \"latidos por minuto\" ) plt . ylabel ( \"N° de mujeres\" ) plt . show () Este histograma nos muestra la forma en que se distribuyen los datos, pero no nos muestra los datos individuales. Para esto podríamos graficar su diagrama de tallos y hojas de la siguiente manera: In [7]: # Diagrama de tallos y hojas def tallos ( d ): \"Genera un simple diagramas de tallos y hojas\" l , t = np . sort ( d ), 10 O = range ( int ( l [ 0 ] - l [ 0 ] % t ),int(l[-1]+11),t) I = np . searchsorted ( l , O ) for e , a , f in zip ( I , I [ 1 :], O ): print ( ' %3d |' % ( f / t ), * ( l [ e : a ] - f ), sep = '' ) tallos ( pulso ) 5|6 6|04448888 7|22226666 8|0000448 Como vemos, la distribución del diagrama de tallos y hojas es similar a la del histograma , pero en este caso si podemos ver los valores de nuestras observaciones. El diagrama se lee así: Por un lado tenemos las decenas de los latidos, las cuales constituyen los tallos de nuestro diagrama (los valores antes del pipe o barra vertical \"|\") y luego vamos agrando hojas a estos tallos , representadas por las unidades de cada latido. De esta forma 5|6, significa que solo aparece el valor 56 una sola vez, en cambio 8|0000, significa que tenemos el valor 80 observado en 4 oportunidades. Diagrama de dispersión Hasta aquí venimos graficando únicamente una sola variable cuantitativa pero ¿qué pasa si queremos trabajar con dos variables? Para estos casos existe el diagrama de dispersión . El diagrama de dispersión es una de las formas más comunes que existen para visualizar datos y constituye una de las mejores forma de observar relaciones entre dos variables cuantitativas . Veremos que se puede observar un montón de cosas por el solo hecho de mirar. Este diagrama es una de las mejores formas de visualizar las asociaciones que pueden existir entre nuestros datos. El diagrama de dispersión empareja los valores de dos variables cuantitativas y luego los representa como puntos geométricos dentro de un diagrama cartesiano. Por ejemplo, volviendo a nuestro dataset faithful , podríamos emparejar a las variables eruptions y waiting en la misma observación como coordenadas (x, y) y luego graficarlas en el eje cartesiano. Con la ayuda de Python podríamos generar el diagrama de dispersión del siguiente modo: In [8]: # diagrama de dispersión disp = faithful . plot ( kind = 'scatter' , x = 'eruptions' , y = 'waiting' ) Como podemos ver con solo observar la dispersión de los datos parece existir una relación lineal entre los datos de este dataset . Medidas de tendencia central Una vez que ya nos dimos una buena idea visual de como se distribuyen los datos y de las relaciones que pueden existir entre los mismos, podemos pasar a calcular medidas numéricas propias de la estadística descriptiva . En general, suele ser interesante conocer cual puede ser el promedio o valor central al que tiende la distribución de nuestros datos , para esto se utilizan las medidas de tendencia central , entre las que podemos encontrar a: La media aritmética La media ponderada La media geométrica La media armónica La mediana La media truncada La moda Veamos como podemos calcularlas con Python : Media aritmética La media aritmética es el valor obtenido al sumar todos los datos y dividir el resultado entre el número total elementos. La calculamos con el método mean . In [9]: # media de variable eruptions faithful [ 'eruptions' ] . mean () Out[9]: 3.4877830882352936 Media ponderada La media ponderada es apropiada cuando en un dataset cada dato tiene una importancia relativa (o peso) respecto de los demás. Como esta media no aplica para nuestro dataset no la vamos a calcular. Media geométrica La media geométrica es útil cuando queremos comparar cosas con propiedades muy diferentes; también es es recomendada para datos de progresión geométrica, para promediar razones, interés compuesto y números índices. Se calcula tomando la raíz n-ésima del producto de todos los datos . La calculamos con la función gmean de SciPy . In [10]: # media geometrica stats . gmean ( faithful [ 'eruptions' ]) Out[10]: 3.2713131325361786 Media armónica La media armónica promedia el número de elementos y los divide por la suma de sus inversos. La media armónica es siempre la media más baja y es recomendada para promediar velocidades. La calculamos con la función hmean de SciPy . In [11]: # media armónica stats . hmean ( faithful [ 'eruptions' ]) Out[11]: 3.0389330499472611 Mediana La mediana representa el valor de posición central en un conjunto de datos ordenados. La podemos calcular utilizando el método median de Pandas In [12]: # mediana faithful [ 'eruptions' ] . median () Out[12]: 4.0 Media truncada La media truncada es una mezcla entre la media aritmética y la mediana . Para calcular el promedio previamente se descartan porciones en el extremo inferior y superior de la distribución de los datos . En Python podemos utilizar la función trim_mean de SciPy . In [13]: # media truncada, recortando el 10 superior e inferior stats . trim_mean ( faithful [ 'eruptions' ], . 10 ) Out[13]: 3.5298073394495413 Moda Por último, la moda es el valor que tiene mayor frecuencia absoluta. Son los picos que vemos en el histograma . Dependiendo de la la distribución de los datos puede existir más de una, como en el caso de la variable eruptions . La calculamos con el método mode . In [14]: # moda faithful [ 'eruptions' ] . mode () Out[14]: 0 1.867 1 4.500 dtype: float64 Medidas de dispersión Las medidas de tendencia central no son las únicas medidas de resumen estadístico que podemos calcular; otras medidas también de gran importancia son las medidas de dispersión . Las medidas de dispersión , también llamadas medidas de variabilidad, muestran la variabilidad de una distribución , indicando por medio de un número si las diferentes puntuaciones de una variable están muy alejadas de la media . Cuanto mayor sea ese valor, mayor será la variabilidad, y cuanto menor sea, más homogénea será a la media . Así se sabe si todos los casos son parecidos o varían mucho entre ellos. Las principales medidas de dispersión son: La varianza El desvío estándar Los cuartiles La covarianza El coeficiente de correlación Analicemos cada uno de ellos: Varianza La varianza intenta describir la dispersión de los datos . Se define como la esperanza del cuadrado de la desviación de dicha variable respecto a su media . Una varianza pequeña indica que los puntos de datos tienden a estar muy cerca de la media y por lo tanto el uno al otro, mientras que una varianza alta indica que los puntos de datos están muy distribuidos alrededor de la media y la una de la otra. La podemos calcular con el método var . In [15]: # varianza faithful [ 'eruptions' ] . var () Out[15]: 1.3027283328494705 Desvío estándar El desvío estándar o desviación típica es una medida que se utiliza para cuantificar la cantidad de variación o dispersión de un conjunto de valores de datos. Un desvío estándar cerca de 0 indica que los puntos de datos tienden a estar muy cerca de la media del conjunto, mientras que un alto desvío estándar indica que los puntos de datos se extienden a lo largo de un rango amplio de valores. Se calcula como la raíz cuadrada de la varianza y con Pandas lo podemos obtener con el método std . In [16]: # desvio estándar faithful [ 'eruptions' ] . std () Out[16]: 1.1413712511052092 Cuartiles Los cuartiles son los tres puntos que dividen el conjunto de datos en cuatro grupos iguales, cada grupo comprende un cuarto de los datos .El (Q1) se define como el número medio entre el número más pequeño y la mediana del conjunto de datos . El segundo cuartil (Q2) es la mediana de los datos . El tercer cuartil (Q3) es el valor medio entre la mediana y el valor más alto del conjunto de datos . Para dividir nuestro dataset en sus cuartiles utilizamos el método quantile . In [17]: # cuartiles faithful [ 'eruptions' ] . quantile ([ . 25 , . 5 , . 75 ]) Out[17]: 0.25 2.16275 0.50 4.00000 0.75 4.45425 dtype: float64 Un gráfico relacionado a los cuartiles y describe varias características importantes al mismo tiempo, tales como la dispersión y simetría es el diagrama de caja . Para su realización se representan los tres cuartiles y los valores mínimo y máximo de los datos, sobre un rectángulo, alineado horizontal o verticalmente. Podemos utilizar la función boxplot de Seaborn para generarlo. In [18]: # diagrama de cajas cajas = sns . boxplot ( list ( faithful [ 'eruptions' ])) Hasta aquí hemos calculado medidas de dispersión para una sola variable, pero nuestro dataset tiene dos variables cuantitativas ; veamos como podemos calcular medidas combinadas para la dos variables. Covarianza La covarianza es el equivalente de la varianza aplicado a una variable bidimensional. Es la media aritmética de los productos de las desviaciones de cada una de las variables respecto a sus medias .La covarianza indica el sentido de la correlación entre las variables; Si es mayor que cero la correlación es directa, en caso de ser menor, la correlación es inversa. La podemos calcular utilizando el método cov . In [19]: # covarianza faithful . cov () Out[19]: eruptions waiting eruptions 1.302728 13.977808 waiting 13.977808 184.823312 Correlación Por último, el coeficiente de correlación es una medida del grado de dependencia lineal entre dos variables. El coeficiente de correlación oscila entre -1 y 1. Un valor de 1 significa que una ecuación lineal describe la relación entre las dos variables a la perfección, con todos los puntos de datos cayendo sobre una línea recta de pendiente positiva. Un valor de -1 implica que todos los puntos de datos se encuentran en una línea con pendiente negativa. Un valor de 0 implica que no existe una correlación lineal entre las variables. Lo podemos calcular con el método corr . In [20]: # coeficiente de correlación faithful . corr () Out[20]: eruptions waiting eruptions 1.000000 0.900811 waiting 0.900811 1.000000 Como podemos ver las dos variables tienen una correlación bastante alta, lo que sugiere que están íntimamente relacionadas; a la misma conclusión habíamos llegado al observar el diagrama de dispersión . Resumen estadístico Hasta aquí hemos calculado tanto las medidas de tendencia central como las medidas de dispersión una por una, pero ¿no sería más conveniente que con un simple comando podemos obtener un resumen estadístico con las principales medidas? Es por esto que Pandas nos ofrece el método describe , un comando para gobernarlos a todos!, el cual nos ofrece un resumen con las principales medidas estadísticas . In [21]: # resumen estadístico faithful [ 'eruptions' ] . describe () Out[21]: count 272.000000 mean 3.487783 std 1.141371 min 1.600000 25% 2.162750 50% 4.000000 75% 4.454250 max 5.100000 Name: eruptions, dtype: float64 Siguiendo la misma línea; Seaborn nos ofrece la función pairplot que nos proporciona un resumen gráfico con histogramas y diagramas de dispersión de las variables de nuestro dataset . In [22]: par = sns . pairplot ( faithful ) Con esto concluye este artículo; ahora ya deberían estar en condiciones de poder analizar tanto variables cuantitativas como variables categóricas . A practicar! Saludos! Este post fue escrito utilizando Jupyter notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Analisis de datos","url":"https://relopezbriega.github.io/blog/2016/03/13/analisis-de-datos-cuantitativos-con-python/"},{"title":"Análisis de datos categóricos con Python","text":"Introducción Cuando trabajamos con estadísticas , es importante reconocer los diferentes tipos de datos : numéricos ( discretos y continuos ), categóricos y ordinales. Los datos no son más que observaciones del mundo en que vivimos, por tanto, los mismos pueden venir en diferentes formas, no solo numérica. Por ejemplo, si le preguntáramos a nuestros amigos ¿cuántas mascotas tienen? nos podrían responder: 0, 1, 2, 4, 3, 8 ; esta información por sí misma puede ser útil, pero para nuestro análisis de mascotas, nos podría servir también otro tipo de información, como por ejemplo el género de cada uno de nuestros amigos; de esta forma obtendríamos la siguiente información: hombre, mujer, mujer, mujer, hombre, mujer . Como vemos, podemos incluir a los datos dentro de tres categorías fundamentales: datos cuantitativos o numéricos, datos cualitativos o categóricos y datos ordinales. Datos cuantitativos Los datos cuantitativos son representados por números; estos números van a ser significativos si representan la medida o la cantidad observada de cierta característica. Dentro de esta categoría podemos encontrar por ejemplo: cantidades de dólares, cuentas, tamaños, número de empleados, y kilómetros por hora. Con los datos cuantitativos , se puede hacer todo tipo de tareas de procesamiento de datos numéricos, tales como sumarlos, calcular promedios, o medir su variabilidad. Asimismo, vamos a poder dividir a los datos cuantitativos en discretos y continuos , dependiendo de los valores potencialmente observables. Los datos discretos solo van a poder asumir un valor de una lista de números específicos. Representan ítems que pueden ser contados ; todos sus posibles valores pueden ser listados. Suele ser relativamente fácil trabajar con este tipo de dato . Los datos continuos representan mediciones ; sus posibles valores no pueden ser contados y sólo pueden ser descritos usando intervalos en la recta de los números reales . Por ejemplo, la cantidad de kilómetros recorridos no puede ser medida con exactitud, puede ser que hayamos recorrido 1.7 km o 1.6987 km; en cualquier medida que tomemos del mundo real, siempre pueden haber pequeñas o grandes variaciones. Generalmente, los datos continuos se suelen redondear a un número fijo de decimales para facilitar su manipulación. Datos cualitativos Si los datos nos dicen en cual de determinadas categorías no numéricas nuestros ítems van a caer, entonces estamos hablando de datos cualitativos o categóricos ; ya que los mismos van a representar determinada cualidad que los ítems poseen. Dentro de esta categoría vamos a encontrar datos como: el sexo de una persona, el estado civil, la ciudad natal, o los tipos de películas que le gustan. Los datos categóricos pueden tomar valores numéricos (por ejemplo, \"1\" para indicar \"masculino\" y \"2\" para indicar \"femenino\"), pero esos números no tienen un sentido matemático. Datos ordinales Una categoría intermedia entre los dos tipos de datos anteriores, son los datos ordinales . En este tipo de datos , va a existir un orden significativo, vamos a poder clasificar un primero, segundo, tercero, etc. es decir, que podemos establecer un ranking para estos datos , el cual posiblemente luego tenga un rol importante en la etapa de análisis. Los datos se dividen en categorías, pero los números colocados en cada categoría tienen un significado. Por ejemplo, la calificación de un restaurante en una escala de 0 (bajo) a 5 (más alta) estrellas representa datos ordinales . Los datos ordinales son a menudo tratados como datos categóricos , en el sentido que se suelen agrupar y ordenar. Sin embargo, a diferencia de los datos categóricos , los números sí tienen un significado matemático. En este artículo me voy a centrar en el segundo grupo, los datos categóricos ; veremos como podemos manipular fácilmente con la ayuda de Python estos datos para poder encontrar patrones , relaciones , tendencias y excepciones . Análisis de datos categóricos con Python Para ejemplificar el análisis, vamos a utilizar nuestras habituales librerías científicas NumPy , Pandas , Matplotlib y Seaborn . También vamos a utilizar la librería pydataset , la cual nos facilita cargar los diferentes dataset para analizar. La idea es realizar un análisis estadístico sobre los datos de los sobrevivientes a la tragedia del Titanic . La tragedia del Titanic El hundimiento del Titanic es uno de los naufragios más infames de la historia. El 15 de abril de 1912, durante su viaje inaugural, el Titanic se hundió después de chocar con un iceberg, matando a miles de personas. Esta tragedia sensacional conmocionó a la comunidad internacional y condujo a mejores normas de seguridad aplicables a los buques. Una de las razones por las que el naufragio dio lugar a semejante cantidad de muertes fue que no había suficientes botes salvavidas para los pasajeros y la tripulación. Aunque hubo algún elemento de suerte involucrada en sobrevivir al hundimiento, algunos grupos de personas tenían más probabilidades de sobrevivir que otros, como las mujeres, los niños y la clase alta. El siguiente dataset proporciona información sobre el destino de los pasajeros en el viaje fatal del trasatlántico Titanic , que se resume de acuerdo con el nivel económico (clase), el sexo, la edad y la supervivencia. In [1]: Ver Código # importando modulos necesarios % matplotlib inline import matplotlib.pyplot as plt import numpy as np import pandas as pd import seaborn as sns from pydataset import data # parametros esteticos de seaborn sns . set_palette ( \"deep\" , desat =. 6 ) sns . set_context ( rc = { \"figure.figsize\" : ( 8 , 4 )}) In [2]: # importando dataset titanic = data ( 'titanic' ) In [3]: # ver primeros 10 registros titanic . head ( 10 ) Out[3]: class age sex survived 1 1st class adults man yes 2 1st class adults man yes 3 1st class adults man yes 4 1st class adults man yes 5 1st class adults man yes 6 1st class adults man yes 7 1st class adults man yes 8 1st class adults man yes 9 1st class adults man yes 10 1st class adults man yes El problema con datos como estos, y en general con la mayoría de las tablas de datos , es que nos presentan mucha información y no nos permiten ver que es lo que realmente sucede o sucedió. Por tanto, deberíamos procesarla de alguna manera para hacernos una imagen de lo que los datos realmente representan y nos quieren decir; y que mejor manera para hacernos una imagen de algo que utilizar visualizaciones . Una buena visualización de los datos puede revelar cosas que es probable que no podamos ver en una tabla de números y nos ayudará a pensar con claridad acerca de los patrones y relaciones que pueden estar escondidos en los datos . También nos va a ayudar a encontrar las características y patrones más importantes o los casos que son realmente excepcionales y no deberíamos de encontrar. Tablas de frecuencia Para hacernos una imagen de los datos , lo primero que tenemos que hacer es agruparlos. Al armar diferentes grupos nos vamos acercando a la comprensión de los datos . La idea es ir amontonamos las cosas que parecen ir juntas, para poder ver como se distribuyen a través de las diferentes categorías. Para los datos categóricos , agrupar es fácil; simplemente debemos contar el número de ítems que corresponden a cada categoría y apilarlos. Una forma en la que podemos agrupar nuestro dataset del Titanic es contando las diferentes clases de pasajeros. Podemos organizar estos conteos en una tabla de frecuencia , que registra los totales y los nombres de las categorías utilizando la función value_counts que nos proporciona Pandas del siguiente modo: In [4]: # tabla de frecuencia de clases de pasajeros pd . value_counts ( titanic [ 'class' ]) Out[4]: 3rd class 706 1st class 325 2nd class 285 dtype: int64 Contar las cantidad de apariciones de cada categoría puede ser útil, pero a veces puede resultar más útil saber la fracción o proporción de los datos de cada categoría, así que podríamos entonces dividir los recuentos por el total de casos para obtener los porcentajes que representa cada categoría. Una tabla de frecuencia relativa muestra los porcentajes, en lugar de los recuentos de los valores en cada categoría. Ambos tipos de tablas muestran cómo los casos se distribuyen a través de las categorías. De esta manera, ellas describen la distribución de una variable categórica , ya que enumeran las posibles categorías y nos dicen con qué frecuencia se produce cada una de ellas. In [5]: # tabla de frecuencia relativa de pasajeros 100 * titanic [ 'class' ] . value_counts () / len ( titanic [ 'class' ]) Out[5]: 3rd class 53.647416 1st class 24.696049 2nd class 21.656535 dtype: float64 Gráficos de tartas y barras Ahora que ya conocemos a las tablas de frecuencia ya estamos en condiciones de crear visualizaciones que realmente nos den una imagen de los datos , sus propiedades y sus relaciones. En este punto, debemos ser sumamente cuidadosos, ya que una mala visualización puede llegar a distorsionar nuestra comprensión, en lugar de ayudarnos. Las mejores visualizaciones de datos siguen un principio fundamental llamado el principio del área . Este principio nos dice que el área ocupada por cada parte del gráfico se debe corresponder con la magnitud del valor que representa. Violaciones del principio de área son una forma común de mentir con estadísticas . Dos gráficos útiles que podemos utilizar para representar nuestros datos y que cumplen con este principio son el gráfico de barras y el gráfico de tarta . Gráfico de barras El gráfico de barras nos ayuda a darnos una impresión visual más precisa de la distribución de nuestros datos . La altura de cada barra muestra el recuento de su categoría. Los barras tienen el mismo ancho, por lo que sus alturas determinan sus áreas, y estas áreas son proporcionales a los recuentos en cada categoría. De esta forma, podemos ver fácilmente que había más del doble de pasajeros de tercera clase, que de primera o segunda clase. Los gráficos de barras hacen que este tipo de comparaciones sean fáciles y naturales. Veamos como podemos crearlos de forma sencilla utilizando el método plot dentro de un DataFrame de Pandas . In [6]: # Gráfico de barras de pasajeros del Titanic plot = titanic [ 'class' ] . value_counts () . plot ( kind = 'bar' , title = 'Pasajeros del Titanic' ) Si quisiéramos enfocarnos en la proporción relativa de los pasajeros de cada una de las clases, simplemente podemos sustituir a los recuentos con porcentajes y utilizar un gráfico de barras de frecuencias relativas . In [7]: # gráfico de barras de frecuencias relativas. plot = ( 100 * titanic [ 'class' ] . value_counts () / len ( titanic [ 'class' ])) . plot ( kind = 'bar' , title = 'Pasajeros del Titanic %' ) Gráfico de tartas El gráfico de tarta muestra el total de casos como un círculo y luego corta este círculo en piezas cuyos tamaños son proporcionales a la fracción que cada categoría representa sobre el total de casos. Los gráfico de tarta dan una impresión rápida de cómo todo un grupo se divide en grupos más pequeños. Lo podríamos graficar del siguiente modo, también utilizando el método plot : In [8]: # Gráfico de tarta de pasajeros del Titanic plot = titanic [ 'class' ] . value_counts () . plot ( kind = 'pie' , autopct = ' %.2f ' , figsize = ( 6 , 6 ), title = 'Pasajeros del Titanic' ) Como se puede apreciar, con el gráfico de tarta no es tan fácil determinar que los pasajeros de tercera clase son más que el doble que los de primera clase; tampoco es fácil determinar si hay más pasajeros de primera o de segunda clase. Para este tipo de comparaciones, son mucho más útiles los gráficos de barras . Relacionando variables categóricas Al analizar la tragedia del Titanic , una de las preguntas que podríamos hacer es ¿existe alguna relación entre la clase de pasajeros y la posibilidad de alcanzar un bote salvavidas y sobrevivir a la tragedia? Para poder responder a esta pregunta, vamos a necesitar analizar a las variables class y survived de nuestro dataset en forma conjunta. Una buena forma de analizar dos variables categóricas en forma conjunta, es agrupar los recuentos en una tabla de doble entrada ; este tipo de tablas se conocen en estadística con el nombre de tabla de contingencia . Veamos como podemos crear esta tabla utilizando la función crosstab de Pandas . In [9]: # Tabla de contingencia class / survived pd . crosstab ( index = titanic [ 'survived' ], columns = titanic [ 'class' ], margins = True ) Out[9]: class 1st class 2nd class 3rd class All survived no 122 167 528 817 yes 203 118 178 499 All 325 285 706 1316 Los márgenes de la tabla, tanto en la derecha y en la parte inferior, nos muestran los totales. La línea inferior de la tabla representa la distribución de frecuencia de la clase de pasajeros. La columna derecha de la tabla es la distribución de frecuencia de la variable supervivencia. Cuando se presenta la información de este modo, cada celda de cada uno de los márgenes de la tabla representa la distribución marginal de esa variable en particular. Cada celda nos va a mostrar el recuento para la combinación de los valores de nuestras dos variables categóricas , en este caso class y survived . Al igual de como habíamos visto con las tablas de frecuencia , también nos podría ser útil representar a las tablas de contingencia con porcentajes relativos; esto lo podríamos realizar utilizando el método apply del siguiente modo: In [10]: # tabla de contingencia en porcentajes relativos total pd . crosstab ( index = titanic [ 'survived' ], columns = titanic [ 'class' ], margins = True ) . apply ( lambda r : r / len ( titanic ) * 100 , axis = 1 ) Out[10]: class 1st class 2nd class 3rd class All survived no 9.270517 12.689970 40.121581 62.082067 yes 15.425532 8.966565 13.525836 37.917933 All 24.696049 21.656535 53.647416 100.000000 Con esta tabla podemos ver fácilmente que solo el 37.91% de los pasajeros sobrevivió a la tragedia y que este 37% se compone de la siguiente forma: del total de pasajeros sobrevivió un 15.42% de pasajeros que eran de primera clase, un 8.97% que eran de segunda clase y un 13.52% que eran pasajeros de tercera clase. Volviendo a nuestra pregunta inicial sobre la posibilidad de sobrevivir según la clase de pasajero, podría ser más útil armar la tabla de porcentajes como un porcentaje relativo sobre el total de cada fila, es decir calcular el porcentaje relativo que cada clase tiene sobre haber sobrevivido o no. Esto lo podemos realizar del siguiente modo: In [11]: # tabla de contingencia en porcentajes relativos segun sobreviviente pd . crosstab ( index = titanic [ 'survived' ], columns = titanic [ 'class' ] ) . apply ( lambda r : r / r . sum () * 100 , axis = 1 ) Out[11]: class 1st class 2nd class 3rd class survived no 14.932681 20.440636 64.626683 yes 40.681363 23.647295 35.671343 Aquí podemos ver que de los pasajeros que sobrevivieron a la tragedia, el 40.68% correspondían a primera clase, el 35.67% a tercera clase y el 23.65% a segunda clase. Por tanto podríamos inferir que los pasajeros de primera clase tenían más posibilidades de sobrevivir. Es más, también podríamos armar la tabla de porcentaje relativos en relación al total de cada clase de pasajero y así podríamos ver que de los pasajeros de primera clase, logró sobrevivir un 62.46%. In [12]: # tabla de contingencia en porcentajes relativos segun clase pd . crosstab ( index = titanic [ 'survived' ], columns = titanic [ 'class' ] ) . apply ( lambda r : r / r . sum () * 100 , axis = 0 ) Out[12]: class 1st class 2nd class 3rd class survived no 37.538462 58.596491 74.787535 yes 62.461538 41.403509 25.212465 Este último resultado lo podríamos representar visualmente con simples gráfico de barras del siguiente modo: In [13]: # Gráfico de barras de sobreviviviente segun clase plot = pd . crosstab ( index = titanic [ 'class' ], columns = titanic [ 'survived' ]) . apply ( lambda r : r / r . sum () * 100 , axis = 1 ) . plot ( kind = 'bar' ) In [14]: # Gráfico de barras de sobreviviviente segun clase plot = pd . crosstab ( index = titanic [ 'survived' ], columns = titanic [ 'class' ] ) . apply ( lambda r : r / r . sum () * 100 , axis = 0 ) . plot ( kind = 'bar' , stacked = True ) Estas mismas manipulaciones las podemos realizar para otro tipo de combinación de variables categóricas , como podría ser el sexo o la edad de los pasajeros, pero eso ya se los dejo a ustedes para que se entretengan y practiquen un rato. Con este termina esta artículo, si les gustó y están interesados en la estadísticas , no duden en visitar mi anterior artículo Probabilidad y Estadística con Python y seguir la novedades del blog! Saludos! Este post fue escrito utilizando Jupyter notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Analisis de datos","url":"https://relopezbriega.github.io/blog/2016/02/29/analisis-de-datos-categoricos-con-python/"},{"title":"Más Álgebra lineal con Python","text":"Introducción El Álgebra lineal constituye la base de gran parte de las matemáticas modernas, ya sea en su fase teórica, aplicada, o computacional. Es un área activa que tiene conexiones con muchas áreas dentro y fuera de las matemáticas, como ser: el análisis funcional , las ecuaciones diferenciales , la investigación operativa , la econometría y la ingeniería . Es por esto, que se vuelve sumamente importante conocer sus métodos en profundidad. La idea de este artículo, es profundizar alguno de los temas que ya vimos en mi artículo anterior ( Álgebra lineal con Python ), presentar algunos nuevos, e ilustrar la utilidad de esta rama de la matemáticas con alguna de sus aplicaciones. Campos Un Campo , $F$, es una estructura algebraica en la cual las operaciones de adición y multiplicación se pueden realizar y cumplen con las siguientes propiedades: La propiedad conmutativa tanto para la adición como para la multiplicación ; es decir: $a + b = b + a$; y $a \\cdot b = b \\cdot a$; para todo $a, b \\in F$ La propiedad asociativa , tanto para la adición como para la multiplicación ; es decir: $(a + b) + c = a + (b + c)$; y $(a \\cdot b) \\cdot c = a \\cdot (b \\cdot c)$; para todo $a, b, c \\in F$ La propiedad distributiva de la multiplicación sobre la adición ; es decir: $a \\cdot (b + c) = a \\cdot b + a \\cdot c$; para todo $a, b, c \\in F$ La existencia de un elemento neutro tanto para la adición como para la multiplicación ; es decir: $a + 0 = a$; y $a \\cdot 1 = a$; para todo $a \\in F$. La existencia de un elemento inverso tanto para la adición como para la multiplicación ; es decir: $a + (-a) = 0$; y $a \\cdot a^{-1} = 1$; para todo $a \\in F$ y $a \\ne 0$. Dos de los Campos más comunes con los que nos vamos a encontrar al trabajar en problemas de Álgebra lineal , van a ser el conjunto de los números reales , $\\mathbb{R}$; y el conjunto de los números complejos , $\\mathbb{C}$. Vectores Muchas nociones físicas, tales como las fuerzas, velocidades y aceleraciones, involucran una magnitud (el valor de la fuerza, velocidad o aceleración) y una dirección. Cualquier entidad que involucre magnitud y dirección se llama vector . Los vectores se representan por flechas en las que la longitud de ellas define la magnitud; y la dirección de la flecha representa la dirección del vector . Podemos pensar en los vectores como una serie de números. Éstos números tienen una orden preestablecido, y podemos identificar cada número individual por su índice en ese orden. Los vectores identifican puntos en el espacio, en donde cada elemento representa una coordenada del eje en el espacio. La típica forma de representarlos es la siguiente: $$v = \\left[ \\begin{array}{c} x_1 \\\\ x_2 \\\\ \\vdots \\\\ x_n \\end{array} \\right]$$ Geométricamente podemos representarlos del siguiente modo en el plano de 2 dimensiones: In [1]: Ver Código # importando modulos necesarios % matplotlib inline import matplotlib.pyplot as plt import numpy as np import scipy.sparse as sp import scipy.sparse.linalg import scipy.linalg as la import sympy # imprimir con notación matemática. sympy . init_printing ( use_latex = 'mathjax' ) In [2]: Ver Código # graficando vector en R^2 [2, 4] def move_spines (): \"\"\"Crea la figura de pyplot y los ejes. Mueve las lineas de la izquierda y de abajo para que se intersecten con el origen. Elimina las lineas de la derecha y la de arriba. Devuelve los ejes.\"\"\" fix , ax = plt . subplots () for spine in [ \"left\" , \"bottom\" ]: ax . spines [ spine ] . set_position ( \"zero\" ) for spine in [ \"right\" , \"top\" ]: ax . spines [ spine ] . set_color ( \"none\" ) return ax def vect_fig ( vector , color ): \"\"\"Genera el grafico de los vectores en el plano\"\"\" v = vector ax . annotate ( \" \" , xy = v , xytext = [ 0 , 0 ], color = color , arrowprops = dict ( facecolor = color , shrink = 0 , alpha = 0.7 , width = 0.5 )) ax . text ( 1.1 * v [ 0 ], 1.1 * v [ 1 ], v ) ax = move_spines () ax . set_xlim ( - 5 , 5 ) ax . set_ylim ( - 5 , 5 ) ax . grid () vect_fig ([ 2 , 4 ], \"blue\" ) Combinaciones lineales Cuando trabajamos con vectores , nos vamos a encontrar con dos operaciones fundamentales, la suma o adición ; y la multiplicación por escalares . Cuando sumamos dos vectores $v$ y $w$, sumamos elemento por elemento, del siguiente modo: $$v + w = \\left[ \\begin{array}{c} v_1 \\\\ v_2 \\\\ \\vdots \\\\ v_n \\end{array} \\right] + \\left[ \\begin{array}{c} w_1 \\\\ w_2 \\\\ \\vdots \\\\ w_n \\end{array} \\right] = \\left[ \\begin{array}{c} v_1 + w_1 \\\\ v_2 + w_2 \\\\ \\vdots \\\\ v_n + w_n \\end{array} \\right]$$ Geométricamente lo podemos ver representado del siguiente modo: In [3]: Ver Código # graficando suma de vectores en R^2 # [2, 4] + [2, -2] ax = move_spines () ax . set_xlim ( - 5 , 5 ) ax . set_ylim ( - 5 , 5 ) ax . grid () vecs = [[ 2 , 4 ], [ 2 , - 2 ]] # lista de vectores for v in vecs : vect_fig ( v , \"blue\" ) v = np . array ([ 2 , 4 ]) + np . array ([ 2 , - 2 ]) vect_fig ( v , \"red\" ) ax . plot ([ 2 , 4 ], [ - 2 , 2 ], linestyle = '--' ) a = ax . plot ([ 2 , 4 ], [ 4 , 2 ], linestyle = '--' ) Cuando multiplicamos vectores por escalares , lo que hacemos es tomar un número $\\alpha$ y un vector $v$; y creamos un nuevo vector $w$ en el cada elemento de $v$ es multiplicado por $\\alpha$ del siguiente modo: $$\\begin{split}\\alpha v = \\left[ \\begin{array}{c} \\alpha v_1 \\\\ \\alpha v_2 \\\\ \\vdots \\\\ \\alpha v_n \\end{array} \\right]\\end{split}$$ Geométricamente podemos representar a esta operación en el plano de 2 dimensiones del siguiente modo: In [4]: Ver Código # graficando multiplicación por escalares en R^2 # [2, 3] * 2 ax = move_spines () ax . set_xlim ( - 6 , 6 ) ax . set_ylim ( - 6 , 6 ) ax . grid () v = np . array ([ 2 , 3 ]) vect_fig ( v , \"blue\" ) v = v * 2 vect_fig ( v , \"red\" ) Cuando combinamos estas dos operaciones, formamos lo que se conoce en Álgebra lineal como combinaciones lineales . Es decir que una combinación lineal va a ser una expresión matemática construida sobre un conjunto de vectores , en el que cada vector es multiplicado por un escalar y los resultados son luego sumados . Matemáticamente lo podemos expresar de la siguiente forma: $$w = \\alpha_1 v_1 + \\alpha_2 v_2 + \\dots + \\alpha_n v_n = \\sum_{i=1}^n \\alpha_i v_i $$ en donde, $v_n$ son vectores y $\\alpha_n$ son escalares . Matrices, combinaciones lineales y Ax = b Una matriz es un arreglo bidimensional de números ordenados en filas y columnas, donde una fila es cada una de las líneas horizontales de la matriz y una columna es cada una de las líneas verticales. En una matriz cada elemento puede ser identificado utilizando dos índices, uno para la fila y otro para la columna en que se encuentra. Las podemos representar de la siguiente manera: $$A=\\begin{bmatrix}a_{11} & a_{12} & \\dots & a_{1n}\\\\a_{21} & a_{22} & \\dots & a_{2n} \\\\ \\vdots & \\vdots & \\ddots & \\vdots \\\\ a_{n1} & a_{n2} & \\dots & a_{nn}\\end{bmatrix}$$ Las matrices se utilizan para múltiples aplicaciones y sirven, en particular, para representar los coeficientes de los sistemas de ecuaciones lineales o para representar combinaciones lineales . Supongamos que tenemos los siguientes 3 vectores: $$x_1 = \\left[ \\begin{array}{c} 1 \\\\ -1 \\\\ 0 \\end{array} \\right] \\ x_2 = \\left[ \\begin{array}{c} 0 \\\\ 1 \\\\ -1 \\end{array} \\right] \\ x_3 = \\left[ \\begin{array}{c} 0 \\\\ 0 \\\\ 1 \\end{array} \\right]$$ su combinación lineal en el espacio de 3 dimensiones va a ser igual a $\\alpha_1 x_1 + \\alpha_2 x_2 + \\alpha_3 x_3$; lo que es lo mismo que decir: $$\\alpha_1 \\left[ \\begin{array}{c} 1 \\\\ -1 \\\\ 0 \\end{array} \\right] + \\alpha_2 \\left[ \\begin{array}{c} 0 \\\\ 1 \\\\ -1 \\end{array} \\right] + \\alpha_3 \\left[ \\begin{array}{c} 0 \\\\ 0 \\\\ 1 \\end{array} \\right] = \\left[ \\begin{array}{c} \\alpha_1 \\\\ \\alpha_2 - \\alpha_1 \\\\ \\alpha_3 - \\alpha_2 \\end{array} \\right]$$ Ahora esta combinación lineal la podríamos reescribir en forma matricial. Los vectores $x_1, x_2$ y $x_3$, pasarían a formar las columnas de la matriz $A$ y los escalares $\\alpha_1, \\alpha_2$ y $\\alpha_3$ pasarían a ser los componentes del vector $x$ del siguiente modo: $$\\begin{bmatrix}1 & 0 & 0\\\\-1 & 1 & 0 \\\\ 0 & -1 & 1\\end{bmatrix}\\begin{bmatrix} \\alpha_1 \\\\ \\alpha_2 \\\\ \\alpha_3\\end{bmatrix}= \\begin{bmatrix}\\alpha_1 \\\\ \\alpha_2 - \\alpha_1 \\\\ \\alpha_3 - \\alpha_2 \\end{bmatrix}$$ De esta forma la matriz $A$ multiplicada por el vector $x$, nos da como resultado la misma combinación lineal $b$. De esta forma, arribamos a una de las ecuaciones más fundamentales del Álgebra lineal : $$Ax = b$$ Esta ecuación no solo nos va a servir para expresar combinaciones lineales , sino que también se vuelve de suma importancia a la hora de resolver sistemas de ecuaciones lineales , en dónde $b$ va a ser conocido y la incógnita pasa a ser $x$. Por ejemplo, supongamos que queremos resolver el siguiente sistemas de ecuaciones de 3 incógnitas: $$ 2x_1 + 3x_2 + 5x_3 = 52 \\\\ 3x_1 + 6x_2 + 2x_3 = 61 \\\\ 8x_1 + 3x_2 + 6x_3 = 75 $$ Podemos ayudarnos de SymPy para expresar a la matriz $A$ y $b$ para luego arribar a la solución del vector $x$. In [5]: # Resolviendo sistema de ecuaciones con SymPy A = sympy . Matrix (( ( 2 , 3 , 5 ), ( 3 , 6 , 2 ), ( 8 , 3 , 6 ) )) A Out[5]: $$\\left[\\begin{matrix}2 & 3 & 5\\\\3 & 6 & 2\\\\8 & 3 & 6\\end{matrix}\\right]$$ In [6]: b = sympy . Matrix ( 3 , 1 ,( 52 , 61 , 75 )) b Out[6]: $$\\left[\\begin{matrix}52\\\\61\\\\75\\end{matrix}\\right]$$ In [7]: # Resolviendo Ax = b x = A . LUsolve ( b ) x Out[7]: $$\\left[\\begin{matrix}3\\\\7\\\\5\\end{matrix}\\right]$$ In [8]: # Comprobando la solución A * x Out[8]: $$\\left[\\begin{matrix}52\\\\61\\\\75\\end{matrix}\\right]$$ La matriz identidad , la matriz transpuesta y la matriz invertible Tres matrices de suma importancia en problemas de Álgebra lineal . Son la matriz identidad , la matriz transpuesta y la matriz invertible . La matriz identidad es el elemento neutro en la multiplicación de matrices , es el equivalente al número 1. Cualquier matriz multiplicada por la matriz identidad nos da como resultado la misma matriz . La matriz identidad es una matriz cuadrada (tiene siempre el mismo número de filas que de columnas); y su diagonal principal se compone de todos elementos 1 y el resto de los elementos se completan con 0. Suele representase con la letra $I$. Por ejemplo la matriz identidad de 3x3 sería la siguiente: $$I=\\begin{bmatrix}1 & 0 & 0 & \\\\0 & 1 & 0\\\\ 0 & 0 & 1\\end{bmatrix}$$ La matriz transpuesta de una matriz $A$ de $m \\times n$ va a ser igual a la matriz $n \\times m$ $A^T$, la cual se obtiene al transformar las filas en columnas y las columnas en filas, del siguiente modo: $$\\begin{bmatrix}a & b & \\\\c & d & \\\\ e & f & \\end{bmatrix}^T= \\begin{bmatrix}a & c & e &\\\\b & d & f & \\end{bmatrix}$$ Una matriz cuadrada va a ser simétrica si $A^T = A$, es decir si $A$ es igual a su propia matriz transpuesta . Algunas de las propiedades de las matrices transpuestas son: a. $(A^T)^T = A$ b. $(A + B)^T = A^T + B^T$ c. $k(A)^T = k(A^T)$ d. $(AB)^T = B^T A^T$ e. $(A^r)^T = (A^T)^r$ para todos los $r$ no negativos. f. Si $A$ es una matriz cuadrada , entonces $A + A^T$ es una matriz simétrica . g. Para cualquier matriz $A$, $A A^T$ y $A^T A$ son matrices simétricas . Veamos algunos ejemplos en Python In [9]: # Matriz transpuesta A = sympy . Matrix ( [[ 2 , - 3 , - 8 , 7 ], [ - 2 , - 1 , 2 , - 7 ], [ 1 , 0 , - 3 , 6 ]] ) A Out[9]: $$\\left[\\begin{matrix}2 & -3 & -8 & 7\\\\-2 & -1 & 2 & -7\\\\1 & 0 & -3 & 6\\end{matrix}\\right]$$ In [10]: A . transpose () Out[10]: $$\\left[\\begin{matrix}2 & -2 & 1\\\\-3 & -1 & 0\\\\-8 & 2 & -3\\\\7 & -7 & 6\\end{matrix}\\right]$$ In [11]: # transpuesta de transpuesta vuelve a A. A . transpose () . transpose () Out[11]: $$\\left[\\begin{matrix}2 & -3 & -8 & 7\\\\-2 & -1 & 2 & -7\\\\1 & 0 & -3 & 6\\end{matrix}\\right]$$ In [12]: # creando matriz simetrica As = A * A . transpose () As Out[12]: $$\\left[\\begin{matrix}126 & -66 & 68\\\\-66 & 58 & -50\\\\68 & -50 & 46\\end{matrix}\\right]$$ In [13]: # comprobando simetria. As . transpose () Out[13]: $$\\left[\\begin{matrix}126 & -66 & 68\\\\-66 & 58 & -50\\\\68 & -50 & 46\\end{matrix}\\right]$$ La matriz invertible es muy importante, ya que esta relacionada con la ecuación $Ax = b$. Si tenemos una matriz cuadrada $A$ de $n \\times n$, entonces la matriz inversa de $A$ es una matriz $A'$ o $A^{-1}$ de $n \\times n$ que hace que la multiplicación $A A^{-1}$ sea igual a la matriz identidad $I$. Es decir que es la matriz recíproca de $A$. $A A^{-1} = I$ o $A^{-1} A = I$ En caso de que estas condiciones se cumplan, decimos que la matriz es invertible . Que una matriz sea invertible tiene importantes implicaciones, como ser: a. Si $A$ es una matriz invertible , entonces su matriz inversa es única. b. Si $A$ es una matriz invertible de $n \\times n$, entonces el sistemas de ecuaciones lineales dado por $Ax = b$ tiene una única solución $x = A^{-1}b$ para cualquier $b$ en $\\mathbb{R}^n$. c. Una matriz va a ser invertible si y solo si su determinante es distinto de cero. En el caso de que el determinante sea cero se dice que la matriz es singular. d. Si $A$ es una matriz invertible , entonces el sistema $Ax = 0$ solo tiene una solución trivial . Es decir, en las que todas las incógnitas son ceros. e. Si $A$ es una matriz invertible , entonces su forma escalonada va a ser igual a la matriz identidad . f. Si $A$ es una matriz invertible , entonces $A^{-1}$ es invertible y: $$(A^{-1})^{-1} = A$$ g. Si $A$ es una matriz invertible y $\\alpha$ es un escalar distinto de cero, entonces $\\alpha A$ es invertible y: $$(\\alpha A)^{-1} = \\frac{1}{\\alpha}A^{-1}$$ . h. Si $A$ y $B$ son matrices invertibles del mismo tamaño, entonces $AB$ es invertible y: $$(AB)^{-1} = B^{-1} A^{-1}$$ . i. Si $A$ es una matriz invertible , entonces $A^T$ es invertible y: $$(A^T)^{-1} = (A^{-1})^T$$ . Con SymPy podemos trabajar con las matrices invertibles del siguiente modo: In [14]: # Matriz invertible A = sympy . Matrix ( [[ 1 , 2 ], [ 3 , 9 ]] ) A Out[14]: $$\\left[\\begin{matrix}1 & 2\\\\3 & 9\\end{matrix}\\right]$$ In [15]: A_inv = A . inv () A_inv Out[15]: $$\\left[\\begin{matrix}3 & - \\frac{2}{3}\\\\-1 & \\frac{1}{3}\\end{matrix}\\right]$$ In [16]: # A * A_inv = I A * A_inv Out[16]: $$\\left[\\begin{matrix}1 & 0\\\\0 & 1\\end{matrix}\\right]$$ In [17]: # forma escalonada igual a indentidad. A . rref () Out[17]: $$\\left ( \\left[\\begin{matrix}1 & 0\\\\0 & 1\\end{matrix}\\right], \\quad \\left ( 0, \\quad 1\\right )\\right )$$ In [18]: # la inversa de A_inv es A A_inv . inv () Out[18]: $$\\left[\\begin{matrix}1 & 2\\\\3 & 9\\end{matrix}\\right]$$ Espacios vectoriales Las Matemáticas derivan su poder en gran medida de su capacidad para encontrar las características comunes de los diversos problemas y estudiarlos de manera abstracta. Existen muchos problemas que implican los conceptos relacionados de adición , multiplicación por escalares , y la linealidad . Para estudiar estas propiedades de manera abstracta, debemos introducir la noción de espacio vectorial . Para alcanzar la definición de un espacio vectorial , debemos combinar los conceptos que venimos viendo hasta ahora de Campo , vector y las operaciones de adición ; y multiplicación por escalares . De esta forma un espacio vectorial , $V$, sobre un Campo , $F$, va a ser un conjunto en el que están definidas las operaciones de adición y multiplicación por escalares , tal que para cualquier par de elementos $x$ e $y$ en $V$, existe un elemento único $x + y$ en $V$, y para cada elemento $\\alpha$ en $F$ y cada elemento $x$ en $V$, exista un único elemento $\\alpha x$ en $V$, de manera que se cumplan las siguientes condiciones: Para todo $x, y$ en $V$, $x + y = y + x$ ( conmutatividad de la adición). Para todo $x, y, z$ en $V$, $(x + y) + z = x + (y + z)$. ( asociatividad de la adición). Existe un elemento en $V$ llamado $0$ tal que $x + 0 = x$ para todo $x$ en $V$. Para cada elemento $x$ en $V$, existe un elemento $y$ en $V$ tal que $x + y = 0$. Para cada elemento $x$ en $V$, $1 x = x$. Para cada par, $\\alpha, \\beta$ en $F$ y cada elemento $x$ en $V$, $(\\alpha \\beta) x = \\alpha (\\beta x)$. Para cada elemento $\\alpha$ en $F$ y cada para de elementos $x, y$ en $V$, $\\alpha(x + y) = \\alpha x + \\alpha y$. Para cada par de elementos $\\alpha, \\beta$ en $F$ y cada elemento $x$ en $V$, $(\\alpha + \\beta)x = \\alpha x + \\beta x$. Los espacios vectoriales más comunes son $\\mathbb{R}^2$, el cual representa el plano de 2 dimensiones y consiste de todos los pares ordenados de los números reales : $$\\mathbb{R}^2 = \\{(x, y): x, y \\in \\mathbb{R}\\}$$ y $\\mathbb{R}^3$, que representa el espacio ordinario de 3 dimensiones y consiste en todos los tríos ordenados de los números reales : $$\\mathbb{R}^3 = \\{(x, y, z): x, y, z \\in \\mathbb{R}\\}$$ Una de las grandes bellezas del Álgebra lineal es que podemos fácilmente pasar a trabajar sobre espacios de $n$ dimensiones, $\\mathbb{R}^n$! Tampoco tenemos porque quedarnos con solo los números reales , ya que la definición que dimos de un espacio vectorial reside sobre un Campo ; y los campos pueden estar representados por números complejos . Por tanto también podemos tener espacios vectoriales $\\mathbb{C}^2, \\mathbb{C}^3, \\dots, \\mathbb{C}^n$. Subespacios Normalmente, en el estudio de cualquier estructura algebraica es interesante examinar subconjuntos que tengan la misma estructura que el conjunto que esta siendo considerado. Así, dentro de los espacios vectoriales , podemos tener subespacios vectoriales , los cuales son un subconjunto que cumplen con las mismas propiedades que el espacio vectorial que los contiene. De esta forma, $\\mathbb{R}^3$ representa un subespacio del espacio vectorial $\\mathbb{R}^n$. Independencia lineal La independencia lineal es un concepto aparentemente simple con consecuencias que se extienden profundamente en muchos aspectos del análisis. Si deseamos entender cuando una matriz puede ser invertible , o cuando un sistema de ecuaciones lineales tiene una única solución, o cuando una estimación por mínimos cuadrados se define de forma única, la idea fundamental más importante es la de independencia lineal de vectores . Dado un conjunto finito de vectores $x_1, x_2, \\dots, x_n$ se dice que los mismos son linealmente independientes , si y solo si, los únicos escalares $\\alpha_1, \\alpha_2, \\dots, \\alpha_n$ que satisfacen la ecuación: $$\\alpha_1 x_1 + \\alpha_2 x_2 + \\dots + \\alpha_n x_n = 0$$ son todos ceros, $\\alpha_1 = \\alpha_2 = \\dots = \\alpha_n = 0$. En caso de que esto no se cumpla, es decir, que existe una solución a la ecuación de arriba en que no todos los escalares son ceros, a esta solución se la llama no trivial y se dice que los vectores son linealmente dependientes . Para ilustrar la definición y que quede más clara, veamos algunos ejemplos. Supongamos que queremos determinar si los siguientes vectores son linealmente independientes : $$\\begin{split}x_1 = \\left[ \\begin{array}{c} 1.2 \\\\ 1.1 \\\\ \\end{array} \\right] \\ \\ \\ x_2 = \\left[ \\begin{array}{c} -2.2 \\\\ 1.4 \\\\ \\end{array} \\right]\\end{split}$$ Para lograr esto, deberíamos resolver el siguiente sistema de ecuaciones y verificar si la única solución es aquella en que los escalares sean ceros. $$\\begin{split}\\alpha_1 \\left[ \\begin{array}{c} 1.2 \\\\ 1.1 \\\\ \\end{array} \\right] + \\alpha_2 \\left[ \\begin{array}{c} -2.2 \\\\ 1.4 \\\\ \\end{array} \\right]\\end{split} = 0 $$ Para resolver este sistema de ecuaciones , podemos recurrir a la ayuda de Python . In [19]: # Resolviendo el sistema de ecuaciones. A = np . array ([[ 1.2 , - 2.2 ], [ 1.1 , 1.4 ]]) b = np . array ([ 0. , 0. ]) x = np . linalg . solve ( A , b ) x Out[19]: array([0., 0.]) In [20]: Ver Código # Solución gráfica. x_vals = np . linspace ( - 5 , 5 , 50 ) # crea 50 valores entre 0 y 5 ax = move_spines () ax . set_xlim ( - 5 , 5 ) ax . set_ylim ( - 5 , 5 ) ax . grid () ax . plot ( x_vals , ( 1.2 * x_vals ) / - 2.2 ) # grafica 1.2x_1 - 2.2x_2 = 0 a = ax . plot ( x_vals , ( 1.1 * x_vals ) / 1.4 ) # grafica 1.1x + 1.4x_2 = 0 Como podemos ver, tanto por la solución numérica como por la solución gráfica, estos vectores son linealmente independientes , ya que la única solución a la ecuación $\\alpha_1 x_1 + \\alpha_2 x_2 + \\dots + \\alpha_n x_n = 0$, es aquella en que los escalares son cero. Determinemos ahora si por ejemplo, los siguientes vectores en $\\mathbb{R}^4$ son linealmente independientes : $\\{(3, 2, 2, 3), (3, 2, 1, 2), (3, 2, 0, 1)\\}$. Aquí, ahora deberíamos resolver la siguiente ecuación: $$\\alpha_1 (3, 2, 2, 3) +\\alpha_2 (3, 2, 1, 2) + \\alpha_3 (3, 2, 0, 1) = (0, 0, 0, 0)$$ Para resolver este sistema de ecuaciones que no es cuadrado (tiene 4 ecuaciones y solo 3 incógnitas); podemos utilizar SymPy . In [21]: # Sympy para resolver el sistema de ecuaciones lineales a1 , a2 , a3 = sympy . symbols ( 'a1, a2, a3' ) A = sympy . Matrix (( ( 3 , 3 , 3 , 0 ), ( 2 , 2 , 2 , 0 ), ( 2 , 1 , 0 , 0 ), ( 3 , 2 , 1 , 0 ) )) A Out[21]: $$\\left[\\begin{matrix}3 & 3 & 3 & 0\\\\2 & 2 & 2 & 0\\\\2 & 1 & 0 & 0\\\\3 & 2 & 1 & 0\\end{matrix}\\right]$$ In [22]: sympy . solve_linear_system ( A , a1 , a2 , a3 ) Out[22]: $$\\left \\{ a_{1} : a_{3}, \\quad a_{2} : - 2 a_{3}\\right \\}$$ Como vemos, esta solución es no trivial , ya que por ejemplo existe la solución $\\alpha_1 = 1, \\ \\alpha_2 = -2 , \\ \\alpha_3 = 1$ en la que los escalares no son ceros. Por lo tanto este sistema es linealmente dependiente . Por último, podríamos considerar si los siguientes polinomios son linealmente independientes : $1 -2x -x^2$, $1 + x$, $1 + x + 2x^2$. En este caso, deberíamos resolver la siguiente ecuación: $$\\alpha_1 (1 − 2x − x^2) + \\alpha_2 (1 + x) + \\alpha_3 (1 + x + 2x^2) = 0$$ y esta ecuación es equivalente a la siguiente: $$(\\alpha_1 + \\alpha_2 + \\alpha_3 ) + (−2 \\alpha_1 + \\alpha_2 + \\alpha_3 )x + (−\\alpha_1 + 2 \\alpha_2 )x^2 = 0$$ Por lo tanto, podemos armar el siguiente sistema de ecuaciones : $$\\alpha_1 + \\alpha_2 + \\alpha_3 = 0, \\\\ -2 \\alpha_1 + \\alpha_2 + \\alpha_3 = 0, \\\\ -\\alpha_1 + 2 \\alpha_2 = 0. $$ El cual podemos nuevamente resolver con la ayuda de SymPy . In [23]: A = sympy . Matrix (( ( 1 , 1 , 1 , 0 ), ( - 2 , 1 , 1 , 0 ), ( - 1 , 2 , 0 , 0 ) )) A Out[23]: $$\\left[\\begin{matrix}1 & 1 & 1 & 0\\\\-2 & 1 & 1 & 0\\\\-1 & 2 & 0 & 0\\end{matrix}\\right]$$ In [24]: sympy . solve_linear_system ( A , a1 , a2 , a3 ) Out[24]: $$\\left \\{ a_{1} : 0, \\quad a_{2} : 0, \\quad a_{3} : 0\\right \\}$$ Como vemos, todos los escalares son ceros, por lo tanto estos polinomios son linealmente independientes . Espacio nulo, espacio columna y espacio fila Un termino particularmente relacionado con la independencia lineal es el de espacio nulo o núcleo . El espacio nulo de una matriz $A$, el cual lo vamos a expresar como $N(A)$, va a consistir de todas las soluciones a la ecuación fundamental $Ax = 0$. Por supuesto, una solución inmediata a esta ecuación es el caso de $x = 0$, que ya vimos que establece la independencia lineal . Esta solución solo va a ser la única que exista para los casos de matrices invertibles . Pero en el caso de las matrices singulares (aquellas que no son invertibles , que tienen determinante igual a cero), van a existir soluciones que no son cero para la ecuación $Ax = 0$. El conjunto de todas estas soluciones, va a representar el espacio nulo . Para encontrar el espacio nulo también nos podemos ayudar de SymPy . In [25]: # Espacio nulo de un matriz A = sympy . Matrix ((( 1 , 5 , 7 ), ( 0 , 0 , 9 ))) A Out[25]: $$\\left[\\begin{matrix}1 & 5 & 7\\\\0 & 0 & 9\\end{matrix}\\right]$$ In [26]: # Calculando el espacio nulo x = A . nullspace () x Out[26]: $$\\left [ \\left[\\begin{matrix}-5\\\\1\\\\0\\end{matrix}\\right]\\right ]$$ In [27]: # Comprobando la solución A_aum = sympy . Matrix ((( 1 , 5 , 7 , 0 ), ( 0 , 0 , 9 , 0 ))) sympy . solve_linear_system ( A_aum , a1 , a2 , a3 ) Out[27]: $$\\left \\{ a_{1} : - 5 a_{2}, \\quad a_{3} : 0\\right \\}$$ In [28]: # Comprobación con numpy A = np . array ([[ 1 , 5 , 7 ], [ 0 , 0 , 9 ]]) x = np . array ([[ - 5 ], [ 1 ], [ 0 ]]) A . dot ( x ) Out[28]: array([[0], [0]]) Otro espacio de suma importancia es el espacio columna . El espacio columna , $C(A)$, consiste en todas las combinaciones lineales de las columnas de una matriz $A$. Estas combinaciones son los posibles vectores $Ax$. Este espacio es fundamental para resolver la ecuación $Ax = b$; ya que para resolver esta ecuación debemos expresar a $b$ como una combinación de columnas. El sistema $Ax = b$, va a tener solución solamente si $b$ esta en el espacio columna de $A$. Como las matrices tienen la forma $m \\times n$, sus columnas tienen $m$ componentes ($n$ son las filas). Por lo tanto el espacio columna es un subespacio de $\\mathbb{R}^m$ y no $\\mathbb{R}^n$. Por último, el otro espacio que conforma los espacios fundamentales de una matriz , es el espacio fila , el cual esta constituido por las combinaciones lineales de las filas de una matriz . Para obtener estos espacios, nuevamente podemos recurrir a SymPy . Para poder obtener estos espacios, primero vamos a tener que obtener la forma escalonada de la matriz , la cual es la forma a la que arribamos luego del proceso de eliminación . In [29]: # A.rref() forma escalonada. A = sympy . Matrix ( [[ 2 , - 3 , - 8 , 7 ], [ - 2 , - 1 , 2 , - 7 ], [ 1 , 0 , - 3 , 6 ]]) A . rref () # [0, 1, 2] es la ubicación de las pivot. Out[29]: $$\\left ( \\left[\\begin{matrix}1 & 0 & 0 & 0\\\\0 & 1 & 0 & 3\\\\0 & 0 & 1 & -2\\end{matrix}\\right], \\quad \\left ( 0, \\quad 1, \\quad 2\\right )\\right )$$ In [30]: # Espacio columna [ A [:, c ] for c in A . rref ()[ 1 ] ] Out[30]: $$\\left [ \\left[\\begin{matrix}2\\\\-2\\\\1\\end{matrix}\\right], \\quad \\left[\\begin{matrix}-3\\\\-1\\\\0\\end{matrix}\\right], \\quad \\left[\\begin{matrix}-8\\\\2\\\\-3\\end{matrix}\\right]\\right ]$$ In [31]: # Espacio fila [ A . rref ()[ 0 ][ r ,:] for r in A . rref ()[ 1 ] ] Out[31]: $$\\left [ \\left[\\begin{matrix}1 & 0 & 0 & 0\\end{matrix}\\right], \\quad \\left[\\begin{matrix}0 & 1 & 0 & 3\\end{matrix}\\right], \\quad \\left[\\begin{matrix}0 & 0 & 1 & -2\\end{matrix}\\right]\\right ]$$ Rango Otro concepto que también esta ligado a la independencia lineal es el de rango . Los números de columnas $m$ y filas $n$ pueden darnos el tamaño de una matriz , pero esto no necesariamente representa el verdadero tamaño del sistema lineal , ya que por ejemplo si existen dos filas iguales en una matriz $A$, la segunda fila desaparecía en el proceso de eliminación . El verdadero tamaño de $A$ va a estar dado por su rango . El rango de una matriz es el número máximo de columnas (filas respectivamente) que son linealmente independientes . Por ejemplo si tenemos la siguiente matriz de 3 x 4: $$A = \\begin{bmatrix}1 & 1 & 2 & 4\\\\1 & 2 & 2 & 5 \\\\ 1 & 3 & 2 & 6\\end{bmatrix}$$ Podemos ver que la tercer columna $(2, 2, 2)$ es un múltiplo de la primera y que la cuarta columna $(4, 5, 6)$ es la suma de las primeras 3 columnas. Por tanto el rango de $A$ va a ser igual a 2; ya que la tercer y cuarta columna pueden ser eliminadas. Obviamente, el rango también lo podemos calcular con la ayuda de Python . In [32]: # Calculando el rango con SymPy A = sympy . Matrix ([[ 1 , 1 , 2 , 4 ], [ 1 , 2 , 2 , 5 ], [ 1 , 3 , 2 , 6 ]]) A Out[32]: $$\\left[\\begin{matrix}1 & 1 & 2 & 4\\\\1 & 2 & 2 & 5\\\\1 & 3 & 2 & 6\\end{matrix}\\right]$$ In [33]: # Rango con SymPy A . rank () Out[33]: $$2$$ In [34]: # Rango con numpy A = np . array ([[ 1 , 1 , 2 , 4 ], [ 1 , 2 , 2 , 5 ], [ 1 , 3 , 2 , 6 ]]) np . linalg . matrix_rank ( A ) Out[34]: 2 Una útil aplicación de calcular el rango de una matriz es la de determinar el número de soluciones al sistema de ecuaciones lineales , de acuerdo al enunciado del Teorema de Rouché–Frobenius . El sistema tiene por lo menos una solución si el rango de la matriz de coeficientes equivale al rango de la matriz aumentada . En ese caso, ésta tiene exactamente una solución si el rango equivale al número de incógnitas. La norma y la Ortogonalidad Si quisiéramos saber cual es el largo del un vector , lo único que necesitamos es el famoso teorema de Pitágoras . En el plano $\\mathbb{R}^2$, el largo de un vector $v=\\begin{bmatrix}a \\\\ b \\end{bmatrix}$ va a ser igual a la distancia desde el origen $(0, 0)$ hasta el punto $(a, b)$. Esta distancia puede ser fácilmente calculada gracias al teorema de Pitágoras y va ser igual a $\\sqrt{a^2 + b^2}$, como se puede ver en la siguiente figura: In [35]: Ver Código # Calculando largo de un vector # forma un triángulo rectángulo ax = move_spines () ax . set_xlim ( - 6 , 6 ) ax . set_ylim ( - 6 , 6 ) ax . grid () v = np . array ([ 4 , 6 ]) vect_fig ( v , \"blue\" ) a = ax . vlines ( x = v [ 0 ], ymin = 0 , ymax = 6 , linestyle = '--' , color = 'g' ) En esta definición podemos observar que $a^2 + b^2 = v \\cdot v$, por lo que ya estamos en condiciones de poder definir lo que en Álgebra lineal se conoce como norma . El largo o norma de un vector $v = \\begin{bmatrix} v_1 \\\\ v_2 \\\\ \\vdots \\\\ v_n \\end{bmatrix}$, en $\\mathbb{R}^n$ va a ser igual a un número no negativo $||v||$ definido por: $$||v|| = \\sqrt{v \\cdot v} = \\sqrt{v_1^2 + v_2^2 + \\dots + v_n^2}$$ Es decir que la norma de un vector va a ser igual a la raíz cuadrada de la suma de los cuadrados de sus componentes. Ortogonalidad El concepto de perpendicularidad es fundamental en geometría . Este concepto llevado a los vectores en $\\mathbb{R}^n$ se llama ortogonalidad . Dos vectores $v$ y $w$ en $\\mathbb{R}^n$ van a ser ortogonales el uno al otro si su producto interior es igual a cero. Es decir, $v \\cdot w = 0$. Geométricamente lo podemos ver de la siguiente manera: In [36]: Ver Código # Vectores ortogonales ax = move_spines () ax . set_xlim ( - 6 , 6 ) ax . set_ylim ( - 6 , 6 ) ax . grid () vecs = [ np . array ([ 4 , 6 ]), np . array ([ - 3 , 2 ])] for v in vecs : vect_fig ( v , \"blue\" ) a = ax . plot ([ - 3 , 4 ], [ 2 , 6 ], linestyle = '--' , color = 'g' ) In [37]: # comprobando su producto interior. v = np . array ([ 4 , 6 ]) w = np . array ([ - 3 , 2 ]) v . dot ( w ) Out[37]: 0 Un conjunto de vectores en $\\mathbb{R}^n$ va a ser ortogonal si todo los pares de los distintos vectores en el conjunto son ortogonales entre sí. O sea: $v_i \\cdot v_j = 0$ para todo $i, j = 1, 2, \\dots, k$ y donde $i \\ne j$. Por ejemplo, si tenemos el siguiente conjunto de vectores en $\\mathbb{R}^3$: $$v1 = \\begin{bmatrix} 2 \\\\ 1 \\\\ -1\\end{bmatrix} \\ v2 = \\begin{bmatrix} 0 \\\\ 1 \\\\ 1\\end{bmatrix} \\ v3 = \\begin{bmatrix} 1 \\\\ -1 \\\\ 1\\end{bmatrix}$$ En este caso, deberíamos combrobar que: $$v1 \\cdot v2 = 0 \\\\ v2 \\cdot v3 = 0 \\\\ v1 \\cdot v3 = 0 $$ In [38]: # comprobando ortogonalidad del conjunto v1 = np . array ([ 2 , 1 , - 1 ]) v2 = np . array ([ 0 , 1 , 1 ]) v3 = np . array ([ 1 , - 1 , 1 ]) v1 . dot ( v2 ), v2 . dot ( v3 ), v1 . dot ( v3 ) Out[38]: (0, 0, 0) Como vemos, este conjunto es ortogonal . Una de las principales ventajas de trabajar con conjuntos de vectores ortogonales es que los mismos son necesariamente linealmente independientes . El concepto de ortogonalidad es uno de los más importantes y útiles en Álgebra lineal y surge en muchas situaciones prácticas, sobre todo cuando queremos calcular distancias. Determinante El determinante es un número especial que puede calcularse sobre las matrices cuadradas . Este número nos va a decir muchas cosas sobre la matriz . Por ejemplo, nos va decir si la matriz es invertible o no. Si el determinante es igual a cero, la matriz no es invertible . Cuando la matriz es invertible , el determinante de $A^{-1}= 1/(\\det \\ A)$. El determinante también puede ser útil para calcular áreas. Para obtener el determinante de una matriz debemos calcular la suma de los productos de las diagonales de la matriz en una dirección menos la suma de los productos de las diagonales en la otra dirección. Se represente con el símbolo $|A|$ o $\\det A$. Algunas de sus propiedades que debemos tener en cuenta son: a. El determinante de la matriz identidad es igual a 1. $\\det I = 1$. b. Una matriz $A$ es singular (no tiene inversa ) si su determinante es igual a cero. c. El determinante cambia de signo cuando dos columnas(o filas) son intercambiadas. d. Si dos filas de una matriz $A$ son iguales, entonces el determinante es cero. e. Si alguna fila de la matriz $A$ son todos ceros, entonces el determinante es cero. f. La matriz transpuesta $A^T$, tiene el mismo determinante que $A$. g. El determinante de $AB$ es igual al determinante de $A$ multiplicado por el determinante de $B$. $\\det (AB) = \\det A \\cdot \\det B$. h. El determinante es una función lineal de cada una de las filas en forma separada. Si multiplicamos solo una fila por $\\alpha$, entonces el determinante también es multiplicado por $\\alpha$. Veamos como podemos obtener el determinante con la ayuda de Python In [39]: # Determinante con sympy A = sympy . Matrix ( [[ 1 , 2 , 3 ], [ 2 , - 2 , 4 ], [ 2 , 2 , 5 ]] ) A . det () Out[39]: $$2$$ In [40]: # Determinante con numpy A = np . array ([[ 1 , 2 , 3 ], [ 2 , - 2 , 4 ], [ 2 , 2 , 5 ]] ) np . linalg . det ( A ) Out[40]: $$1.9999999999999998$$ In [41]: # Determinante como funcion lineal de fila A [ 0 ] = A [ 0 : 1 ] * 5 np . linalg . det ( A ) Out[41]: $$9.999999999999998$$ In [42]: # cambio de signo de determinante A = sympy . Matrix ( [[ 2 , - 2 , 4 ], [ 1 , 2 , 3 ], [ 2 , 2 , 5 ]] ) A . det () Out[42]: $$-2$$ Eigenvalores y Eigenvectores Cuando estamos resolviendo ecuaciones lineales del tipo $Ax = b$, estamos trabajando con problemas estáticos . ¿Pero qué pasa si quisiéramos trabajar con problemas dinámicos ?. Es en este tipo de situaciones donde los Eigenvalores y Eigenvectores tienen su mayor importancia. Supongamos que tenemos una matriz cuadrada $A$ de $n \\times n$. Una pregunta natural que nos podríamos hacer sobre $A$ es: ¿Existe algún vector $x$ distinto de cero para el cual $Ax$ es un escalar múltiplo de $x$?. Si llevamos esta pregunta al lenguaje matemático nos vamos a encontrar con la siguiente ecuación: $$Ax = \\lambda x$$ Cuando esta ecuación es válida y $x$ no es cero, decimos que $\\lambda$ es el Eigenvalor o valor propio de $A$ y $x$ es su correspondiente Eigenvector o vector propio . Muchos problemas en ciencia derivan en problemas de Eigenvalores , en los cuales la principal pregunta es: ¿Cuáles son los Eigenvalores de una matriz dada, y cuáles son sus correspondientes Eigenvectores . Un área donde nos va a ser de mucha utilidad esta teoría, es en problemas con sistemas de ecuaciones diferenciales lineales . Calculando Eigenvalores Hasta aquí todo muy bien, pero dada una matriz cuadrada $A$ de $n \\times n$, ¿cómo podemos obtener sus Eigenvalores ?. Podemos comenzar por observar que la ecuación $Ax = \\lambda x$ es equivalente a $(A - \\lambda I)x = 0$. Dado que estamos interesados en soluciones a esta ecuación que sean distintas de cero, la matriz $A - \\lambda I$ debe ser singular , no invertible , por lo tanto su determinante debe ser cero, $\\det (A - \\lambda I) = 0$. De esta forma, podemos utilizar esta ecuación para encontrar los Eigenvalores de $A$. Particularmente, podríamos formar el polinomio característico de la matriz $A$, el cual va a tener grado $n$ y por lo tanto va a tener $n$ soluciones, es decir que vamos a encontrar $n$ Eigenvalores . Algo que debemos tener en cuenta es, que a pesar de que la matriz $A$ sea real , debemos estar preparados para encontrar Eigenvalores que sean complejos . Para que quede más claro, veamos un ejemplo de como podemos calcular los Eigenvalores . Supongamos que tenemos la siguiente matriz : $$A = \\begin{bmatrix} 3 & 2 \\\\ 7 & -2 \\end{bmatrix}$$ Su polinomio característico va a ser igual a: $$p(\\lambda) = \\det (A - \\lambda I) = \\det \\begin{bmatrix}3 - \\lambda & 2 \\\\ 7 & -2-\\lambda\\end{bmatrix} = (3 - \\lambda)(-2-\\lambda) - 14 \\\\ =\\lambda^2 - \\lambda - 20 = (\\lambda - 5) (\\lambda + 4)$$ Por lo tanto los Eigenvalores de $A$ van a ser $5$ y $-4$. Obviamente, también los podemos obtener mucho más fácilmente con la ayuda de Python . In [43]: # Eigenvalores con numpy A = np . array ([[ 3 , 2 ], [ 7 , - 2 ]]) x , v = np . linalg . eig ( A ) # x Eigenvalor, v Eigenvector x , v Out[43]: (array([ 5., -4.]), array([[ 0.70710678, -0.27472113], [ 0.70710678, 0.96152395]])) In [44]: # Eigenvalores con SymPy A = sympy . Matrix ([[ 3 , 2 ], [ 7 , - 2 ]]) # Eigenvalor A . eigenvals () Out[44]: $$\\left \\{ -4 : 1, \\quad 5 : 1\\right \\}$$ In [45]: # Eigenvector A . eigenvects () Out[45]: $$\\left [ \\left ( -4, \\quad 1, \\quad \\left [ \\left[\\begin{matrix}- \\frac{2}{7}\\\\1\\end{matrix}\\right]\\right ]\\right ), \\quad \\left ( 5, \\quad 1, \\quad \\left [ \\left[\\begin{matrix}1\\\\1\\end{matrix}\\right]\\right ]\\right )\\right ]$$ In [46]: # comprobando la solución Ax = λx # x eigenvector, v eigenvalue x = A . eigenvects ()[ 0 ][ 2 ][ 0 ] v = A . eigenvects ()[ 0 ][ 0 ] # Ax == vx A * x , v * x Out[46]: $$\\left ( \\left[\\begin{matrix}\\frac{8}{7}\\\\-4\\end{matrix}\\right], \\quad \\left[\\begin{matrix}\\frac{8}{7}\\\\-4\\end{matrix}\\right]\\right )$$ Con esto termino con este recorrido por los principales conceptos del Álgebra lineal , muchos de los cuales veremos en próximos artículos que tienen muchas aplicaciones interesantes. Espero que les sea de utilidad y les sirva de referencia. Saludos! Este post fue escrito utilizando Jupyter notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Algebra","url":"https://relopezbriega.github.io/blog/2016/02/10/mas-algebra-lineal-con-python/"},{"title":"Ecuaciones en derivadas parciales con Python","text":"Introducción Nuestra comprensión de los procesos fundamentales de la naturaleza se basa en gran medida en Ecuaciones en derivadas parciales . Ejemplos de ello son las vibraciones de los sólidos, la dinámica de los fluidos, la difusión de los productos químicos, la propagación del calor, la estructura de las moléculas, las interacciones entre fotones y electrones, y la radiación de ondas electromagnéticas. Las Ecuaciones en derivadas parciales también juegan un papel central en las matemáticas modernas, especialmente en la geometría y el análisis ; lo que las convierte en una herramienta de suma utilidad que debemos conocer. Nota : Este artículo corresponde a la tercer entrega de mi serie de artículos sobre Cálculo con Python ; los anteriores fueron: Introducción al Cálculo y Ecuaciones Diferenciales con Python , los cuales es recomendable haber leído previamente. ¿Qué es una ecuación en derivadas parciales? Una Ecuación en derivadas parciales es una ecuación que, como su nombre lo indica, contiene derivadas parciales . A diferencia de lo que habíamos visto con las ecuaciones diferenciales ordinarias , en donde la función incógnita depende solo de una variable ; en las Ecuaciones en derivadas parciales , o EDP para abreviar, la función incógnita va a depender de dos o más variables independientes $x, y, \\dots$. Generalmente a la función incógnita la vamos a expresar como $u(x, y, \\dots)$ y a sus derivadas parciales como $\\partial u / \\partial x = u_x$ o $\\partial u / \\partial y = u_y$ dependiendo de sobre que variable estemos derivando. Entonces una Ecuación en derivadas parciales va a ser la identidad que relaciona a las variables independientes ($x, y, \\dots$), con la variable dependiente $u$ (nuestra función incógnita), y las derivadas parciales de $u$. Lo podríamos expresar de la siguiente forma: $$F(x, y, u(x, y), u_x(x, y), u_y(x, y)) = F(x, y, u, u_x, u_y) = 0$$ Al igual de como pasaba con las ecuaciones diferenciales ordinarias , el orden de una Ecuación en derivadas parciales va a estar dado por la mayor derivada presente. Por lo tanto, el caso que expresamos más arriba corresponde a una EDP , con dos variables independientes , de primer orden . Si quisiéramos expresar una EDP de segundo orden , podríamos hacerlo de la siguiente manera: $$F(x, y, u, u_x, u_y, u_{xx}, u_{xy}, u_{yy})=0.$$ Clasificación de ecuaciones en derivadas parciales La clasificación de las Ecuaciones en derivadas parciales va a ser algo fundamental, ya que la teoría y los métodos para poder solucionarlas van a depender de la clase de ecuación con la que estemos tratando. Las clasificaciones más importantes que debemos tener en cuenta, son: 1- El orden de la EDP : Como ya mencionamos, el mismo va a estar dado por el orden de la mayor derivada presente. 2- Número de variables: Esta clasificación va a estar dada por la cantidad de variables independientes que contenga la EDP . 3- Linealidad: Esta es una de las clasificaciones más importantes, vamos a poder clasificar a las Ecuaciones en derivadas parciales en lineales o no lineales . En las lineales , la variable dependiente $u$ y todas sus derivadas, van a aparecer en una forma lineal , es decir, que van a tener grado uno (no van a estar elevadas al cuadrado, o multiplicadas entre sí). Más precisamente, una Ecuación en derivadas parciales de segundo orden con dos variables, va a tomar la siguiente forma: $$Au_{xx} + Bu_{xy} + Cu_{yy} + Du_x + Eu_y + Fu = G$$ en donde $A, B, C, D, E, F,$ y $G$ pueden ser constantes o una función dada de $x$ e $y$. Por ejemplo: $u_{tt} = e^tu_{xx} + \\sin t$, sería una ecuación lineal . $uu_{xx} + u_y = 0$, sería una ecuación no lineal . $u_{xx} + yu_{yy} + u_x = 0$, sería una ecuación lineal . $xu_x + yu_y + u^2 = 0$, sería una ecuación no lineal . Tipos de ecuaciones lineales: Asimismo, a las Ecuaciones en derivadas parciales lineales de segundo orden las vamos a poder subdividir en las siguientes categorías: Ecuaciones parabólicas : Las cuales van a describir el flujo del calor y el proceso de difusión . Éstas ecuaciones van a satisfacer la condición $B^2 -4AC = 0$. Ecuaciones hiperbólicas : Las cuales describen los sistemas de vibración y los movimientos de ondas . Satisfacen la condición $B^2 -4AC > 0$. Ecuaciones Elípticas : Las cuales describen los fenómenos de estados estacionarios y satisfacen la condición $B^2 -4AC < 0$. 4- Homogeneidad: Otra clasificación que podemos utilizar, es la de homogeneidad. Una ecuación va a ser homogénea , si el lado derecho de la ecuación, $G(x, y)$, es idénticamente cero para todo $x$ e $y$. En caso contrario, se la llama no homogénea . 5- Tipos de coeficientes: Por último, podemos clasificar a las EDP de acuerdo a sus coeficientes $A, B, C, D, E, $ y $F$, si los mismos son constantes , se dice que la ecuación es de coeficientes constantes , en caso contrario será de coeficientes variables . ¿Cómo resolver ecuaciones en derivadas parciales? Existen varios métodos que podemos utilizar para intentar resolver las Ecuaciones en derivadas parciales , la principal idea detrás la mayoría de estos métodos es la transformar a estas ecuaciones en ecuaciones diferenciales ordinarias ( EDO ), o en alguna ecuación algebraica ; las cuales son más sencillas de resolver. Algunos los métodos que podemos utilizar son: 1- El método de separación de variables : Este método es uno de los más importantes y más productivos a la hora de resolver a las Ecuaciones en derivadas parciales . La idea es reducir a la EDP de $n$ variables, en $n$ EDOs . 2- El método de la transformada integral : Este método es similar al que ya vimos al resolver EDOs . La idea es aplicar una transformada integral para reducir una EDP de $n$ variables, en otra de $n - 1$ variables. De esta forma una EDP de 2 variables, puede ser transformada en una EDO . 3- El método del cambio de coordenadas : Este método intenta cambiar a la EDP original en una EDO o en otra EDP más sencilla de resolver por medio del cambio de coordenadas del problema. 4- El método de perturbación : Este método aplica la teoría perturbacional para intentar cambiar un problema de EDP no lineal en una serie de problemas lineales que se aproximan al no lineal original. 5- El método de expansión de autofunciones : Este método intentan encontrar la solución de una EDP como una suma infinita de autofunciones . Estas autofunciones son halladas por medio de la resolución del problema del valor propio que corresponde al problema original. 6- El método de las ecuaciones integrales : Este método convierte a la EDP en una ecuación integral ; la cual luego es resuelta aplicando ciertas técnicas particulares que se aplican a ese tipo ecuaciones. 7- Métodos numéricos : La mayoría de las técnicas para resolver numéricamente a las EDP se basan en la idea de discretizar el problema en cada variable independiente que se produce en la EDP , y de esta forma, reformular el problema en una forma algebraica. Esto usualmente resulta en problemas de álgebra lineal de gran escala. Dos de las técnicas principales para reformular las EDP a una forma algebraica son, los métodos de diferencias finitas (MDF), donde las derivadas del problema son aproximadas por medio de la fórmula de diferencias finitas ; y los métodos de los elementos finitos (MEF), en donde la función incógnita se escribe como combinación lineal de funciones de base simple que pueden ser derivadas e integradas fácilmente. En muchos casos, estos métodos numéricos van a ser las únicas herramientas que podamos utilizar para resolver a las EDP . Solución de ecuaciones en derivadas parciales básicas Como resolver este tipo de ecuaciones es una tarea realmente complicada, vamos a empezar por resolver analíticamente las más fáciles para ganar confianza y así luego poder pasar a ecuaciones más complicadas. La más simple de las Ecuaciones en derivadas parciales que nos podemos encontrar es: $u_x = 0$, en donde $u = u(x, y)$. Esta ecuación nos dice que la derivada parcial de $u$ con respecto a $x$ es cero, lo que significa que $u$ no depende de $x$. Por lo tanto, la solución de esta ecuación va a ser $u=f(y)$, en donde $f$ es una función arbitraria de una variable. Por ejemplo, $u = y^2 - y$ podría ser una posible solución. Subiendo un poco más la complejidad, podemos pasar a una EDP de segundo orden, como la siguiente: $u_{xx} = 0$ En este caso, podemos integrar una vez $u_{xx}$ para obtener $u_x(x, y) = f(y)$. Si volvemos a integrar este resultado, podemos arribar a la solución final $u(x, y) = f(y)x + g(y)$, en donde $f$ y $g$ son dos funciones arbitrarias. Por último, si quisiéramos resolver la siguiente EDP : $u_{xy} = 0$ Primero integramos con respecto a $x$ tomando a $y$ como fija, de esta forma obtenemos $u_y(x, y) = f(y)$. Luego podemos integrar con respecto a $y$ tomando a $x$ como fija, y llegamos a la solución: $u(x, y) = F(y) + g(x)$, en donde $F' = f$ Como podemos ver de estos ejemplos, las soluciones analíticas de las EDP dependen de funciones arbitrarias (en lugar de constantes arbitrarias como era el caso de las EDO ). Por lo tanto vamos a necesitar condiciones auxiliares para poder determinar una única solución. La condición inicial y la condición de frontera Al igual que nos pasaba cuando vimos las ecuaciones diferenciales ordinarias ; las EDP pueden tener muchas soluciones, pero a nosotros nos va a interesar encontrar la solución para un caso particular; para lograr esto, debemos imponer unas condiciones auxiliares al problema original. Estas condiciones van a estar motivadas por la Física del problema que estemos analizando y pueden llevar a ser de dos tipos diferentes: condiciones iniciales y condiciones de frontera . La condición inicial va a establecer el estado del problema al momento de tiempo cero, $t_0$. Por ejemplo para el problema de difusión , la condición inicial va a ser: $$u(x, t_0) = \\phi(x)$$ donde $\\phi(x)= \\phi(x, y, z)$ es una función que puede representar el estado de concentración inicial. Para el problema del flujo del calor , $\\phi(x)$ va a representar la temperatura inicial. La condición de frontera nos va a delimitar el dominio en el que nuestra EDP es válida. Así por ejemplo, volviendo al problema de difusión , el dominio en el que nuestra EDP es válida, puede estar delimitado por la superficie del objeto que contiene al líquido. Existen varios tipos de condiciones de frontera , de las cuales las más importantes son: La condición de frontera de Dirichlet , en dónde los valores válidos de la función incógnita $u$ son especificados. La condición de frontera de Neumann , en donde los valores válidos especificados son dados para alguna de las derivadas de $u$. La condición de frontera de Robin , en donde los valores válidos son especificados por una combinación lineal de una función y las derivadas de $u$. Interpretación geométrica de EDP de primer orden Las EDP de primer orden poseen una interpretación geométrica la cual nos puede facilitar alcanzar una solución general para ellas. Coeficientes constantes Tomemos la siguiente ecuación de coeficientes constantes: $au_x + bu_y = 0$, en donde $a$ y $b$ son constantes y ambas no pueden ser cero. En esta ecuación la cantidad $au_x + bu_y$ es la derivada direccional de $u$ en la dirección del vector $V = (a, b) = ai + bj$. Como esta cantidad tiene que ser cero, esto significa que $u (x, y)$ debe ser constante en la dirección de $V$. El vector $(b, -a)$ es ortogonal a $V$, por lo tanto, las líneas paralelas a $V$ (ver el gráfico más abajo) tienen las ecuaciones $bx - ay$ constantes. (Se las llama las líneas características .) Entonces, la solución es constante en cada una de esas líneas. Por lo tanto, $u (x, y)$ depende de $bx - ay$ solamente. De esta forma podemos llegar a la solución general para este tipo de ecuaciones, que va a ser: $u(x, y) = f(bx - ay)$, en donde $f$ es una función arbitraria de una variable. Entonces, si por ejemplo, quisiéramos resolver la EDP , $4u_x - 3u_y= 0$, con la condición de frontera que $u(0, y) = y^3$. Podemos aplicar la solución general que obtuvimos arriba y llegar al resultado $u(x, y) = f(-3x - 4y)$. Ahora, solo nos faltaría aplicar la condición para poder determinar cual es la función arbitraria $f$. Si sustituimos $x=0$ en nuestra solución, obtenemos $y^3 = f(-4y)$. Si decimos que $z = -4y$, entonces nuestra función es $f(z) = -z^3 / 64$. Por lo tanto la solución de nuestra EDP que satisface la condición de frontera es $u(x, y) = (3x + 4y)^3 / 64$. Coeficientes variables Consideremos ahora la siguiente EDP de coeficientes variables: $u_x + yu_y = 0$ Esta ecuación es similar a la que vimos anteriormente, con la diferencia de que ahora tenemos al coeficiente variable $y$. Utilizando la misma intuición geométrica que usamos antes, podemos ver que aquí también la derivada direccional de $u$ en el vector $v=(1, y)$ es constante, pero esta vez el vector no es constante, sino que es variable con $y$. Las curvas que tienen a $v$ como su vector tangente, tienen pendiente $y/1$, es decir: $\\frac{dy}{dx} = \\frac{y}{1}$. Podemos resolver esta ecuación como una EDO y así obtener: $y = Ce^x$, o lo que es lo mismo $e^{-x}y = C$. La solución de nuestra EDP entonces va a ser constante en estas curvas características (ver gráfico); y van a responder a la siguiente solución general: $u(x, y) = f(e^{-x}y)$, en donde $f$ es una función arbitraria. Resolviendo ecuaciones en derivadas parciales con Python Es tiempo de nuevamente recurrir a nuestros queridos paquetes científicos de Python , NumPy , Matplotlib , SymPy y SciPy para ayudarnos a resolver las Ecuaciones en derivadas parciales . Así como en el caso de las Ecuaciones diferenciales ordinarias vimos que existía dentro del paquete SymPy , el solucionador genérico sympy.dsolve ; para el caso de las EDP , vamos a tener al solucionador sympy.pdsolve . Aunque en este caso, es mucho más limitado que su versión para EDO ; ya que solo vamos a poder resolver EDP de primer orden. Veamos como funciona: In [1]: Ver Código # importando modulos necesarios % matplotlib inline import matplotlib.pyplot as plt import matplotlib as mpl import numpy as np import sympy # imprimir con notación matemática. sympy . init_printing ( use_latex = 'mathjax' ) In [2]: # Defino las variables x , y , u , z = sympy . symbols ( 'x y u z' ) f = sympy . Function ( 'f' ) In [3]: # Defino la EDP 4u_x - 3u_y = 0 u = f ( x , y ) u_x = u . diff ( x ) u_y = u . diff ( y ) eq = sympy . Eq ( 4 * u_x - 3 * u_y ) eq Out[3]: $$4 \\frac{\\partial}{\\partial x} f{\\left (x,y \\right )} - 3 \\frac{\\partial}{\\partial y} f{\\left (x,y \\right )} = 0$$ In [4]: # Resuelvo la ecuación sympy . pdsolve ( eq ) Out[4]: $$f{\\left (x,y \\right )} = F{\\left (- 3 x - 4 y \\right )}$$ In [5]: # Defino la EDP u_x + yu_y = 0 u = f ( x , y ) u_x = u . diff ( x ) u_y = u . diff ( y ) eq2 = sympy . Eq ( u_x + y * u_y ) eq2 Out[5]: $$y \\frac{\\partial}{\\partial y} f{\\left (x,y \\right )} + \\frac{\\partial}{\\partial x} f{\\left (x,y \\right )} = 0$$ In [6]: # Resuelvo la ecuación sympy . pdsolve ( eq2 ) Out[6]: $$f{\\left (x,y \\right )} = F{\\left (y e^{- x} \\right )}$$ In [7]: # Calsificación de EDP. sympy . classify_pde ( eq2 ) Out[7]: ('1st_linear_variable_coeff',) Como podemos comprobar, obtuvimos los mismos resultados utilizando sympy.pdsolve que en nuestro análisis manual con la interpretación geométrica. Otra limitación que vamos a tener al trabajar con sympy.pdsolve es que no podemos aplicar nuestras condiciones auxiliares al problema, por lo que para despejar la función arbitraria, deberíamos hacer un trabajo manual. Asimismo, SymPy también nos ofrece la función classify_pde la cual nos ayuda a saber con que tipo de EDP estamos tratando. (Recordemos que pdsolve solo puede resolver EDPs de primer orden). Separación de variables Otra forma en que nos podemos ayudar de SymPy para resolver EDPs , es utilizando el método de separación de variables . La característica esencial de esta técnica es transformar la EDP en un conjunto de EDOs (las cuales podemos solucionar con la ayuda de SymPy ). De esta forma, la solución requerida de la EDP se expresa como un producto $u (x, y) = X (x) Y (y) \\ne 0$, o como una suma $u (x, y) = X (x) + Y (y)$, donde $X (x)$ e $Y (y)$ son funciones de $x$ e $y$, respectivamente. Muchos de los problemas significativos en ecuaciones en derivadas parciales pueden ser resueltos por este método. Para ilustrar como funciona esta técnica, veamos un ejemplo. Vamos a resolver la siguiente EDP . $y^2u_x^2 + x^2u_y^2 = (xyu)^2$, que cumple con la condición $u(x, 0) = 3e^{x^2/4}$. Podemos entonces asumir que $u(x, y) = f(x) g(y) \\ne 0$ es una solución separable de ella; por tanto lo reemplazamos en la ecuación para obtener: $$y^2(f'(x) g(y))^2 + x^2(f(x) g'(y))^2 = x^2 y^2 (f(x) g(y))^2$$ lo que es equivalente a decir; $$\\frac{1}{x^2}\\left(\\frac{f'(x)}{f(x)}\\right)^2 + \\frac{1}{y^2}\\left(\\frac{g'(y)}{g(y)}\\right)^2 = 1$$ Luego, ayudándonos de la constante de separación $\\lambda^2$, podemos separar a esta ecuación en dos EDOs , del siguiente modo; primero igualamos la ecuación anterior a $\\lambda^2$: $$\\frac{1}{x^2}\\left(\\frac{f'(x)}{f(x)}\\right)^2 = 1 - \\frac{1}{y^2}\\left(\\frac{g'(y)}{g(y)}\\right)^2 = \\lambda^2$$ y luego separamos ambas ecuaciones para obtener: $$\\frac{1}{x}\\frac{f'(x)}{f(x)} = \\lambda \\\\ \\frac{g'(x)}{yg(y)}= \\sqrt{1 - \\lambda^2}$$ Ahora podemos utilizar sympy.dsolve para resolver ambas EDOs : In [8]: # EDO n° 1 edo1 = sympy . Eq (( 1 / x ) * ( f ( x ) . diff ( x ) / f ( x )) - z ) edo1 Out[8]: $$- z + \\frac{\\frac{d}{d x} f{\\left (x \\right )}}{x f{\\left (x \\right )}} = 0$$ In [9]: # Resolviendo EDO n° 1 sympy . dsolve ( edo1 ) Out[9]: $$f{\\left (x \\right )} = C_{1} e^{\\frac{x^{2} z}{2}}$$ In [10]: # EDO n° 2 edo2 = sympy . Eq (( f ( y ) . diff ( y )) / ( y * f ( y )) - sympy . sqrt ( 1 - z ** 2 )) edo2 Out[10]: $$- \\sqrt{- z^{2} + 1} + \\frac{\\frac{d}{d y} f{\\left (y \\right )}}{y f{\\left (y \\right )}} = 0$$ In [11]: # Resolviendo EDO n° 2 sympy . dsolve ( edo2 ) Out[11]: $$\\left [ f{\\left (y \\right )} = C_{1} e^{- \\frac{1}{2} \\sqrt{y^{4} \\left(- z^{2} + 1\\right)}}, \\quad f{\\left (y \\right )} = C_{1} e^{\\frac{1}{2} \\sqrt{y^{4} \\left(- z^{2} + 1\\right)}}\\right ]$$ Entonces ahora podemos utilizar estos resultados para armar la solución final a nuestra EDP original, el cual va a ser: $$u(x, y) = C e^{\\frac{\\lambda}{2}x^2 + \\frac{1}{2}y^2\\sqrt{1 - \\lambda^2}}$$ En donde $C = C_1 C_2$ es una constante arbitraria . Por último, utilizando la condición $u(x, 0) = 3e^{x^2/4}$, podemos despejar tanto a $C$ ($C=3$) como a $\\lambda$ ($\\lambda = 1/2$) y arribar a la solución final: $$u(x, y) = 3 e^{\\frac{1}{4}\\left(x^2 + y^2\\sqrt{3}\\right)}$$ Si bien debemos realizar un trabajo manual previo, aun así SymPy sigue siendo de gran ayuda para facilitarnos llegar a la solución final. Métodos numéricos - Método de Elementos Finitos Por último, para cerrar este artículo, veamos como podemos aplicar el métodos de los elementos finitos (MEF) con Python . Para esto nos vamos ayudar de la librería FEniCS , la cual es un framework para resolver numéricamente problemas generales de EDP utilizando el métodos de los elementos finitos . Para instalar esta librería en Ubuntu , pueden utilizar los siguientes comandos: sudo add-apt-repository ppa:fenics-packages/fenics sudo apt-get update sudo apt-get install fenics Deben tener en cuenta que por ahora solo funciona con Python 2 . La interfaz principal que vamos a utilizar para trabajar con este framework nos la proporcionan las librerías dolfin y mshr ; las cuales debemos importar para poder trabajar con el. Una vez importadas, podemos configurar algunos de sus parámetros para lograr el comportamiento deseado. In [12]: # importando modulos de fenics import dolfin import mshr dolfin . parameters [ \"reorder_dofs_serial\" ] = False dolfin . parameters [ \"allow_extrapolation\" ] = True El problema que vamos a resolver con la ayuda de FEniCS , va a ser la siguiente EDP : $$u_{xx} + u_{yy} = 0$$ Con las siguientes condiciones de frontera : $$u(x=0) = 3 ; \\ u(x=1)=-1 ; \\ u(y=0) = -5 ; \\ u(y=1) = 5$$ El primer paso en la solución de una EDP utilizando el métodos de los elementos finitos , es definir una malla que describa la discretización del dominio del problema. Para este caso, vamos a utilizar la función RectangleMesh que nos ofrece FEniCS . In [13]: # Discretizando el problema N1 = N2 = 75 mesh = dolfin . RectangleMesh ( dolfin . Point ( 0 , 0 ), dolfin . Point ( 1 , 1 ), N1 , N2 ) El siguiente paso es definir una representación del espacio funcional para las funciones de ensayo y prueba. Para esto vamos a utilizar la clase FunctionSpace . El constructor de esta clase tiene al menos tres argumentos: un objeto de malla , el nombre del tipo de función base, y el grado de la función base. En este caso, vamos a utilizar la función de Lagrange . In [14]: # Funciones bases V = dolfin . FunctionSpace ( mesh , 'Lagrange' , 1 ) u = dolfin . TrialFunction ( V ) v = dolfin . TestFunction ( V ) DEBUG:FFC:Reusing form from cache. Ahora debemos definir a nuestra EDP en su formulación débil equivalente para poder tratarla como un problema de álgebra lineal que podamos resolver con el MEF . In [15]: # Formulación debil de la EDP a = dolfin . inner ( dolfin . nabla_grad ( u ), dolfin . nabla_grad ( v )) * dolfin . dx f = dolfin . Constant ( 0.0 ) L = f * v * dolfin . dx Por último, solo nos falta definir las condiciones de frontera . In [16]: # Defino condiciones de frontera def u0_top_boundary ( x , on_boundary ): return on_boundary and abs ( x [ 1 ] - 1 ) < 1e-8 def u0_bottom_boundary ( x , on_boundary ): return on_boundary and abs ( x [ 1 ]) < 1e-8 def u0_left_boundary ( x , on_boundary ): return on_boundary and abs ( x [ 0 ]) < 1e-8 def u0_right_boundary ( x , on_boundary ): return on_boundary and abs ( x [ 0 ] - 1 ) < 1e-8 In [17]: # Definiendo condiciones de frontera de Dirichlet bc_t = dolfin . DirichletBC ( V , dolfin . Constant ( 5 ), u0_top_boundary ) bc_b = dolfin . DirichletBC ( V , dolfin . Constant ( - 5 ), u0_bottom_boundary ) bc_l = dolfin . DirichletBC ( V , dolfin . Constant ( 3 ), u0_left_boundary ) bc_r = dolfin . DirichletBC ( V , dolfin . Constant ( - 1 ), u0_right_boundary ) # Lista de condiciones de frontera bcs = [ bc_t , bc_b , bc_r , bc_l ] Con esta especificación de las condiciones de frontera , ya estamos listos para resolver nuestra EDP utilizando la función dolfin.solve . El vector resultante, luego lo podemos convertir a una matriz de NumPy y utilizarla para graficar la solución con Matplotlib . In [18]: # Resolviendo la EDP u_sol = dolfin . Function ( V ) dolfin . solve ( a == L , u_sol , bcs ) DEBUG:FFC:Reusing form from cache. DEBUG:FFC:Reusing form from cache. In [19]: # graficando la solución u_mat = u_sol . vector () . array () . reshape ( N1 + 1 , N2 + 1 ) x = np . linspace ( 0 , 1 , N1 + 2 ) y = np . linspace ( 0 , 1 , N1 + 2 ) X , Y = np . meshgrid ( x , y ) fig , ax = plt . subplots ( 1 , 1 , figsize = ( 8 , 6 )) c = ax . pcolor ( X , Y , u_mat , vmin =- 5 , vmax = 5 , cmap = mpl . cm . get_cmap ( 'RdBu_r' )) cb = plt . colorbar ( c , ax = ax ) ax . set_xlabel ( r \"$x_1$\" , fontsize = 18 ) ax . set_ylabel ( r \"$x_2$\" , fontsize = 18 ) cb . set_label ( r \"$u(x_1, x_2)$\" , fontsize = 18 ) fig . tight_layout () Para profundizar en como utilizar el framework FEniCS , les recomiendo que visiten la documentación del sitio, que tienen varios ejemplos. Con esto concluyo este artículo. Obviamente, no es más que una introducción al fascinante y complejo mundo de las Ecuaciones en derivadas parciales , cada clase de EDP es un mundo en sí mismo y quedaron muchos temas sin tratar; los cuales tal vez profundice en algún otro artículo. Espero que les pueda servir como referencia y lo hayan encontrado instructivo. Saludos! Este post fue escrito utilizando Jupyter notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Calculo","url":"https://relopezbriega.github.io/blog/2016/01/27/ecuaciones-en-derivadas-parciales-con-python/"},{"title":"Ecuaciones Diferenciales con Python","text":"Introducción Vivimos en un mundo en constante cambio. La posición de la Tierra cambia con el tiempo; la velocidad de un objeto en caída libre cambia con la distancia; el área de un círculo cambia según el tamaño de su radio ; la trayectoria de un proyectil cambia según la velocidad y el ángulo de disparo. Al intentar modelar matemáticamente cualquiera de estos fenómenos, veremos que generalmente adoptan la forma de una o más Ecuaciones diferenciales . Nota : Antes de continuar leyendo, si no tienen frescos sus conocimientos básicos de Cálculo integral y Cálculo diferencial ; les recomiendo que visiten mi anterior artículo de Introducción al Cálculo . ¿Qué es una ecuación diferencial? Una Ecuación diferencial es una ecuación que involucra una variable dependiente y sus derivadas con respecto a una o más variables independientes. Muchas de las leyes de la naturaleza, en Física , Química , Biología , y Astronomía ; encuentran la forma más natural de ser expresadas en el lenguaje de las Ecuaciones diferenciales . Estas ecuaciones no sólo tienen aplicaciones en la ciencias físicas, sino que también abundan sus aplicaciones en las ciencias aplicadas como ser Ingeniería , Finanzas y Economía . Es fácil entender la razón detrás de esta amplia utilidad de las Ecuaciones diferenciales . Si recordamos que $y = f(x)$ es una función , entonces su derivada $dy / dx$ puede ser interpretada como el ritmo de cambio de $y$ con respecto a $x$. En muchos procesos naturales, las variables involucradas y su ritmo de cambio están conectados entre sí por medio de los principios científicos básicos que rigen el proceso. Cuando esta conexión es expresada matemáticamente, el resultado generalmente es una Ecuación diferencial . Para ilustrar el caso, veamos un ejemplo. Según la segunda ley de la dinámica de Newton , la aceleración $a$ de un cuerpo de masa $m$ es proporcional a la fuerza total $F$ que actúa sobre él. Si expresamos esto matemáticamente en la forma de una ecuación, entonces tememos que: $$F = ma$$ A primera vista, esta ecuación no parece ser una Ecuación diferencial , pero supongamos que un objeto de masa $m$ esta en caída libre bajo la influencia de la fuerza de gravedad . En este caso, la única fuerza actuando sobre el objeto es $mg$, donde $g$ es la aceleración debido a la gravedad . Si consideramos a $u$, como la posición del objeto desde una altura determinada; entonces la velocidad de caída del objeto va a ser igual al ritmo de cambio de la posición $u$ con respecto al tiempo $t$; es decir que la velocidad es igual a $v = du / dt$, o sea, la derivada de la posición del objeto con respecto al tiempo; y como la aceleración no es otra cosa que el ritmo de cambio de la velocidad , entonces podemos definir a la aceleración como una segunda derivada de la posición del objeto con respecto al tiempo, lo que es igual a decir que $g = d^2u / dt^2$. De esta forma, podemos reescribir la ecuación inicial de la segunda ley de la dinámica de Newton como la siguiente Ecuación diferencial . $$F = m\\frac{d^2u}{dt^2}$$ De esta manera, estamos expresando a la segunda ley de la dinámica de Newton en función de la posición del objeto. Ecuaciones diferenciales ordinarias y parciales El caso precedente, es el caso típico de una Ecuación diferencial ordinaria , ya que todas las derivadas involucradas son tomadas con respecto a una única y misma variable independiente. Por otro lado, si en la ecuación tenemos derivadas de más de una variable independiente, debemos hablar de Ecuaciones difenciales parciales . Por ejemplo, si $w = f(x, y, z, t)$ es una función de tiempo y 3 coordenadas de un punto en el espacio, entonces las siguientes son sus Ecuaciones diferenciales parciales : $$\\frac{\\partial^2 w}{\\partial x^2} + \\frac{\\partial^2 w}{\\partial y^2} + \\frac{\\partial^2 w}{\\partial z^2} = 0; \\\\ \\\\ a^2\\left(\\frac{\\partial^2 w}{\\partial x^2} + \\frac{\\partial^2 w}{\\partial y^2} + \\frac{\\partial^2 w}{\\partial z^2}\\right) = \\frac{\\partial w}{\\partial t}; \\\\ \\\\ a^2\\left(\\frac{\\partial^2 w}{\\partial x^2} + \\frac{\\partial^2 w}{\\partial y^2} + \\frac{\\partial^2 w}{\\partial z^2}\\right) = \\frac{\\partial^2 w}{\\partial t^2}. $$ En esta caso, el símbolo $\\partial$, es la notación matemática para expresar que estamos derivando parcialmente, así por ejemplo, si queremos expresar que vamos a derivar z con respecto a x, escribimos $\\partial z / \\partial x$. Estos ejemplos, tienen una gran aplicación en Física y se conocen como la ecuación de Laplace , la ecuación del calor y la ecuación de onda respectivamente. En general, las Ecuaciones diferenciales parciales surgen en problemas de campos eléctricos , mecánica de fluidos , difusión y movimientos de ondas . La teoría de estas ecuaciones es bastante diferente con respecto a las ecuaciones diferenciales ordinarias y suelen ser también más complicadas de resolver. En este artículo me voy a enfocar principalmente en las ecuaciones diferenciales ordinarias , o EDOs para abreviar. Orden de las Ecuaciones diferenciales El orden de una Ecuación diferencial va a ser igual al orden de la mayor derivada presente. Así, en nuestro primer ejemplo, la Ecuación diferencial de la segunda ley de la dinámica de Newton es de segundo orden, ya que nos encontramos ante la segunda derivada de la posición del objeto con respecto al tiempo. La ecuación general de las ecuaciones diferenciales ordinarias de grado $n$ es la siguiente: $$F\\left(x, y, \\frac{dy}{dx}, \\frac{d^2y}{dx^2}, \\dots , \\frac{d^ny}{dx^n}\\right) = 0 $$ o utilizando la notación prima para las derivadas , $$F(x, y, y', y'', \\dots, y^{(n)}) = 0$$ La más simple de todas las ecuaciones diferenciales ordinarias es la siguiente ecuación de primer orden: $$ \\frac{dy}{dx} = f(x)$$ y para resolverla simplemente debemos calcular su integral indefinida : $$y = \\int f(x) dx + c$$ . Ecuaciones diferenciales separables Una ecuación separable es una ecuación diferencial de primer orden en la que la expresión para $dx / dy$ se puede factorizar como una función de x multiplicada por una función de y. En otras palabras, puede ser escrita en la forma: $$\\frac{dy}{dx} = f(x)g(y)$$ El nombre separable viene del hecho de que la expresión en el lado derecho se puede \"separar\" en una función de $x$ y una función de $y$. Para resolver este tipo de ecuaciones, podemos reescribirlas en la forma diferencial: $$\\frac{dy}{g(y)} = f(x)dx$$ y luego podemos resolver la ecuación original integrando: $$\\int \\frac{dy}{g(y)} = \\int f(x) dx + c$$ Éstas suelen ser las Ecuaciones diferenciales más fáciles de resolver, ya que el problema de resolverlas puede ser reducido a un problema de integración ; a pesar de que igualmente muchas veces estas integrales pueden ser difíciles de calcular. Ecuaciones diferenciales lineales Uno de los tipos más importantes de Ecuaciones diferenciales son las Ecuaciones diferenciales lineales . Este tipo de ecuaciones son muy comunes en varias ciencias y tienen la ventaja de que pueden llegar a ser resueltas en forma analítica ya que su ecuación diferencial de primer orden adopta la forma: $$\\frac{dy}{dx} + P(x)y = Q(x) $$ donde, $P$ y $Q$ son funciones continuas de $x$ en un determinado intervalo. Para resolver este tipo de ecuaciones de la forma $ y' + P(x)y = Q(x)$, debemos multiplicar los dos lados de la ecuación por el factor de integración $e^{\\int P(x) dx}$ y luego integrar ambos lados. Así, si por ejemplo quisiéramos resolver la siguiente Ecuación diferencial : $$\\frac{dy}{dx} + 3x^2y = 6x^2$$ Primero calculamos el factor de integración , $I(x) = e^{\\int 3x^2 \\ dx}$, lo que es igual a $e^{x^3}$. Luego multiplicamos ambos lados de la ecuación por el recién calculado factor de integración . $$e^{x^3}\\frac{dy}{dx} + 3x^2e^{x^3}y = 6x^2e^{x^3}$$ simplificando, obtenemos: $$\\frac{d}{dx}(e^{x^3}y) = 6x^2e^{x^3}$$ Por último, integramos ambos lados de la ecuación: $$e^{x^3}y = \\int 6x^2e^{x^3} \\ dx = 2e^{x^3} + C$$ y podemos obtener la solución final: $$y = Ce^{-x^3} +2 $$ Condición inicial Cuando utilizamos Ecuaciones diferenciales , generalmente no estamos interesados en encontrar una familia de soluciones (la solución general), como la que hayamos en el caso precedente, sino que vamos a estar más interesados en una solución que satisface un requerimiento particular.En muchos problemas físicos debemos de encontrar una solución particular que satisface una condición de la forma $y(t_o) = y_0$. Esto se conoce como la condición inicial , y el problema de encontrar una solución de la Ecuación diferencial que satisface la condición inicial se conoce como el problema de valor inicial . Series de potencias Cuando comenzamos a lidiar con las Ecuaciones diferenciales , veremos que existen un gran número de ellas que no pueden ser resueltas en forma analítica utilizando los principios del Cálculo integral y el Cálculo diferencial ; pero sin embargo, tal vez podamos encontrar soluciones aproximadas para este tipo de ecuaciones en términos de Series de potencias . ¿Qué es una serie de potencias? Una Serie de potencias es una serie , generalmente infinita, que posee la siguiente forma: $$\\sum_{n=0}^{\\infty} C_nX^n = C_0 + C_1X + C_2X^2 + C_3X^3 + \\dots $$ En dónde $X$ es una variable y las $C_n$ son constantes o los coeficientes de la serie. Una Serie de potencias puede converger para algunos valores de $X$ y divergir para otros valores de $X$. La suma de la serie es una función . $$f(x) = C_0 + C_1X + C_2X^2 + C_3X^3 + \\dots + C_nX^n + \\dots$$ El dominio de esta función va a estar dado por el conjunto de todos los $X$ para los que la serie converge . Series de Taylor Las Series de Taylor son un caso especial de Serie de potencias cuyos términos adoptan la forma $(x - a)^n$. Las Series de Taylor nos van a permitir aproximar funciones continuas que no pueden resolverse en forma analítica y se van a calcular a partir de las derivadas de estas funciones . Su definición matemática es la siguiente: $$f(x) = \\sum_{n=0}^{\\infty}\\frac{f^{(n)}(a)}{n!}(x - a)^n$$ Lo que es equivalente a decir: $$f(x) = f(a) + \\frac{f'(a)}{1!}(x -a) + \\frac{f''(a)}{2!}(x -a)^2 + \\frac{f'''(a)}{3!}(x -a)^3 + \\dots$$ Una de las razones de que las Series de Taylor sean importantes es que nos permiten integrar funciones que de otra forma no podíamos manejar. De hecho, a menudo Newton integraba funciones por medio de, en primer lugar, expresarlas como Series de potencias y luego integrando la serie término a término. Por ejemplo, la función $f(x) = e^{-x^2}$, no puede ser integrada por los métodos normales del Cálculo integral , por lo que debemos recurrir a las Series de Taylor para aproximar la solución de su integral . Podríamos construir la Serie de Taylor de esta función, del siguiente modo: $$e^{-x^2} = \\sum_{n=0}^{\\infty}\\frac{(-x^2)^n}{n!} = \\sum_{n=0}^{\\infty}(-1)^n\\frac{x^{2n}}{n!} = 1 - \\frac{x^2}{1!} + \\frac{x^4}{2!} -\\frac{x^6}{3!} + \\dots$$ Resolviendo Ecuaciones diferenciales con Python Mientras que algunos problemas de Ecuaciones diferenciales ordinarias se pueden resolver con métodos analíticos, como hemos mencionado anteriormente, son mucho más comunes los problemas que no se pueden resolver analíticamente. Por lo tanto, en estos casos debemos recurrir a los métodos numéricos. Es aquí, dónde el poder de las computadoras y en especial, de los paquetes científicos de Python como NumPy , Matplotlib , SymPy y SciPy , se vuelven sumamente útiles. Veamos como podemos utilizar la fuerza computacional para resolver Ecuaciones diferenciales . Soluciones analíticas con Python SymPy nos proporciona un solucionador genérico de Ecuaciones diferenciales ordinarias , sympy.dsolve , el cual es capaz de encontrar soluciones analíticas a muchas EDOs elementales. Mientras sympy.dsolve se puede utilizar para resolver muchas EDOs sencillas simbólicamente, como veremos a continuación, debemos tener en cuenta que la mayoría de las EDOs no se pueden resolver analíticamente. Por ejemplo, retomando el ejemplo que resolvimos analíticamente más arriba, veamos si llegamos al mismo resultado utilizando SymPy para solucionar la siguiente Ecuación diferencial ordinaria : $$\\frac{dy}{dx} = -3x^2y + 6x^2$$ In [1]: Ver Código # importando modulos necesarios % matplotlib inline import matplotlib.pyplot as plt import numpy as np import sympy from scipy import integrate # imprimir con notación matemática. sympy . init_printing ( use_latex = 'mathjax' ) In [2]: # Resolviendo ecuación diferencial # defino las incognitas x = sympy . Symbol ( 'x' ) y = sympy . Function ( 'y' ) # expreso la ecuacion f = 6 * x ** 2 - 3 * x ** 2 * ( y ( x )) sympy . Eq ( y ( x ) . diff ( x ), f ) Out[2]: $$\\frac{d}{d x} y{\\left (x \\right )} = - 3 x^{2} y{\\left (x \\right )} + 6 x^{2}$$ Aquí primero definimos la incógnitas $x$ utilizando el objeto Symbol e $y$, que al ser una función, la definimos con el objeto Function , luego expresamos en Python la ecuación que define a la función. Ahora solo nos restaría aplicar la función dsolve para resolver nuestra EDO . In [3]: # Resolviendo la ecuación sympy . dsolve ( y ( x ) . diff ( x ) - f ) Out[3]: $$y{\\left (x \\right )} = C_{1} e^{- x^{3}} + 2$$ Como podemos ver, arribamos exactamente al mismo resultado. Siguiendo el mismo procedimiento, podemos resolver otras EDOs , por ejemplo si quisiéramos resolver la siguiente Ecuación diferencial : $$\\frac{dy}{dx} = \\frac{1}{2}(y^2 -1) $$ que cumpla con la condición inicial $y(0) = 2$, debemos realizar el siguiente procedimiento: In [4]: # definiendo la ecuación eq = 1.0 / 2 * ( y ( x ) ** 2 - 1 ) # Condición inicial ics = { y ( 0 ): 2 } # Resolviendo la ecuación edo_sol = sympy . dsolve ( y ( x ) . diff ( x ) - eq ) edo_sol Out[4]: $$y{\\left (x \\right )} = \\frac{C_{1} + e^{x}}{C_{1} - e^{x}}$$ Aquí encontramos la solución general de nuestra Ecuación diferencial , ahora reemplazamos los valores de la condición inicial en nuestra ecuación. In [5]: C_eq = sympy . Eq ( edo_sol . lhs . subs ( x , 0 ) . subs ( ics ), edo_sol . rhs . subs ( x , 0 )) C_eq Out[5]: $$2 = \\frac{C_{1} + 1}{C_{1} - 1}$$ y por último despejamos el valor de la constante de integración resolviendo la ecuación. In [6]: sympy . solve ( C_eq ) Out[6]: $$\\left [ 3\\right ]$$ Como vemos, el valor de la constante de integración es 3, por lo tanto nuestra solución para el problema del valore inicial es: $$y{\\left (x \\right )} = \\frac{3 + e^{x}}{3 - e^{x}}$$ Para los casos en que no exista una solución analítica, pero sí una solución aproximada por una Serie de potencias , sympy.dsolve nos va a devolver la serie como solución. Veamos el caso de la siguiente Ecuación diferencial : $$\\frac{dy}{dx} = x^2 + y^2 -1$$ In [7]: # Solución con serie de potencias f = y ( x ) ** 2 + x ** 2 - 1 sympy . dsolve ( y ( x ) . diff ( x ) - f ) Out[7]: $$y{\\left (x \\right )} = \\frac{x^{3}}{3} \\left(C_{1} \\left(3 C_{1} - 1\\right) + 1\\right) + \\frac{x^{5}}{60} \\left(C_{1} \\left(4 C_{1} - 7\\right) + 10 C_{1} - 6\\right) + C_{1} + C_{1} x + C_{1} x^{2} + \\frac{C_{1} x^{4}}{12} + \\mathcal{O}\\left(x^{6}\\right)$$ Campos de direcciones Los Campos de direcciones es una técnica sencilla pero útil para visualizar posibles soluciones a las ecuaciones diferenciales de primer orden . Se compone de líneas cortas que muestran la pendiente de la función incógnita en el plano x-y. Este gráfico se puede producir fácilmente debido a que la pendiente de $y(x)$ en los puntos arbitrarios del plano x-y está dada por la definición misma de la Ecuación diferencial ordinaria : $$\\frac{dy}{dx} = f(x, y(x))$$ Es decir, que sólo tenemos que iterar sobre los valores $x$ e $y$ en la grilla de coordenadas de interés y evaluar $f(x, y(x))$ para saber la pendiente de $y(x)$ en ese punto. Cuantos más segmentos de líneas trazamos en un Campo de dirección , más clara será la imagen. La razón por la cual el gráfico de Campos de direcciones es útil, es que las curvas suaves y continuos que son tangentes a las líneas de pendiente en cada punto del gráfico, son las posibles soluciones a la Ecuación diferencial ordinaria . Por supuesto que calcular las pendientes y dibujar los segmentos de línea para un gran número de puntos a mano sería algo realmente tedioso, pero para eso existen las computadoras y Python !. Veamos un ejemplo, supongamos que tenemos la siguiente Ecuación diferencial ordinaria , la cual según lo que vimos más arriba, sabemos que no tiene una solución analítica: $$\\frac{dy}{dx} = x^2 + y^2 -1$$ entonces, con la ayuda de Python , podemos graficar su Campo de dirección del siguiente modo: In [8]: Ver Código def plot_direction_field ( x , y_x , f_xy , x_lim = ( - 5 , 5 ), y_lim = ( - 5 , 5 ), ax = None ): \"\"\"Esta función dibuja el campo de dirección de una EDO\"\"\" f_np = sympy . lambdify (( x , y_x ), f_xy , modules = 'numpy' ) x_vec = np . linspace ( x_lim [ 0 ], x_lim [ 1 ], 20 ) y_vec = np . linspace ( y_lim [ 0 ], y_lim [ 1 ], 20 ) if ax is None : _ , ax = plt . subplots ( figsize = ( 4 , 4 )) dx = x_vec [ 1 ] - x_vec [ 0 ] dy = y_vec [ 1 ] - y_vec [ 0 ] for m , xx in enumerate ( x_vec ): for n , yy in enumerate ( y_vec ): Dy = f_np ( xx , yy ) * dx Dx = 0.8 * dx ** 2 / np . sqrt ( dx ** 2 + Dy ** 2 ) Dy = 0.8 * Dy * dy / np . sqrt ( dx ** 2 + Dy ** 2 ) ax . plot ([ xx - Dx / 2 , xx + Dx / 2 ], [ yy - Dy / 2 , yy + Dy / 2 ], 'b' , lw = 0.5 ) ax . axis ( 'tight' ) ax . set_title ( r \"$ %s $\" % ( sympy . latex ( sympy . Eq ( y ( x ) . diff ( x ), f_xy ))), fontsize = 18 ) return ax In [9]: # Defino incognitas x = sympy . symbols ( 'x' ) y = sympy . Function ( 'y' ) # Defino la función f = y ( x ) ** 2 + x ** 2 - 1 # grafico de campo de dirección fig , axes = plt . subplots ( 1 , 1 , figsize = ( 8 , 6 )) campo_dir = plot_direction_field ( x , y ( x ), f , ax = axes ) Las líneas de dirección en el gráfico de arriba sugieren cómo las curvas que son soluciones a la Ecuación diferencial ordinaria se van a comportar. Por lo tanto, los Campos de direcciones son una herramienta muy útil para visualizar posibles soluciones para Ecuaciones diferenciales ordinarias que no se pueden resolver analíticamente. Este gráfico, también nos puede ayudar a determinar el rango de validez de la solución aproximada por la Serie de potencias . Por ejemplo si resolvemos nuestra EDO para la condición inicial $y(0) = 0$, veamos a que conclusiones arribamos. In [10]: # Condición inicial ics = { y ( 0 ): 0 } # Resolviendo la ecuación diferencial edo_sol = sympy . dsolve ( y ( x ) . diff ( x ) - f , ics = ics ) edo_sol Out[10]: $$y{\\left (x \\right )} = - x + \\frac{2 x^{3}}{3} - \\frac{4 x^{5}}{15} + \\mathcal{O}\\left(x^{6}\\right)$$ In [11]: fig , axes = plt . subplots ( 1 , 2 , figsize = ( 10 , 5 )) # panel izquierdo - solución aproximada por Serie de potencias plot_direction_field ( x , y ( x ), f , ax = axes [ 0 ]) x_vec = np . linspace ( - 3 , 3 , 100 ) axes [ 0 ] . plot ( x_vec , sympy . lambdify ( x , edo_sol . rhs . removeO ())( x_vec ), 'b' , lw = 2 ) # panel derecho - Solución por método iterativo plot_direction_field ( x , y ( x ), f , ax = axes [ 1 ]) x_vec = np . linspace ( - 1 , 1 , 100 ) axes [ 1 ] . plot ( x_vec , sympy . lambdify ( x , edo_sol . rhs . removeO ())( x_vec ), 'b' , lw = 2 ) # Resolviendo la EDO en forma iterativa edo_sol_m = edo_sol_p = edo_sol dx = 0.125 # x positivos for x0 in np . arange ( 1 , 2. , dx ): x_vec = np . linspace ( x0 , x0 + dx , 100 ) ics = { y ( x0 ): edo_sol_p . rhs . removeO () . subs ( x , x0 )} edo_sol_p = sympy . dsolve ( y ( x ) . diff ( x ) - f , ics = ics , n = 6 ) axes [ 1 ] . plot ( x_vec , sympy . lambdify ( x , edo_sol_p . rhs . removeO ())( x_vec ), 'r' , lw = 2 ) # x negativos for x0 in np . arange ( 1 , 5 , dx ): x_vec = np . linspace ( - x0 - dx , - x0 , 100 ) ics = { y ( - x0 ): edo_sol_m . rhs . removeO () . subs ( x , - x0 )} edo_sol_m = sympy . dsolve ( y ( x ) . diff ( x ) - f , ics = ics , n = 6 ) axes [ 1 ] . plot ( x_vec , sympy . lambdify ( x , edo_sol_m . rhs . removeO ())( x_vec ), 'r' , lw = 2 ) En el panel de la izquierda podemos ver el gráfico de la solución aproximada por la Serie de potencias ; en el panel de la derecha vemos el gráfico al que podemos arribar resolviendo la EDO para valores incrementales de $x$ para la condición inicial en forma iterativa. Como podemos ver, las solución aproximada se alinea bien con el campo de direcciones para los valores de $x$ entre $-1.5$ y $1.5$, luego comienza a desviarse, lo que nos indica que la solución aproximada ya no sería válida. En el panel de la derecha podemos ver una solución que se adapta mucho mejor con el campo de direcciones . Transformada de Laplace Un método alternativo que podemos utilizar para resolver en forma analítica Ecuaciones diferenciales ordinarias complejas, es utilizar la Transformada de Laplace , que es un tipo particular de transformada integral . La idea es que podemos utilizar esta técnica para transformar nuestra Ecuación diferencial en algo más simple, resolver esta ecuación más simple y, a continuación, invertir la transformación para recuperar la solución a la Ecuación diferencial original. ¿Qué es una Transformada de Laplace? Para poder comprender la Transformada de Laplace , primero debemos revisar la definición general de la transformada integral , la cuál adapta la siguiente forma: $$T(f(t)) = \\int_{\\alpha}^{\\beta} K (s, t) \\ f(t) \\ dt = F(s) $$ En este caso, $f(t)$ es la función que queremos transformar, y $F(s)$ es la función transformada. Los límites de la integración, $\\alpha$ y $\\beta$, pueden ser cualquier valor entre $-\\infty$ y $+\\infty$ y $K(s, t)$ es lo que se conoce como el núcleo o kernel de la transformada, y podemos elegir el kernel que nos plazca. La idea es poder elegir un kernel que nos dé la oportunidad de simplificar la Ecuación diferencial con mayor facilidad. Si nos restringimos a Ecuaciones diferenciales con coeficientes constantes, entonces un kernel que resulta realmente útil es $e^{-st}$, ya que al diferenciar este kernel con respecto de $t$, terminamos obteniendo potencias de $s$, que podemos equiparar a los coeficientes constantes. De esta forma, podemos arribar a la definición de la Transformada de Laplace : $$\\mathcal{L}\\{f(t)\\}=\\int_0^{\\infty} e^{-st} \\ f(t) \\ dt$$ Tengan en cuenta, que además de usar el kernel $e^{-st}$, los límites de la integración van desde $0$ a $\\infty$, ya que valores negativos de $t$ harían divergir la integral . Tabla de transformadas de Laplace Calcular la Transformada de Laplace a veces puede resultar en una tarea complicada. Por suerte, podemos recurrir a la siguiente tabla para resolver la Transformada de Laplace para las funciones más comunes: Función original Transformada de Laplace Restricciones $1$ $\\frac{1}{s}$ $s>0$ $e^{at}$ $\\frac{1}{s -a}$ $s>a$ $t^n$ $\\frac{n!}{s^{n+1}}$ $s>0$, $n$ un entero $> 0$ $\\cos at$ $\\frac{s}{s^2 + a^2}$ $s>0$ $\\sin at$ $\\frac{a}{s^2 + a^2}$ $s>0$ $\\cosh at$ $\\frac{s}{s^2 - a^2}$ $s>|a|$ $\\sinh at$ $\\frac{a}{s^2 - a^2}$ $s>|a|$ $e^{at}\\cos bt$ $\\frac{s- a}{(s^2 - a^2) + b^2}$ $s>a$ $e^{at}\\sin bt$ $\\frac{b}{(s^2 - a^2) + b^2}$ $s>a$ $t^n e^{at}$ $\\frac{n!}{(s-a)^{n+1}}$ $s>a$, $n$ un entero $> 0$ $f(ct)$ $\\frac{1}{c}\\mathcal{L}\\{f(s/c)\\}$ $c>0$ Propiedades de las transformadas de Laplace Las Transformadas de Laplace poseen algunas propiedades que también nos van a facilitar el trabajo de resolverlas, algunas de ellas son: La Transformada de Laplace es un operador lineal : Esta propiedad nos dice la Transformada de Laplace de una suma, es igual a la suma de las Transformadas de Laplace de cada uno de los términos. Es decir: $$\\mathcal{L}\\{c_1f_1(t) + c_2f_2(t)\\}=c_1\\mathcal{L}\\{f_1(t)\\} + c_2\\mathcal{L}\\{f_2(t)\\}$$ La Transformada de Laplace de la primer derivada : Esta propiedad nos dice que si $f(t)$ es continua y $f'(t)$ es continua en el intervalo $0 \\le t \\le \\alpha$. Entonces la Transformada de Laplace de la primer derivada es: $$\\mathcal{L}\\{f'(t)\\} = s\\mathcal{L}\\{f(t)\\} - f(0)$$ La Transformada de Laplace de derivadas de orden superior : Esta propiedad es la generalización de la propiedad anterior para derivadas de orden n. Su formula es: $$\\mathcal{L}\\{f^{(n)}(t)\\} = s^n\\mathcal{L}\\{f(t)\\} - s^{n-1}f(0) - \\dots -f^{(n-1)}(0) \\\\ = s^n\\mathcal{L}\\{f(t)\\} - \\sum_{i=1}^n s^{n-i}f^{(i-1)}(0)$$ Resolviendo ecuaciones diferenciales con la transformada de Laplace La principal ventaja de utilizar Transformadas de Laplace es que cambia la Ecuación diferencial en una ecuación algebraica , lo que simplifica el proceso para calcular su solución. La única parte complicada es encontrar las transformaciones y las inversas de las transformaciones de los varios términos de la Ecuación diferencial que queramos resolver. Veamos entonces como Python y SymPy nos ayudan a resolver Ecuaciones diferenciales utilizando las Transformadas de Laplace . Vamos a intentar resolver la siguiente ecuación: $$y'' + 3y' + 2y = 0$$ con las siguientes condiciones iniciales: $y(0) = 2$ y $y'(0) = -3$ In [12]: # Ejemplo de transformada de Laplace # Defino las incognitas t = sympy . symbols ( \"t\" , positive = True ) y = sympy . Function ( \"y\" ) # Defino la ecuación edo = y ( t ) . diff ( t , t ) + 3 * y ( t ) . diff ( t ) + 2 * y ( t ) sympy . Eq ( edo ) Out[12]: $$2 y{\\left (t \\right )} + 3 \\frac{d}{d t} y{\\left (t \\right )} + \\frac{d^{2}}{d t^{2}} y{\\left (t \\right )} = 0$$ In [13]: # simbolos adicionales. s , Y = sympy . symbols ( \"s, Y\" , real = True ) In [14]: # Calculo la transformada de Laplace L_edo = sympy . laplace_transform ( edo , t , s , noconds = True ) sympy . Eq ( L_edo ) Out[14]: $$2 \\mathcal{L}_{t}\\left[y{\\left (t \\right )}\\right]\\left(s\\right) + 3 \\mathcal{L}_{t}\\left[\\frac{d}{d t} y{\\left (t \\right )}\\right]\\left(s\\right) + \\mathcal{L}_{t}\\left[\\frac{d^{2}}{d t^{2}} y{\\left (t \\right )}\\right]\\left(s\\right) = 0$$ Como podemos ver en este resultado, al aplicar la función sympy.laplace_transform sobre la derivada de $y(t)$, SymPy nos devuelve un termino con la forma $\\mathcal{L}_{t}\\left[\\frac{d}{d t} y{\\left (t \\right )}\\right]\\left(s\\right)$. Esto es una complicación si queremos arribar a una ecuación algebraica . Por tanto antes de proceder, deberíamos utilizar la siguiente función para resolver este inconveniente. In [15]: def laplace_transform_derivatives ( e ): \"\"\" Evalua las transformadas de Laplace de derivadas de funciones sin evaluar. \"\"\" if isinstance ( e , sympy . LaplaceTransform ): if isinstance ( e . args [ 0 ], sympy . Derivative ): d , t , s = e . args n = len ( d . args ) - 1 return (( s ** n ) * sympy . LaplaceTransform ( d . args [ 0 ], t , s ) - sum ([ s ** ( n - i ) * sympy . diff ( d . args [ 0 ], t , i - 1 ) . subs ( t , 0 ) for i in range ( 1 , n + 1 )])) if isinstance ( e , ( sympy . Add , sympy . Mul )): t = type ( e ) return t ( * [ laplace_transform_derivatives ( arg ) for arg in e . args ]) return e In [16]: # Aplicamos la nueva funcion para evaluar las transformadas de Laplace # de derivadas L_edo_2 = laplace_transform_derivatives ( L_edo ) sympy . Eq ( L_edo_2 ) Out[16]: $$s^{2} \\mathcal{L}_{t}\\left[y{\\left (t \\right )}\\right]\\left(s\\right) + 3 s \\mathcal{L}_{t}\\left[y{\\left (t \\right )}\\right]\\left(s\\right) - s y{\\left (0 \\right )} + 2 \\mathcal{L}_{t}\\left[y{\\left (t \\right )}\\right]\\left(s\\right) - 3 y{\\left (0 \\right )} - \\left. \\frac{d}{d t} y{\\left (t \\right )} \\right|_{\\substack{ t=0 }} = 0$$ In [17]: # reemplazamos la transfomada de Laplace de y(t) por la incognita Y # para facilitar la lectura de la ecuación. L_edo_3 = L_edo_2 . subs ( sympy . laplace_transform ( y ( t ), t , s ), Y ) sympy . Eq ( L_edo_3 ) Out[17]: $$Y s^{2} + 3 Y s + 2 Y - s y{\\left (0 \\right )} - 3 y{\\left (0 \\right )} - \\left. \\frac{d}{d t} y{\\left (t \\right )} \\right|_{\\substack{ t=0 }} = 0$$ In [18]: # Definimos las condiciones iniciales ics = { y ( 0 ): 2 , y ( t ) . diff ( t ) . subs ( t , 0 ): - 3 } ics Out[18]: $$\\left \\{ y{\\left (0 \\right )} : 2, \\quad \\left. \\frac{d}{d t} y{\\left (t \\right )} \\right|_{\\substack{ t=0 }} : -3\\right \\}$$ In [19]: # Aplicamos las condiciones iniciales L_edo_4 = L_edo_3 . subs ( ics ) L_edo_4 Out[19]: $$Y s^{2} + 3 Y s + 2 Y - 2 s - 3$$ In [20]: # Resolvemos la ecuación y arribamos a la Transformada de Laplace # que es equivalente a nuestra ecuación diferencial Y_sol = sympy . solve ( L_edo_4 , Y ) Y_sol Out[20]: $$\\left [ \\frac{2 s + 3}{s^{2} + 3 s + 2}\\right ]$$ In [21]: # Por último, calculamos al inversa de la Transformada de Laplace que # obtuvimos arriba, para obtener la solución de nuestra ecuación diferencial. y_sol = sympy . inverse_laplace_transform ( Y_sol [ 0 ], s , t ) y_sol Out[21]: $$\\frac{e^{t} + 1}{e^{2 t}}$$ In [22]: # Comprobamos la solución. y_sol . subs ( t , 0 ), sympy . diff ( y_sol ) . subs ( t , 0 ) Out[22]: $$\\left ( 2, \\quad -3\\right )$$ Como podemos ver Transformadas de Laplace , pueden ser una buena alternativa para resolver Ecuaciones diferenciales en forma analítica. Pero aún así, siguen existiendo ecuaciones que se resisten a ser resueltas por medios analíticos, para estos casos, debemos recurrir a los métodos numéricos. Métodos numéricos Cuando todo lo demás falla, llegan los métodos numéricos al rescate; siempre vamos a poder calcular una aproximación numérica a una solución de una Ecuación diferencial . De éstos métodos ya no vamos a obtener las formulas elegantes y acabadas que veníamos viendo, sino que vamos a obtener números. Existen muchos enfoques para resolver ecuaciones diferenciales ordinarias en forma numérica, y generalmente están diseñados para resolver problemas que están formulados como un sistema de ecuaciones diferenciales de primer orden de la forma estándar: $$ \\frac{dy}{dx} = f(x, y(x))$$ donde $y(x)$ es un vector de la funciones incógnitas de $x$. La idea básica de muchos de los métodos numéricos para resolver ecuaciones diferenciales ordinarias es capturada por el Método de Euler , veamos de que se trata este método. El Método de Euler Para entender este método, revisemos nuevamente la siguiente Ecuación diferencial : $$ \\frac{dy}{dx} = f(x, y(x))$$ El Método de Euler señala que puede que no tengamos la función real que representa la solución a la Ecuación diferencial precedente. Sin embargo, si poseemos la pendiente de la curva en cualquier punto. Es decir, el ritmo de cambio de la curva, que no es otra cosa que su derivada , la cual podemos utilizar para iterar sobre soluciones en distintos puntos. Supongamos entonces que tenemos el punto $(x_0, y_0)$, que se encuentra en la curva solución . Dada la definición de la ecuación que estamos analizando, sabemos que la pendiente de la curva solución en ese punto es $f(x_0, y_0)$. ¿Qué pasaría entonces si quisiéramos encontrar la solución numérica en el punto $(x, y)$ que se encuentra a una corta distancia $h$?. En este caso, podríamos definir a $y$ como $y = y_0 + \\Delta y$, es decir, $y$ va ser igual al valor de $y$ en el punto inicial, más el cambio que ocurrió en $y$ al movernos a la distancia $h$. Y como el cambio en $y$ va a estar dado por la pendiente de la curva, que en este caso sabemos que es igual a $f(x_0, y_0)$, podemos definir la siguiente relación de recurrencia que es la base para encontrar las soluciones numéricas con el Método de Euler : $$y = y_0 + f(x_0, y_0)h$$ La cual podemos generalizar para todo $n$ del siguiente forma: $$y_n = y_{n -1} + f(x_{n - 1}, y_{n - 1})h$$ . Este método esta íntimamente relacionado con los Campos de direcciones que analizamos más arriba. En general, el Método de Euler dice que empecemos por el punto dado por el condición incial y continuemos en la dirección indicada por el Campo de direcciones . Luego nos detengamos, miramos a la pendiente en la nueva ubicación, y procedemos en esa dirección. El Método de Runge-Kutta Otro método que podemos utilizar para encontrar soluciones numéricas a Ecuaciones diferenciales , y que incluso es más preciso que el Método de Euler , es el Método de Runge-Kutta . En el cual la relación de recurrencia va a estar dada por un promedio ponderado de términos de la siguiente manera: $$y_{k + 1} = y_k + \\frac{1}{6}(k_1 + 2k_2 + 2k_3 + k_4)$$ en dónde: $$k_1 = f(x_k, y_k)h_k, \\\\ k_2 = f\\left(x_k + \\frac{h_k}{2}, y_k + \\frac{k_1}{2}\\right)h_k, \\\\ k_3 = f\\left(x_k + \\frac{h_k}{2}, y_k + \\frac{k_2}{2}\\right)h_k, \\\\ k_4 = f\\left(x_k + h_k, y_k + k_3\\right)h_k. \\\\ $$ Soluciones numéricas con Python Para poder resolver Ecuaciones diferenciales en forma numérica con Python , podemos utilizar las herramienta de integración que nos ofrece SciPy , particularmente los dos solucionadores de ecuaciones diferenciales ordinarias , integrate.odeint y integrate.ode . La principal diferencia entre ambos, es que integrate.ode es más flexible, ya que nos ofrece la posibilidad de elegir entre distintos solucionadores . Veamos algunos ejemplos: Comencemos por la siguiente función: $$f(x, y(x)) = x + y(x)^2$$ In [23]: # Defino la función f = y ( x ) ** 2 + x f Out[23]: $$x + y^{2}{\\left (x \\right )}$$ In [24]: # la convierto en una función ejecutable f_np = sympy . lambdify (( y ( x ), x ), f ) # Definimos los valores de la condición inicial y el rango de x sobre los # que vamos a iterar para calcular y(x) y0 = 0 xp = np . linspace ( 0 , 1.9 , 100 ) # Calculando la solución numerica para los valores de y0 y xp yp = integrate . odeint ( f_np , y0 , xp ) # Aplicamos el mismo procedimiento para valores de x negativos xn = np . linspace ( 0 , - 5 , 100 ) yn = integrate . odeint ( f_np , y0 , xn ) Los resultados son dos matrices unidimensionales de NumPy $yp$ y $yn$, de la misma longitud que las correspondientes matrices de coordenadas $xp$ y $xn$, que contienen las soluciones numéricas de la ecuación diferencial ordinaria para esos puntos específicos. Para visualizar la solución, podemos graficar las matrices $yp$ y $yn$, junto con su Campo de direcciones . In [25]: fig , axes = plt . subplots ( 1 , 1 , figsize = ( 8 , 6 )) plot_direction_field ( x , y ( x ), f , ax = axes ) axes . plot ( xn , yn , 'b' , lw = 2 ) axes . plot ( xp , yp , 'r' , lw = 2 ) plt . show () En este ejemplo, solucionamos solo una ecuación. Generalmente, la mayoría de los problemas se presentan en la forma de sistemas de ecuaciones diferenciales ordinarias , es decir, que incluyen varias ecuaciones a resolver. Para ver como podemos utilizar a integrate.odeint para resolver este tipo de problemas, consideremos el siguiente sistema de ecuaciones diferenciales ordinarias , conocido el atractor de Lorenz : $$x'(t) = \\sigma(y -x), \\\\ y'(t) = x(\\rho -z)-y, \\\\ z'(t) = xy - \\beta z $$ Estas ecuaciones son conocidas por sus soluciones caóticas, que dependen sensiblemente de los valores de los parámetros $\\sigma$, $\\rho$ y $\\beta$. Veamos como podemos resolverlas con la ayuda de Python . In [26]: # Definimos el sistema de ecuaciones def f ( xyz , t , sigma , rho , beta ): x , y , z = xyz return [ sigma * ( y - x ), x * ( rho - z ) - y , x * y - beta * z ] # Asignamos valores a los parámetros sigma , rho , beta = 8 , 28 , 8 / 3.0 # Condición inicial y valores de t sobre los que calcular xyz0 = [ 1.0 , 1.0 , 1.0 ] t = np . linspace ( 0 , 25 , 10000 ) # Resolvemos las ecuaciones xyz1 = integrate . odeint ( f , xyz0 , t , args = ( sigma , rho , beta )) xyz2 = integrate . odeint ( f , xyz0 , t , args = ( sigma , rho , 0.6 * beta )) xyz3 = integrate . odeint ( f , xyz0 , t , args = ( 2 * sigma , rho , 0.6 * beta )) In [27]: # Graficamos las soluciones from mpl_toolkits.mplot3d.axes3d import Axes3D fig , ( ax1 , ax2 , ax3 ) = plt . subplots ( 1 , 3 , figsize = ( 12 , 4 ), subplot_kw = { 'projection' : '3d' }) for ax , xyz , c in [( ax1 , xyz1 , 'r' ), ( ax2 , xyz2 , 'b' ), ( ax3 , xyz3 , 'g' )]: ax . plot ( xyz [:, 0 ], xyz [:, 1 ], xyz [:, 2 ], c , alpha = 0.5 ) ax . set_xlabel ( '$x$' , fontsize = 16 ) ax . set_ylabel ( '$y$' , fontsize = 16 ) ax . set_zlabel ( '$z$' , fontsize = 16 ) ax . set_xticks ([ - 15 , 0 , 15 ]) ax . set_yticks ([ - 20 , 0 , 20 ]) ax . set_zticks ([ 0 , 20 , 40 ]) Como podemos ver, los solucionadores numéricos que nos ofrece SciPy son simples de utilizar y pueden simplificar bastante el trabajo de resolver Ecuaciones diferenciales . Método analítico vs Método numérico Al resolver una ecuación diferencial ordinaria en forma analítica, el resultado es una función, $f$, que nos permite calcular la población, $f(t)$, para cualquier valor de $t$. Al resolver una ecuación diferencial ordinaria en forma numéricamente, se obtienen dos matrices de una dimensión. Podemos pensar a estas matrices como una aproximación discreta de la función continua $f$: \"discreta\" , ya que sólo se define para ciertos valores de $t$, y \"aproximada\" , porque cada valor $F_i$ es sólo una estimación del verdadero valor de $f(t)$. Por tanto, estas son limitaciones de las soluciones numéricas. La principal ventaja es que se puede calcular soluciones numéricas de ecuaciones diferenciales ordinarias que no tienen soluciones analíticas, que son la gran mayoría de las ecuaciones diferenciales ordinarias no lineales. Con esto concluyo este paseo por una de las más fructíferas ideas de las matemáticas aplicadas, las Ecuaciones diferenciales ; espero que les pueda servir de guía para facilitarles su solución. Saludos! Este post fue escrito utilizando Jupyter notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Calculo","url":"https://relopezbriega.github.io/blog/2016/01/10/ecuaciones-diferenciales-con-python/"},{"title":"El número $e$. El número de las finanzas","text":"Introducción El artículo de hoy se lo voy a dedicar a uno los números más fascinantes de las Matemáticas , el número $e$ . Este número es de suma importancia, ya que lo podemos encontrar en una gran variedad de fenómenos, desde Física y Biología, hasta Finanzas, Arte y Música. Así como el famoso $\\pi$ , gobierna el círculo, $e$ gobierna el Cálculo y los Logaritmos . ¿Qué es el número $e$? El número $e$ es una de las más importantes constantes matemáticas , pertenece al grupo de los números irracionales , es decir, que el mismo no puede ser expresado como una fracción de dos números enteros y su expansión decimal es infinita. Asimismo, también es un número trascendental , lo que quiere decir que tampoco puede ser expresado algebraicamente. Sus primeros dígitos son: $$e = 2.7182818284590452353602874713527\\dots$$ Otra cosa que hace a $e$ sumamente interesante, es que es la base del Logaritmo natural , es decir que si $\\ln(x) = y$, entonces $e^y = x$. También podemos encontrar a $e$ , en la función exponencial , $e^x$, la cual es la inversa de la función del Logaritmo natural , y tiene la particularidad de que la derivada de $e^x$, es la misma función $e^x$. Esta característica, es lo que que hace a $e$ fundamental para el Cálculo . Historia del número $e$ Para poder entender más en profundidad la naturaleza del número $e$ , debemos recorrer un poco de su historia, que a diferencia de $\\pi$, que tiene una historia milenaria, es relativamente reciente. Invención del Logaritmo La historia del número $e$ comienza en el siglo XVII, cuando John Napier inventó el concepto de Logaritmo . Pocas veces en la historia de la ciencia, un concepto matemático fue recibido con tanto entusiasmo por la comunidad científica, como fue el caso del Logaritmo . La idea básica detrás de este concepto abstracto es la siguiente: Si uno pudiera escribir cualquier número positivo como la potencia de otro número fijo (llamado base), entonces la multiplicación y la división de números pasaría a ser equivalente a sumar o restar sus potencias; lo que simplificaría y facilitaría los cálculos. Para entenderlo mejor, veamos un ejemplo, supongamos que tenemos la siguiente tabla de potencias de 2. $n$ -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10 11 12 $2^n$ 1/8 1/4 1/2 1 2 4 8 16 32 64 128 256 512 1024 2048 4096 Ahora si quisiéramos saber el resultado de multiplicar 32 por 64; simplemente deberíamos buscar las potencias de estos dos números en la tabla anterior, 5 para 32 y 6 para 64, luego sumamos estos dos números, los que nos da como resultado 11 y por último si vamos a buscar el resultado de elevar 2 a la 11 potencia en la misma tabla, encontramos el resultado de nuestra multiplicación, que es 2048. Utilizando una simple tabla de Logaritmos , se podían resolver multiplicaciones y divisiones complejas en segundos, algo muy útil en el siglo XVII, dónde no existían las calculadoras!. Si bien John Napier desarrollo el concepto de Logaritmo , el mismo difiere un poco del concepto moderno que tenemos hoy en día; y tampoco fue el responsable de notar el poder $e$ como base de los mismos. Tendríamos que esperar algunos años más hasta que $e$ se apoderara por completo de los Logaritmos , como la base más natural para ellos. Veamos el camino que hubo que recorrer. El área de la hipérbola Un problema muy común en Matemáticas , es el de encontrar el área de una figura plana, este proceso se conoce con el nombre de cuadratura o integración . Desde que los matemáticos comenzaron a trabajar en estos problemas, una de las figuras que más ha resistido obstinadamente todos los intentos de cuadratura era la hipérbola , la cual esta definida por la función $f(x) = \\frac{1}{x}$. Un matemático que se interesó por un tema muy cercano a este, también en el siglo XVII, fue Pierre de Fermat . Fermat se interesó en la cuadratura de curvas cuya función general tiene la forma $f(x) = x^n$, dónde $n$ es un número entero . Estas curvas son llamadas generalmente parábolas . Utilizando el método de exhaución , el cuál explique en mi artículo anterior , Fermat fue capaz de llegar a una fórmula general para encontrar el área de esta familia de curvas. La fórmula a la que arribó fue la siguiente: $$A = \\frac{a^{n + 1}}{n + 1}$$ Como podemos ver, esta no es ni más ni menos que la regla de integración para funciones de grado $n$. $$\\int x^{n} \\ dx = \\frac{x^{n + 1}}{n + 1}$$ Debemos recordar, sin embargo, que el trabajo de Fermat fue hecho alrededor de 1640, aproximadamente unos 30 años antes de que Newton y Leibniz establecieran esta fórmula como parte del cálculo integral . A pesar de que Fermat pudo probar que su fórmula podía ser aplicada tanto para los casos de $n$ siendo un entero positivo como negativo; hubo un caso que se le siguió escapando, el caso de $n = -1$, es decir el caso del área de la hipérbola , $f(x) = \\frac{1}{x}$; ya que este caso hace al denominador de su formula ($n + 1$) igual a 0. A pesar de todos sus esfuerzos, Fermat nunca pudo llegar a resolver este caso. La luz recién llegaría en el año 1661, cuando Christiaan Huygens se diera cuenta de que para el caso de $n = -1$, todos los rectángulos utilizados en la aproximación del área bajo la hipérbola , tenían la misma área , es decir que a medida que la distancia crece geométricamente desde 0, las área se incrementan en la misma proporción. Lo que implica que la relación entre el área y la distancia, es logarítmica !. Por tanto para resolver el problema del área de la hipérbola , $y = \\frac{1}{x}$, simplemente deberíamos aplicar la función logarítmica sobre $x$; el único problema restante, era encontrar la base de esa función logarítmica , que como ya se pueden ir imaginando, no es ni nada más ni nada menos que el número $e$ . Es decir, que entonces para resolver el área bajo la hipérbola , debemos calcular el Logaritmo natural de $x$. Lo que llevado a la notación actual del cálculo integral equivale a decir que: $$\\int \\left(\\frac{1}{x}\\right) \\ dx = \\ln (x)$$ Desde este momento en adelante, $e$ comenzaría a convertirse en el amo y señor de los Logaritmos . Pero no se iba a quedar solo con eso, ya que aún quedaban por descubrirse otras importantes propiedades de este misterioso número. Euler Sin dudas, uno de los más grandes matemáticos de todos los tiempos fue Leonhard Euler . Prácticamente no dejó una rama de las Matemáticas sin tocar, dejando su marca en campos tan diversos como el análisis matemático , la teoría de números , la mecánica y la hidrodinámica , la cartografía , la topología , la óptica y la astronomía . A él también le debemos muchos de los símbolos matemáticos que usamos hoy en día, como ser, $i, \\ f(x), \\ \\pi$ y el mismo símbolo del número $e$ . La más influyente de sus numerosas obras fue su Introductio in analysin infinitorum , una obra en dos volúmenes publicados en 1748 y considerada como la base de análisis matemático moderno. En esta obra, Euler convierte a la función en el concepto central del análisis . Su definición de función es la que se suele utilizar hoy en día tanto en Matemáticas aplicadas, como en Física . En el Introductio , Euler , hace notar por primera vez, el rol central que tiene el número $e$ , y más precisamente la función exponencial $e^x$, y su función inversa, la función logarítmica $\\ln x$ para el Cálculo . Euler puso a éstas dos últimas funciones en iguales condiciones, al darles definiciones independientes a cada una de ellas. Como ser: $$e^x = \\lim_{n \\to \\infty} \\left(1 + \\frac{x}{n}\\right)^n \\\\ \\\\ \\\\ \\ln x = \\lim_{n \\to \\infty} n(x^{1/n} - 1)$$ Finalmente, a Euler también le debemos una de las ecuaciones más famosas e increíbles de todas las Matemáticas . La ecuación: $$e^{i\\pi} + 1 = 0$$ En una sola fórmula, Euler logró conectar a las cinco más importantes constantes de las Matemáticas , y también tres de las más importantes operaciones matemáticas ( adición , multiplicación y exponenciación ). Las cinco constantes simbolizan las cuatro principales ramas de las Matemáticas clásicas: la Aritmética , representada por el 0 y el 1; el Álgebra por $i$; la Geometría , por $\\pi$; y el Análisis , representado por $e$ . A partir de la obra de Euler , el número $e$ , se convirtió también en amo y señor del Cálculo ; gobernando así, tanto a los Logaritmos como al Análisis . Calculando a $e$ Existen muchas maneras de calcular al número $e$ , por ejemplo una forma de calcularlo es aplicando el siguiente límite : $$e = \\lim_{n \\to \\infty} \\left(1 + \\frac{1}{n}\\right)^n$$ A medida que $n$ se va haciendo cada vez más grande, vamos ganando precisión en el cálculo de los decimales de $e$ . In [1]: Ver Código % matplotlib inline import matplotlib.pyplot as plt import numpy as np import pandas as pd def f ( n ): return ( 1 + 1.0 / n ) ** n n = np . array ([ 1 , 5 , 10 , 100 , 1000 , 10000 , 100000 , 1000000 , 100000000 ]) y = f ( n ) tabla = pd . DataFrame ( list ( zip ( n , y )), columns = [ 'n' , 'e' ]) tabla Out[1]: n e 0 1 2.000000 1 5 2.488320 2 10 2.593742 3 100 2.704814 4 1000 2.716924 5 10000 2.718146 6 100000 2.718268 7 1000000 2.718280 8 100000000 2.718282 In [2]: Ver Código # Graficando (1 + 1/n)**n n = np . arange ( 1 , 100000 ) plt . figure ( figsize = ( 8 , 6 )) plt . title ( r \"Graficando $(1 + 1/n)^n$\" ) plt . xlabel ( 'n' ) plt . ylabel ( r \"$(1 + 1/n)^n$\" ) plt . plot ( n , f ( n )) plt . axhline ( y = np . e , color = 'r' , label = '$e$' ) plt . xlim ([ 0 , 100 ]) plt . legend () plt . show () Como podemos ver, tanto del ejemplo numérico como del gráfico, esta definición de $e$ , tarda bastante en converger hacia el valor exacto. Necesitamos un valor bastante grande de $n$ para ganar precisión. Otra definición de $e$ que podemos utilizar para calcularlo y que converge mucho más rápido, es su definición como una serie infinita de factoriales . $$e = \\frac{1}{0!} + \\frac{1}{1!} + \\frac{1}{2!}+ \\frac{1}{3!}+ \\frac{1}{4!}+ \\frac{1}{5!}+ \\frac{1}{6!}+ \\frac{1}{7!} + \\dots$$ Por último, también podríamos expresar a $e$ , utilizando fracciones continuas , del siguiente modo: Al igual que ocurre con el número $\\pi$ se conocen millones de dígitos del número $e$ . Por ejemplo, para generar la imagen de la cabecera del artículo, yo utilicé 390 decimales de $e$ . $e$ en las finanzas Como comenté a lo largo de todo el artículo, nos podemos topar con el número $e$ en infinidad de situaciones; pero una de las áreas donde más lo podemos encontrar es en la finanzas, ya que $e$ se encuentra escondido en la definición de una de las formulas más fundamentales del Cálculo financiero , la fórmula del interés compuesto . Por ejemplo, si repasamos la formula del valor futuro : $$FV = PV \\left(1 + \\frac{r}{n}\\right)^n$$ donde $FV$ es el valor futuro ; $PV$ es el valor presente de nuestra inversión; $r$ es la tasa de interés anual, expresada como valor decimal; y $n$ es el número de períodos. Y si miramos detenidamente a esta ecuación , podemos ver cierta similitud con la definición de $e$ que dimos más arriba. $$e = \\lim_{n \\to \\infty} \\left(1 + \\frac{1}{n}\\right)^n$$ Es más, si tomáramos el caso hipotético en el que el valor presente sea igual a 1, es decir $PV = 1$ y la tasa de interés sea del 100 %, $r = 1$. Podemos ver que la fórmula del valor futuro se convierte en la definición del número $e$ !. Por esta razón, al número $e$ lo vamos a encontrar en infinidad de situaciones en el Cálculo financiero . Por ejemplo, la definición de la fórmula de la capitalización continua utiliza al número $e$ explícitamente: $$FV = PV \\cdot e^{rt}$$ donde $FV$ es el valor futuro ; $PV$ es el valor presente de nuestra inversión; $r$ es la tasa de interés efectiva y $t$ es el tiempo de capitalización. Éstos son sólo algunos casos, a medida que nos adentramos más en la complejidades del Cálculo financiero vamos a ver que $e$ continua apareciendo una y otra vez; pero eso va a quedar para próximos artículos! Aquí concluye el artículo, espero les haya gustado y hayan sentido cierta fascinación por este místico número que encontramos por todas partes! Saludos! Este post fue escrito utilizando Jupyter notebook. Pueden descargar este notebook o ver su versión estática en nbviewer .","tags":"Finanzas","url":"https://relopezbriega.github.io/blog/2015/12/13/el-numero-e-el-numero-de-las-finanzas/"},{"title":"Introducción al Cálculo con Python","text":"Introducción El Cálculo es una rama muy importante de la Matemática moderna; tiene profundas raíces en problemas físicos y gran parte de su potencia y belleza derivan de la variedad de sus aplicaciones. Las subramas conocidas como Cálculo integral y Cálculo diferencial son instrumentos naturales y poderosos para atacar múltiples problemas que surgen en Física , Astronomía , Ingeniería , Química , Geología , Biología , y en otros campos de las ciencias. El Cálculo no sólo es un instrumento técnico, sino que contiene una colección de ideas fascinantes y atrayentes que han ocupado el pensamiento humano durante cientos de años. Estas ideas están relacionadas con la velocidad, el área, el volumen, la razón de crecimiento, la tangente a una línea, y demás. Historia El origen del Cálculo se remonta a más de 2300 años, cuando los griegos intentaban resolver el problema del área ideando el procedimiento que llamaron método de exhaución . La idea esencial de este método consiste en intentar determinar el área de una región por medio de aproximaciones utilizando regiones poligonales cuya área sea más fácil de calcular, la idea es continuar con el proceso aumentando los lados de los polígonos hasta llegar a la mejor aproximación posible de la región que queremos determinar. Este método fue usado satisfactoriamente por Arquímedes (287-212 A.C.) para hallar fórmulas exactas de las áreas del círculo y de algunas otras figuras especiales. En la siguiente figura podemos ver al método de exhaución aplicado para determinar el área del círculo . Desde Arquímedes , gradualmente, el método de exhaución fue transformándose en lo que hoy se conoce como Cálculo integral , nueva y potente disciplina que, como ya mencionamos, tiene numerosas aplicaciones no sólo en problemas relativos a áreas y volúmenes, sino también en problemas de otras ciencias. El Cálculo integral , que mantiene alguno de los caracteres originales del método de exhaución , recibió su mayor impulso en el siglo XVII, debido a los esfuerzos de Isaac Newton (1642-1727) y Gottfried Leibniz (1646-1716), y su desarrollo continuó durante el siglo XIX, hasta que Augustin-Louis Cauchy (1789-1857) y Bernhard Riemann (1826-1866) le dieron una base matemática firme. Funciones Las Funciones son los objetos fundamentales con los que tratamos en el Cálculo . Las mismas pueden ser representadas de diferentes maneras: por una ecuación , en una tabla, por un gráfico, o en palabras. Se utilizan principalmente como modelos matemáticos para representar fenómenos del mundo real. La palabra Función fue introducida en las Matemáticas por Leibniz , quien utilizaba este término para designar cierto tipo de fórmulas matemáticas. Una Función surge cada vez que una cantidad depende de otra. Más precisamente la definición de Función es esencialmente la siguiente: Dados dos conjuntos de objetos, el conjunto X y el conjunto Y, una Función es una regla que asocia a cada objeto de X, uno y sólo un, objeto en Y. El conjunto X se denomina el dominio de la Función . Los objetos de Y, asociados con los objetos en X forman otro conjunto denominado el recorrido de la Función . Generalmente se utilizan las letras $f, g, h, G$ y $H$ para designarlas. Si $f$ es una función dada y $x$ es un objeto de su dominio, la notación $f(x)$ se utiliza para designar el objeto que en el recorrido corresponde a $x$, en la Función $f$, y se denomina el valor de la función $f$ en $x$. El símbolo $f(x)$ se lee, «f de x». Muchas veces resulta útil pensar en una Función como si fuera una máquina. Si $x$ está en el dominio de la función $f$, entonces cuando $x$ entra en la máquina, se acepta como una entrada y la máquina produce una salida $f(x)$ de acuerdo a la regla de la función. Así, podemos pensar al dominio como el conjunto de todas las entradas posibles y al recorrido como el conjunto de todas las salidas posibles. El método más común para la visualización de una Función es su gráfica . Si $f$ es una Función con dominio $D$, a continuación, su gráfica es el conjunto de pares ordenados . $$\\{(x, f(x)) \\mid x \\in D \\} $$ Aquí debemos tener en cuenta que el par $(x, f(x))$, es un par entrada-salida , el valor de $x$ representa el valor de entrada, mientras que el valor de $f(x)$ representa la salida de la Función . En otras palabras, la gráfica de $f$ se compone de todos puntos $(x, y)$ en el plano de coordenadas tal que $y=f(x)$ y $x$ está en el dominio de $f$. La gráfica de una Función $f$ nos da una imagen útil del comportamiento o la \"historia de vida\" de la misma. Funciones con Python Para definir las Funciones en Python utilizamos la instrucción def . Así por ejemplo si quisiéramos definir a la Función $f(x) = \\sqrt{x + 2}$ dentro de Python , lo podríamos hacer de la siguiente forma: In [1]: import numpy as np def f ( x ): return np . sqrt ( x + 2 ) En este ejemplo, primero estamos importando la librería numpy , para trabajar más fácilmente con vectores , los cuales simplifican los cálculos numéricos. Luego utilizamos la instrucción def para definir la función, que este caso se va a llamar f y va a tener como único parámetro al objeto x. Esta función nos va a devolver el valor de la raíz cuadrada de $x + 2$. Ahora, si por ejemplo quisiéramos saber los valores de la función $f(x)$ para los $x, -2, -1, 0, 2, 4$ y $6$. podríamos invocar a esta función de la siguiente manera: In [2]: x = np . array ([ - 2 , - 1 , 0 , 2 , 4 , 6 ]) # Creando el vector de valores de x y = f ( x ) y Out[2]: array([ 0. , 1. , 1.41421356, 2. , 2.44948974, 2.82842712]) Si quisiéramos verlo en forma de tabla, podemos ayudarnos de la librería pandas y su estructura de datos DataFrame , la cual tiene una forma tabular. In [3]: import pandas as pd tabla = pd . DataFrame ( list ( zip ( x , y )), columns = [ 'x' , 'f(x)' ] ) tabla Out[3]: x f(x) 0 -2 0.000000 1 -1 1.000000 2 0 1.414214 3 2 2.000000 4 4 2.449490 5 6 2.828427 Por último, si quisiéramos graficar funciones con Python , podemos utilizar la librería Matplotlib , y pasarle los valores de $x$ e $y$ al método plot del objeto pyplot . In [4]: % matplotlib inline import matplotlib.pyplot as plt def move_spines (): \"\"\"Esta funcion divide pone al eje y en el valor 0 de x para dividir claramente los valores positivos y negativos.\"\"\" fix , ax = plt . subplots () for spine in [ \"left\" , \"bottom\" ]: ax . spines [ spine ] . set_position ( \"zero\" ) for spine in [ \"right\" , \"top\" ]: ax . spines [ spine ] . set_color ( \"none\" ) return ax x = np . linspace ( - 2 , 6 , num = 30 ) ax = move_spines () ax . grid () ax . plot ( x , f ( x )) plt . title ( r \"Grafico de $f(x)=\\sqrt{x + 2}$\" ) plt . ylabel ( 'f(x)' ) plt . xlabel ( 'x' ) plt . show () Límites Uno de los conceptos más importantes dentro del Cálculo es el concepto de Límite . Se dice que una función $f$ tiende hacia el Límite $l$ cerca de $a$, si se puede hacer que $f(x)$ este tan próxima como queramos de $l$, haciendo que $x$ esté suficientemente cerca de $a$, pero siendo distinta de $a$. Así por ejemplo si analizamos la función $f(x) = x^2 - x + 2$, para los valores cercanos a 2, podríamos ver los siguientes resultados. In [5]: Ver Código def f ( x ): return x ** 2 - x + 2 x = np . array ([ 1 , 1.5 , 1.9 , 1.95 , 1.99 , 1.999 , 2.001 , 2.05 , 2.1 , 2.2 , 2.5 , 3 ]) y = f ( x ) tabla = pd . DataFrame ( list ( zip ( x , y )), columns = [ 'x' , 'f(x)' ]) tabla Out[5]: x f(x) 0 1.000 2.000000 1 1.500 2.750000 2 1.900 3.710000 3 1.950 3.852500 4 1.990 3.970100 5 1.999 3.997001 6 2.001 4.003001 7 2.050 4.152500 8 2.100 4.310000 9 2.200 4.640000 10 2.500 5.750000 11 3.000 8.000000 de acuerdo con esta tabla, podemos ver que a medida que hacemos al valor de $x$ cercano a 2, vemos que $f(x)$ se hace muy cercana a 4. Incluso podríamos hacer a $f(x)$ tan cercana como queramos a 4, haciendo que $x$ este lo suficientemente cerca de 2. Por lo tanto, podemos expresar esta propiedad diciendo que el \" Límite de la función $f(x) = x^2 - x + 2$ cuando $x$ se acerca a 2 es igual a 4.\" y lo podemos representar con la siguiente notación: $$\\lim_{x\\to 2} \\left(x^2 -x + 2\\right) = 4$$ Gráficamente lo podemos ver del siguiente modo. In [6]: Ver Código x = np . linspace ( - 2 , 4 , num = 30 ) ax = move_spines () ax . grid () ax . plot ( x , f ( x )) ax . scatter ( 2 , 4 , label = \"limite cuando x tiende a 2\" , color = 'r' ) plt . legend () plt . title ( r \"Grafico de $f(x)=x^2 -x + 2$\" ) plt . ylabel ( 'f(x)' ) plt . xlabel ( 'x' ) plt . show () Las leyes de los límites Calcular el valor exacto de los Límites muchas veces no suele tan fácil como reemplazar el valor de $a$ en $f(x)$. Es por esto que es importante conocer algunas propiedades de los Límites , ellas son: Ley de la suma: El límite de la suma de dos funciones es la suma de sus límites. Ley de la diferencia: El límite de la diferencia de dos funciones es la diferencia de sus límites. Ley del producto: El límite del producto de dos funciones es el producto de sus límites. ley del múltiplo constante: El límite de una constante por una función es la constante por el límite de la función. Ley del cociente: El límite del cociente de dos funciones es el cociente de sus límites, siempre que el límite del denominador sea diferente de cero . Es decir que si tenemos a la constante $C$ y a los límites $\\lim_{x\\to a} f(x)$ y $\\lim_{x\\to a} g(x)$. Entonces podemos expresar estas propiedades matemáticamente de la siguiente forma: 1- Ley de la suma: $\\lim_{x\\to a} [f(x) + g(x)] = \\lim_{x\\to a} f(x) + \\lim_{x\\to a} g(x)$. 2- Ley de la diferencia: $\\lim_{x\\to a} [f(x) - g(x)] = \\lim_{x\\to a} f(x) - \\lim_{x\\to a} g(x)$. 3- Ley del producto: $\\lim_{x\\to a} [f(x) \\cdot g(x)] = \\lim_{x\\to a} f(x) \\cdot \\lim_{x\\to a} g(x)$. 4- ley del multiplo constante: $\\lim_{x\\to a} [C \\cdot f(x)] = C \\cdot \\lim_{x\\to a} f(x)$. 5- Ley del cociente: $\\lim_{x\\to a} \\left[\\frac{f(x)}{g(x)}\\right] = \\frac{\\lim_{x\\to a} f(x)}{\\lim_{x\\to a} g(x)}$, si $\\lim_{x\\to a} g(x) \\ne 0$. Calculando Límites con Python Con Python , podemos resolver Límites fácilmente utilizando la librería SymPy , la cual nos proporciona el objeto Limit para representarlos en Python . Su sintaxis es la siguiente: Limit(función, variable, punto) . Entonces para calcular el límite de $f(x)$ cuando $x$ tiende a 0, debemos escribir: Limit(f(x), x, 0) Lo utilizamos de la siguiente forma: In [7]: from sympy.interactive import printing from sympy import Limit , limit , Symbol , S # imprimir con notación matemática. printing . init_printing ( use_latex = 'mathjax' ) x = Symbol ( 'x' ) # Creando el simbolo x. Limit ( x ** 2 - x + 2 , x , 2 ) # Creando el objeto Limit Out[7]: $$\\lim_{x \\to 2^+}\\left(x^{2} - x + 2\\right)$$ In [8]: # Resolviendo el Limite con el metodo doit() Limit ( x ** 2 - x + 2 , x , 2 ) . doit () Out[8]: $$4$$ In [9]: # La funcion limit nos da directamente el resultado limit ( x ** 2 - x + 2 , x , 2 ) Out[9]: $$4$$ In [10]: # Resolviendo limite 1/x cuando x tiende a infinito Limit ( 1 / x , x , S . Infinity ) Out[10]: $$\\lim_{x \\to \\infty} \\frac{1}{x}$$ In [11]: Limit ( 1 / x , x , S . Infinity ) . doit () Out[11]: $$0$$ Como vemos, primero creamos el símbolo para representar a la variable x utilizando el objeto Symbol , y luego creamos nuestro límite utilizando el objeto Limit . Por último para resolver el límite, simplemente llamamos al método doit() sobre el objeto Limit que acabamos de crear. También podemos calcular los Límites de valores de $x$ que tiendan hacia el infinito utilizando la clase especial S.Infinity que nos proporciona SymPy . Ahora que ya conocemos que es una Función y que es un Límite , ya estamos en condiciones de adentrarnos en el Cálculo diferencial y analizar el concepto de Derivada . Derivadas Para poder comprender el concepto de Derivada primero debemos abordar el problema de la recta tangente a un curva. La palabra tangente se deriva de la palabra griega Tangens , que significa \"que toca\". Así una tangente a una curva es una línea que toca la curva. En otras palabras, una línea tangente debe tener la misma dirección que la curva en el punto de contacto. Para un círculo podríamos simplemente seguir la definición de Euclides y decir que la tangente es una línea que cruza el círculo una y sólo una vez (ver figura a ). Pero para curvas más complicadas este definición es inadecuada. Por ejemplo en la figura b podemos ver dos líneas $l$ y $t$ que pasan por el punto $P$ en una curva $C$ . La línea $l$ cruza a la curva $C$ sólo una vez, pero ciertamente no se parece a lo que pensamos como una tangente . La línea $t$, en cambio, se parece a una tangente pero intercepta a $C$ dos veces. El intento de resolver este problema fue lo que condujo a Fermat a descubrir algunas de las ideas rudimentarias referentes a la noción de Derivada . Aunque la derivada se introdujo inicialmente para el estudio del problema de la tangente, pronto se vio que proporcionaba también un instrumento para el cálculo de velocidades y, en general para el estudio de la variación o tasa de cambio de una función. La Derivada de una función es una medida de la rapidez con la que cambia el valor de dicha función, según cambie el valor de su variable independiente . La Derivada de una función es un concepto local, es decir, se calcula como el límite de la rapidez de cambio medio de la función en un cierto intervalo, cuando el intervalo considerado para la variable independiente se torna cada vez más pequeño. Por ello se habla del valor de la derivada de una cierta función en un punto dado. Entonces el valor de la Derivada de una función en un punto puede interpretarse geométricamente, ya que se corresponde con la pendiente de la recta tangente a la gráfica de la función en dicho punto. La recta tangente es a su vez la gráfica de la mejor aproximación lineal de la función alrededor de dicho punto. La noción de Derivada puede generalizarse para el caso de funciones de más de una variable con la derivada parcial y el diferencial . Matemáticamente, la Derivada es una caso especial de Límite , el cual surge cada vez que queremos calcular la pendiente de la recta tangente o la velocidad de cambio de un objeto. Éste Límite ocurre tan frecuentemente que se le ha da un notación y un nombre determinados. Así la Derivada de una función $f$ en el punto a , representada por $f'(a)$, es: $$f'(a) = \\lim_{h \\to 0}\\frac{f(a + h) - f(a)}{h}$$ donde $h$ representa la variación de $a$. Esta misma definición, puede ser representada también del siguiente modo, utilizando la notación de Leibniz . $$\\frac{dy}{dx} = \\lim_{dx \\to 0}\\frac{f(x + dx) - f(x)}{dx}$$ Así, por ejemplo si quisiéramos saber cuál es la Derivada de la función $f(x) = x^3$, podemos aplicar la definición anterior del siguiente modo. Comenzamos, definiendo a $f(x + dx) = (x + dx)^3$, luego expandimos a: $$(x + dx)^3 = f(x + dx) = x^3 + 3x^2dx + 3xdx^2 + dx^3$$ Luego reemplazamos esta función en nuestra definición de Derivada : $$\\frac{dx}{dy} = \\frac{x^3 + 3x^2dx + 3xdx^2 + dx^3 - x^3}{dx}$$ Simplificamos los términos: $$\\frac{dx}{dy} = \\frac{3x^2dx + 3xdx^2 + dx^3}{dx} \\Rightarrow 3x^2 + 3xdx + dx^2$$ y cuando $dx$ tiende a cero, obtenemos finalmente la función Derivada : $$\\frac{d}{dx}x^3 = 3x^2$$ Reglas de Derivación Si fuera siempre necesario calcular las Derivadas directamente de la definición, como hicimos anteriormente, éstos cálculos podrían ser tediosos y complicados. Afortunadamente, varias reglas se han desarrollado para encontrar Derivadas sin tener que usar la definición directamente. Estas fórmulas simplifican enormemente la tarea de la diferenciación y se conocen como reglas de derivación . Algunas de ellas son las siguientes: Funciones comunes Función original Función Derivada Constantes $c$ 0 $x$ 1 Cuadrado $x^2$ $2x$ Raiz cuadrada $\\sqrt{x}$ $\\frac{1}{2}x^{-\\frac{1}{2}}$ Exponenciales $e^x$ $e^x$ $a^x$ $a^x(\\ln a)$ Logaritmicas $\\ln x$ $\\frac{1}{x}$ $\\log_{a} x$ $\\frac{1}{x \\ln a}$ Trigonométricas $\\sin x$ $\\cos x$ $\\cos x$ $-\\sin x$ $\\tan x$ $\\sec^2(x)$ Trigonométricas inversas $\\sin^{-1}(x)$ $\\frac{1}{\\sqrt{1-x^2}}$ $\\cos^{-1}(x)$ $\\frac{-1}{\\sqrt{1-x^2}}$ $\\tan^{-1}(x)$ $\\frac{1}{1-x^2}$ 1- Regla de la función de grado n: Esta regla nos dice que una función de grado n, donde n es un exponente real, se representa por $f(x)=x^{n}$ y su derivada es $f'(x)=nx^{n-1}$. Así por ejemplo, si quisiéramos saber la derivada de $f(x) = x^5$, aplicando la regla obtenemos, $f'(x) = 5x^{5-1} \\Rightarrow 5x^4$. 2- Regla de la multiplicación por una constante: Esta regla establece que una función con la forma $f(x) = Cx$, donde $C$ es una constante; entonces la derivada de esta función va a ser igual a: $f'(x)= Cx'$; es decir a la constante por la derivada de $x$. Así por ejemplo si tenemos la función $f(x)=5x^3$, primero debemos a obtener la derivada de $x^3$, la cual aplicando la regla anterior sabemos que es $3x^2$ y luego a esta derivada la multiplicamos por la constante 5, para obtener el resultado final $f'(x)=15x^2$. 3- Regla de la suma: Esta regla establece que la derivada de la suma de dos funciones es igual a la suma de las derivadas de cada una de ellas. Es decir, $(f+g)'(x)=f'(x)+g'(x)$. Así por ejemplo la derivada de la función $f(x) = 5x^3 + x^2$ va a ser igual a $f'(x) = 15x^2 + 2x$. 4- Regla de la diferencia: Esta regla establece que la derivada de la diferencia entre dos funciones es igual a la diferencia entre las derivadas de cada una de ellas. Es decir, $(f-g)'(x)=f'(x)-g'(x)$. Así por ejemplo la derivada de la función $f(x) = 5x^3 - x^2$ va a ser igual a $f'(x) = 15x^2 - 2x$. 5- Regla del producto: Esta regla establece que la derivada de un producto de dos funciones es equivalente a la suma entre el producto de la primera función sin derivar y la derivada de la segunda función y el producto de la derivada de la primera función por la segunda función sin derivar. Es decir, $(f\\cdot g)' = f'\\cdot g + f\\cdot g'$. Así por ejemplo si quisiéramos derivar la función $h(x)=(2x + 1)(x^3 + 2)$, primero obtenemos las derivadas de cada termino, $f'(x)=2$ y $g'(x)=3x^2$ y luego aplicamos la formula $h'(x)=2(x^3 +2) + (2x + 1)3x^2$, los que nos da un resultado final de $h'(x)=8x^3 + 3x^2 + 4$. 6- Regla del cociente: Esta regla establece que la derivada de un cociente de dos funciones es la función ubicada en el denominador por la derivada del numerador menos la derivada de la función en el denominador por la función del numerador sin derivar, todo sobre la función del denominador al cuadrado. Es decir, $\\left(\\frac{f}{g}\\right)'=\\frac{f'g-fg'}{g^{2}}$. Por ejemplo, para obtener la derivada de la función $h(x) = \\frac{3x + 1}{2x}$, aplicando la formula obtenemos que $h'(x) = \\frac{3 \\cdot (2x) - (3x + 1) \\cdot 2}{2x^2}$, y simplificando llegamos al resultado final de $h'(x) = -\\frac{1}{2x^2}$. 7- Regla de la cadena: La regla de la cadena es una fórmula para calcular la derivada de la composición de dos o más funciones. Esto es, si $f$ y $g$ son dos funciones, entonces la regla de la cadena expresa la derivada de la función compuesta $f(g(x))$ en términos de las derivadas de $f$ y $g$. Esta derivada va a ser calculada de acuerdo a la siguiente formula: $f'(g(x)) = f'(g(x)) \\cdot g'(x)$. Por ejemplo, si quisiéramos saber la derivada de la función $h(x) = \\sin(x^2)$, aplicando la formula obtenemos que $h'(x) = \\cos(g(x)) \\cdot 2x$, lo que es igual a $h'(x) = 2x \\cos(x^2)$. Derivadas de mayor orden Si tenemos una función $f$, de la cual podemos obtener su derivada $f'$, la cual también es otra función que podemos derivar, entonces podemos obtener la derivada de segundo orden de $f$, la cual representaremos como $f''$. Es decir, que la derivada de segundo orden de $f$, va a ser igual a la derivada de su derivada. Siguiendo el mismo proceso, podemos seguir subiendo en la jerarquía y obtener por ejemplo, la tercer derivada de $f$. Utilizando la notación de Leibniz , expresaríamos a la segunda derivada del siguiente modo: $$\\frac{d}{dy}\\left(\\frac{dy}{dx}\\right)= \\frac{d^2y}{dx^2}$$ Calculando Derivadas con Python Con Python , podemos resolver Derivadas utilizando nuevamente la librería SymPy . En este caso, ahora vamos a utilizar el objeto Derivative . Su sintaxis es la siguiente: Derivative(funcion, variable, orden de derivación) . Lo utilizamos de la siguiente forma: In [12]: from sympy import Derivative , diff , simplify fx = ( 2 * x + 1 ) * ( x ** 3 + 2 ) dx = Derivative ( fx , x ) . doit () dx Out[12]: $$2 x^{3} + 3 x^{2} \\left(2 x + 1\\right) + 4$$ In [13]: # simplificando los resultados simplify ( dx ) Out[13]: $$8 x^{3} + 3 x^{2} + 4$$ In [14]: # Derivada de segundo orden con el 3er argumento. Derivative ( fx , x , 2 ) . doit () Out[14]: $$6 x \\left(4 x + 1\\right)$$ In [15]: # Calculando derivada de (3x +1) / (2x) fx = ( 3 * x + 1 ) / ( 2 * x ) dx = Derivative ( fx , x ) . doit () simplify ( dx ) Out[15]: $$- \\frac{1}{2 x^{2}}$$ In [16]: # la función diff nos da directamente el resultado simplify ( diff ( fx , x )) Out[16]: $$- \\frac{1}{2 x^{2}}$$ In [17]: # con el metodo subs sustituimos el valor de x # para obtener el resultado numérico. Ej x = 1. diff ( fx , x ) . subs ( x , 1 ) Out[17]: $$- \\frac{1}{2}$$ Como podemos ver, el método para calcular las Derivadas con Python , es muy similar al que vimos anteriormente al calcular los Límites . En el ejemplo, también utilizamos la función simplify , la cual nos ayuda a simplificar los resultados; y el método subs para sustituir el valor de $x$ y obtener el resultado numérico. Ahora que ya conocemos al Cálculo diferencial , es tiempo de pasar hacia la otra rama del Cálculo , el Cálculo integral , y analizar el concepto de Integración . Integrales La idea de Integral es el concepto básico del Cálculo integral . Pero para poder comprender este concepto, primero debemos abordar el problema del área . Como bien sabemos, el área es una medida de la extensión de una superficie. Determinar esta medida para superficies con líneas rectas , suele ser bastante fácil. Por ejemplo para un rectángulo, su área se define como el producto de la longitud y el ancho. O para un triángulo como la mitad de la base por la altura. El área de cualquier otro polígono se encuentra al dividirlo en triángulos y luego sumar las áreas de cada uno ellos. Pero para los casos de las regiones con líneas curvas , el cálculo del área ya no suele ser tan fácil. Para estos casos debemos recurrir a un método similar al de exhaución que mencionábamos en la introducción del artículo. Es decir, que vamos a ir dividiendo la región en varios rectángulos de $\\Delta x$ de ancho y luego podemos ir calculando el área como la suma del las áreas de cada uno de estos rectángulos. A medida que vamos agregando más rectángulos, haciendo $\\Delta x$ cada vez más pequeño, nos vamos aproximando cada vez más al valor real del área de la superficie curva. Hasta el punto de que, cuando $\\Delta x$ tiende a cero, podemos alcanzar el resultado exacto del área de nuestra superficie curva. Es decir, que realizando una suma de infinitamente más angostos rectángulos, podemos determinar el resultado exacto del área de nuestra superficie curva. Este proceso lo podemos ver más claramente en la siguiente figura. Como vemos, al igual que pasaba con el caso de las Derivadas , al querer calcular el área de una superficie curva, nos encontramos ante un caso especial de Límite (aquí vemos también por qué el concepto de Límite es tan importante para el Cálculo !). Este tipo de Límite surge en una amplia variedad de situaciones, no solo al calcular áreas , sino que también lo podemos encontrar al calcular la distancia recorrida por un objeto o el volumen de un sólido. Por lo tanto, se le ha dado una notación y un nombre determinado. De esta forma la definición matemática de la Integral definida, sería la siguiente: Si $f$ es una función definida por $a \\leqslant x \\leqslant b$, podemos dividir el intervalo $[a, b]$ en $n$ subintervalos de $\\Delta x(b - a) / n$ de ancho. Dónde $x_0(=a), x_1, x_2, \\dots, x_n(=b)$ serán los puntos finales de estos subintervalos y $x_1^*, x_2^*, \\dots, x_n^*$, serán puntos intermedios en estos subintervalos, de tal forma que $x_i^*$ se encuentre en el k-simo subintervalo $[x_{i-1}, x_i]$. Entonces la Integral definida de $f$ entre $a$ y $b$, es: $$\\int_a^b f(x) dx = \\lim_{n \\to \\infty}\\sum_{i=1}^n f(x_i^*) \\Delta x $$ El símbolo de la Integral , $\\int$, fue introducido por Leibniz , viene a ser una \"S\" alargada y fue elegido ya que la Integral es en definitiva un Límite de sumas infinitesimales . En esta notación, $a$ y $b$ son los límites de la integración y $dx$ indica que $x$ es la variable independiente. La suma: $$\\sum_{i=1}^n f(x_i^*) \\Delta x $$ que vemos en la definición, es conocida como la suma de Reimann , en honor al matemático alemán Bernhard Reimann que la desarrolló. Integrales definidas e indefinidas Una distinción importante que debemos hacer al hablar de Integrales , es la diferencia entre una Integral definida y una integral indefinida o antiderivada . Mientras que la Integral definida , que representamos con el símbolo, $\\int_a^b f(x) dx$, es un número , un resultado preciso de la medida de un área , distancia o volumen; la integral indefinida , que representamos como, $\\int f(x) dx$, es una función o familia de funciones. Más adelante, cuando hablemos del teorema fundamental del cálculo , veremos por qué esta distinción es tan importante. Pero antes, veamos como podemos hacer para calcular Integrales . Reglas de integración Cómo podemos ver de la definición que dimos de Integrales , estas parecen sumamente complicadas de calcular. Por suerte, al igual que para el caso de Derivadas , existen varias reglas que podemos utilizar para poder calcular las integrales indefinidas , en forma más sencilla. Algunas de ellas son: Funciones comunes Función original Integral indefinida ($C$ es una constante) Constante $\\int a \\ dx$ $ax + C$ Variable $\\int x \\ dx$ $\\frac{x^2}{2} + C$ Cuadrado $\\int x^2 \\ dx$ $\\frac{x^3}{3} + C$ Reciproca $\\int \\left(\\frac{1}{x}\\right) \\ dx$ $\\ln |x| + C $ Exponenciales $\\int e^x \\ dx $ $e^x + C$ $\\int a^x \\ dx$ $\\frac{a^x}{\\ln (a)} + C$ $\\int \\ln (x) \\ dx$ $x \\ \\ln(x) - x + C$ Trigonométricas $\\int \\sin (x) \\ dx$ $- \\cos (x) + C$ $\\int \\cos (x) \\ dx$ $\\sin (x) + C$ $\\int \\sec^2(x) \\ dx$ $\\tan(x) + C$ 1- Regla de la función de grado n: Esta regla nos dice que una función de grado n, donde n es un exponente real distinto de -1, se representa por $f(x)=x^{n}$ y su integral es $\\int x^{n} \\ dx = \\frac{x^{n + 1}}{n + 1} + C$. Así por ejemplo, si quisiéramos saber la integral de $f(x) = x^3$, aplicando la regla obtenemos, $\\int x^3 \\ dx = \\frac{x^4}{4} + C$. 2- Regla de la multiplicación por una constante: Esta regla establece que una función con la forma $f(x) = Cx$, donde $C$ es una constante; entonces la integral de esta función va a ser igual a: $\\int Cx \\ dx = C\\int x \\ dx$; es decir a la constante por la integral de $x$. Así por ejemplo si tenemos la función $f(x)=4x^3$, primero debemos a obtener la integral de $x^3$, la cual aplicando la regla anterior sabemos que es $\\int x^3 \\ dx = \\frac{x^4}{4} + C $ y luego a esta integral la multiplicamos por la constante 4, para obtener el resultado final $\\int 4 x^3 \\ dx = x^4 + C$. 3- Regla de la suma: Esta regla establece que la integral de la suma de dos funciones es igual a la suma de las integrales de cada una de ellas. Es decir, $\\int (f + g) \\ dx = \\int f \\ dx + \\int g \\ dx$. Así por ejemplo la integral de la función $f(x) = 4x^3 + x^2$ va a ser igual a $\\int (4x^3 + x^2) \\ dx = x^4 + \\frac{x^3}{3} + C$. 4- Regla de la diferencia: Esta regla establece que la integral de la diferencia entre dos funciones es igual a la diferencia entre las integrales de cada una de ellas. Es decir, $\\int (f - g) \\ dx = \\int f \\ dx - \\int g \\ dx$. Así por ejemplo la integral de la función $f(x) = 4x^3 - x^2$ va a ser igual a $\\int (4x^3 - x^2) \\ dx = x^4 - \\frac{x^3}{3} + C$. En todos estos ejemplos, podemos ver la aparición de una misteriosa constante $C$, esta es la que se conoce como constante de integración . Esta constante expresa una ambigüedad inherente a la construcción de las integrales. Es por esta ambigüedad que cuando hablamos de la integral indefinida decimos que expresa una familia de funciones $f(x) + C$. Teorema fundamental del Cálculo El teorema fundamental del cálculo establece una conexión entre las dos ramas del Cálculo : el Cálculo diferencial y el Cálculo integral . Como ya hemos visto, el Cálculo diferencial surgió del problema de la tangente , mientras que el Cálculo integral surgió de un problema aparentemente sin relación con este, el problema del área . Fue Isaac Barrow , quien descubrió que estos dos problemas están en realidad estrechamente relacionados. De hecho, se dio cuenta de que la derivación y la integración son procesos inversos. El teorema fundamental del cálculo nos da la relación inversa precisa entre la Derivada y la Integral . Fueron Newton y Leibniz quienes aprovecharon esta relación y la utilizaron para desarrollar el Cálculo . En particular, vieron que esta relación les permitía calcular áreas e Integrales con mucha facilidad y sin tener que calcularlas como límites de sumas. Es decir, que si tomamos una función $f$, y obtenemos primero su Derivada , y luego calculamos la Integral sobre esta función Derivada $f'(x)$. Obtenemos nuevamente función original $f$. Lo que una hace, la otra lo deshace. El teorema fundamental del cálculo es sin duda el teorema más importante en el Cálculo y, de hecho, se ubica como uno de los grandes logros de la mente humana. Matemáticamente, este teorema se suele dividir en dos partes y nos dice lo siguiente: Teorema fundamental del Calculo, parte 1. si $f$ es una función continua en el intervalo $[a, b]$, entonces la función $g$ definida como: $$g(x) = \\int_a^x f(t) dt \\qquad a \\leqslant x \\leqslant b $$ es continua en el intervalo $[a, b]$ y diferenciable en $(a, b)$, y $g'(x) = f(x)$. Teorema fundamental del Calculo, parte 2. si $f$ es una función continua en el intervalo $[a, b]$, entonces: $$\\int_a^b f(x) dx = F(b) - F(a) $$ en donde $F$ es la antiderivada de $f$, o sea, una función tal que F' = f. En definitiva, lo que nos dice la primera parte es que las operaciones de derivación y de integración son operaciones inversas. La segunda parte nos proporciona un método para calcular integrales definidas , en base a la antiderivada o integral indefinida . Así, por ejemplo, si quisiéramos calcular la Integral : $$\\int_0^3 (x^3 - 6x) dx$$ primero obtenemos su integral indefinida . $$\\int (x^3 - 6x) dx = \\frac{x^4}{4} - 6\\frac{x^2}{2}$$ y por último aplicamos la segunda parte del teorema fundamental del cálculo para obtener la integral definida en $[0, 3]$, reemplazando estos valores en la integral indefinida que acabamos de obtener. $$\\int_0^3 (x^3 - 6x) dx = \\left(\\frac{1}{4} \\cdot 3^4 - 3 \\cdot 3^2 \\right) - \\left(\\frac{1}{4} \\cdot 0^4 - 3 \\cdot 0^2 \\right) = -\\frac{27}{4}$$ Calculando Integrales con Python Con Python , podemos resolver Integrales con la ayuda de la, en este punto ya invaluable, librería SymPy . En este caso, vamos a utilizar el objeto Integral . Su sintaxis es la siguiente: Integral(funcion, variable) . Lo utilizamos de la siguiente forma: In [18]: from sympy import Integral , integrate fx = x ** 3 - 6 * x dx = Integral ( fx , x ) . doit () dx Out[18]: $$\\frac{x^{4}}{4} - 3 x^{2}$$ In [19]: # la función integrate nos da el mismo resultado integrate ( fx , x ) Out[19]: $$\\frac{x^{4}}{4} - 3 x^{2}$$ El objeto Integral también nos permite calcular integrales definidas . En este caso, en el segundo argumento le pasamos una tupla cuyo primer elemento es la variable de integración, su segundo elemento es el límite inferior de integración y el último es el límite superior. In [20]: # Calculando integral definida para [0, 3] Integral ( fx , ( x , 0 , 3 )) . doit () Out[20]: $$- \\frac{27}{4}$$ In [21]: # Comprobando Teorema fundamental del calculo. # Integración y diferenciacion son operaciones inversas. diff ( integrate ( fx )) Out[21]: $$x^{3} - 6 x$$ In [22]: integrate ( diff ( fx )) Out[22]: $$x^{3} - 6 x$$ Como podemos ver, el método para calcular las Integrales con Python , es muy similar a lo que ya veníamos utilizando al calcular Límites y Derivadas . Para calcular Integrales en forma numérica, también podemos recurrir al módulo scipy.integrate , el cual es muy útil para resolver ecuaciones diferenciales , pero eso ya va a quedar para otro artículo. Con esto concluyo esta introducción por el fascinante mundo del Cálculo , espero lo hayan disfrutado tanto como yo! Saludos! Este post fue escrito utilizando Jupyter notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Calculo","url":"https://relopezbriega.github.io/blog/2015/12/02/introduccion-al-calculo-con-python/"},{"title":"Hasta el infinito y más alla","text":"La línea consta de un número infinito de puntos; el plano, de un número infinito de líneas; el volumen, de un número infinito de planos; el hipervolumen, de un número infinito de volúmenes... No, decididamente no es éste... el mejor modo de iniciar mi relato. Jorge Luis Borges - El libro de arena Introducción El concepto de infinito ha obsesionado a la mente humana por miles de años. La idea de que las cosas pueden seguir y seguir para siempre, que pueden no tener ni principio ni final, ni centro, ni límites; desafía a la intuición. Científicos, filósofos y teólogos por igual, han tratado de entenderlo, de cortar su tamaño, de averiguar su forma y dimensión; y en última instancia, de decidir si es un concepto bienvenido en nuestras descripciones del Universo que nos rodea. A pesar de todas las dificultades que puede traer el lidiar con el infinito , éste sigue siendo un tema fascinante. Se encuentra en el corazón de todo tipo de preguntas fundamentales del hombre. ¿Se puede vivir para siempre? ¿El universo tiene un final? ¿Tuvo un comienzo? ¿Tiene el universo un \"borde\" o simplemente no existen límites a su tamaño? Aunque es fácil pensar en las listas de números o secuencias de 'tics' de un reloj que continúan para siempre, hay otros tipos de infinito que parecen ser más difíciles de concebir. ¿Qué es el infinito? Generalmente existe la tendencia de pensar en el infinito simplemente como un número muy grande, sólo un poco más grande que el mayor número que se pueda imaginar, siempre fuera de su alcance; pero el infinito no es un número ; sino que es un concepto, la idea de algo que no tiene fin . El infinito en la matemáticas Si bien el concepto de infinito parece ser un tema elusivo y complejo, aun así no a escapado a su estudio por parte de las matemáticas . Su estudio ya comenzó en la antigua Grecia, quienes con teoremas tales como que la cantidad de números primos no tiene límites, se enfrentaron ante la perspectiva de lo infinito . Aristóteles evitó la actualidad del infinito mediante la definición de una infinidad potencial , de forma de permitir que éstos teoremas continúen como válidos. Según esta distinción hecha por Aristóteles entre el infinito actual y el infito potencial , los números enteros son potencialmente infinitos porque siempre puede sumarse uno para conseguir un número mayor, pero el conjunto infinito de números enteros como tal, es decir el infinito actual de números enteros, no existe. Según los griegos el infinito actual nunca puede existir, sino que solo puede existir el infinito potencial; como reflejo del pensamiento griego, Aristóteles concluye que el infinito es imperfecto, inacabado e impensable. Esta definición de un infinito potencial y no real, funcionó y satisfizo a los matemáticos y filósofos por casi dos milenios. Notación del infinito El símbolo $\\infty $ que representa al infinito se lo debemos al matemático ingles John Wallis , quien fue un precursor del cálculo infinitesimal e introdujo la notación $\\infty$, que representa una curva sin fin, en el siglo diecisiete. En camino hacia un mejor entendimiento del infinito A medida que se continuaba explorando el concepto del infinito , nuevas paradojas continuaban apareciendo. En la edad media se entendía que un círculo más grande debería contener más puntos que un círculo más pequeño, sin embargo se puede encontrar una correspondencia de uno a uno entre los puntos de ambos círculos, como lo demuestra la siguiente figura: En 1600, Galileo entendió que el problema era el estar utilizando un razonamiento finito sobre cosas infinitas. Postuló que \"Es un error hablar de cantidades infinitas como siendo la mayor o menor o igual que a la otra\" y afirmó que el infinito no es un noción inconsistente, sino que obedece a reglas diferentes. Otro de los temas que confundían a los matemáticos, era el comportamiento de las series infinitas, sobre todo aquellas que no eran convergentes . Recordemos que una serie es convergente cuando la suma de sus términos da como resultado un número finito; en caso contrario, se dice que la serie es divergente . Así por ejemplo la serie geométrica , sabemos que converge en 1, y se puede demostrar geométricamente: $$\\sum\\limits_{i=1}^\\infty\\frac{1}{2^n} = \\frac{1}{2} + \\frac{1}{4} + \\frac{1}{8} + \\frac{1}{16} + \\dots = 1$$ Pero en cambio el resultado de la siguiente serie no se puede determinar: $$ S = 1 - 1 + 1 - 1 + 1 - 1 + 1 - 1 + 1 \\dots $$ Si por ejemplo agrupamos los términos de la siguiente forma, el resultado parece ser obviamente cero. $$ S = (1 - 1) + (1 - 1) + (1 - 1) + (1 - 1) \\dots $$ Pero si los agrupamos de esta otra forma, el resultado parece ser ahora claramente uno. $$ S = 1 + (- 1 + 1) + (- 1 + 1) + (- 1 + 1) + (- 1 + 1) \\dots $$ y no termina acá, ya que incluso podríamos agrupar la serie del siguiente modo y el resultado sería $\\frac{1}{2}$. $$ S = 1 - ( 1 - 1 + 1 - 1 + 1 - 1 + 1 - \\dots) \\Rightarrow S = 1 - S \\Rightarrow 2S = 1 \\Rightarrow S = \\frac{1}{2} $$ es decir que el valor de la serie S, podría ser tanto 1, como 0, como $\\frac{1}{2}$.No es sorprendente que argumentos como éste, hacían que los matemáticos se pusieran muy nerviosos al tratar con los infinitos y quisieran evitarlos o incluso eliminarlos a toda costa. Fuera de toda esta ambigüedad y confusión, la claridad surgió repentinamente en el siglo XIX, debido a los esfuerzos en solitario de un hombre brillante. Georg Cantor produjo una teoría que respondió a todas las objeciones de sus predecesores y reveló la riqueza inesperada escondida en el ámbito de lo infinito . De repente, los infinitos actuales se convirtieron en parte de las matemáticas . Cantor y la teoría de conjuntos para explicar el infinito Georg Cantor , aplicando ideas sumamente originales, postuló la teoría de conjuntos y utilizando el concepto de la cardinalidad de los conjuntos , se propuso explicar el concepto de infinito . Segun la teoría de Cantor , dos conjuntos se dicen que tienen el mismo número de elementos, es decir, que tienen la misma cardinalidad , si existe una función definida entre ellos de forma tal que a cada elemento le corresponde sólo otro elemento del otro conjunto , y viceversa; o sea, que exista una correspondencia de uno a uno entre los elementos de ambos conjuntos . A partir de esta definición se puede establecer la idea de conjunto infinito . Se dice que un conjunto es infinito si existe un subconjunto con la misma cardinalidad que él. Esta definición plantea una contradicción con la intuición, pues todo subconjunto como parte del conjunto total parece que deba tener menos elementos. Eso es así, efectivamente, en los conjuntos finitos , pero no en los infinitos . ¿Distintos tipos de infinitos? Infinitos numerables y no numerables Otro de los grandes aportes de Cantor , fue la distinción entre los infinitos numerables y no numerables . Cantor definió a los infinitos numerables como aquellos en los que se puede encontrar una correspondencia uno-a-uno con la lista de números naturales 1, 2, 3, 4, 5, 6,. . . Así, por ejemplo, los números pares son un infinito numerable , también lo son todos los números impares y ambos tienen el mismo tamaño. Por ejemplo, la correspondencia entre los primeros números impares sería la siguiente: $$ 1 \\longrightarrow 3 \\\\ 2 \\longrightarrow 5 \\\\ 3 \\longrightarrow 7 \\\\ 4 \\longrightarrow 9 \\\\ 5 \\longrightarrow 11 \\\\ 6 \\longrightarrow 13 \\\\ 7 \\longrightarrow 15 \\\\ 8 \\longrightarrow 17 \\\\ 9 \\longrightarrow 19 \\\\ 10 \\longrightarrow \\dots \\\\ $$ Por lo tanto, todos los conjuntos infinitos numerables tienen el mismo \"tamaño\" o cardinalidad en el sentido de Cantor . El también pensaba que éstos eran los infinitos más pequeños que pudieran existir y por lo tanto los representó utilizando la primera letra del alfabeto hebreo, el símbolo Aleph-cero , $\\aleph_{0}$. Un punto importante a tener en cuenta, es que esta definición excluye cualquier conjunto finito de objetos, ya que un conjunto finito sólo puede ponerse en correspondencia uno-a-uno con otro conjunto que contenga el mismo número de miembros. Este análisis llevó a algunas conclusiones sorprendentes. Cantor mostró que los números racionales , los cuales se forman al dividir un número entero por otro (por ejemplo $\\frac{1}{2}$, o $\\frac{13}{2}$) son también un conjunto infinito numerable . El truco fue encontrar un sistema para poder contarlos sin perderse ninguno. Él utilizó el famoso proceso ahora conocido como la diagonal de Cantor para hacer esto. Fue contando cada uno de ellos de acuerdo al siguiente ordenamiento: Cantor también pudo demostrar con un nuevo tipo de argumento matemático que existen infinitos más grandes y que no se pueden contar. Esto son los que hoy en día se conocen como infinitos no numerables . Dentro de esta categoría podemos encontrar al conjunto de los números reales , los cuales incluyen a los números irracionales , quienes no se pueden escribir como fracciones y tienen una expansión infinita de sus parte decimal. Para demostrar que los números reales son un infinito no numerable , Cantor comenzó por suponer que se podían contar, lo que significaba que debería ser capaz de elaborar una receta sistemática para contar todos los decimales interminables que no terminaran en una cadena infinita de ceros. Los primeros de estos números podrían parecerse a éstos: $$ 1 \\longrightarrow 0.\\underline{2}34567891\\dots \\\\ 2 \\longrightarrow 0.5\\underline{7}5603737\\dots \\\\ 3 \\longrightarrow 0.46\\underline{3}214516\\dots \\\\ 4 \\longrightarrow 0.846\\underline{2}16388\\dots \\\\ 5 \\longrightarrow 0.5621\\underline{9}4632\\dots \\\\ 6 \\longrightarrow 0.46673\\underline{2}271\\dots \\\\ \\dots $$ Luego creó un nuevo decimal tomando el primer dígito después del punto decimal del primer número, el segundo dígito del segundo número, y así sucesivamente para siempre(como por ejemplo los dígitos subrayados en la serie de arriba). Siguiendo el ejemplo, el nuevo decimal comenzaría como sigue: $0.273292\\dots$ Por último a este decimal recién creado, le sumó 1 a cada uno de sus infinitos dígitos. Obteniendo el número $0.384303\\dots$ El problema es que este número que acabó creando no puede aparecer en la lista ordenada original de todos los decimales que había asumido debía existir. Deberá ser distinto a cada número de la lista por lo menos en uno de sus dígitos, ya que fue construido expresamente de ese modo. Por lo tanto, los números reales (a veces llamados los números decimales o el continuo de números) no se pueden contar y son un infinito no numerable . Así mismo, también deben ser más grande que los números naturales o los números racionales . Cantor representó a este nuevo tipo de infinito con el símbolo $\\aleph_{1}$; ya que creía que no debía existir otro tipo de infinito que fuera mayor que el de los números naturales y menor que el de los números reales , aunque nunca fue capaz de demostrarlo. Esta es lo que se conoce como la hipótisis del continuo . Este descubrimiento de Cantor , que existen infinitos de diferentes tamaños y se pueden distinguir de una manera completamente inequívoca, fue uno de los grandes descubrimientos de las matemáticas . También estaba completamente en contra de la opinión dominante de la época. Una jerarquía de infinitos Además de todos los grandes aportes que Cantor realizó a la comprensión del infinito , su descubrimiento más espectacular fue que los infinitos no sólo son incontables, sino que son insuperables!. Descubrió que debe existir una jerarquía ascendente interminable de infinitos . No hay uno más grande que todos que pueda contener a todos ellos. No hay un universo de universos que podemos anotar y capturar. Cantor fue capaz de demostrar que existe una jerarquía ascendente de infinitos sin fin, una jerarquía infinita de infinitos !. Para demostrar este punto, utilizó el concepto de conjunto potencia . Recordemos que un conjunto potencia de un conjunto A, expresado por $P_{A}$, es el conjunto formado por todos los distintos subconjuntos de A. Así por ejemplo el conjunto potencia del conjunto $A=\\{1,2,3\\}$; va a ser igual a $P_{A}=\\{\\emptyset,\\{1\\},\\{2\\},\\{3\\},\\{1,2\\},\\{2,3\\}, \\{1,3\\},\\{1,2,3\\}\\}$. Un teorema importante de la teoría de conjuntos establece que si A es un conjunto con k elementos, es decir que n(A) = k; entonces el conjunto potencia de A tiene exactamente $2^k$ elementos. En nuestro ejemplo anterior podemos ver que n(A)=3, por lo tanto $n(P_{A}) = 2^3$, lo que es igual a los 8 elementos que vimos que tiene el conjunto potencia de A. Entonces, si se toma cualquier conjunto infinito , siempre es posible generar una que es infinitamente más grande considerando a su conjunto potencia , es decir, el conjunto que contiene todos sus subconjuntos. Así, de un conjunto infinito como $\\aleph_{0}$ podemos crear un conjunto infinitamente más grande mediante la formación de su conjunto potencia, $P_{\\aleph_{0}}$. Luego podemos hacer lo mismo otra vez, formando el conjunto potencia de $P_{\\aleph_{0}}$, el cual será infinitamente más grande que $P_{\\aleph_{0}}$. Y así sucesivamente, sin fin. De esta forma las matemáticas crean una jerarquía sin fin de infinitos ascendentes . El infinito nunca puede ser capturado por las fórmulas. También muestra que el número de posibles verdades es infinito . Hotel infinito Una de las imágenes que se suele utilizar para ilustrar el concepto de infinito es el relato del hotel infinito ideado por el gran matemático David Hilber , el cual explica de manera simple e intuitiva, las paradojas relacionadas con el concepto del infinito . El hotel infinito es un hotel como cualquier otro, con la única excepción de que cuenta con un número infinito de habitaciones! Tan pronto se abrieron las puertas de este hotel la gente comenzó a abarrotarlo y pronto se encontraron con que el hotel de habitaciones infinitas se encontraba lleno de infinitos huéspedes. En ese momento surgió la primera paradoja , así que se tomó como medida que los huéspedes siempre tendrían habitación asegurada pero con el acuerdo previo de que tendrían que cambiar de habitación cada vez que se les pidiera. Fue entonces cuando llegó un nuevo huesped al hotel que ya se encontraba lleno. El hombre pidió su habitación y el recepcionista, consciente de que no habría ningún problema, tomó un micrófono por el que avisó a todos los huéspedes que por favor revisaran el número de su habitación, le sumaran uno y se cambiaran a ese número de habitación, de esta manera el nuevo huésped pudo dormir tranquilamente en la habitación número 1. Pero, ¿qué pasó entonces con el huésped que se encontraba en la última habitación? Sencillamente no hay última habitación ya que $\\infty + 1$ sigue siendo $\\infty$. La segunda paradoja surgió cuando llegó un representante de una agencia de viajes solicitando hospedar a un infinito número de turistas en el hotel de infinitas habitaciones, las cuales ya estaban todas ellas ocupadas. Pero el recepcionista nuevamente no tuvo ningún problema, tomó el micrófono y pidió a todos los huéspedes que se mudaran a la habitación correspondiente al resultado de multiplicar por 2 el número de su habitación actual. De esa forma todos los huéspedes se mudaron a una habitación par, y todas las habitaciones impares quedaron libres. Como hay infinitos números impares, los infinitos turistas pudieron alojarse sin más problemas. La tercer paradoja se presentó cuando llegó otro representante de la agencia de viajes aún más preocupado que el primero y avisó que ahora la agencia tenía un infinito número de excursiones con un infinito número de turistas cada una. ¿cómo podrían hospedar a un número infinito de infinitos turistas en un hotel que ya se encontraba lleno? El recepcionista permaneció inmutable, tomó tranquilamente el micrófono y se comunicó solamente con las habitaciones cuyo número fuera primo o alguna potencia de éstos ($p^n$), les pidió que elevaran el número 2 al número de la habitación en la que se encontraban ($2^{p^n}$) y se cambiaran a esa habitación. Entonces asignó a cada una de las excursiones un número primo (distinto de 2), a cada uno de los turistas de cada una de las excursiones un número impar (t), de manera que la habitación de cada uno de los turistas, se calculaba tomando el número primo de su excursión (p) y elevandolo al número que les tocó dentro de su excursión (t) lo que da $p^t$. Existiendo un número infinito de números primos y un número infinito de números impares, fácilmente se logró hospedar a un número infinito de infinitos huéspedes dentro del hotel infinito . Las paradojas de Zenón y el concepto de límite Como venimos viendo, el concepto de infinito esta repleto de paradojas ; y algunas de las más famosas de ellas son también de las más antiguas. Se las debemos a Zenón de Elea , quien fue discípulo del filósofo Parménides y por lo tanto sostenía, al igual que su maestro, que el universo era intemporal e inmutable y que por lo tanto el movimiento era solo una ilusión. Para sostener su tesis, Zenón de Elea propuso una serie de argumentos que hoy en día se conocen como las paradojas de Zenón . La primera paradoja pretende demostrar que el movimiento es imposible, porque si quisiéramos caminar de un punto a otro, primero deberíamos cruzar la mitad de la distancia, luego la mitad de la distancia restante, luego la mitad del resto, y así sucesivamente. O sea, que si los dos puntos están a un kilómetro de distancia, en primer lugar debemos llegar a $\\frac{1}{2}$ km desde su inicio, a continuación, $\\frac{3}{4}$ km desde su inicio, luego a $\\frac{7}{8}$ km y así sucesivamente. Según el argumento de Zenón sólo se podría llegar si se toma un número infinito de pasos; y como los griegos rechazaban el infinito , por lo tanto deberíamos rechazar el movimiento también y concluir que este no es posible. En su segunda paradoja , Zenón crea el escenario de una carrera entre el famoso atleta Aquiles y un rival mucho más lento, una tortuga. Aquiles comienza en la posición 0, mientras que la tortuga, que sólo puede correr a la mitad de la velocidad de Aquiles, tiene una ventaja inicial de un kilómetro. Ambos comienzan a correr al mismo tiempo. De acuerdo al planteamiento de este escenario, podríamos pensar que Aquiles, al correr el doble de rápido que la tortuga, la superaría en la marca de 2 kilómetros. Sin embargo, cuando Aquiles llega a la marca de 1 km, la tortuga ya ha avanzado a $1 + \\frac{1}{2}$ km; cuando Aquiles llega al punto $1\\frac{1}{2}$ km, la tortuga ha alcanzado $1 + \\frac{1}{2} + \\frac{1}{4}$ km; y así sucesivamente. Cuando, después de N pasos, Aquiles alcanza una distancia de $2 - \\frac{1}{2}^{N-1}$ desde el punto inicial, la tortuga se encuentra todavía en la delantera, ya que está a una distancia de $2 - \\frac{1}{2}^{N+1}$. No importa lo grande que sea N (el número de divisiones del viaje), Aquiles nunca se adelanta a la tortuga! Resolviendo las paradojas Para poder resolver estas paradojas, debemos recurrir al cálculo infinitesimal , y más particularmente al concepto de límite . El límite de una sucesión es la noción intuitiva de que la sucesión se aproxima arbitrariamente a un único punto o valor. Es decir, que la sucesión va a converger en un valor determinado. En el caso de las dos paradojas de Zenón , nos encontramos ante series geométricas , las cuales ya vimos que tienen un límite y convergen en un valor finito. Por tanto se puede demostrar que Aquiles realmente alcanzará a la tortuga. Los tiempos en los que Aquiles recorre la distancia que lo separa del punto anterior en el que se encontraba la tortuga son cada vez más y más pequeños (hasta el infinito más pequeños), y su suma da un resultado finito, que es el momento en que alcanzará a la tortuga. Límites al infinito Como pudimos ver a lo largo de todo el artículo, el infinito es un concepto muy especial. Sabemos que no podemos llegar a el, pero todavía podemos tratar de averiguar el valor de las funciones o sucesiones que tratan con el infinito . Por ejemplo, si quisiéramos saber cual es el resultado de $\\frac{1}{\\infty}$, deberíamos responder que no existe solución, ya que el infinito no es un número, y por lo tanto no podríamos determinar el resultado de esa operación. Sin embargo, a pesar de que no podemos trabajar con el infinito en sí mismo, podemos aproximarnos bastante a el y ver que sucede a medida que vamos dividiendo a uno por valores cada vez más grandes. Por ejemplo, si reemplazamos al $\\infty$, por $x$, tendríamos una ecuación a la que podríamos ir aplicando valores cada vez más grandes de $x$ y ver a que resultado llegamos. Ayudándonos de nuestro buen amigo Python podríamos ver los resultados fácilmente y comprobar que a medida que $x$ se hace cada vez más grande, el valor de la función $\\frac{1}{x}$ se aproxima cada vez más a cero. In [1]: Ver Código % matplotlib inline import matplotlib.pyplot as plt import numpy as np import pandas as pd def f ( x ): return x **- 1.0 # 1/x == x**-1 x = np . array ([ 1 , 2 , 4 , 10 , 100 , 1000 , 10000 , 100000 , 1000000 ]) y = f ( x ) # Construyendo tabla de valores x e y con pandas tabla = pd . DataFrame ( x , columns = [ 'x' ]) tabla [ '1/x' ] = y tabla Out[1]: x 1/x 0 1 1.000000 1 2 0.500000 2 4 0.250000 3 10 0.100000 4 100 0.010000 5 1000 0.001000 6 10000 0.000100 7 100000 0.000010 8 1000000 0.000001 In [2]: Ver Código # Graficando 1/x x = np . arange ( 1 , 1000 ) # Rango de valores de x plt . figure ( figsize = ( 8 , 6 )) plt . title ( r \"Graficando $\\frac {1}{x} $\" ) plt . xlabel ( 'x' ) plt . ylabel ( r \"$f(x) = \\frac {1}{x} $\" ) plt . plot ( x , f ( x ), label = r \"$\\frac {1}{x} $ tiende a cero a medida que x crece\" ) plt . ylim ([ 0 , . 12 ]) plt . legend () plt . show () Como nos muestran tanto los cálculos como el gráfico, a medida que vamos aumentando el valor de $x$, la función $\\frac{1}{x}$ se va aproximando cada vez más a cero. Por tanto podemos conjeturar que la función $\\frac{1}{x}$ tiende a cero. Si bien, no podemos decir que pasa en el infinito , si podemos decir que pasa a medida que nos vamos acercando cada vez más a él. Por tanto podríamos decir que a medida que el valor $x$ tiende a $\\infty$, el valor de la función $\\frac{1}{x}$, se aproxima a su límite cero. En el lenguaje de las matemáticas , lo expresaríamos de la siguiente forma: $$\\lim_{x\\to \\infty} \\left(\\frac{1}{x}\\right) = 0$$ El concepto de Límite es una de las herramientas principales sobre las que se sustenta el cálculo . Muchas veces, una función puede ser indefinida en un punto, pero podemos pensar en lo que pasa a medida que la función \"se acerca\" cada vez más a ese punto. Otras veces, la función puede ser definida en un punto, pero puede aproximarse a un límite diferente. Con esto termina este artículo, para concluir cerremos la curva sin fin que representa al infinito con otra frase de Borges , que fue con quien comenzamos el artículo! Hay un concepto que es el corruptor y el desatinador de los otros. No hablo del Mal cuyo limitado imperio es la ética; hablo del infinito. Jorge Luis Borges - Avatares de la tortuga Espero les haya parecido interesante y no se hayan sentido tan desconcertados al toparse con el concepto de infinito como la probe serpiente pitón de la cabecera del artículo!. Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Matematica","url":"https://relopezbriega.github.io/blog/2015/11/22/hasta-el-infinito-y-mas-alla/"},{"title":"Números complejos con Python","text":"Introducción Si bien el conjunto de los números reales $\\mathbb{R}$, parece contener todos los números que podríamos llegar a necesitar. Existe todavía una dificultad, el hecho de que sólo se pueden tomar raíces cuadradas de los números positivos (o cero) y no de los negativos. Desde el punto de vista matemático resultaría conveniente poder extraer raíces cuadradas de números negativos tanto como de números positivos. Por tal motivo, que tal si \"inventamos\" una raíz cuadrada para el número -1 y la expresamos con el símbolo \"$i$\", de modo que tenemos $$i^2 = -1$$ La cantidad $i$ no puede ser, por supuesto, un número real puesto que el producto de un número real por sí mismo es siempre positivo (o cero, si el propio número es el cero). Por esta razón se ha aplicado convencionalmente el término \"imaginario\" a los números cuyos cuadrados son negativos. Sin embargo, es importante resaltar el hecho de que estos números imaginarios no son menos reales que los números reales a los que estamos acostumbrados. Cuando estos números imaginarios se combinan con los números reales obtenemos lo que se conoce como números complejos ; de esta forma, los números complejos vienen a completar a los números reales y nos permiten realizar todo tipo de operaciones algebraicas. ¿Qué es un número complejo? Los números complejos incluyen todas las raíces de los polinomios , a diferencia de lo que pasaba cuando solo teníamos a los números reales . Todo número complejo puede representarse como la suma de un número real y un número imaginario , el cual es un múltiplo real de la unidad imaginaria, que se indica con la letra $i$. O sea, que los números complejos tienen la forma $$a + bi$$ donde a y b son números reales llamados parte real y parte imaginaria , respectivamente, del número complejo . Las reglas para sumar y multiplicar tales números se siguen de las reglas ordinarias del álgebra, con la regla añadida de que $ i^2 = -1$. Veamos las distintas operaciones matemáticas que podemos hacer con estos números. Operaciones con números complejos Las operaciones que podemos realizar con los números complejos son las siguientes: Suma Para sumar dos números complejos simplemente sumamos cada elemento en forma separada. Es decir que: $$(a+bi) + (c+di) = (a+c) + (b+d)i$$ Así, por ejemplo $(2 + 2i) + (1 + 5i) = 3 + 7i$. Producto por escalar Para calcular el producto escalar de un número complejo , multiplicamos al escalar por cada una de sus partes, la real y la imaginaria. Es decir que: $$r(a+bi) = (ra) + (rb)i$$ Así, por ejemplo $ 3(2 + 3i) = 6 + 9i$. Multiplicación Para multiplicar dos números complejos , debemos realizar su multiplicación binomial . Es decir que: $$(a+bi)(c+di) = ac + adi + bci + bdi^2$$ En este punto, debemos recordad que $i^2$ es igual a -1; lo que nos facilita la solución del cálculo de la multiplicación. También existe una formula más simple para obtener el resultado de la multiplicación de dos números complejos , que es: $$(a+bi)(c+di) = (ac-bd) + (ad+bc)i$$ Así, por ejemplo $ (3 + 2i)(2 + 6i) = (3\\times2 - 2\\times6) + (3\\times6 + 2\\times2)i = -6 + 22i$. Igualdad Dos números complejos van a ser iguales si y solo si sus partes reales e imaginarias son iguales. Es decir que: $$(a + bi) = (c + di) \\iff a = c \\wedge b = d$$ Así, por ejemplo $ (3 + 2i) = (3 + 2i)$, ya que 3 = 3 y 2 = 2. Resta La resta de dos números complejos , funciona de forma similar a la suma. $$(a+bi) - (c+di) = (a-c) + (b-d)i$$ Así, por ejemplo $(2 + 2i) - (1 + 5i) = 1 -3i$. Conjugado El conjugado de un número complejo se obtiene cambiando el signo de su componente imaginario. Por lo tanto, el conjugado de un número complejo $z = a + bi$, es $\\overline{z} = a - bi$. Para expresar que estamos buscando el conjugado , escribimos una línea sobre el número complejo . Así, por ejemplo $$ \\overline{ 2 + 3i} = 2 - 3i$$ División Para dividir dos números complejos , debemos utilizar el conjugado ; ya que para realizar la división debemos multiplicar tanto el divisor como el dividendo por el conjugado del divisor . Así, por ejemplo si quisiéramos dividir: $$\\frac{2 + 3i}{4 - 5i}$$ Debemos realizar el siguiente cálculo: $$\\frac{2 + 3i}{4 - 5i}\\times\\frac{4 + 5i}{4 + 5i}$$ y teniendo en cuenta que la multiplicación de un número complejo por su conjugado , responde a la formula: $$(a + bi)(a - bi) = a^2 + b^2$$ Podemos resolver la división de la siguiente forma: $$\\frac{2 + 3i}{4 - 5i}\\times\\frac{4 + 5i}{4 + 5i} = \\frac{8 + 10i + 12i + 15i^2}{16 + 25} = \\frac{-7 + 22i}{41}$$ Lo que nos lleva al resultado final: $$ = \\frac{-7}{41} + \\frac{22}{41}i$$ Para simplificar el procedimiento, y no tener que realizar tantos cálculos, podríamos utilizar la siguiente formula: $$\\frac{(a + bi)}{(c + di)} = {(ac+bd) + (bc-ad)i \\over c^2+d^2} = \\left({ac + bd \\over c^2 + d^2} + {(bc - ad)i \\over c^2 + d^2}\\right)$$ Valor absoluto o módulo de un número complejo El valor absoluto , módulo o magnitud de un número complejo viene dado por la siguiente expresión: $$ |a + bi| = \\sqrt{a^2 + b^2} $$ Así, por ejemplo $|4 + 3i| = \\sqrt{16 + 9} = 5$. Plano de los números complejos o Diagrama de Argand El plano de Argand es un plano euclídeo ordinario con coordenadas cartesianas estándar $x$ e $y$ , donde $x$ indica la distancia horizontal (positiva hacia la derecha y negativa hacia la izquierda) y donde $y$ indica la distancia vertical (positiva hacia arriba y negativa hacia abajo). El número complejo $z = x + yi$ viene representado entonces por el punto del plano de Argand cuyas coordenadas son $( x , y )$. Nótese que 0 (considerado como un número complejo) viene representado por el origen de coordenadas, y 1 viene representado por un punto en el eje x. El plano de Argand proporciona simplemente un modo de organizar nuestra familia de números complejos en una imagen geométricamente útil. Las operaciones algebraicas básicas de la suma y multiplicación de números complejos encuentran ahora una forma geométrica clara. Consideremos por ejemplo la suma. Supongamos que $u$ y $v$ son dos números complejos representados en el plano de Argand de acuerdo con el esquema anterior. Entonces su suma $u + v$ viene representada como la suma vectorial de los dos puntos; es decir, el punto $u + v$ está en el lugar que completa el paralelogramo formado por $u, v$ y el origen 0. Números complejos en Python Python trae soporte por defecto para los números complejos , dónde la parte imaginaria va a estar representada por la letra j en lugar de utilizar la i como en la notación matemática. Veamos algunos ejemplos de las cosas que podemos hacer con ellos. In [1]: # Creando un número complejo c1 = 4 + 3 j c1 Out[1]: (4+3j) In [2]: # Verificando el tipo de dato type ( c1 ) Out[2]: complex In [3]: # Creando un número complejo con complex c2 = complex ( 2 , - 3 ) c2 Out[3]: (2-3j) In [4]: # sumando dos números complejos c1 = 2 + 2 j c2 = 1 + 5 j c1 + c2 Out[4]: (3+7j) In [5]: # Restando dos números complejos c1 - c2 Out[5]: (1-3j) In [6]: # Producto escalar c1 = 2 + 3 j 3 * c1 Out[6]: (6+9j) In [7]: # Multiplicando dos números complejos c1 = 3 + 2 j c2 = 2 + 6 j c1 * c2 Out[7]: (-6+22j) In [8]: # Igualdad ente números complejos c2 = 3 + 2 j c1 == c2 Out[8]: True In [9]: # Conjugado de un número complejo c1 = 2 + 3 j c1 . conjugate () Out[9]: (2-3j) In [10]: # División de números complejos c1 = 1 + 1 j c2 = - 1 + 1 j c1 / c2 Out[10]: (-0-1j) In [11]: # Valor absoluto o magnitud c1 = 4 + 3 j abs ( c1 ) Out[11]: 5.0 In [12]: # Parte real c1 . real Out[12]: 4.0 In [13]: # Parte imaginaria c1 . imag Out[13]: 3.0 In [14]: # Grafico en plano de argand # Graficos embebidos % matplotlib inline import matplotlib.pyplot as plt In [15]: def move_spines (): \"\"\"Crea la figura de pyplot y los ejes. Mueve las lineas de la izquierda y de abajo para que se intersecten con el origen. Elimina las lineas de la derecha y la de arriba. Devuelve los ejes.\"\"\" fix , ax = plt . subplots () for spine in [ \"left\" , \"bottom\" ]: ax . spines [ spine ] . set_position ( \"zero\" ) for spine in [ \"right\" , \"top\" ]: ax . spines [ spine ] . set_color ( \"none\" ) return ax ax = move_spines () ax . set_xlim ( - 5 , 5 ) ax . set_ylim ( - 5 , 5 ) ax . grid () ax . scatter ( c1 . real , c1 . imag ) plt . title ( \"Plano de Argand\" ) plt . show () Aplicaciones de los números complejos Dado que los números complejos proporcionan un sistema para encontrar las raíces de polinomios y los polinomios se utilizan como modelos teóricos en diversos campos, los números complejos gozan de un gran importancia en varias áreas especializadas. Entre estas áreas especializadas se encuentran la ingeniería, la ingeniería eléctrica y la mecánica cuántica . Algunos temas en los que se utilizan números complejos incluyen la investigación de la corriente eléctrica, longitud de onda, el flujo de líquido en relación a los obstáculos, el análisis de la tensión en las vigas, el movimiento de los amortiguadores en automóviles, el estudio de resonancia de las estructuras, el diseño de dinamos y motores eléctricos, y la manipulación de grandes matrices utilizadas en el modelado. Por ejemplo, en ingeniería electrica para el análisis de circuitos de corriente alterna, es necesario representar cantidades multidimensionales. Con el fin de realizar esta tarea, los números escalares fueron abandonados y los números complejos se utilizan para expresar las dos dimensiones de frecuencia y desplazamiento de fase. Así, por ejemplo si sabemos que el voltaje en un circuito es 45 + 10j voltios y la impedancia es de 3 + 4j ohms. Si queremos saber cual es la corriente, simplemente deberíamos resolver la ecuación $E = I \\dot Z$ donde E es la tensión, I es la corriente, y Z es la impedancia. Reemplazando los términos en la formula, obtenemos que: $$ I = \\frac{45 + 10j}{3 + 4j}$$ In [16]: # Calculando I I = ( 45 + 10 j ) / ( 3 + 4 j ) I Out[16]: (7-6j) Por tanto, la corriente es de 7 - 6j amps. Además de todas estas aplicaciones, los números complejos nos permiten también realizar uno de los gráficos más hermosos de las matemáticas como es el fractal de Julia !!. In [17]: # importando librerías necesarias import numpy as np import numba In [18]: # Graficando el fractal de Julia def py_julia_fractal ( z_re , z_im , j ): '''Crea el grafico del fractal de Julia.''' for m in range ( len ( z_re )): for n in range ( len ( z_im )): z = z_re [ m ] + 1 j * z_im [ n ] for t in range ( 256 ): z = z ** 2 - 0.05 + 0.68 j if np . abs ( z ) > 2.0 : j [ m , n ] = t break jit_julia_fractal = numba . jit ( nopython = True )( py_julia_fractal ) In [19]: N = 1024 j = np . zeros (( N , N ), np . int64 ) z_real = np . linspace ( - 1.5 , 1.5 , N ) z_imag = np . linspace ( - 1.5 , 1.5 , N ) jit_julia_fractal ( z_real , z_imag , j ) fig , ax = plt . subplots ( figsize = ( 8 , 8 )) ax . imshow ( j , cmap = plt . cm . RdBu_r , extent = [ - 1.5 , 1.5 , - 1.5 , 1.5 ]) ax . set_xlabel ( \"$\\mathrm {Re} (z)$\" , fontsize = 18 ) ax . set_ylabel ( \"$\\mathrm {Im} (z)$\" , fontsize = 18 ) plt . show () O el también famoso conjunto de Mandelbrot . In [20]: # Graficando el conjunto de Mandelbrot def mandelbrot ( h , w , maxit = 20 ): '''Crea el grafico del fractal de Mandelbrot del tamaño (h,w).''' y , x = np . ogrid [ - 1.4 : 1.4 : h * 1 j , - 2 : 0.8 : w * 1 j ] c = x + y * 1 j z = c divtime = maxit + np . zeros ( z . shape , dtype = int ) for i in range ( maxit ): z = z ** 2 + c diverge = z * np . conj ( z ) > 2 ** 2 div_now = diverge & ( divtime == maxit ) divtime [ div_now ] = i z [ diverge ] = 2 return divtime plt . figure ( figsize = ( 8 , 8 )) plt . imshow ( mandelbrot ( 2000 , 2000 )) plt . show () Con esto termino este artículo, espero que les haya gustado y les sea de utilidad. Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Matematica","url":"https://relopezbriega.github.io/blog/2015/10/12/numeros-complejos-con-python/"},{"title":"Conjuntos con Python","text":"Introducción Una característica notable de los seres humanos es su inherente necesidad y capacidad de agrupar objetos de acuerdo a criterios específicos. La idea de la clasificación de ciertos objetos en grupos similares, o conjuntos , es uno de los conceptos más fundamentales de la matemática moderna. La teoría de conjuntos ha sido el marco unificador para todas las matemáticas desde que el matemático alemán Georg Cantor la formulara alrededor de 1870. Ningún campo de las matemáticas podría describirse hoy en día sin hacer referencia a algún tipo de conjunto abstracto. En términos más generales, el concepto de membresía de un conjunto , que se encuentra en el corazón de la teoría de conjuntos , explica cómo sentencias con sustantivos y predicados son formulados en nuestro lenguaje, o en cualquier lenguaje abstracto como las matemáticas. Debido a esto, la teoría de conjuntos está íntimamente ligada a la lógica y sirve de base para todas las matemáticas. ¿Qué es un conjunto? Un conjunto es una colección de objetos distintos, a menudo llamados elementos o miembros . Existen dos características hacen de los conjuntos algo totalmente distinto a cualquier otra colección de objetos. En primer lugar, un conjunto está siempre \"bien definido\", es decir que si realizamos la pregunta ¿Este objeto particular, se encuentra en esta colección? ; siempre debe existir una respuesta clara por sí o por no basada en una regla o algunos criterios dados. La segunda característica, es que no hay dos miembros de un mismo conjunto que sean exactamente iguales, es decir, que no hay elementos repetidos. Un conjunto puede contener cualquier cosa imaginable, incluyendo números, letras, colores, incluso otros conjuntos !. Sin embargo, ninguno de los objetos del conjunto puede ser el propio conjunto . Descartamos esta posibilidad para evitar encontrarnos con la Paradoja de Russell , un problema famoso en la lógica matemática desenterrado por el gran lógico británico Bertrand Russell en 1901. Notación de Conjuntos Cuando escribimos a los conjuntos utilizamos letras mayúsculas para sus nombres y para representar al conjunto propiamente dicho simplemente listamos sus elementos separándolos por comas y luego englobamos todos estos elementos dentro de un par de llaves. Así, por ejemplo, A = {1,2,3, ..., 10} es el conjunto de los 10 primeros números naturales o para contar , B = {Rojo, Azul, Verde} es el conjunto de colores primarios, N = {1,2,3, ...} es el conjunto de todos los números naturales , y Z = {..., - 3, -2, -1,0,1,2,3, ...} es el conjunto de todos los números enteros . Los puntos suspensivos \"...\" se utilizan para describir el carácter infinito de los números en los conjuntos N y Z. También se utiliza el símbolo $ \\in $ para expresar que determina objeto pertenece o es miembro de un conjunto y el símbolo $ \\notin $ para indicar que no pertenece a un conjunto . Utilizando los ejemplos anteriores, podríamos por ejemplo escribir que $7 \\in A$ y $12 \\notin A$. Dado que muchos conjuntos no se pueden describir listando todos sus miembros, ya que en muchos casos esto es imposible, también se utiliza la mucho más potente notación de constructor de conjuntos o predicado. En esta notación escribimos el conjunto de acuerdo a qué tipos de objetos pertenecen al conjunto , que se colocan a la izquierda del símbolo \"|\", que significa \"de tal manera que,\" dentro de las llaves; así como las condiciones que estos objetos deben cumplir para pertenecer al conjunto , las cuales se colocan a la derecha de \"|\" dentro de las llaves. Por ejemplo, el conjunto de los números racionales , o fracciones, que se denota por Q no puede ser descrito por el método de listar todos sus miembros. En su lugar, se define a Q utilizando la notación de predicado de la siguiente manera: $Q=\\{\\frac{p}{q} \\mid p, q \\in Z$ y $q \\ne 0 \\}$ Esto se lee \"Q es el conjunto de todas las fracciones de la forma p sobre q, tal que p y q son números enteros y q no es cero.\" También podríamos escribir al conjunto A de nuestro ejemplo anterior como $A = \\{x \\mid x \\in N$ y $x < 11\\}$. Conjuntos numéricos Dentro de las matemáticas, los principales conjuntos numéricos que podemos encontrar y que tienen un carácter universal son: $\\mathbb{N} = \\{1,2,3, ...\\}$ es el conjunto de los números naturales . $\\mathbb{W} = \\{0,1,2,3, ...\\}$ es el conjunto de los números enteros positivos. $\\mathbb{Z} = \\{...,-3,-2,-1,0,1,2,3, ...\\}$ es el conjunto de todos los números enteros . $\\mathbb{Q} =\\{\\frac{p}{q} \\mid p, q \\in Z$ y $q \\ne 0 \\}$ es el conjunto de los números racionales . $\\mathbb{R}$, es el conjunto de los números reales . Estos son todos los números que pueden ser colocados en una recta numérica unidimensional que se extiende sin fin tanto en negativo como positivo. $\\mathbb{I}$, es el conjunto de los números irracionales . Algunos de los números más importantes en matemáticas pertenecen a este conjunto,incluyendo $\\pi, \\sqrt{2}, e$ y $\\phi$. $\\mathbb{C}$, es el conjunto de los números complejos . Estos son los números que contienen una parte real y otra parte imaginaria. Igualdad entre conjuntos El concepto de igualdad en los conjuntos , difiere levemente del clásico concepto de igualdad que solemos tener. Dos conjuntos A y B se dice que son iguales (expresado por A = B), si y sólo si ambos conjuntos tienen exactamente los mismos elementos. Por ejemplo el conjunto A={1,2,3,4} es igual al conjunto B={4,3,2,1}. Un conjunto importante, y que todavía no hemos mencionado es el conjunto vacío , el cual no tiene elementos y por tanto no puede ser igualado con ningún otro conjunto . Se expresa con el símbolo $\\emptyset$ o {}. Cardinalidad La cardinalidad de un conjunto A es el número de elementos que pertenecen a A y lo expresamos como n(A). La cardinalidad de un conjunto puede ser pensada tambien como una medida de su \"tamaño\". Si la cardinalidad de un conjunto es un número entero , entonces el conjunto se dice que es finito. De lo contrario, el conjunto se dice que es infinito. Así por ejemplo la cardinalidad del conjunto A={1,2,...,9,10} es 10 y lo expresamos como n(A)=10. Subconjunto y subconjunto propio Si todos los elementos de un conjunto A son también elementos de otro conjunto B, entonces A se llama un subconjunto de B y lo expresamos como $A \\subseteq B$. En cierto sentido, se puede pensar al subconjunto A como dentro, o contenido en el conjunto B. Si un conjunto A es un subconjunto de B y los dos conjuntos no son iguales, entonces llamamos A un subconjunto propio de B y lo expresamos como $A \\subset B$. En este caso, se dice que el conjunto A esta propiamente contenido en B. Algunas propiedades importantes relacionadas con subconjuntos y subconjuntos propios son las siguientes: Cualquier conjunto A es un subconjunto de sí mismo. Por lo tanto $A \\subseteq A$. Esto es claramente cierto. Menos obvio es el hecho de que el conjunto vacío es un subconjunto de cualquier conjunto A. Por lo tanto $\\emptyset \\subseteq A$. Esta propiedad se prueba a través de la contradicción, ya que si asumimos que existe un conjunto A del que el conjunto vacío no es un subconjunto , entonces esto quiere decir que el conjunto vacío debe contener un elemento que no se encuentra en A y esto es absurdo ya que el conjunto vacío no contiene ningún elemento. El conjunto vacío es un subconjunto propio de cualquier conjunto A, siempre y cuando A no se también un conjunto vacío . Para los conjuntos finitos A y B, si $A \\subseteq B$, entonces $n(A) \\leq n(B)$. De forma similar, para los conjuntos finitos A y B, si $A \\subset B$, entonces $n(A) < n(B)$. Conjunto potencia El conjunto potencia de un conjunto A, expresado por $P_{A}$, es el conjunto formado por todos los distintos subconjuntos de A. Así por ejemplo el conjunto potencia del conjunto $A=\\{1,2,3\\}$; va a ser igual a $P_{A}=\\{\\emptyset,\\{1\\},\\{2\\},\\{3\\},\\{1,2\\},\\{2,3\\}, \\{1,3\\},\\{1,2,3\\}\\}$. Un teorema importante de la teoría de conjuntos establece que si A es un conjunto con k elementos, es decir que n(A) = k; entonces el conjunto potencia de A tiene exactamente $2^k$ elementos. Escribimos esto como $n(P_{A}) = 2^k$. En nuestro ejemplo anterior podemos ver que n(A)=3, por lo tanto $n(P_{A}) = 2^3$, lo que es igual a los 8 elementos que vimos que tiene el conjunto potencia de A. Algebra de conjuntos El álgebra de conjuntos es el estudio de las operaciones básicas que podemos realizar con los conjuntos . Las operaciones básicas del álgebra de conjuntos son: Unión . La unión de dos conjuntos A y B es el conjunto $A \\cup B$ que contiene todos los elementos de A y de B. Intersección . La intersección de dos conjuntos A y B es el conjunto $A \\cap B$ que contiene todos los elementos comunes de A y B. Diferencia . La diferencia entre dos conjuntos A y B es el conjunto $A \\setminus B$ que contiene todos los elementos de A que no pertenecen a B. Complemento . El complemento de un conjunto A es el conjunto $A^∁$ que contiene todos los elementos que no pertenecen a A. Producto cartesiano . El producto cartesiano de dos conjuntos A y B es el conjunto $A \\times B$ que contiene todos los pares ordenados (a, b) cuyo primer elemento pertenece a A y su segundo elemento pertenece a B. Conjuntos con Python Luego de todo este repaso por los fundamentos de la teoría de conjuntos , es tiempo de ver como podemos utilizar a los conjuntos dentro de Python ; ya que el lenguaje trae como una de sus estructuras de datos por defecto a los conjuntos . También veremos que podemos utilizar el constructor FiniteSet que nos proporciona sympy , el cual tiene ciertas ventajas sobre la versión por defecto de Python . In [1]: # Creando un conjunto en python A = { 1 , 2 , 3 } A Out[1]: {1, 2, 3} In [2]: # Creando un conjunto a partir de una lista lista = [ \"bananas\" , \"manzanas\" , \"naranjas\" , \"limones\" ] B = set ( lista ) B Out[2]: {'bananas', 'limones', 'manzanas', 'naranjas'} In [3]: # Los conjuntos eliminan los elementos duplicados lista = [ \"bananas\" , \"manzanas\" , \"naranjas\" , \"limones\" , \"bananas\" , \"bananas\" , \"limones\" , \"naranjas\" ] B = set ( lista ) B Out[3]: {'bananas', 'limones', 'manzanas', 'naranjas'} In [4]: # Creando el conjunto vacío O = set () O Out[4]: set() In [5]: # Cardinalidad de un conjunto con len(). print ( \"La cardinalidad del conjunto A = {0} es {1} \" . format ( A , len ( A ))) La cardinalidad del conjunto A = {1, 2, 3} es 3 In [6]: # comprobando membresía 2 in A Out[6]: True In [7]: # Igualdad entre conjuntos. El orden de los elementos no importa. A = { 1 , 2 , 3 , 4 } B = { 4 , 2 , 3 , 1 } A == B Out[7]: True In [8]: # Subconjunto. No hay distincion entre subconjunto y propio # para el conjunto por defecto de python. A = { 1 , 2 , 3 } B = { 1 , 2 , 3 , 4 , 5 } A . issubset ( B ) Out[8]: True In [9]: # Subconjunto propio A . issubset ( B ) and A != B Out[9]: True In [10]: # Union de conjuntos A = { 1 , 2 , 3 , 4 , 5 } B = { 4 , 5 , 6 , 7 , 8 , 9 , 10 } A . union ( B ) Out[10]: {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} In [11]: # Intersección de conjuntos A . intersection ( B ) Out[11]: {4, 5} In [12]: # Diferencia entre conjuntos A - B Out[12]: {1, 2, 3} In [13]: B - A Out[13]: {6, 7, 8, 9, 10} In [14]: # Utilizando FiniteSet de sympy from sympy import FiniteSet C = FiniteSet ( 1 , 2 , 3 ) C Out[14]: {1, 2, 3} In [15]: # Generando el conjunto potencia. Esto no se puede # hacer utilizando el conjunto por defecto de python. C . powerset () Out[15]: {EmptySet(), {1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}, {1, 2, 3}} In [16]: # Cardinalidad print ( \"La cardinalidad del conjunto potencia del conjunto C = {0} es {1} \" . format ( C , len ( C . powerset ()))) La cardinalidad del conjunto potencia del conjunto C = {1, 2, 3} es 8 In [17]: # Igualdad A = FiniteSet ( 1 , 2 , 3 ) B = FiniteSet ( 1 , 3 , 2 ) A == B Out[17]: True In [18]: A = FiniteSet ( 1 , 2 , 3 ) B = FiniteSet ( 1 , 3 , 4 ) A == B Out[18]: False In [19]: # Subconjunto y subconjunto propio A = FiniteSet ( 1 , 2 , 3 ) B = FiniteSet ( 1 , 2 , 3 , 4 , 5 ) A . is_subset ( B ) Out[19]: True In [20]: A . is_proper_subset ( B ) Out[20]: True In [21]: # A == B. El test de subconjunto propio da falso B = FiniteSet ( 2 , 1 , 3 ) A . is_proper_subset ( B ) Out[21]: False In [22]: # Union de dos conjuntos A = FiniteSet ( 1 , 2 , 3 ) B = FiniteSet ( 2 , 4 , 6 ) A . union ( B ) Out[22]: {1, 2, 3, 4, 6} In [23]: # Interseccion de dos conjuntos A = FiniteSet ( 1 , 2 ) B = FiniteSet ( 2 , 3 ) A . intersect ( B ) Out[23]: {2} In [24]: # Diferencia entre conjuntos A - B Out[24]: {1} In [25]: # Calculando el producto cartesiano. Con el conjunto por # defecto de python no podemos hacer esto con el operador * A = FiniteSet ( 1 , 2 ) B = FiniteSet ( 3 , 4 ) P = A * B P Out[25]: {1, 2} x {3, 4} In [26]: for elem in P : print ( elem ) (1, 3) (1, 4) (2, 3) (2, 4) In [27]: # Elevar a la n potencia un conjunto. Calcula el n # producto cartesiano del mismo conjunto. A = FiniteSet ( 1 , 2 , 3 , 4 ) P2 = A ** 2 P2 Out[27]: {1, 2, 3, 4} x {1, 2, 3, 4} In [28]: P3 = A ** 3 P3 Out[28]: {1, 2, 3, 4} x {1, 2, 3, 4} x {1, 2, 3, 4} In [29]: for elem in P3 : print ( elem ) (1, 1, 1) (1, 1, 2) (1, 1, 3) (1, 1, 4) (1, 2, 1) (1, 2, 2) (1, 2, 3) (1, 2, 4) (1, 3, 1) (1, 3, 2) (1, 3, 3) (1, 3, 4) (1, 4, 1) (1, 4, 2) (1, 4, 3) (1, 4, 4) (2, 1, 1) (2, 1, 2) (2, 1, 3) (2, 1, 4) (2, 2, 1) (2, 2, 2) (2, 2, 3) (2, 2, 4) (2, 3, 1) (2, 3, 2) (2, 3, 3) (2, 3, 4) (2, 4, 1) (2, 4, 2) (2, 4, 3) (2, 4, 4) (3, 1, 1) (3, 1, 2) (3, 1, 3) (3, 1, 4) (3, 2, 1) (3, 2, 2) (3, 2, 3) (3, 2, 4) (3, 3, 1) (3, 3, 2) (3, 3, 3) (3, 3, 4) (3, 4, 1) (3, 4, 2) (3, 4, 3) (3, 4, 4) (4, 1, 1) (4, 1, 2) (4, 1, 3) (4, 1, 4) (4, 2, 1) (4, 2, 2) (4, 2, 3) (4, 2, 4) (4, 3, 1) (4, 3, 2) (4, 3, 3) (4, 3, 4) (4, 4, 1) (4, 4, 2) (4, 4, 3) (4, 4, 4) In [30]: # graficos embebidos % matplotlib inline In [31]: # Dibujanto el diagrama de venn de 2 conjuntos from matplotlib_venn import venn2 , venn2_circles import matplotlib.pyplot as plt A = FiniteSet ( 1 , 3 , 5 , 7 , 9 , 11 , 13 , 15 , 17 , 19 ) B = FiniteSet ( 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 8 ) plt . figure ( figsize = ( 6 , 8 )) v = venn2 ( subsets = [ A , B ], set_labels = ( 'A' , 'B' )) v . get_label_by_id ( '10' ) . set_text ( A - B ) v . get_label_by_id ( '11' ) . set_text ( A . intersection ( B )) v . get_label_by_id ( '01' ) . set_text ( B - A ) c = venn2_circles ( subsets = [ A , B ], linestyle = 'dashed' ) c [ 0 ] . set_ls ( 'solid' ) plt . show () Además de las aplicaciones que pueden tener los conjuntos de Python en matemáticas, los mismos también pueden ser una estructura de datos poderosa y ayudarnos a resolver varios problemas de programación en forma muy sencilla. A tenerlos en cuenta! Con esto termino este artículo; espero que les haya gustado y les sea de utilidad. Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Matematica","url":"https://relopezbriega.github.io/blog/2015/10/11/conjuntos-con-python/"},{"title":"Machine Learning con Python","text":"Una de las ramas de estudio que cada vez esta ganando más popularidad dentro de las ciencias de la computación es el aprendizaje automático o Machine Learning . Muchos de los servicios que utilizamos en nuestro día a día como google, gmail, netflix, spotify o amazon se valen de las herramientas que les brinda el Machine Learning para alcanzar un servicio cada vez más personalizado y lograr así ventajas competitivas sobre sus rivales. Qué es Machine Learning? Pero, ¿qué es exactamente Machine Learning ?. El Machine Learning es el diseño y estudio de las herramientas informáticas que utilizan la experiencia pasada para tomar decisiones futuras; es el estudio de programas que pueden aprenden de los datos. El objetivo fundamental del Machine Learning es generalizar, o inducir una regla desconocida a partir de ejemplos donde esa regla es aplicada . El ejemplo más típico donde podemos ver el uso del Machine Learning es en el filtrado de los correo basura o spam. Mediante la observación de miles de correos electrónicos que han sido marcados previamente como basura, los filtros de spam aprenden a clasificar los mensajes nuevos. El Machine Learning combina conceptos y técnicas de diferentes áreas del conocimiento, como las matemáticas , estadísticas y las ciencias de la computación ; por tal motivo, hay muchas maneras de aprender la disciplina. Tipos de Machine Learning El Machine Learning tiene una amplia gama de aplicaciones, incluyendo motores de búsqueda, diagnósticos médicos, detección de fraude en el uso de tarjetas de crédito, análisis del mercado de valores, clasificación de secuencias de ADN, reconocimiento del habla y del lenguaje escrito, juegos y robótica. Pero para poder abordar cada uno de estos temas es crucial en primer lugar distingir los distintos tipos de problemas de Machine Learning con los que nos podemos encontrar. Aprendizaje supervisado En los problemas de aprendizaje supervisado se enseña o entrena al algoritmo a partir de datos que ya vienen etiquetados con la respuesta correcta. Cuanto mayor es el conjunto de datos más el algoritmo puede aprender sobre el tema. Una vez concluído el entrenamiento, se le brindan nuevos datos, ya sin las etiquetas de las respuestas correctas, y el algoritmo de aprendizaje utiliza la experiencia pasada que adquirió durante la etapa de entrenamiento para predecir un resultado. Esto es similar al método de aprendizaje que se utiliza en las escuelas, donde se nos enseñan problemas y las formas de resolverlos, para que luego podamos aplicar los mismos métodos en situaciones similares. Aprendizaje no supervisado En los problemas de aprendizaje no supervisado el algoritmo es entrenado usando un conjunto de datos que no tiene ninguna etiqueta; en este caso, nunca se le dice al algoritmo lo que representan los datos. La idea es que el algoritmo pueda encontrar por si solo patrones que ayuden a entender el conjunto de datos. El aprendizaje no supervisado es similar al método que utilizamos para aprender a hablar cuando somos bebes, en un principio escuchamos hablar a nuestros padres y no entendemos nada; pero a medida que vamos escuchando miles de conversaciones, nuestro cerebro comenzará a formar un modelo sobre cómo funciona el lenguaje y comenzaremos a reconocer patrones y a esperar ciertos sonidos. Aprendizaje por refuerzo En los problemas de aprendizaje por refuerzo, el algoritmo aprende observando el mundo que le rodea. Su información de entrada es el feedback o retroalimentación que obtiene del mundo exterior como respuesta a sus acciones. Por lo tanto, el sistema aprende a base de ensayo-error. Un buen ejemplo de este tipo de aprendizaje lo podemos encontrar en los juegos, donde vamos probando nuevas estrategias y vamos seleccionando y perfeccionando aquellas que nos ayudan a ganar el juego. A medida que vamos adquiriendo más practica, el efecto acumulativo del refuerzo a nuestras acciones victoriosas terminará creando una estrategia ganadora. Sobreentrenamiento Como mencionamos cuando definimos al Machine Learning , la idea fundamental es encontrar patrones que podamos generalizar para luego poder aplicar esta generalización sobre los casos que todavía no hemos observado y realizar predicciones. Pero también puede ocurrir que durante el entrenamiento solo descubramos casualidades en los datos que se parecen a patrones interesantes, pero que no generalicen. Esto es lo que se conoce con el nombre de sobreentrenamiento o sobreajuste . El sobreentrenamiento es la tendencia que tienen la mayoría de los algoritmos de Machine Learning a ajustarse a unas características muy específicas de los datos de entrenamiento que no tienen relación causal con la función objetivo que estamos buscando para generalizar. El ejemplo más extremo de un modelo sobreentrenado es un modelo que solo memoriza las respuestas correctas; este modelo al ser utilizado con datos que nunca antes ha visto va a tener un rendimiento azaroso, ya que nunca logró generalizar un patrón para predecir. Como evitar el sobreentrenamiento Como mencionamos anteriormente, todos los modelos de Machine Learning tienen tendencia al sobreentrenamiento ; es por esto que debemos aprender a convivir con el mismo y tratar de tomar medidas preventivas para reducirlo lo más posible. Las dos principales estrategias para lidiar son el sobreentrenamiento son: la retención de datos y la validación cruzada . En el primer caso, la idea es dividir nuestro conjunto de datos , en uno o varios conjuntos de entrenamiento y otro/s conjuntos de evaluación. Es decir, que no le vamos a pasar todos nuestros datos al algoritmo durante el entrenamiento, sino que vamos a retener una parte de los datos de entrenamiento para realizar una evaluación de la efectividad del modelo. Con esto lo que buscamos es evitar que los mismos datos que usamos para entrenar sean los mismos que utilizamos para evaluar. De esta forma vamos a poder analizar con más precisión como el modelo se va comportando a medida que más lo vamos entrenando y poder detectar el punto crítico en el que el modelo deja de generalizar y comienza a sobreajustarse a los datos de entrenamiento. La validación cruzada es un procedimiento más sofisticado que el anterior. En lugar de solo obtener una simple estimación de la efectividad de la generalización ; la idea es realizar un análisis estadístico para obtener otras medidas del rendimiento estimado, como la media y la varianza, y así poder entender cómo se espera que el rendimiento varíe a través de los distintos conjuntos de datos. Esta variación es fundamental para la evaluación de la confianza en la estimación del rendimiento. La validación cruzada también hace un mejor uso de un conjunto de datos limitado; ya que a diferencia de la simple división de los datos en uno el entrenamiento y otro de evaluación; la validación cruzada calcula sus estimaciones sobre todo el conjunto de datos mediante la realización de múltiples divisiones e intercambios sistemáticos entre datos de entrenamiento y datos de evaluación. Pasos para construir un modelo de machine learning Construir un modelo de Machine Learning , no se reduce solo a utilizar un algoritmo de aprendizaje o utilizar una librería de Machine Learning ; sino que es todo un proceso que suele involucrar los siguientes pasos: Recolectar los datos . Podemos recolectar los datos desde muchas fuentes, podemos por ejemplo extraer los datos de un sitio web o obtener los datos utilizando una API o desde una base de datos. Podemos también utilizar otros dispositivos que recolectan los datos por nosotros; o utilizar datos que son de dominio público. El número de opciones que tenemos para recolectar datos no tiene fin!. Este paso parece obvio, pero es uno de los que más complicaciones trae y más tiempo consume. Preprocesar los datos . Una vez que tenemos los datos, tenemos que asegurarnos que tiene el formato correcto para nutrir nuestro algoritmo de aprendizaje. Es prácticamente inevitable tener que realizar varias tareas de preprocesamiento antes de poder utilizar los datos. Igualmente este punto suele ser mucho más sencillo que el paso anterior. Explorar los datos . Una vez que ya tenemos los datos y están con el formato correcto, podemos realizar un pre análisis para corregir los casos de valores faltantes o intentar encontrar a simple vista algún patrón en los mismos que nos facilite la construcción del modelo. En esta etapa suelen ser de mucha utilidad las medidas estadísticas y los gráficos en 2 y 3 dimensiones para tener una idea visual de como se comportan nuestros datos. En este punto podemos detectar valores atípicos que debamos descartar; o encontrar las características que más influencia tienen para realizar una predicción. Entrenar el algoritmo . Aquí es donde comenzamos a utilizar las técnicas de Machine Learning realmente. En esta etapa nutrimos al o los algoritmos de aprendizaje con los datos que venimos procesando en las etapas anteriores. La idea es que los algoritmos puedan extraer información útil de los datos que le pasamos para luego poder hacer predicciones. Evaluar el algoritmo . En esta etapa ponemos a prueba la información o conocimiento que el algoritmo obtuvo del entrenamiento del paso anterior. Evaluamos que tan preciso es el algoritmo en sus predicciones y si no estamos muy conforme con su rendimiento, podemos volver a la etapa anterior y continuar entrenando el algoritmo cambiando algunos parámetros hasta lograr un rendimiento aceptable. Utilizar el modelo . En esta ultima etapa, ya ponemos a nuestro modelo a enfrentarse al problema real. Aquí también podemos medir su rendimiento, lo que tal vez nos obligue a revisar todos los pasos anteriores. Librerías de Python para machine learning Como siempre me gusta comentar, una de las grandes ventajas que ofrece Python sobre otros lenguajes de programación; es lo grande y prolifera que es la comunidad de desarrolladores que lo rodean; comunidad que ha contribuido con una gran variedad de librerías de primer nivel que extienden la funcionalidades del lenguaje. Para el caso de Machine Learning , las principales librerías que podemos utilizar son: Scikit-Learn Scikit-learn es la principal librería que existe para trabajar con Machine Learning , incluye la implementación de un gran número de algoritmos de aprendizaje. La podemos utilizar para clasificaciones , extraccion de características , regresiones , agrupaciones , reducción de dimensiones , selección de modelos , o preprocesamiento . Posee una API que es consistente en todos los modelos y se integra muy bien con el resto de los paquetes científicos que ofrece Python . Esta librería también nos facilita las tareas de evaluación, diagnostico y validaciones cruzadas ya que nos proporciona varios métodos de fábrica para poder realizar estas tareas en forma muy simple. Statsmodels Statsmodels es otra gran librería que hace foco en modelos estadísticos y se utiliza principalmente para análisis predictivos y exploratorios. Al igual que Scikit-learn , también se integra muy bien con el resto de los paquetes cientificos de Python . Si deseamos ajustar modelos lineales, hacer una análisis estadístico, o tal vez un poco de modelado predictivo, entonces Statsmodels es la librería ideal. Las pruebas estadísticas que ofrece son bastante amplias y abarcan tareas de validación para la mayoría de los casos. PyMC pyMC es un módulo de Python que implementa modelos estadísticos bayesianos, incluyendo la cadena de Markov Monte Carlo(MCMC) . pyMC ofrece funcionalidades para hacer el análisis bayesiano lo mas simple posible. Incluye los modelos bayesianos , distribuciones estadísticas y herramientas de diagnostico para la covarianza de los modelos. Si queremos realizar un análisis bayesiano esta es sin duda la librería a utilizar. NTLK NLTK es la librería líder para el procesamiento del lenguaje natural o NLP por sus siglas en inglés. Proporciona interfaces fáciles de usar a más de 50 cuerpos y recursos léxicos, como WordNet , junto con un conjunto de bibliotecas de procesamiento de texto para la clasificación, tokenización, el etiquetado, el análisis y el razonamiento semántico. Obviamente, aquí solo estoy listando unas pocas de las muchas librerías que existen en Python para trabajar con problemas de Machine Learning , los invito a realizar su propia investigación sobre el tema. Algoritmos más utilizados Los algoritmos que más se suelen utilizar en los problemas de Machine Learning son los siguientes: Regresión Lineal Regresión Logística Arboles de Decision Random Forest SVM o Máquinas de vectores de soporte. KNN o K vecinos más cercanos. K-means Todos ellos se pueden aplicar a casi cualquier problema de datos y obviamente estan todos implementados por la excelente librería de Python , Scikit-learn . Veamos algunos ejemplos de ellos. Regresión Lineal Se utiliza para estimar los valores reales (costo de las viviendas, el número de llamadas, ventas totales, etc.) basados en variables continuas. La idea es tratar de establecer la relación entre las variables independientes y dependientes por medio de ajustar una mejor línea recta con respecto a los puntos. Esta línea de mejor ajuste se conoce como línea de regresión y esta representada por la siguiente ecuación lineal: $$Y = \\beta_{0} + \\beta_{1}X_{1} + \\beta_{2}X_{2} + ... + \\beta_{n}X_{n}$$ Veamos un pequeño ejemplo de como se implementa en Python . En este ejemplo voy a utilizar el dataset Boston que ya viene junto con Scikit-learn y es ideal para practicar con Regresiones Lineales ; el mismo contiene precios de casas de varias áreas de la ciudad de Boston. In [1]: # graficos embebidos % matplotlib inline In [2]: # importando pandas, numpy y matplotlib import pandas as pd import numpy as np import matplotlib.pyplot as plt # importando los datasets de sklearn from sklearn import datasets boston = datasets . load_boston () boston_df = pd . DataFrame ( boston . data , columns = boston . feature_names ) boston_df [ 'TARGET' ] = boston . target boston_df . head () # estructura de nuestro dataset. Out[2]: .dataframe thead tr:only-child th { text-align: right; } .dataframe thead th { text-align: left; } .dataframe tbody tr th { vertical-align: top; } CRIM ZN INDUS CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT TARGET 0 0.00632 18.0 2.31 0.0 0.538 6.575 65.2 4.0900 1.0 296.0 15.3 396.90 4.98 24.0 1 0.02731 0.0 7.07 0.0 0.469 6.421 78.9 4.9671 2.0 242.0 17.8 396.90 9.14 21.6 2 0.02729 0.0 7.07 0.0 0.469 7.185 61.1 4.9671 2.0 242.0 17.8 392.83 4.03 34.7 3 0.03237 0.0 2.18 0.0 0.458 6.998 45.8 6.0622 3.0 222.0 18.7 394.63 2.94 33.4 4 0.06905 0.0 2.18 0.0 0.458 7.147 54.2 6.0622 3.0 222.0 18.7 396.90 5.33 36.2 In [3]: # importando el modelo de regresión lineal from sklearn.linear_model import LinearRegression rl = LinearRegression () # Creando el modelo. rl . fit ( boston . data , boston . target ) # ajustando el modelo Out[3]: LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False) In [4]: # Lista de coeficientes B para cada X list ( zip ( boston . feature_names , rl . coef_ )) Out[4]: [('CRIM', -0.10717055656035711), ('ZN', 0.046395219529796805), ('INDUS', 0.020860239532172288), ('CHAS', 2.6885613993179822), ('NOX', -17.795758660308522), ('RM', 3.8047524602580101), ('AGE', 0.00075106170332574131), ('DIS', -1.4757587965198171), ('RAD', 0.30565503833910218), ('TAX', -0.012329346305270897), ('PTRATIO', -0.95346355469055977), ('B', 0.0093925127221893244), ('LSTAT', -0.5254666329007841)] In [5]: # haciendo las predicciones predicciones = rl . predict ( boston . data ) predicciones_df = pd . DataFrame ( predicciones , columns = [ 'Pred' ]) predicciones_df . head () # predicciones de las primeras 5 lineas Out[5]: .dataframe thead tr:only-child th { text-align: right; } .dataframe thead th { text-align: left; } .dataframe tbody tr th { vertical-align: top; } Pred 0 30.008213 1 25.029861 2 30.570232 3 28.608141 4 27.942882 In [6]: # Calculando el desvio np . mean ( boston . target - predicciones ) Out[6]: 5.6871503553921065e-15 Como podemos ver, el desvío del modelo es pequeño, por lo que sus resultados para este ejemplo son bastante confiables. Regresión Logística Los modelos lineales, también pueden ser utilizados para clasificaciones; es decir, que primero ajustamos el modelo lineal a la probabilidad de que una cierta clase o categoría ocurra y, a luego, utilizamos una función para crear un umbral en el cual especificamos el resultado de una de estas clases o categorías. La función que utiliza este modelo, no es ni más ni menos que la función logística. $$f(x) = \\frac{1}{1 + e^{-1}}$$ Veamos, aquí también un pequeño ejemplo en Python . In [7]: # Creando un dataset de ejemplo from sklearn.datasets import make_classification X , y = make_classification ( n_samples = 1000 , n_features = 4 ) In [8]: # Importando el modelo from sklearn.linear_model import LogisticRegression rlog = LogisticRegression () # Creando el modelo # Dividiendo el dataset en entrenamiento y evaluacion X_entrenamiento = X [: - 200 ] X_evaluacion = X [ - 200 :] y_entrenamiento = y [: - 200 ] y_evaluacion = y [ - 200 :] rlog . fit ( X_entrenamiento , y_entrenamiento ) #ajustando el modelo # Realizando las predicciones y_predic_entrenamiento = rlog . predict ( X_entrenamiento ) y_predic_evaluacion = rlog . predict ( X_evaluacion ) In [9]: # Verificando la exactitud del modelo entrenamiento = ( y_predic_entrenamiento == y_entrenamiento ) . sum () . astype ( float ) / y_entrenamiento . shape [ 0 ] print ( \"sobre datos de entrenamiento: {0:.2f} \" . format ( entrenamiento )) evaluacion = ( y_predic_evaluacion == y_evaluacion ) . sum () . astype ( float ) / y_evaluacion . shape [ 0 ] print ( \"sobre datos de evaluación: {0:.2f} \" . format ( evaluacion )) sobre datos de entrenamiento: 0.92 sobre datos de evaluación: 0.91 Como podemos ver en este ejemplo también nuestro modelo tiene bastante precisión clasificando las categorías de nuestro dataset. Arboles de decisión Los Arboles de Decision son diagramas con construcciones lógicas, muy similares a los sistemas de predicción basados en reglas, que sirven para representar y categorizar una serie de condiciones que ocurren de forma sucesiva, para la resolución de un problema. Los Arboles de Decision están compuestos por nodos interiores, nodos terminales y ramas que emanan de los nodos interiores. Cada nodo interior en el árbol contiene una prueba de un atributo, y cada rama representa un valor distinto del atributo. Siguiendo las ramas desde el nodo raíz hacia abajo, cada ruta finalmente termina en un nodo terminal creando una segmentación de los datos. Veamos aquí también un pequeño ejemplo en Python . In [10]: # Creando un dataset de ejemplo X , y = datasets . make_classification ( 1000 , 20 , n_informative = 3 ) # Importando el arbol de decisión from sklearn.tree import DecisionTreeClassifier from sklearn import tree ad = DecisionTreeClassifier ( criterion = 'entropy' , max_depth = 5 ) # Creando el modelo ad . fit ( X , y ) # Ajustando el modelo #generando archivo para graficar el arbol with open ( \"mi_arbol.dot\" , 'w' ) as archivo_dot : tree . export_graphviz ( ad , out_file = archivo_dot ) In [11]: # utilizando el lenguaje dot para graficar el arbol. ! dot -Tjpeg mi_arbol.dot -o arbol_decision.jpeg Luego de usar el lenguaje dot para convertir nuestro arbol a formato jpeg, ya podemos ver la imagen del mismo. In [12]: # verificando la precisión print ( \"precisión del modelo: {0: .2f} \" . format (( y == ad . predict ( X )) . mean ())) precisión del modelo: 0.96 En este ejemplo, nuestro árbol tiene una precisión del 89%. Tener en cuenta que los Arboles de Decision tienen tendencia a sobreentrenar los datos. Random Forest En lugar de utilizar solo un arbol para decidir, ¿por qué no utilizar todo un bosque?!!. Esta es la idea central detrás del algoritmo de Random Forest . Tarbaja construyendo una gran cantidad de arboles de decision muy poco profundos, y luego toma la clase que cada árbol eligió. Esta idea es muy poderosa en Machine Learning . Si tenemos en cuenta que un sencillo clasificador entrenado podría tener sólo el 60 por ciento de precisión, podemos entrenar un montón de clasificadores que sean por lo general acertados y luego podemos utilizar la sabiduría de todos los aprendices juntos. Con Python los podemos utilizar de la siguiente manera: In [13]: # Creando un dataset de ejemplo X , y = datasets . make_classification ( 1000 ) # Importando el random forest from sklearn.ensemble import RandomForestClassifier rf = RandomForestClassifier () # Creando el modelo rf . fit ( X , y ) # Ajustando el modelo # verificando la precisión print ( \"precisión del modelo: {0: .2f} \" . format (( y == rf . predict ( X )) . mean ())) precisión del modelo: 0.99 SVM o Máquinas de vectores de soporte La idea detrás de SVM es encontrar un plano que separe los grupos dentro de los datos de la mejor forma posible. Aquí, la separación significa que la elección del plano maximiza el margen entre los puntos más cercanos en el plano; éstos puntos se denominan vectores de soporte. Pasemos al ejemplo. In [14]: # importanto SVM from sklearn import svm # importando el dataset iris iris = datasets . load_iris () X = iris . data [:, : 2 ] # solo tomamos las primeras 2 características y = iris . target h = . 02 # tamaño de la malla del grafico # Creando el SVM con sus diferentes métodos C = 1.0 # parametro de regulacion SVM svc = svm . SVC ( kernel = 'linear' , C = C ) . fit ( X , y ) rbf_svc = svm . SVC ( kernel = 'rbf' , gamma = 0.7 , C = C ) . fit ( X , y ) poly_svc = svm . SVC ( kernel = 'poly' , degree = 3 , C = C ) . fit ( X , y ) lin_svc = svm . LinearSVC ( C = C ) . fit ( X , y ) # crear el area para graficar x_min , x_max = X [:, 0 ] . min () - 1 , X [:, 0 ] . max () + 1 y_min , y_max = X [:, 1 ] . min () - 1 , X [:, 1 ] . max () + 1 xx , yy = np . meshgrid ( np . arange ( x_min , x_max , h ), np . arange ( y_min , y_max , h )) # titulos de los graficos titles = [ 'SVC con el motor lineal' , 'LinearSVC' , 'SVC con el motor RBF' , 'SVC con el motor polinomial' ] for i , clf in enumerate (( svc , lin_svc , rbf_svc , poly_svc )): # Realizando el gráfico, se le asigna un color a cada punto plt . subplot ( 2 , 2 , i + 1 ) plt . subplots_adjust ( wspace = 0.4 , hspace = 0.4 ) Z = clf . predict ( np . c_ [ xx . ravel (), yy . ravel ()]) Z = Z . reshape ( xx . shape ) plt . contourf ( xx , yy , Z , cmap = plt . cm . Paired , alpha = 0.8 ) # Graficando tambien los puntos de datos plt . scatter ( X [:, 0 ], X [:, 1 ], c = y , cmap = plt . cm . Paired ) plt . xlabel ( 'largo del petalo' ) plt . ylabel ( 'ancho del petalo' ) plt . xlim ( xx . min (), xx . max ()) plt . ylim ( yy . min (), yy . max ()) plt . xticks (()) plt . yticks (()) plt . title ( titles [ i ]) plt . show () KNN o k vecinos más cercanos Este es un método de clasificación no paramétrico, que estima el valor de la probabilidad a posteriori de que un elemento $x$ pertenezca a una clase en particular a partir de la información proporcionada por el conjunto de prototipos. La regresión KNN se calcula simplemente tomando el promedio del punto k más cercano al punto que se está probando. In [15]: # Creando el dataset iris iris = datasets . load_iris () X = iris . data y = iris . target iris . feature_names Out[15]: ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)'] In [16]: # importando KNN from sklearn.neighbors import KNeighborsRegressor knnr = KNeighborsRegressor ( n_neighbors = 10 ) # Creando el modelo con 10 vecinos knnr . fit ( X , y ) # Ajustando el modelo # Verificando el error medio del modelo print ( \"El error medio del modelo es: {:.2f} \" . format ( np . power ( y - knnr . predict ( X ), 2 ) . mean ())) El error medio del modelo es: 0.02 K-means K-means es probablemente uno de los algoritmos de agrupamiento más conocidos y, en un sentido más amplio, una de las técnicas de aprendizaje no supervisado más conocidas. K-means es en realidad un algoritmo muy simple que funciona para reducir al mínimo la suma de las distancias cuadradas desde la media dentro del agrupamiento. Para hacer esto establece primero un número previamente especificado de conglomerados, K, y luego va asignando cada observación a la agrupación más cercana de acuerdo a su media. Veamos el ejemplo In [17]: # Creando el dataset grupos , pos_correcta = datasets . make_blobs ( 1000 , centers = 3 , cluster_std = 1.75 ) # Graficando los grupos de datos f , ax = plt . subplots ( figsize = ( 7 , 5 )) colores = [ 'r' , 'g' , 'b' ] for i in range ( 3 ): p = grupos [ pos_correcta == i ] ax . scatter ( p [:, 0 ], p [:, 1 ], c = colores [ i ], label = \"Grupo {} \" . format ( i )) ax . set_title ( \"Agrupamiento perfecto\" ) ax . legend () plt . show () In [18]: # importando KMeans from sklearn.cluster import KMeans # Creando el modelo kmeans = KMeans ( n_clusters = 3 ) kmeans . fit ( grupos ) # Ajustando el modelo Out[18]: KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300, n_clusters=3, n_init=10, n_jobs=1, precompute_distances='auto', random_state=None, tol=0.0001, verbose=0) In [19]: # verificando los centros de los grupos kmeans . cluster_centers_ Out[19]: array([[-9.90500465, -4.48254047], [-8.1907267 , 7.77491011], [ 1.9875472 , 4.07789958]]) In [20]: # Graficando segun modelo f , ax = plt . subplots ( figsize = ( 7 , 5 )) colores = [ 'r' , 'g' , 'b' ] for i in range ( 3 ): p = grupos [ pos_correcta == i ] ax . scatter ( p [:, 0 ], p [:, 1 ], c = colores [ i ], label = \"Grupo {} \" . format ( i )) ax . scatter ( kmeans . cluster_centers_ [:, 0 ], kmeans . cluster_centers_ [:, 1 ], s = 100 , color = 'black' , label = 'Centros' ) ax . set_title ( \"Agrupamiento s/modelo\" ) ax . legend () plt . show () Con esto doy por concluída esta introducción al Machine Learning con Python , espero les sea útil. Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Machine Learning","url":"https://relopezbriega.github.io/blog/2015/10/10/machine-learning-con-python/"},{"title":"De tíos, primos, teoremas y conjeturas","text":"El tío Petros y la conjetura Hace un tiempo atrás, quede atrapado en la lectura de la apasionante novela de Apostolos Doxiadis, El tío Petros y la conjetura de Goldbach . La novela trata basicamente de la relación entre un joven, en busca de su vocación, y su tío, quien en el pasado fue un prodigio de las matemáticas, pero que luego se recluyó de su familia y de la comunidad científica consumido por el intento solitario de demostrar uno de los problemas abiertos más difíciles de la teoría de números , la Conjetura de Goldbach . Lo que más me sorprendió del problema que consumió la vida del querido tío Petros, es lo simple que es su enunciado; la Conjetura de Goldbach nos dice que \"Todo número par mayor que 2 puede escribirse como suma de dos números primos.\" Este enunciado, junto con la otra conjetura postulada por Goldbach , conocida como la Conjetura debil de Goldbach , que nos dice que \"Todo número impar mayor que 5 puede expresarse como suma de tres números primos.\" ; trae a colación el concepto de los números primos como bloques constructores de los enteros. Los números primos Pero, ¿cómo es esto de que los números primos pueden ser considerados como bloques constructores de los números enteros? Un número primo es un entero positivo que solo puede ser dividido en dos factores distintos, 1 y si mismo. De esta definición se desprende que el número 1 no es un número primo ya que solo puede ser dividido en un solo factor ; en cambio, el número 2 si es primo , el único número primo que es par. La idea de que los números primos pueden ser considerados como bloques constructores de los números enteros surge del enunciado del Teorema fundamental de la aritmética que nos dice que \"Todo entero positivo puede ser representado de forma única como un producto de factores primos\" ; así por ejemplo el número $28$ puede ser representado como $2^2 * 7$; o el $60$ como $2^2 * 3 * 5$. Este teorema es un concepto fundamental en criptografía , los principales algoritmos de cifrado que se utilizan hoy en día residen en la factorización de primos , ya que es un proceso que requiere de mucho esfuerzo para calcularse mientras más grande sea el número que queremos factorizar. Otro aspecto interesante de los números primos es que parecen surgir en forma aleatoria, hay veces que pueden aparecer en pares como (11, 13), (29, 31) o (59, 61) pero otras veces puede haber un largo intervalo entre ellos. Aún no se ha encontrado una formula que pueda predecir cual va a ser el próximo número primo . Como parece ser bastante difícil encontrar un patrón en los números primos , el esfuerzo de los matemáticos paso de intentar encontrar un patrón a intentar comprender la distribución de los números primos dentro de todos los enteros; es decir, intentar responder la pregunta de ¿Cuál es la probabilidad de que un número sea primo si elijo un número al azar en el rango de 0 a N?. Uno de los primeros en dar una respuesta bastante aproximada a esta pregunta fue Gauss , quien con tan solo 15 años de edad propuso la formula $\\pi(x)\\approx\\frac{x}{ln(x)}$ para responder a esa pregunta. La formula propuesta Gauss implica que a medida que los números se hacen cada vez más grandes, los números primos son cada vez más escasos. Esto es lo que se conoce como teorema de los números primos . A pesar de que los números primos son cada vez más escasos mientras más grandes, siguen surgiendo indefinidamente, son infinitos; esto esta bien demostrado por el ya famoso teorema de Euclides ; quien incluyo una de las más bellas demostraciones de las matemáticas en su obra Elementos en el 300 AC. Encontrando los números primos Para encontrar todos los números primos menores que un número N, uno de los algoritmos más eficientes y más fáciles de utilizar es lo que se conoce como la criba de Eratóstenes , el procedimiento que se utiliza consiste en crear una tabla con todos los números naturales comprendidos entre 2 y N, y luego ir tachando los números que no son primos de la siguiente manera: Comenzando por el 2, se tachan todos sus múltiplos; comenzando de nuevo, cuando se encuentra un número entero que no ha sido tachado, ese número es declarado primo, y se procede a tachar todos sus múltiplos, así sucesivamente. La siguiente animación describe el procedimiento graficamente. Ejemplo en Python Veamos un ejemplo en Python de como implementar la criba de Eratóstenes y un proceso de factorización de primos . In [1]: # Factorizando primos en Python import numpy as np def criba_eratostenes ( n ): \"\"\"Criba Eratostenes\"\"\" l = [] multiplos = set () for i in range ( 2 , n + 1 ): if i not in multiplos : l . append ( i ) multiplos . update ( range ( i * i , n + 1 , i )) return l def factorizar_primos ( n ): \"\"\"Factoriza un entero positivo en primos >>>factorizar_primos(28) [2, 2, 7] \"\"\" if n <= 1 : return \"Ingresar un entero mayor a 1\" factores = [] primos = criba_eratostenes ( n ) pindex = 0 p = primos [ pindex ] num = n while p != num : if num % p == 0 : factores . append ( p ) num //= p else : pindex += 1 p = primos [ pindex ] factores . append ( p ) return factores factorizar_primos ( 28 ) # Factores primos de 28 Out[1]: [2, 2, 7] In [2]: # Factores primos de 1982 factorizar_primos ( 1982 ) Out[2]: [2, 991] In [3]: # Factores primos de 2015 factorizar_primos ( 2015 ) Out[3]: [5, 13, 31] La conjetura de Goldbach y Python La Conjetura de Goldbach , es otro ejemplo de como también podemos construir todos los números enteros con simples operaciones aritméticas como la suma y no más que tres números primos . Al día de hoy, la conjetura continua sin poder ser demostrada; y es considerada uno de los problemas más difíciles de las matemáticas. La mayoría de los matemáticos estiman que es verdadera, ya que se ha mostrado cierta hasta por lo menos el $10^{18}$; aunque algunos dudan que sea cierta para números extremadamente grandes, el gran Ramanujan dicen que se incluía en este último grupo. Obviamente, como este blog esta dedicado a Python , no podría concluir este artículo sin incluir una implementación de la Conjetura de Goldbach en uno de los lenguajes que mejor se lleva con las matemáticas! En esta implementación vamos a utilizar tres funciones, en primer lugar una criba de primos vectorizada utilizando numpy , para lograr un mejor rendimiento que con la criba de Eratóstenes del ejemplo anterior; y luego vamos a tener dos sencillas funciones adicionales, una que nos devuelva la composición de Goldbach para cualquier número par que le pasemos como parámetro.(esta función solo nos va a devolver la primer solución que encuentra, ya que pueden existir varias soluciones para algunos enteros pares). Por último, la restante función va a listar los resultados de la Conjetura de Goldbach para un rango de números enteros. In [4]: # La conjetura de Goldbach en Python import numpy as np def criba_primos ( n ): \"\"\"Criba generadora de números primos. Input n>=6, devuleve un array de primos, 2 <= p < n \"\"\" criba = np . ones ( n / 3 + ( n % 6 == 2 ), dtype = np . bool ) for i in range ( 1 , int ( n ** 0.5 / 3 + 1 )): if criba [ i ]: k = 3 * i + 1 | 1 criba [ k * k / 3 :: 2 * k ] = False criba [ k * ( k - 2 * ( i & 1 ) + 4 ) / 3 :: 2 * k ] = False return np . r_ [ 2 , 3 , (( 3 * np . nonzero ( criba )[ 0 ][ 1 :] + 1 ) | 1 )] def goldbach ( n ): \"\"\"imprime la composición de goldbach para n. >>> goldbach(28) (5, 23) \"\"\" primos = criba_primos ( n ) lo = 0 hi = len ( primos ) - 1 while lo <= hi : sum = primos [ lo ] + primos [ hi ] if sum == n : break elif sum < n : lo += 1 else : hi -= 1 else : print ( \"No se encontro resultado de la conjetura de Goldbach para {} \" . format ( n )) return primos [ lo ], primos [ hi ] def goldbach_list ( lower , upper ): \"\"\"Imprime la composición de Goldbach para todos los números pares grandes que 'lower' y menores o iguales que 'upper'. >>> goldbach_list(9,20) 10 = 3 + 7 12 = 5 + 7 14 = 3 + 11 16 = 3 + 13 18 = 5 + 13 20 = 3 + 17 \"\"\" # La conjetura se aplica a pares mayores que 2. if lower % 2 != 0 : lower += 1 if lower < 4 : lower = 4 if upper % 2 != 0 : upper -= 1 for n in range ( lower , upper + 1 , 2 ): gb = goldbach ( n ) print ( \" {0} = {1} + {2} \" . format ( n , gb [ 0 ], gb [ 1 ])) # Goldbach entre 2000 y 2016 goldbach_list ( 2000 , 2016 ) 2000 = 3 + 1997 2002 = 3 + 1999 2004 = 5 + 1999 2006 = 3 + 2003 2008 = 5 + 2003 2010 = 7 + 2003 2012 = 13 + 1999 2014 = 3 + 2011 2016 = 5 + 2011 In [5]: # Goldbach entre 9.999.980 y 10.000.000 goldbach_list ( 9999980 , 10000000 ) 9999980 = 7 + 9999973 9999982 = 11 + 9999971 9999984 = 11 + 9999973 9999986 = 13 + 9999973 9999988 = 17 + 9999971 9999990 = 17 + 9999973 9999992 = 19 + 9999973 9999994 = 3 + 9999991 9999996 = 5 + 9999991 9999998 = 7 + 9999991 10000000 = 29 + 9999971 In [6]: # Goldbach entre 99.999.980 y 100.000.000 goldbach_list ( 99999980 , 100000000 ) 99999980 = 193 + 99999787 99999982 = 11 + 99999971 99999984 = 13 + 99999971 99999986 = 139 + 99999847 99999988 = 17 + 99999971 99999990 = 19 + 99999971 99999992 = 3 + 99999989 99999994 = 5 + 99999989 99999996 = 7 + 99999989 99999998 = 67 + 99999931 100000000 = 11 + 99999989 Con esto termino, el que quiera puede divertirse intentando comprobar la Conjetura de Goldbach , aunque corre el riesgo de terminar desperdiciando su tiempo como el bueno del tío Petros en la novela. Espero que les haya parecido interesante el artículo. Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su versión estática en nbviewer .","tags":"Matematica","url":"https://relopezbriega.github.io/blog/2015/09/13/de-tios-primos-teoremas-y-conjeturas/"},{"title":"Introducción a Finanzas con Python","text":"Introducción En el vertiginoso mundo actual de las finanzas ; dónde la velocidad, frecuencia y volumen de los datos aumentan a un ritmo considerable; la aplicación combinada de tecnología y software, junto con algoritmos avanzados y diferentes métodos para recopilar, procesar y analizar datos se ha vuelto fundamental para obtener la información necesaria para una eficiente toma de decisiones. Es dentro de este contexto, que se viene produciendo un gran crecimento en la utilización de Python dentro de la industria de las finanzas . Python se esta comenzando a utilizar ampliamente en diversos sectores de las finanzas , como la banca, la gestión de inversiones, los seguros, e incluso en los bienes raíces; se utiliza principalmente para la construcción de herramientas que ayuden en la creación de modelos financieros , gestión de riesgos , y el comercio. Incluso las grandes corporaciones financieras, como Bank of America o JP Morgan , estan comenzando a utilizar Python para construir su infraestructura para la gestión de posiciones financieras, precios de activos , gestión de riesgos , sistemas de comercio y comercio algoritmico . Algunas de las razones que hacen de Python un lenguaje de programación tan atractivo en el mundo de las finanzas son: Su simple sintaxis : Python es mundialmente conocido por lo fácil que resulta leerlo, muchas veces no existen casi diferencias entre seudo código y Python ; tampoco suelen existir muchas diferencias entre expresar un algoritmo matematicamente o en Python . Su ecosistema : Python es mucho más que un lenguaje de programación , es todo un ecosistema en sí mismo; ya que podemos encontrar un sinnúmero de herramientas para realizar cualquier tipo de tareas; en Python podemos encontrar módulos para realizar cálculos científicos, módulos para desarrollar aplicaciones webs, módulos para realizar tareas de administración de sistemas, módulos para trabajar con bases de datos; entre otros. Todos ellos muy fácilmente integrables dentro del lenguaje. La variedad del ecosistema de herramientas de Python , nos ofrece la posibilidad de desarrollar una solución completa a cualquier tipo de problema utilizando un solo lenguaje de programación . Su integración : Otras de las características por la que Python es también famoso, es por su fácil integración con otros lenguajes de programación . Generalmente, las grandes empresas suelen tener herramientas desarrolladas en distintos lenguajes de programación ; las características dinámicas de Python , hacen que sea ideal para unir todos esos distintos componentes en una sola gran aplicación. Python puede ser enlazado fácilmente a herramientas desarrollas en C , C++ o Fortran . Eficiencia y productividad : Por último, otra de las características que hacen a Python tan atractivo, es que con él, se pueden lograr resultados de calidad en una forma mucho más eficiente y productiva. La mayoría de sus módulos están ampliamente testeados y cuentan con el soporte de una amplia comunidad de usuarios; sus características dinámicas e interactivas lo hacen ideal para el análisis exploratorio de datos facilitando los análisis financieros. También es sabido, que la elegancia de su sintaxis hace que se necesiten mucho menos líneas de código para desarrollar un programa en Python que en casi cualquier otro lenguaje de programación . Principales librerías Las principales librerías que vamos a utilizar para realizar tareas de analisis financiero con Python son muchas de las que ya he venido hablando en anteriores artículos; principalmente: Pandas : La cual fue diseñada desde un comienzo para facilitar el análisis de datos financieros, principalmente las series de tiempo propias del mercado cambiario de acciones. Con las estructuras de datos que nos brinda esta librería se vuelve sumamente fácil modelar y resolver problemas financieros. Numpy : El principal modulo matemático que nos ofrece Python , en el no solo vamos a encontrar las siempre prácticas matrices que facilitan en sobremanera el manejo de información numérica; sino que también vamos a poder encontrar un gran número de funciones matemáticas. Matplotlib : La siempre vigente librería para realizar gráficos en Python . statsmodels : Si de estadística se trata, no hay como esta librería para realizar cualquier tipo de analisis estadístico. PuLP : La cual nos permite crear modelos de programación lineal en forma muy sencilla. Quandl : Este módulo nos permite interactuar fácilmente con la API de quandl.com para obtener en forma muy sencilla todo tipo de información financiera. Zipline : Zipline es una librería para el comercio algoritmico ; esta basada en eventos y trata de aproximarse lo más cerca posible a como operan los verdades sistemas de comercio electrónico de las principales bolsas del mundo. Zipline es una de las principales tecnologías detrás del popular sitio quantopian.com , la comunidad web que pone a prueba distintos algoritmos de comercio algoritmico . Bueno, pero basta de introducciones y pasamos a describir los principales conceptos financieros y como podemos calcularlos con la ayuda de Python , ya que el tiempo es dinero!!. Conceptos básicos de Finanzas Los conceptos más básicos que podemos encontrar dentro de las finanzas son: valor futuro , valor presente , y la tasa interna de retorno . Estos conceptos nos dicen cuanto nuestro dinero va a crecer si lo depositamos en un banco ( valor futuro ), cuanto vale hoy la promesa de unos pagos que recibiremos en el futuro( valor presente ), y qué tasa de rendimiento podemos obtener de nuestras inversiones ( tasa interna de retorno ). Recordemos que todos los activos financieros y toda planificación financiera siempre tiene una dimensión de tiempo; así por ejemplo si depositamos USD 100 en un banco que nos paga una tasa de interés anual de 6% , luego de un año obtendríamos un importe de USD 106. Valor Futuro El valor futuro o FV (por sus siglas en inglés), nos indica el valor en el futuro que tendrá el dinero depositado hoy en una cuenta bancaria que nos pague intereses. El valor futuro de USD X depositado hoy en una cuenta que paga r% de interés anual y que es dejado en la cuenta durante n años es $X * (1 + r)^n$. El valor futuro es nuestro primer ejemplo de interés compuesto , es decir, el principio de que podemos ganar intereses sobre los intereses. De la definición que dimos del valor futuro , podemos obtener su expresión matemática: $$FV = X * ( 1 + r )^n$$ Como podemos ver, su cálculo es bastante simple. Veamos un ejemplo de como calcular el FV de un depósito de USD 1000 a 3 años y con una tasa de interés del 6% anual. In [1]: # graficos embebidos % matplotlib inline In [2]: # Ejemplo FV con python # $1000 al 6% anual por 3 años. # importando librerías import numpy as np import matplotlib.pyplot as plt x = - 1000 # deposito r = . 06 # tasa de interes n = 3 # cantidad de años # usando la funcion fv de numpy FV = np . fv ( pv = x , rate = r , nper = n , pmt = 0 ) FV Out[2]: 1191.016 In [3]: # Controlando el resultado x * ( 1 + r ) ** n Out[3]: -1191.016 In [4]: # Graficando las funciones con interes de 6 y 12 % a 20 años. t = list ( range ( 0 , 21 )) def fv6 ( num ): return np . fv ( pv =- 1000 , rate = r , pmt = 0 , nper = num ) def fv12 ( num ): return np . fv ( pv =- 1000 , rate =. 12 , pmt = 0 , nper = num ) In [5]: plt . title ( \"Graficando FV 6 y 12 %\" ) plt . plot ( t , fv6 ( t ), label = \"interes 6 %\" ) plt . plot ( t , fv12 ( t ), label = \"interes 12 %\" ) plt . legend ( loc = 'upper left' ) plt . show () Al graficar dos funciones de FV , una con una tasa de interes del 6% y otra con una tasa más alta del 12%, podemos ver que el valor futuro suele ser bastante sensitivo a los cambios en la tasa de interes, pequeñas variaciones en ella pueden generar grandes saltos a lo largo del tiempo. Anualidades Como podemos ver en el ejemplo anterior, la función fv de Numpy tiene varios parámetros, esto es así, porque existen otros casos en los que el cálculo del valor futuro se puede volver más complicado; es aquí cuando comenzamos a hablar de anulidades . La idea de las anualidades es no solo quedarse con el cálculo simple de cuanto me va a rendir un solo deposito inicial a fin de un período, sino también poder calcular el valor futuro de múltiples depósitos que se reinvierten a una misma tasa de interés. Supongamos por ejemplo que queremos hacer 10 depósitos anuales de USD 1000 cada uno, los cuales vamos a ir depositando al comienzo de cada año. ¿Cuál sería en este caso el valor futuro de nuestra anualidad luego del décimo año?. Ayudémonos de Python para calcular la respuesta! In [6]: # Calculando el valor de la anualidad con 6% anual x = - 1000 # valor de depositos r = . 06 # tasa de interes n = 10 # cantidad de años # usando la funcion fv de numpy FV = np . fv ( pv = 0 , rate = r , nper = n , pmt = x , when = 'begin' ) FV Out[6]: 13971.642638923764 Aquí comenzamos con un valor presente (PV) de cero, luego realizamos el primer deposito de USD 1000 al comienzo del primer año y continuamos con los sucesivos depósitos al comienzo de cada uno de los restantes años. Para poder entender mejor como funciona la función fv de Numpy voy a explicar un poco más sus parámetros. pv = este parametro es el valor presente de nuestra inversión o anualidad ; en nuestro ejemplo empezamos con un valor de cero; ya que luego vamos a ir realizando los diferentes depósitos de USD 1000. rate = es la tasa efectiva de interés que nos rendirá la anualidad por cada período. nper = Es el número de períodos. Tener en cuenta que si aquí estamos utilizando como unidad de medida de años, nuestra tasa de interés deberá estar expresada en la misma unidad. pmt = El valor de los depósitos que vamos a ir invirtiendo en nuestra anualidad . En nuestro caso el valor de -1000 refleja el importe que vamos a ir depositando año a año.(se expresa con signo negativo, ya que un deposito implica una salida de dinero). when = Este parámetro nos dice cuando se van a hacer efectivos nuestros depósitos, ya que el resultado puede ser muy distinto si realizamos el deposito al comienzo(como en nuestro ejemplo) o al final de cada período. In [7]: # mismo caso pero con la diferencia de que los depositos se # realizan al final de cada período. FV = np . fv ( pv = 0 , rate = r , nper = n , pmt = x , when = 'end' ) FV Out[7]: 13180.79494238091 En este último ejemplo, el valor es menor por las perdidas relativas de interés que vamos teniendo al realizar los depósitos al final de cada período en lugar de al comienzo. Valor Presente El valor presente o PV (por sus siglas en inglés), nos indica el valor que tienen hoy un pago o pagos que recibiremos en el futuro. Supongamos por ejemplo que sabemos que un tío nos va a regalar USD 1000 dentro de 3 años porque somos su sobrino favorito, si también sabemos que un banco nos pagaría un 6% de interés por los depósitos en una caja de ahorro, podríamos calcular el valor presente que tendría ese pago futuro de nuestro tío en el día de hoy. La formula para calcular el valor presente la podemos derivar de la que utilizamos para calcular el valor futuro y se expresaría del siguiente modo: $$PV =\\frac{fv}{(1 + r)^n}$$ Aplicando la esta formula sobre los datos con que contamos, podríamos calcular el valor de hoy de la promesa de pago de USD 1000 de nuestro tío, los que nos daría un valor presente de USD 839.62 como se desprende del siguiente cálculo. In [8]: fv = 1000 # valor futuro r = . 06 # tasa de interes n = 3 # cantidad de años fv / (( 1 + r ) ** n ) Out[8]: 839.6192830323018 In [9]: # usando la funcion pv de numpy PV = np . pv ( fv = fv , rate = r , nper = n , pmt = 0 ) PV Out[9]: -839.6192830323018 Estos USD 839.62 en realidad lo que representan es que si nosotros hoy depositáramos en la caja de ahorro de un banco que nos pague 6% anual de interés el importe de USD 839.62, obtendríamos dentro de los 3 años los mismos USD 1000 que nos ofreció dar nuestro tío dentro de 3 años; o lo que es lo mismo que decir que el valor futuro dentro de 3 años de USD 839.62 son los USD 1000 de nuestro querido tío. In [10]: # Calculando el valor futuro de los 839.62 np . fv ( pv =- 839.62 , rate = r , nper = n , pmt = 0 ) Out[10]: 1000.00085392 Valor presente y anualidades Al igual que en el caso del valor futuro , aquí también podemos encontrarnos con las anualidades , es decir, una serie de pagos iguales que recibiremos. El valor presente de una anualidad nos va a decir el valor que tienen hoy los futuros pagos de la anualidad . Así, por ejemplo si nuestro tío en lugar de regalarnos USD 1000 dentro de 3 años, decide darnos USD 250 al final de cada año durante 5 años; y asumiendo la misma tasa de interés que nos ofrece el banco de 6% anual. El valor presente de esta anualidad sería USD 1053.09. In [11]: # Calculando el valor de la anualidad PV = np . pv ( fv = 0 , rate = r , nper = 5 , pmt =- 250 , when = 'end' ) PV Out[11]: 1053.090946391429 Eligiendo la tasa de descuento Uno de los puntos sobre el que hacer más foco al calcular el valor presente de un flujo de fondos futuro, es el de como elegir la tasa para descontar estos fondos, ya que la tasa que utilicemos es la pieza clave para la exactitud de nuestros cálculos. El principio básico que nos debe guiar la elegir la tasa de descuento es el de tratar de elegir que sea apropiada al riesgo y la duración de los flujos de fondos que estamos descontando. En el ejemplo que venimos viendo, como sabemos que nuestro tío es una persona muy solvente y de palabra, podemos considerar que no existe mucho riesgo en ese flujo de fondos, por lo que utilizar la tasa de interés de una caja de ahorro parece ser un buen criterio para descontar ese flujo. En los casos de las empresas, las mismas suelen utilizar el costo del capital como una tasa de descuento apropiada para descontar el flujo futuro de sus inversiones o proyectos. Valor Presente Neto Un concepto que merece una especial mención, por su importancia dentro del mundo de las finanzas, cuando hablamos del valor presente , es el de Valor Presente Neto . Cuando estamos descontando flujos de fondos futuros para traerlos a su valor actual, puede ser que éstos flujos no sean homogeneos, por lo que ya no podríamos tratarlos como una anualidad , ya que los pagos son por importes distintos todos los años; para estos casos debemos utilizar el Valor Presente Neto . El Valor Presente Neto o NPV (por sus siglas en inglés) de una serie de flujos futuros de fondos es su igual a su valor presente menos el importe de la inversión inicial necesaria para obtener esos mismos flujos de fondos futuros. Su expresión matemática sería la siguiente: $$NPV = \\sum\\limits_{t=1}^n \\frac{V_{t}}{(1 + r)^t} - I_{0}$$ donde, $V_{t}$ representa el flujo de fondos de cada período $t$; $I_{0}$ es el valor inicial de la inversión; $r$ es la tasa de descuento utilizada; y $n$ es la cantidad de períodos considerados. Volviendo al ejemplo que veníamos utilizando de nuestro generoso tío, esta vez no ofrece pagarnos USD 500 al final del primer año, USD 750 al final del segundo año, USD 1000 al final del tercer año, USD 1250 al final del cuarto año y USD 500 al final del quinto año. El NPV de este flujo de fondos sería de USD 3342.56. In [12]: # Calculando el valor presente neto. NPV = np . npv ( rate =. 06 , values = [ 0 , 500 , 750 , 1000 , 1250 , 500 ]) NPV Out[12]: 3342.560891731083 El Valor Presente Neto es sumamente utilizado en los análisis financieros, principalmente para evaluar inversiones o proyectos. Como regla general se considera que si el NPV de un proyecto o inversión es positivo, se trata de un proyecto rentable en el que deberíamos invertir; en cambio si el NPV es negativo estamos ante un mal negocio. Si por ejemplo, tendríamos que invertir hoy USD 4000 para poder obtener un flujo de fondos de USD 500 al final del primer año, USD 750 al final del segundo año, USD 1000 al final del tercer año, USD 1250 al final del cuarto año y USD 500 al final del quinto año; estaríamos haciendo un mal negocio, ya que como sabemos el valor presente de esos flujos de fondos es de USD 3342.56, un valor mucho menor a los USD 4000 que deberíamos invertir. In [13]: # Calculando el NPV de la inversión de 4000. NPV = np . npv ( rate =. 06 , values = [ - 4000 , 500 , 750 , 1000 , 1250 , 500 ]) NPV Out[13]: -657.4391082689172 En el ejemplo podemos ver que al utilizar la función npv de Numpy , el primer valor en la lista de valores que le pasamos al parámetro values debe ser el monto de la inversión inicial, y como implica un desembolso de dinero, su signo es negativo. Si en lugar de tener que invertir USD 4000, tendríamos que invertir USD 3000 para obtener el mismo flujo de fondos, ya estaríamos realizando una buena inversión, con NPV positivo. In [14]: # Calculando el NPV de la inversión de 3000. NPV = np . npv ( rate =. 06 , values = [ - 3000 , 500 , 750 , 1000 , 1250 , 500 ]) NPV Out[14]: 342.56089173108285 Tasa interna de Retorno La tasa interna de retorno o IRR (por sus siglas en inglés) es la tasa de descuento que hace que el Valor Presente Neto de los flujos de fondos futuros sea igual a cero ; también puede ser definida como la tasa de interés compuesto que nos paga nuestra inversión. Al igual que como sucede con el Valor Presente Neto , podemos utilizar a la tasa interna de retorno para tomar decisiones financieras. Aquí la regla general es que, al momento de decidir entre diferentes inversiones, deberíamos elegir aquella con una tasa interna de retorno más alta; ya que es la que en menos tiempo no va a devolver nuestra inversión inicial. Veamos un ejemplo, supongamos que tenemos USD 1000 para invertir, y que podemos decidir invertir ese dinero en una compañía que nos va a pagar USD 300 al final de cada uno de los próximos cuatro años; o por otra lado, podemos invertir el dinero en una caja de ahorro de un banco que nos va a pagar 5% anual. ¿Dónde deberíamos invertir nuestro dinero? In [15]: # Calculando la tasa interna de retorno de la inversion en la compañía IRR = np . irr ([ - 1000 , 300 , 300 , 300 , 300 ]) IRR * 100 Out[15]: 7.713847295208343 Al calcular la IRR de la inversión que podríamos hacer en la compañía, vemos que nos da un valor de 7.71%; esta tasa es más alta que la tasa del 5% que nos ofrece el banco por el deposito en su caja de ahorro, por lo que deberíamos decidir invertir nuestro dinero en la compañía en lugar de en el banco. La IRR graficamente Como se desprende de su definición, la tasa interna de retorno es la tasa que hace que el NPV se haga cero, por lo que si nos propusiesemos graficar el NPV en función de la tasa de descuento , podríamos encontrar a simple vista, cual es la IRR de un determinado flujo de fondos. Veamos un ejemplo, graficando el flujo de fondos con el que trabajamos anteriormente. In [16]: # Graficando el NPV en función de la tasa de descuento def npv_irr ( tasas ): result = [] for tasa in tasas : result . append ( np . npv ( tasa / 100 ,[ - 1000 , 300 , 300 , 300 , 300 ] )) return result tasas = list ( range ( 14 )) plt . title ( \"NPV y la tasa de descuento\" ) plt . plot ( tasas , npv_irr ( tasas ), marker = 'o' , label = 'NPV' ) plt . axhline ( 0 , color = 'red' ) axes = plt . gca () axes . set_ylim ([ - 200 , 250 ]) plt . xticks ( tasas ) plt . legend ( loc = 'upper right' ) plt . show () Como podemos ver en el gráfico, la función de NPV , se hace cero en aproximadamente 7.71%; es decir, el valor de la IRR para ese flujo de fondos. Información financiera y Pandas En las finanzas, una de las formas de datos más comunes e importantes con la que nos vamos a encontrar son las series de tiempo ; para trabajar con este tipo de información en Python , no existe mejor librería que Pandas ; sus dos estructuras de datos básicas, las Series y el Dataframe , nos ayudan a manipular información financiera de forma muy conveniente. Además Pandas nos proporciona una gran batería de métodos y funciones que nos facilitan la obtención y el análisis de datos financieros. Veamos algunos ejemplos de las cosas que podemos con Pandas . In [17]: # Importando pandas y datetime import pandas as pd import pandas_datareader.data as web import datetime as dt # Extrayendo información financiera de Yahoo! Finance inicio = dt . datetime ( 2014 , 1 , 1 ) fin = dt . datetime ( 2014 , 12 , 31 ) msft = web . DataReader ( \"MSFT\" , 'yahoo' , inicio , fin ) # información de Microsoft aapl = web . DataReader ( \"AAPL\" , 'yahoo' , inicio , fin ) # información de Apple In [18]: msft [: 3 ] Out[18]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } High Low Open Close Volume Adj Close Date 2014-01-02 37.400002 37.099998 37.349998 37.160000 30632200.0 32.951675 2014-01-03 37.220001 36.599998 37.200001 36.910000 31134800.0 32.729988 2014-01-06 36.889999 36.110001 36.849998 36.130001 43603700.0 32.038334 In [19]: aapl [: 3 ] Out[19]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } High Low Open Close Volume Adj Close Date 2014-01-02 79.575714 78.860001 79.382858 79.018570 58671200.0 67.251503 2014-01-03 79.099998 77.204285 78.980003 77.282860 98116900.0 65.774300 2014-01-06 78.114288 76.228569 76.778572 77.704285 103152700.0 66.132957 In [20]: # Seleccionando solo el Adj Close price de Enero 2014 msft01 = msft [ '2014-01' ][[ 'Close' ]] aapl01 = aapl [ '2014-01' ][[ 'Close' ]] msft01 [: 3 ] Out[20]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } Close Date 2014-01-02 37.160000 2014-01-03 36.910000 2014-01-06 36.130001 In [21]: aapl01 . head () # head() nos muestra los primeros 5 registros Out[21]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } Close Date 2014-01-02 79.018570 2014-01-03 77.282860 2014-01-06 77.704285 2014-01-07 77.148575 2014-01-08 77.637146 In [22]: # tambien se puede seleccionar un rango de tiempo msft [ '2014-02' : '2014-02-13' ] # desde el 1 al 13 de febrero Out[22]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } High Low Open Close Volume Adj Close Date 2014-02-03 37.990002 36.430000 37.740002 36.480000 64063100.0 32.348686 2014-02-04 37.189999 36.250000 36.970001 36.349998 54697900.0 32.233410 2014-02-05 36.470001 35.799999 36.290001 35.820000 55814400.0 31.763430 2014-02-06 36.250000 35.689999 35.799999 36.180000 35351800.0 32.082653 2014-02-07 36.590000 36.009998 36.320000 36.560001 33260500.0 32.419632 2014-02-10 36.799999 36.290001 36.630001 36.799999 26767000.0 32.632454 2014-02-11 37.259998 36.860001 36.880001 37.169998 32141400.0 32.960541 2014-02-12 37.599998 37.299999 37.349998 37.470001 27051800.0 33.226574 2014-02-13 37.860001 37.330002 37.330002 37.610001 37635500.0 33.350716 In [23]: # combinando ambos resultados close = pd . concat ([ msft01 , aapl01 ], keys = [ 'MSFT' , 'AAPL' ]) close [: 5 ] Out[23]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } Close Date MSFT 2014-01-02 37.160000 2014-01-03 36.910000 2014-01-06 36.130001 2014-01-07 36.410000 2014-01-08 35.759998 In [24]: # seleccionando los primeros 5 registros de AAPL close . loc [ 'AAPL' ][: 5 ] Out[24]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } Close Date 2014-01-02 79.018570 2014-01-03 77.282860 2014-01-06 77.704285 2014-01-07 77.148575 2014-01-08 77.637146 In [25]: # insertando una nueva columna con el simbolo msft . insert ( 0 , 'Symbol' , 'MSFT' ) aapl . insert ( 0 , 'Symbol' , 'AAPL' ) msft . head () Out[25]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } Symbol High Low Open Close Volume Adj Close Date 2014-01-02 MSFT 37.400002 37.099998 37.349998 37.160000 30632200.0 32.951675 2014-01-03 MSFT 37.220001 36.599998 37.200001 36.910000 31134800.0 32.729988 2014-01-06 MSFT 36.889999 36.110001 36.849998 36.130001 43603700.0 32.038334 2014-01-07 MSFT 36.490002 36.209999 36.330002 36.410000 35802800.0 32.286613 2014-01-08 MSFT 36.139999 35.580002 36.000000 35.759998 59971700.0 31.710232 In [26]: # concatenando toda la información y reseteando el indice combinado = pd . concat ([ msft , aapl ]) . sort_index () datos_todo = combinado . reset_index () datos_todo . head () Out[26]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } Date Symbol High Low Open Close Volume Adj Close 0 2014-01-02 MSFT 37.400002 37.099998 37.349998 37.160000 30632200.0 32.951675 1 2014-01-02 AAPL 79.575714 78.860001 79.382858 79.018570 58671200.0 67.251503 2 2014-01-03 MSFT 37.220001 36.599998 37.200001 36.910000 31134800.0 32.729988 3 2014-01-03 AAPL 79.099998 77.204285 78.980003 77.282860 98116900.0 65.774300 4 2014-01-06 MSFT 36.889999 36.110001 36.849998 36.130001 43603700.0 32.038334 In [27]: # Armando una tabla pivot del precio de cierre pivot = datos_todo . pivot ( index = 'Date' , columns = 'Symbol' , values = 'Close' ) pivot . head () Out[27]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } Symbol AAPL MSFT Date 2014-01-02 79.018570 37.160000 2014-01-03 77.282860 36.910000 2014-01-06 77.704285 36.130001 2014-01-07 77.148575 36.410000 2014-01-08 77.637146 35.759998 In [28]: # Obteniendo datos de multiples empresas def all_stocks ( symbols , start , end ): def data ( symbols ): return web . DataReader ( symbols , 'yahoo' , start , end ) datas = map ( data , symbols ) return pd . concat ( datas , keys = symbols , names = [ 'symbols' , 'Date' ]) simbolos = [ 'AAPL' , 'MSFT' , 'GOOG' , 'IBM' ] all_data = all_stocks ( simbolos , inicio , fin ) all_data . head () Out[28]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } High Low Open Close Volume Adj Close symbols Date AAPL 2014-01-02 79.575714 78.860001 79.382858 79.018570 58671200.0 67.251503 2014-01-03 79.099998 77.204285 78.980003 77.282860 98116900.0 65.774300 2014-01-06 78.114288 76.228569 76.778572 77.704285 103152700.0 66.132957 2014-01-07 77.994286 76.845711 77.760002 77.148575 79302300.0 65.660004 2014-01-08 77.937141 76.955711 76.972855 77.637146 64632400.0 66.075813 In [29]: all_data . loc [ 'GOOG' ] . head () # información de google Out[29]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } High Low Open Close Volume Adj Close Date 2014-01-02 555.263550 550.549194 554.125916 552.963501 3666400.0 552.963501 2014-01-03 554.856201 548.894958 553.897461 548.929749 3355000.0 548.929749 2014-01-06 555.814941 549.645081 552.908875 555.049927 3561600.0 555.049927 2014-01-07 566.162659 556.957520 558.865112 565.750366 5138400.0 565.750366 2014-01-08 569.953003 562.983337 569.297241 566.927673 4514100.0 566.927673 In [30]: # Graficando los datos. solo_cierre = all_data [[ 'Close' ]] . reset_index () pivot_cierre = solo_cierre . pivot ( 'Date' , 'symbols' , 'Close' ) pivot_cierre . head () Out[30]: .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } symbols AAPL GOOG IBM MSFT Date 2014-01-02 79.018570 552.963501 185.529999 37.160000 2014-01-03 77.282860 548.929749 186.639999 36.910000 2014-01-06 77.704285 555.049927 186.000000 36.130001 2014-01-07 77.148575 565.750366 189.710007 36.410000 2014-01-08 77.637146 566.927673 187.970001 35.759998 In [31]: # Graficando la información de Apple plot = pivot_cierre [ 'AAPL' ] . plot ( figsize = ( 12 , 8 )) In [32]: # Graficando todos plot = pivot_cierre . plot ( figsize = ( 12 , 8 )) Como podemos ver Pandas es una librería muy versátil, con ella podemos hacer todo tipo de manipulaciones de datos, desde obtener los datos desde la web hasta realizar concatenaciones, tablas pivot o incluso realizar gráficos. Con esto termino esta introducción a finanzas con Python ; los dejo para que se entretengan con sus propios ejemplos, a practicar! Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Finanzas","url":"https://relopezbriega.github.io/blog/2015/08/28/introduccion-a-finanzas-con-python/"},{"title":"Batman, ecuaciones y python","text":"Batman siempre fue mi superhéroe favorito porque es uno de los pocos héroes que no posee ningún superpoder, sino que debe recurrir a su intelecto y a la ciencia para construir las bati-herramientas que utiliza para combatir al crimen. Además posee ese toque de oscuridad producto de la dualidad entre realizar el bien, protegiendo a la gente de ciudad gótica, y su sed de venganza contra el crimen y la corrupción que acabó con la vida de su familia. Es un personaje con muchos recursos, en cada nueva aparición podemos verlo utilizar nuevas y muy modernas bati-herramientas; su intelecto es tan agudo que incluso escondió una ecuación matemática en su bati-señal!! La ecuación de batman fue creada por el profesor de matemáticas Matthew Register y se popularizó a través de un post de uno de sus alumnos en la red social reddit ; su expresión matemática es la siguiente: $$ ((\\frac{x}{7})^2 \\cdot \\sqrt{\\frac{||x|-3|}{(|x|-3)}}+ (\\frac{y}{3})^2 \\cdot \\sqrt{\\frac{|y+3 \\cdot \\frac{\\sqrt{33}}{7}|}{y+3 \\cdot \\frac{\\sqrt{33}}{7}}}-1) \\cdot (|\\frac{x}{2}|-((3 \\cdot \\frac{\\sqrt{33}-7)}{112}) \\cdot x^2-3+\\sqrt{1-(||x|-2|-1)^2}-y) \\cdot (9 \\cdot \\sqrt{\\frac{|(|x|-1) \\cdot (|x|-0.75)|}{((1-|x|)*(|x|-0.75))}}-8 \\cdot |x|-y) \\cdot (3 \\cdot |x|+0.75 \\cdot \\sqrt{\\frac{|(|x|-0.75) \\cdot (|x|-0.5)|}{((0.75-|x|) \\cdot (|x|-0.5))}}-y) \\cdot (2.25 \\cdot \\sqrt{\\frac{|(x-0.5) \\cdot (x+0.5)|}{((0.5-x) \\cdot (0.5+x))}}-y) \\cdot (\\frac{6 \\cdot \\sqrt{10}}{7}+(1.5-0.5 \\cdot |x|) \\cdot \\sqrt{\\frac{||x|-1|}{|x|-1}}-(\\frac{6 \\cdot \\sqrt{10}}{14}) \\cdot \\sqrt{4-(|x|-1)^2}-y) =0 $$ Si bien a simple vista la ecuación parece sumamente compleja e imposible de graficar, la misma se puede descomponer en seis curvas distintas, mucho más simples. La primera de estas curvas, es la función del elipse $(\\frac{x}{7})^2 + (\\frac{y}{3})^2 = 1$, restringida a la región $\\sqrt{\\frac{||x|-3|}{(|x|-3)}}$ y $\\sqrt{\\frac{|y+3 \\cdot \\frac{\\sqrt{33}}{7}|}{y+3 \\cdot \\frac{\\sqrt{33}}{7}}}$ para cortar la parte central. Los cinco términos siguientes pueden ser entendidos como simples funciones de x, tres de los cuales son lineales. Por ejemplo, la siguiente función es la que grafica las curvas de la parte inferior de la bati-señal. $y = |\\frac{x}{2}|-(\\frac{3 \\cdot \\sqrt{33} -7}{112})\\cdot x^2 - 3 + \\sqrt{1-(||x|-2| -1)^2}$ Las restantes ecuaciones de las curvas que completan el gráfico, son las siguientes: $y = \\frac{6\\cdot\\sqrt{10}}{7} + (-0.5|x| + 1.5) - \\frac{3\\cdot\\sqrt{10}}{7}\\cdot\\sqrt{4 - (|x|-1)^2}, |x| > 1$ $y = 9 -8|x|, 0.75 < |x| < 1$ $y = 3|x| + 0.75, 0.5 < |x| < 0.75$ $y = 2.25, |x| < 0.5$ La ecuación de batman puede ser fácilmente graficada utilizando Matplotlib del siguiente modo: In [1]: # graficos embebidos % matplotlib inline In [2]: # Importando lo necesario para los cálculos import matplotlib.pyplot as plt from numpy import sqrt from numpy import meshgrid from numpy import arange In [3]: # Graficando la ecuación de Batman. xs = arange ( - 7.25 , 7.25 , 0.01 ) ys = arange ( - 5 , 5 , 0.01 ) x , y = meshgrid ( xs , ys ) eq1 = (( x / 7 ) ** 2 * sqrt ( abs ( abs ( x ) - 3 ) / ( abs ( x ) - 3 )) + ( y / 3 ) ** 2 * sqrt ( abs ( y + 3 / 7 * sqrt ( 33 )) / ( y + 3 / 7 * sqrt ( 33 ))) - 1 ) eq2 = ( abs ( x / 2 ) - (( 3 * sqrt ( 33 ) - 7 ) / 112 ) * x ** 2 - 3 + sqrt ( 1 - ( abs ( abs ( x ) - 2 ) - 1 ) ** 2 ) - y ) eq3 = ( 9 * sqrt ( abs (( abs ( x ) - 1 ) * ( abs ( x ) -. 75 )) / (( 1 - abs ( x )) * ( abs ( x ) -. 75 ))) - 8 * abs ( x ) - y ) eq4 = ( 3 * abs ( x ) +. 75 * sqrt ( abs (( abs ( x ) -. 75 ) * ( abs ( x ) -. 5 )) / (( . 75 - abs ( x )) * ( abs ( x ) -. 5 ))) - y ) eq5 = ( 2.25 * sqrt ( abs (( x -. 5 ) * ( x +. 5 )) / (( . 5 - x ) * ( . 5 + x ))) - y ) eq6 = ( 6 * sqrt ( 10 ) / 7 + ( 1.5 -. 5 * abs ( x )) * sqrt ( abs ( abs ( x ) - 1 ) / ( abs ( x ) - 1 )) - ( 6 * sqrt ( 10 ) / 14 ) * sqrt ( 4 - ( abs ( x ) - 1 ) ** 2 ) - y ) for f , c in [( eq1 , \"red\" ), ( eq2 , \"purple\" ), ( eq3 , \"green\" ), ( eq4 , \"blue\" ), ( eq5 , \"orange\" ), ( eq6 , \"black\" )]: plt . contour ( x , y , f , [ 0 ], colors = c ) plt . show () Ahora ya saben...si están en algún apuro y necesitan la ayuda del bati-héroe, solo necesitan graficar una ecuación para llamarlo con la bati-señal! Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Programacion","url":"https://relopezbriega.github.io/blog/2015/08/20/batman-ecuaciones-y-python/"},{"title":"Expresiones Regulares con Python","text":"Introducción Uno de los problemas más comunes con que nos solemos encontrar al desarrollar cualquier programa informático, es el de procesamiento de texto. Esta tarea puede resultar bastante trivial para el cerebro humano, ya que nosotros podemos detectar con facilidad que es un número y que una letra, o cuales son palabras que cumplen con un determinado patrón y cuales no; pero estas mismas tareas no son tan fáciles para una computadora. Es por esto, que el procesamiento de texto siempre ha sido uno de los temas más relevantes en las ciencias de la computación . Luego de varias décadas de investigación se logró desarrollar un poderoso y versátil lenguaje que cualquier computadora puede utilizar para reconocer patrones de texto; este lenguale es lo que hoy en día se conoce con el nombre de expresiones regulares ; las operaciones de validación, búsqueda, extracción y sustitución de texto ahora son tareas mucho más sencillas para las computadoras gracias a las expresiones regulares . ¿Qué son las Expresiones Regulares? Las expresiones regulares , a menudo llamada también regex , son unas secuencias de caracteres que forma un patrón de búsqueda, las cuales son formalizadas por medio de una sintaxis específica. Los patrones se interpretan como un conjunto de instrucciones, que luego se ejecutan sobre un texto de entrada para producir un subconjunto o una versión modificada del texto original. Las expresiones regulares pueden incluir patrones de coincidencia literal, de repetición, de composición, de ramificación, y otras sofisticadas reglas de reconocimiento de texto . Las expresiones regulares deberían formar parte del arsenal de cualquier buen programador ya que un gran número de problemas de procesamiento de texto pueden ser fácilmente resueltos con ellas. Componentes de las Expresiones Regulares Las expresiones regulares son un mini lenguaje en sí mismo, por lo que para poder utilizarlas eficientemente primero debemos entender los componentes de su sintaxis; ellos son: Literales : Cualquier caracter se encuentra a sí mismo, a menos que se trate de un metacaracter con significado especial. Una serie de caracteres encuentra esa misma serie en el texto de entrada, por lo tanto la plantilla \"raul\" encontrará todas las apariciones de \"raul\" en el texto que procesamos. Secuencias de escape : La sintaxis de las expresiones regulares nos permite utilizar las secuencias de escape que ya conocemos de otros lenguajes de programación para esos casos especiales como ser finales de línea, tabs, barras diagonales, etc. Las principales secuencias de escape que podemos encontrar, son: Secuencia de escape Significado \\n Nueva línea (new line). El cursor pasa a la primera posición de la línea siguiente. \\t Tabulador. El cursor pasa a la siguiente posición de tabulación. \\\\ Barra diagonal inversa \\v Tabulación vertical. \\ooo Carácter ASCII en notación octal. \\xhh Carácter ASCII en notación hexadecimal. \\xhhhh Carácter Unicode en notación hexadecimal. Clases de caracteres : Se pueden especificar clases de caracteres encerrando una lista de caracteres entre corchetes [], la que que encontrará uno cualquiera de los caracteres de la lista. Si el primer símbolo después del \"[\" es \"^\", la clase encuentra cualquier caracter que no está en la lista. Metacaracteres : Los metacaracteres son caracteres especiales que son la esencia de las expresiones regulares . Como son sumamente importantes para entender la sintaxis de las expresiones regulares y existen diferentes tipos, voy a dedicar una sección a explicarlos un poco más en detalle. Metacaracteres Metacaracteres - delimitadores Esta clase de metacaracteres nos permite delimitar dónde queremos buscar los patrones de búsqueda. Ellos son: Metacaracter Descripción ^ inicio de línea. $ fin de línea. \\A inicio de texto. \\Z fin de texto. . cualquier caracter en la línea. \\b encuentra límite de palabra. \\B encuentra distinto a límite de palabra. Metacaracteres - clases predefinidas Estas son clases predefinidas que nos facilitan la utilización de las expresiones regulares . Ellos son: Metacaracter Descripción \\w un caracter alfanumérico (incluye \"_\"). \\W un caracter no alfanumérico. \\d un caracter numérico. \\D un caracter no numérico. \\s cualquier espacio (lo mismo que [ \\t\\n\\r\\f]). \\S un no espacio. Metacaracteres - iteradores Cualquier elemento de una expresion regular puede ser seguido por otro tipo de metacaracteres, los iteradores . Usando estos metacaracteres se puede especificar el número de ocurrencias del caracter previo, de un metacaracter o de una subexpresión. Ellos son: Metacaracter Descripción * cero o más, similar a {0,}. + una o más, similar a {1,}. ? cero o una, similar a {0,1}. {n} exactamente n veces. {n,} por lo menos n veces. {n,m} por lo menos n pero no más de m veces. *? cero o más, similar a {0,}?. +? una o más, similar a {1,}?. ?? cero o una, similar a {0,1}?. {n}? exactamente n veces. {n,}? por lo menos n veces. {n,m}? por lo menos n pero no más de m veces. En estos metacaracteres, los dígitos entre llaves de la forma {n,m}, especifican el mínimo número de ocurrencias en n y el máximo en m. Metacaracteres - alternativas Se puede especificar una serie de alternativas para una plantilla usando \"|\" para separarlas, entonces do|re|mi encontrará cualquier \"do\", \"re\", o \"mi\" en el texto de entrada.Las alternativas son evaluadas de izquierda a derecha, por lo tanto la primera alternativa que coincide plenamente con la expresión analizada es la que se selecciona. Por ejemplo: si se buscan foo|foot en \"barefoot'', sólo la parte \"foo\" da resultado positivo, porque es la primera alternativa probada, y porque tiene éxito en la búsqueda de la cadena analizada. Ejemplo : foo(bar|foo) --> encuentra las cadenas 'foobar' o 'foofoo'. Metacaracteres - subexpresiones La construcción ( ... ) también puede ser empleada para definir subexpresiones de expresiones regulares . Ejemplos : (foobar){10} --> encuentra cadenas que contienen 8, 9 o 10 instancias de 'foobar' foob([0-9]|a+)r --> encuentra 'foob0r', 'foob1r' , 'foobar', 'foobaar', 'foobaar' etc. Metacaracteres - memorias (backreferences) Los metacaracteres \\1 a \\9 son interpretados como memorias. \\ encuentra la subexpresión previamente encontrada # . Ejemplos : (.)\\1+ --> encuentra 'aaaa' y 'cc'. (.+)\\1+ --> también encuentra 'abab' y '123123' (['\"]?)(\\d+)\\1 --> encuentra '\"13\" (entre comillas dobles), o '4' (entre comillas simples) o 77 (sin comillas) etc. Expresiones Regulares con Python Luego de esta introducción, llegó el tiempo de empezar a jugar con las expresiones regulares y Python . Como no podría ser de otra forma tratandose de Python y su filosofía de todas las baterías incluídas ; en la librería estandar de Python podemos encontrar el módulo re , el cual nos proporciona todas las operaciones necesarias para trabajar con las expresiones regulares . Por lo tanto, en primer lugar lo que debemos hacer es importar el modulo re . In [1]: # importando el modulo de regex de python import re Buscando coincidencias Una vez que hemos importado el módulo, podemos empezar a tratar de buscar coincidencias con un determinado patrón de búsqueda. Para hacer esto, primero debemos compilar nuestra expresion regular en un objeto de patrones de Python , el cual posee métodos para diversas operaciones, tales como la búsqueda de coincidencias de patrones o realizar sustituciones de texto. In [2]: # compilando la regex patron = re . compile ( r '\\bfoo\\b' ) # busca la palabra foo Ahora que ya tenemos el objeto de expresión regular compilado podemos utilizar alguno de los siguientes métodos para buscar coincidencias con nuestro texto. match() : El cual determinada si la regex tiene coincidencias en el comienzo del texto. search() : El cual escanea todo el texto buscando cualquier ubicación donde haya una coincidencia. findall() : El cual encuentra todos los subtextos donde haya una coincidencia y nos devuelve estas coincidencias como una lista. finditer() : El cual es similar al anterior pero en lugar de devolvernos una lista nos devuelve un iterador . Veamoslos en acción. In [3]: # texto de entrada texto = \"\"\" bar foo bar foo barbarfoo foofoo foo bar \"\"\" In [4]: # match nos devuelve None porque no hubo coincidencia al comienzo del texto print ( patron . match ( texto )) None In [5]: # match encuentra una coindencia en el comienzo del texto m = patron . match ( 'foo bar' ) m Out[5]: <_sre.SRE_Match object; span=(0, 3), match='foo'> In [6]: # search nos devuelve la coincidencia en cualquier ubicacion. s = patron . search ( texto ) s Out[6]: <_sre.SRE_Match object; span=(5, 8), match='foo'> In [7]: # findall nos devuelve una lista con todas las coincidencias fa = patron . findall ( texto ) fa Out[7]: ['foo', 'foo', 'foo'] In [8]: # finditer nos devuelve un iterador fi = patron . finditer ( texto ) fi Out[8]: <callable_iterator at 0x7f413db74240> In [9]: # iterando por las distintas coincidencias next ( fi ) Out[9]: <_sre.SRE_Match object; span=(5, 8), match='foo'> In [10]: next ( fi ) Out[10]: <_sre.SRE_Match object; span=(13, 16), match='foo'> Como podemos ver en estos ejemplos, cuando hay coincidencias, Python nos devuelve un Objeto de coincidencia (salvo por el método findall() que devuelve una lista). Este Objeto de coincidencia también tiene sus propios métodos que nos proporcionan información adicional sobre la coincidencia; éstos métodos son: group() : El cual devuelve el texto que coincide con la expresion regular . start() : El cual devuelve la posición inicial de la coincidencia. end() : El cual devuelve la posición final de la coincidencia. span() : El cual devuelve una tupla con la posición inicial y final de la coincidencia. In [11]: # Métodos del objeto de coincidencia m . group (), m . start (), m . end (), m . span () Out[11]: ('foo', 0, 3, (0, 3)) In [12]: s . group (), s . start (), s . end (), s . span () Out[12]: ('foo', 5, 8, (5, 8)) Modificando el texto de entrada Además de buscar coincidencias de nuestro patrón de búsqueda en un texto, podemos utilizar ese mismo patrón para realizar modificaciones al texto de entrada. Para estos casos podemos utilizar los siguientes métodos: split() : El cual divide el texto en una lista, realizando las divisiones del texto en cada lugar donde se cumple con la expresion regular . sub() : El cual encuentra todos los subtextos donde existe una coincidencia con la expresion regular y luego los reemplaza con un nuevo texto. subn() : El cual es similar al anterior pero además de devolver el nuevo texto, también devuelve el numero de reemplazos que realizó. Veamoslos en acción. In [13]: # texto de entrada becquer = \"\"\"Podrá nublarse el sol eternamente; Podrá secarse en un instante el mar; Podrá romperse el eje de la tierra como un débil cristal. ¡todo sucederá! Podrá la muerte cubrirme con su fúnebre crespón; Pero jamás en mí podrá apagarse la llama de tu amor.\"\"\" In [14]: # patron para dividir donde no encuentre un caracter alfanumerico patron = re . compile ( r '\\W+' ) In [15]: palabras = patron . split ( becquer ) palabras [: 10 ] # 10 primeras palabras Out[15]: ['Podrá', 'nublarse', 'el', 'sol', 'eternamente', 'Podrá', 'secarse', 'en', 'un', 'instante'] In [16]: # Utilizando la version no compilada de split. re . split ( r '\\n' , becquer ) # Dividiendo por linea. Out[16]: ['Podrá nublarse el sol eternamente; ', 'Podrá secarse en un instante el mar; ', 'Podrá romperse el eje de la tierra ', 'como un débil cristal. ', '¡todo sucederá! Podrá la muerte ', 'cubrirme con su fúnebre crespón; ', 'Pero jamás en mí podrá apagarse ', 'la llama de tu amor.'] In [17]: # Utilizando el tope de divisiones patron . split ( becquer , 5 ) Out[17]: ['Podrá', 'nublarse', 'el', 'sol', 'eternamente', 'Podrá secarse en un instante el mar; \\nPodrá romperse el eje de la tierra \\ncomo un débil cristal. \\n¡todo sucederá! Podrá la muerte \\ncubrirme con su fúnebre crespón; \\nPero jamás en mí podrá apagarse \\nla llama de tu amor.'] In [18]: # Cambiando \"Podrá\" o \"podra\" por \"Puede\" podra = re . compile ( r '\\b(P|p)odrá\\b' ) puede = podra . sub ( \"Puede\" , becquer ) print ( puede ) Puede nublarse el sol eternamente; Puede secarse en un instante el mar; Puede romperse el eje de la tierra como un débil cristal. ¡todo sucederá! Puede la muerte cubrirme con su fúnebre crespón; Pero jamás en mí Puede apagarse la llama de tu amor. In [19]: # Limitando el número de reemplazos puede = podra . sub ( \"Puede\" , becquer , 2 ) print ( puede ) Puede nublarse el sol eternamente; Puede secarse en un instante el mar; Podrá romperse el eje de la tierra como un débil cristal. ¡todo sucederá! Podrá la muerte cubrirme con su fúnebre crespón; Pero jamás en mí podrá apagarse la llama de tu amor. In [20]: # Utilizando la version no compilada de subn re . subn ( r '\\b(P|p)odrá\\b' , \"Puede\" , becquer ) # se realizaron 5 reemplazos Out[20]: ('Puede nublarse el sol eternamente; \\nPuede secarse en un instante el mar; \\nPuede romperse el eje de la tierra \\ncomo un débil cristal. \\n¡todo sucederá! Puede la muerte \\ncubrirme con su fúnebre crespón; \\nPero jamás en mí Puede apagarse \\nla llama de tu amor.', 5) Funciones no compiladas En estos últimos ejemplos, pudimos ver casos donde utilizamos las funciones al nivel del módulo split() y subn() . Para cada uno de los ejemplos que vimos (match, search, findall, finditer, split, sub y subn) existe una versión al nivel del módulo que se puede utilizar sin necesidad de compilar primero el patrón de búsqueda; simplemente le pasamos como primer argumento la expresion regular y el resultado será el mismo. La ventaja que tiene la versión compila sobre las funciones no compiladas es que si vamos a utilizar la expresion regular dentro de un bucle nos vamos a ahorrar varias llamadas de funciones y por lo tanto mejorar la performance de nuestro programa. In [21]: # Ejemplo de findall con la funcion a nivel del modulo # findall nos devuelve una lista con todas las coincidencias re . findall ( r '\\bfoo\\b' , texto ) Out[21]: ['foo', 'foo', 'foo'] Banderas de compilación Las banderas de compilación permiten modificar algunos aspectos de cómo funcionan las expresiones regulares . Todas ellas están disponibles en el módulo re bajo dos nombres, un nombre largo como IGNORECASE y una forma abreviada de una sola letra como I. Múltiples banderas pueden ser especificadas utilizando el operador \"|\" OR; Por ejemplo, re.I | RE.M establece las banderas de E y M. Algunas de las banderas de compilación que podemos encontrar son: IGNORECASE, I : Para realizar búsquedas sin tener en cuenta las minúsculas o mayúsculas. VERBOSE, X : Que habilita la modo verborrágico, el cual permite organizar el patrón de búsqueda de una forma que sea más sencilla de entender y leer. ASCII, A : Que hace que las secuencias de escape \\w, \\b, \\s and \\d funciones para coincidencias con los caracteres ASCII. DOTALL, S : La cual hace que el metacaracter . funcione para cualquier caracter, incluyendo el las líneas nuevas. LOCALE, L : Esta opción hace que \\w, \\W, \\b, \\B, \\s, y \\S dependientes de la localización actual. MULTILINE, M : Que habilita la coincidencia en múltiples líneas, afectando el funcionamiento de los metacaracteres ^ and $. In [22]: # Ejemplo de IGNORECASE # Cambiando \"Podrá\" o \"podra\" por \"Puede\" podra = re . compile ( r 'podrá\\b' , re . I ) # el patrón se vuelve más sencillo puede = podra . sub ( \"puede\" , becquer ) print ( puede ) puede nublarse el sol eternamente; puede secarse en un instante el mar; puede romperse el eje de la tierra como un débil cristal. ¡todo sucederá! puede la muerte cubrirme con su fúnebre crespón; Pero jamás en mí puede apagarse la llama de tu amor. In [23]: # Ejemplo de VERBOSE mail = re . compile ( r \"\"\" \\b # comienzo de delimitador de palabra [\\w.%+-] # usuario: Cualquier caracter alfanumerico mas los signos (.%+-) +@ # seguido de @ [\\w.-] # dominio: Cualquier caracter alfanumerico mas los signos (.-) +\\. # seguido de . [a-zA-Z]{2,6} # dominio de alto nivel: 2 a 6 letras en minúsculas o mayúsculas. \\b # fin de delimitador de palabra \"\"\" , re . X ) In [24]: mails = \"\"\"[email protected], Raul Lopez Briega, foo bar, [email protected], [email protected], https://relopezbriega.com.ar, https://relopezbriega.github.io, python@python, [email protected], [email protected] \"\"\" In [25]: # filtrando los mails con estructura válida mail . findall ( mails ) Out[25]: ['[email protected]', '[email protected]', '[email protected]', '[email protected]'] Como podemos ver en este último ejemplo, la opción VERBOSE puede ser muy util para que cualquier persona que lea nuestra expresion regular pueda entenderla más fácilmente. Nombrando los grupos Otra de las funciones interesantes que nos ofrece el módulo re de Python ; es la posibilidad de ponerle nombres a los grupos de nuestras expresiones regulares . Así por ejemplo, en lugar de acceder a los grupos por sus índices, como en este caso... In [26]: # Accediendo a los grupos por sus indices patron = re . compile ( r \"(\\w+) (\\w+)\" ) s = patron . search ( \"Raul Lopez\" ) In [27]: # grupo 1 s . group ( 1 ) Out[27]: 'Raul' In [28]: # grupo 2 s . group ( 2 ) Out[28]: 'Lopez' Podemos utilizar la sintaxis especial (?P<nombre>patron) que nos ofrece Python para nombrar estos grupos y que sea más fácil identificarlos. In [29]: # Accediendo a los grupos por nombres patron = re . compile ( r \"(?P<nombre>\\w+) (?P<apellido>\\w+)\" ) s = patron . search ( \"Raul Lopez\" ) In [30]: # grupo nombre s . group ( \"nombre\" ) Out[30]: 'Raul' In [31]: # grupo apellido s . group ( \"apellido\" ) Out[31]: 'Lopez' Otros ejemplos de expresiones regulares Por último, para ir cerrando esta introducción a las expresiones regulares , les dejo algunos ejemplos de las expresiones regulares más utilizadas. Validando mails Para validar que un mail tenga la estructura correcta, podemos utilizar la siguiente expresion regular : regex : \\b[\\w.%+-]+@[\\w.-]+\\.[a-zA-Z]{2,6}\\b Este es el patrón que utilizamos en el ejemplo de la opción VERBOSE. Validando una URL Para validar que una URL tenga una estructura correcta, podemos utilizar esta expresion regular : regex : ^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([\\/\\w \\.-]*)*\\/?$ In [32]: # Validando una URL url = re . compile ( r \"^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([\\/\\w \\.-]*)*\\/?$\" ) # vemos que https://relopezbriega.com.ar lo acepta como una url válida. url . search ( \"https://relopezbriega.com.ar\" ) Out[32]: <_sre.SRE_Match object; span=(0, 27), match='https://relopezbriega.com.ar'> In [33]: # pero https://google.com/un/archivo!.html no la acepta por el carcter ! print ( url . search ( \"https://google.com/un/archivo!.html\" )) None Validando una dirección IP Para validar que una dirección IP tenga una estructura correcta, podemos utilizar esta expresión regular : regex : ^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ In [34]: # Validando una dirección IP patron = ( '^(?:(?:25[0-5]|2[0-4][0-9]|' '[01]?[0-9][0-9]?)\\.) {3} ' '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' ) ip = re . compile ( patron ) # la ip 73.60.124.136 es valida ip . search ( \"73.60.124.136\" ) Out[34]: <_sre.SRE_Match object; span=(0, 13), match='73.60.124.136'> In [35]: # pero la ip 256.60.124.136 no es valida print ( ip . search ( \"256.60.124.136\" )) None Validando una fecha Para validar que una fecha tenga una estructura dd/mm/yyyy, podemos utilizar esta expresión regular : regex : ^(0?[1-9]|[12][0-9]|3[01])/(0?[1-9]|1[012])/((19|20)\\d\\d)$ In [36]: # Validando una fecha fecha = re . compile ( r '^(0?[1-9]|[12][0-9]|3[01])/(0?[1-9]|1[012])/((19|20)\\d\\d)$' ) # validando 13/02/1982 fecha . search ( \"13/02/1982\" ) Out[36]: <_sre.SRE_Match object; span=(0, 10), match='13/02/1982'> In [37]: # no valida 13-02-1982 print ( fecha . search ( \"13-02-1982\" )) None In [38]: # no valida 32/12/2015 print ( fecha . search ( \"32/12/2015\" )) None In [39]: # no valida 30/14/2015 print ( fecha . search ( \"30/14/2015\" )) None Y con estos ejemplos termino este tutorial, espero que les haya sido de utilidad. Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Programacion","url":"https://relopezbriega.github.io/blog/2015/07/19/expresiones-regulares-con-python/"},{"title":"Probabilidad y Estadística con Python","text":"Actualmente con el boom de la Big Data , tener nociones de probabilidad y estadística se ha hecho fundamental. En los últimos años ha habido un resurgimiento de todo lo relacionado con estadística , data mining y machine learning empujados principalmente por la explosión de datos con que contamos, estos conceptos combinados forman la base de lo que actualmente se conoce como la Ciencia de Datos . Dentro de este contexto, Python es uno de los lenguajes que más nos facilita trabajar con datos. Realizar complejos análisis estadísticos nunca fue tan fácil como con Python ! ¿Qué es la Estadística? La estadística suele ser definida como la ciencia de aprender de los datos o como la ciencia de obtener conclusiones en la presencia de incertidumbre. Se relaciona principalmente con la recolección, análisis e interpretación de datos , así como también con la efectiva comunicación y presentación de los resultados basados en esos datos . Como por datos entendemos a cualquier clase de información grabada, la estadística juego un rol importante en muchas disciplinas científicas. La estadística puede ser muy importante para una efectiva toma de decisiones. Existe una gran cantidad de valiosa información escondida entre los datos , pero esta información no suele ser fácilmente accesible, la estadística nos brinda los principios fundamentales que nos permiten extraer y entender esa información; tambien nos proporciona las herramientas necesarias para verificar la calidad de nuestros datos y nuestra información. La estadística suele ser dividida en dos grandes ramas: La estadística descriptiva : La cual se dedica a recolectar, ordenar, analizar y representar a un conjunto de datos , con el fin de describir apropiadamente las características de este. Calcula los parámetros estadísticos que describen el conjunto estudiado. Algunas de las herramientas que utiliza son gráficos, medidas de frecuencias, medidas de centralización, medidas de posición, medidas de dispersión, entre otras. La estadistica inferencial : La cual estudia cómo sacar conclusiones generales para toda la población a partir del estudio de una muestra , y el grado de fiabilidad o significación de los resultados obtenidos. Sus principales herramientas son el muestreo , la estimación de parámetros y el contraste de hipótesis. ¿Qué es la Probabilidad? La probabilidad mide la mayor o menor posibilidad de que se dé un determinado resultado (suceso o evento) cuando se realiza un experimento aleatorio. Para calcular la probabilidad de un evento se toma en cuenta todos los casos posibles de ocurrencia del mismo; es decir, de cuántas formas puede ocurrir determinada situación.Los casos favorables de ocurrencia de un evento serán los que cumplan con la condición que estamos buscando. La probabilidad toma valores entre 0 y 1 (o expresados en tanto por ciento, entre 0% y 100%). La probabilidad es a la vez el inverso y complemento para la estadística . Dónde la estadística nos ayuda a ir desde los datos observados hasta hacer generalizaciones sobre como funcionan las cosas; la probabilidad funciona en la dirección inversa: si asumimos que sabemos como las cosas funcionan, entonces podemos averiguar la clase de datos que vamos a ver y cuan probable es que los veamos. La probabilidad también funciona como complemento de la estadística cuando nos proporciona una sólida base para la estadistica inferencial . Cuando hay incertidumbre, no sabemos que puede pasar y hay alguna posibilidad de errores, utilizando probabilidades podemos aprender formas de controlar la tasa de errores para reducirlos. Actividades básicas del analisis estadístico Las técnicas estadísticas deberían ser vistas como una parte importante de cualquier proceso de toma de dicisiones, permitiendo tomar decisiones estratégicamente informadas que combinen intuición con experiencia y un entendimiento estadístico de los datos que tenemos disponibles. Un análisis estadístico suele contener 5 actividades básicas: Diseño del análisis : Esta actividad involucra el planeamiento de los detalles para obtener los datos que necesitamos y la generación de la hipótesis a ser evaluada. Exploración de datos : En esta actividad nos dedicamos a jugar con nuestros datos, los describimos, los resumimos, realizamos gráficos para mirarlos desde distintos ángulos. Esta exploración nos ayuda a asegurarnos que los datos que obtuvimos son completos y que la etapa de diseño fue correcta. Armado del modelo : En esta actividad intentamos armar un modelo que explique el comportamiento de nuestros datos y pueda llegar a hacer predicciones sobre los mismos. La idea es que el modelo pueda describir las propiedades fundamentales de nuestros datos. Realizar estimaciones : Aquí vamos a intentar realizar estimaciones basadas en el modelo que armamos anteriormente. También vamos a intentar estimar el tamaño del error que nuestro modelo puede tener en sus predicciones. Contraste de la hipótesis : Esta actividad es la que va a producir la decisión final sobre si las predicciones del modelo son correctas y ayudarnos a concluir si los datos que poseemos confirman o rechazan la hipótesis que generamos en la actividad 1. Conceptos básicos de la estadística descriptiva En estadística descriptiva se utilizan distintas medidas para intentar describir las propiedades de nuestros datos, algunos de los conceptos básicos, son: Media aritmética : La media aritmética es el valor obtenido al sumar todos los datos y dividir el resultado entre el número total elementos. Se suele representar con la letra griega $\\mu$. Si tenemos una muestra de $n$ valores, $x_i$, la media aritmética , $\\mu$, es la suma de los valores divididos por el numero de elementos; en otras palabras: $$\\mu = \\frac{1}{n} \\sum_{i}x_i$$ Desviación respecto a la media : La desviación respecto a la media es la diferencia en valor absoluto entre cada valor de la variable estadística y la media aritmética. $$D_i = |x_i - \\mu|$$ Varianza : La varianza es la media aritmética del cuadrado de las desviaciones respecto a la media de una distribución estadística. La varianza intenta describir la dispersión de los datos . Se representa como $\\sigma^2$. $$\\sigma^2 = \\frac{\\sum\\limits_{i=1}^n(x_i - \\mu)^2}{n} $$ Desviación típica : La desviación típica es la raíz cuadrada de la varianza. Se representa con la letra griega $\\sigma$. $$\\sigma = \\sqrt{\\frac{\\sum\\limits_{i=1}^n(x_i - \\mu)^2}{n}} $$ Moda : La moda es el valor que tiene mayor frecuencia absoluta. Se representa con $M_0$ Mediana : La mediana es el valor que ocupa el lugar central de todos los datos cuando éstos están ordenados de menor a mayor. Se representa con $\\widetilde{x}$. Correlación : La correlación trata de establecer la relación o dependencia que existe entre las dos variables que intervienen en una distribución bidimensional. Es decir, determinar si los cambios en una de las variables influyen en los cambios de la otra. En caso de que suceda, diremos que las variables están correlacionadas o que hay correlación entre ellas. La correlación es positiva cuando los valores de las variables aumenta juntos; y es negativa cuando un valor de una variable se reduce cuando el valor de la otra variable aumenta. Covarianza : La covarianza es el equivalente de la varianza aplicado a una variable bidimensional. Es la media aritmética de los productos de las desviaciones de cada una de las variables respecto a sus medias respectivas.La covarianza indica el sentido de la correlación entre las variables; Si $\\sigma_{xy} > 0$ la correlación es directa; Si $\\sigma_{xy} < 0$ la correlación es inversa. $$\\sigma_{xy} = \\frac{\\sum\\limits_{i=1}^n(x_i - \\mu_x)(y_i -\\mu_y)}{n}$$ Valor atípico : Un valor atípico es una observación que se aleja demasiado de la moda; esta muy lejos de la tendencia principal del resto de los datos . Pueden ser causados por errores en la recolección de datos o medidas inusuales. Generalmente se recomienda eliminarlos del conjunto de datos . Librerías de Python para probabilidad y estadística Como ya les vengo mostrando en mis anteriores artículos, Python se lleva muy bien con las matemáticas. Además, la comunidad python es tan amplia que solemos encontrar una librería para cualquier problema al que nos enfrentemos. En este caso, los principales módulos que Python nos ofrece para trabajar con probabilidad y estadística , son: numpy : El popular paquete matemático de Python , se utiliza tanto que mucha gente ya lo considera parte integral del lenguaje. Nos proporciona algunas funciones estadísticas que podemos aplicar fácilmente sobre los arrays de Numpy . scipy.stats : Este submodulo del paquete científico Scipy es el complemento perfecto para Numpy , las funciones estadisticas que no encontremos en uno, las podemos encontrar en el otro. statsmodels : Esta librería nos brinda un gran número de herramientas para explorar datos , estimar modelos estadísticos, realizar pruebas estadísticas y muchas cosas más. matplotlib : Es la librería más popular en Python para visualizaciones y gráficos. Ella nos va a permitir realizar los gráficos de las distintas distribuciones de datos. seaborn : Esta librería es un complemento ideal de matplotlib para realizar gráficos estadísticos. pandas : Esta es la librería más popular para análisis de datos y financieros. Posee algunas funciones muy útiles para realizar estadística descriptiva sobre nuestros datos y nos facilita sobremanera el trabajar con series de tiempo . pyMC : pyMC es un módulo de Python que implementa modelos estadísticos bayesianos, incluyendo la cadena de Markov Monte Carlo(MCMC) . pyMC ofrece funcionalidades para hacer el análisis bayesiano lo mas simple posible. Ejemplos en Python Calcular los principales indicadores de la estadística descriptiva con Python es muy fácil!. In [1]: # Ejemplos de estadistica descriptiva con python import numpy as np # importando numpy from scipy import stats # importando scipy.stats import pandas as pd # importando pandas np . random . seed ( 2131982 ) # para poder replicar el random In [2]: datos = np . random . randn ( 5 , 4 ) # datos normalmente distribuidos datos Out[2]: array([[ 0.46038022, -1.08942528, -0.62681496, -0.63329028], [-0.1074033 , -0.88138082, -0.34466623, -0.28320214], [ 0.94051171, 0.86693793, 1.20947882, -0.16894118], [-0.12790177, -0.58099931, -0.46188426, -0.18148302], [-0.76959435, -1.37414587, 1.37696874, -0.18040537]]) In [3]: # media arítmetica datos . mean () # Calcula la media aritmetica de Out[3]: -0.14786303590303568 In [4]: np . mean ( datos ) # Mismo resultado desde la funcion de numpy Out[4]: -0.14786303590303568 In [5]: datos . mean ( axis = 1 ) # media aritmetica de cada fila Out[5]: array([-0.47228757, -0.40416312, 0.71199682, -0.33806709, -0.23679421]) In [6]: datos . mean ( axis = 0 ) # media aritmetica de cada columna Out[6]: array([ 0.0791985 , -0.61180267, 0.23061642, -0.2894644 ]) In [7]: # mediana np . median ( datos ) Out[7]: -0.23234258265023794 In [8]: np . median ( datos , 0 ) # media aritmetica de cada columna Out[8]: array([-0.1074033 , -0.88138082, -0.34466623, -0.18148302]) In [9]: # Desviación típica np . std ( datos ) Out[9]: 0.73755354584071608 In [10]: np . std ( datos , 0 ) # Desviación típica de cada columna Out[10]: array([ 0.58057213, 0.78352862, 0.87384108, 0.17682485]) In [11]: # varianza np . var ( datos ) Out[11]: 0.54398523298221324 In [12]: np . var ( datos , 0 ) # varianza de cada columna Out[12]: array([ 0.337064 , 0.6139171 , 0.76359823, 0.03126703]) In [13]: # moda stats . mode ( datos ) # Calcula la moda de cada columna # el 2do array devuelve la frecuencia. Out[13]: (array([[-0.76959435, -1.37414587, -0.62681496, -0.63329028]]), array([[ 1., 1., 1., 1.]])) In [14]: datos2 = np . array ([ 1 , 2 , 3 , 6 , 6 , 1 , 2 , 4 , 2 , 2 , 6 , 6 , 8 , 10 , 6 ]) stats . mode ( datos2 ) # aqui la moda es el 6 porque aparece 5 veces en el vector. Out[14]: (array([6]), array([ 5.])) In [15]: # correlacion np . corrcoef ( datos ) # Crea matriz de correlación. Out[15]: array([[ 1. , 0.82333743, 0.15257202, 0.78798675, -0.02292073], [ 0.82333743, 1. , -0.13709662, 0.86873632, 0.41234875], [ 0.15257202, -0.13709662, 1. , -0.47691376, 0.21216856], [ 0.78798675, 0.86873632, -0.47691376, 1. , -0.03445705], [-0.02292073, 0.41234875, 0.21216856, -0.03445705, 1. ]]) In [16]: # calculando la correlación entre dos vectores. np . corrcoef ( datos [ 0 ], datos [ 1 ]) Out[16]: array([[ 1. , 0.82333743], [ 0.82333743, 1. ]]) In [17]: # covarianza np . cov ( datos ) # calcula matriz de covarianza Out[17]: array([[ 0.43350958, 0.18087281, 0.06082243, 0.11328658, -0.01782409], [ 0.18087281, 0.11132485, -0.0276957 , 0.06329134, 0.16249513], [ 0.06082243, -0.0276957 , 0.36658864, -0.06305065, 0.15172255], [ 0.11328658, 0.06329134, -0.06305065, 0.04767826, -0.00888624], [-0.01782409, 0.16249513, 0.15172255, -0.00888624, 1.39495179]]) In [18]: # covarianza de dos vectores np . cov ( datos [ 0 ], datos [ 1 ]) Out[18]: array([[ 0.43350958, 0.18087281], [ 0.18087281, 0.11132485]]) In [19]: # usando pandas dataframe = pd . DataFrame ( datos , index = [ 'a' , 'b' , 'c' , 'd' , 'e' ], columns = [ 'col1' , 'col2' , 'col3' , 'col4' ]) dataframe Out[19]: col1 col2 col3 col4 a 0.460380 -1.089425 -0.626815 -0.633290 b -0.107403 -0.881381 -0.344666 -0.283202 c 0.940512 0.866938 1.209479 -0.168941 d -0.127902 -0.580999 -0.461884 -0.181483 e -0.769594 -1.374146 1.376969 -0.180405 In [20]: # resumen estadistadistico con pandas dataframe . describe () Out[20]: col1 col2 col3 col4 count 5.000000 5.000000 5.000000 5.000000 mean 0.079199 -0.611803 0.230616 -0.289464 std 0.649099 0.876012 0.976984 0.197696 min -0.769594 -1.374146 -0.626815 -0.633290 25% -0.127902 -1.089425 -0.461884 -0.283202 50% -0.107403 -0.881381 -0.344666 -0.181483 75% 0.460380 -0.580999 1.209479 -0.180405 max 0.940512 0.866938 1.376969 -0.168941 In [21]: # sumando las columnas dataframe . sum () Out[21]: col1 0.395993 col2 -3.059013 col3 1.153082 col4 -1.447322 dtype: float64 In [22]: # sumando filas dataframe . sum ( axis = 1 ) Out[22]: a -1.889150 b -1.616652 c 2.847987 d -1.352268 e -0.947177 dtype: float64 In [23]: dataframe . cumsum () # acumulados Out[23]: col1 col2 col3 col4 a 0.460380 -1.089425 -0.626815 -0.633290 b 0.352977 -1.970806 -0.971481 -0.916492 c 1.293489 -1.103868 0.237998 -1.085434 d 1.165587 -1.684867 -0.223887 -1.266917 e 0.395993 -3.059013 1.153082 -1.447322 In [24]: # media aritmetica de cada columna con pandas dataframe . mean () Out[24]: col1 0.079199 col2 -0.611803 col3 0.230616 col4 -0.289464 dtype: float64 In [25]: # media aritmetica de cada fila con pandas dataframe . mean ( axis = 1 ) Out[25]: a -0.472288 b -0.404163 c 0.711997 d -0.338067 e -0.236794 dtype: float64 Histogramas y Distribuciones Muchas veces los indicadores de la estadística descriptiva no nos proporcionan una imagen clara de nuestros datos . Por esta razón, siempre es útil complementarlos con gráficos de las distribuciones de los datos , que describan con qué frecuencia aparece cada valor. La representación más común de una distribución es un histograma , que es un gráfico que muestra la frecuencia o probabilidad de cada valor. El histograma muestra las frecuencias como un gráfico de barras que indica cuan frecuente un determinado valor ocurre en el conjunto de datos . El eje horizontal representa los valores del conjunto de datos y el eje vertical representa la frecuencia con que esos valores ocurren. Las distribuciones se pueden clasificar en dos grandes grupos: Las distribuciones continuas , que son aquellas que presentan un número infinito de posibles soluciones. Dentro de este grupo vamos a encontrar a las distribuciones: normal , gamma , chi cuadrado , t de Student , pareto , entre otras Las distribuciones discretas , que son aquellas en las que la variable puede tomar un número determinado de valores. Los principales exponentes de este grupo son las distribuciones: poisson , binomial , hipergeométrica , bernoulli entre otras Veamos algunos ejemplos graficados con la ayuda de Python . Distribución normal La distribución normal es una de las principales distribuciones, ya que es la que con más frecuencia aparece aproximada en los fenómenos reales. Tiene una forma acampanada y es simétrica respecto de un determinado parámetro estadístico. Con la ayuda de Python la podemos graficar de la siguiente manera: In [26]: # Graficos embebidos. % matplotlib inline In [27]: import matplotlib.pyplot as plt # importando matplotlib import seaborn as sns # importando seaborn # parametros esteticos de seaborn sns . set_palette ( \"deep\" , desat =. 6 ) sns . set_context ( rc = { \"figure.figsize\" : ( 8 , 4 )}) In [28]: mu , sigma = 0 , 0.1 # media y desvio estandar s = np . random . normal ( mu , sigma , 1000 ) #creando muestra de datos In [29]: # histograma de distribución normal. cuenta , cajas , ignorar = plt . hist ( s , 30 , normed = True ) normal = plt . plot ( cajas , 1 / ( sigma * np . sqrt ( 2 * np . pi )) * np . exp ( - ( cajas - mu ) ** 2 / ( 2 * sigma ** 2 ) ), linewidth = 2 , color = 'r' ) Distribuciones simetricas y asimetricas Una distribución es simétrica cuando moda, mediana y media coinciden aproximadamente en sus valores. Si una distribución es simétrica, existe el mismo número de valores a la derecha que a la izquierda de la media, por tanto, el mismo número de desviaciones con signo positivo que con signo negativo. Una distribución tiene asimetria positiva (o a la derecha) si la \"cola\" a la derecha de la media es más larga que la de la izquierda, es decir, si hay valores más separados de la media a la derecha. De la misma forma una distribución tiene asimetria negativa (o a la izquierda) si la \"cola\" a la izquierda de la media es más larga que la de la derecha, es decir, si hay valores más separados de la media a la izquierda. Las distribuciones asimétricas suelen ser problemáticas, ya que la mayoría de los métodos estadísticos suelen estar desarrollados para distribuciones del tipo normal . Para salvar estos problemas se suelen realizar transformaciones a los datos para hacer a estas distribuciones más simétricas y acercarse a la distribución normal . In [30]: # Dibujando la distribucion Gamma x = stats . gamma ( 3 ) . rvs ( 5000 ) gamma = plt . hist ( x , 70 , histtype = \"stepfilled\" , alpha =. 7 ) En este ejemplo podemos ver que la distribución gamma que dibujamos tiene una asimetria positiva. In [31]: # Calculando la simetria con scipy stats . skew ( x ) Out[31]: 1.1437199125547868 Cuartiles y diagramas de cajas Los cuartiles son los tres valores de la variable estadística que dividen a un conjunto de datos ordenados en cuatro partes iguales. Q1, Q2 y Q3 determinan los valores correspondientes al 25%, al 50% y al 75% de los datos. Q2 coincide con la mediana . Los diagramas de cajas son una presentación visual que describe varias características importantes al mismo tiempo, tales como la dispersión y simetría. Para su realización se representan los tres cuartiles y los valores mínimo y máximo de los datos, sobre un rectángulo, alineado horizontal o verticalmente. Estos gráficos nos proporcionan abundante información y son sumamente útiles para encontrar valores atípicos y comparar dos conjunto de datos . In [32]: # Ejemplo de grafico de cajas en python datos_1 = np . random . normal ( 100 , 10 , 200 ) datos_2 = np . random . normal ( 80 , 30 , 200 ) datos_3 = np . random . normal ( 90 , 20 , 200 ) datos_4 = np . random . normal ( 70 , 25 , 200 ) datos_graf = [ datos_1 , datos_2 , datos_3 , datos_4 ] # Creando el objeto figura fig = plt . figure ( 1 , figsize = ( 9 , 6 )) # Creando el subgrafico ax = fig . add_subplot ( 111 ) # creando el grafico de cajas bp = ax . boxplot ( datos_graf ) # visualizar mas facile los atípicos for flier in bp [ 'fliers' ]: flier . set ( marker = 'o' , color = 'red' , alpha = 0.5 ) # los puntos aislados son valores atípicos In [33]: # usando seaborn sns . boxplot ( datos_graf , names = [ \"grupo1\" , \"grupo2\" , \"grupo3\" , \"grupo 4\" ], color = \"PaleGreen\" ); Regresiones Las regresiones es una de las herramientas principales de la estadistica inferencial . El objetivo del análisis de regresión es describir la relación entre un conjunto de variables, llamadas variables dependientes, y otro conjunto de variables, llamadas variables independientes o explicativas. Más específicamente, el análisis de regresión ayuda a entender cómo el valor típico de la variable dependiente cambia cuando cualquiera de las variables independientes es cambiada, mientras que se mantienen las otras variables independientes fijas. El producto final del análisis de regresión es la estimación de una función de las variables independientes llamada la función de regresión . La idea es que en base a esta función de regresión podamos hacer estimaciones sobre eventos futuros. La regresión lineal es una de las técnicas más simples y mayormente utilizadas en los análisis de regresiones . Hace suposiciones muy rígidas sobre la relación entre la variable dependiente $y$ y variable independiente $x$. Asume que la relación va a tomar la forma: $$ y = \\beta_0 + \\beta_1 * x$$ Uno de los métodos más populares para realizar regresiones lineales es el de mínimos cuadrados ordinarios (OLS, por sus siglas en inglés), este método es el estimador más simple y común en la que los dos $\\beta$s se eligen para minimizar el cuadrado de la distancia entre los valores estimados y los valores reales. Realizar análisis de regresiones en Python es sumamente fácil gracias a statsmodels . Veamos un pequeño ejemplo utilizando el dataset longley , el cual es ideal para realizar regresiones: In [34]: # importanto la api de statsmodels import statsmodels.formula.api as smf import statsmodels.api as sm # Creando un DataFrame de pandas. df = pd . read_csv ( 'https://vincentarelbundock.github.io/Rdatasets/csv/datasets/longley.csv' , index_col = 0 ) df . head () # longley dataset Out[34]: GNP.deflator GNP Unemployed Armed.Forces Population Year Employed 1947 83.0 234.289 235.6 159.0 107.608 1947 60.323 1948 88.5 259.426 232.5 145.6 108.632 1948 61.122 1949 88.2 258.054 368.2 161.6 109.773 1949 60.171 1950 89.5 284.599 335.1 165.0 110.929 1950 61.187 1951 96.2 328.975 209.9 309.9 112.075 1951 63.221 In [35]: # utilizando la api de formula de statsmodels est = smf . ols ( formula = 'Employed ~ GNP' , data = df ) . fit () est . summary () # Employed se estima en base a GNP. Out[35]: OLS Regression Results Dep. Variable: Employed R-squared: 0.967 Model: OLS Adj. R-squared: 0.965 Method: Least Squares F-statistic: 415.1 Date: Sat, 27 Jun 2015 Prob (F-statistic): 8.36e-12 Time: 15:30:24 Log-Likelihood: -14.904 No. Observations: 16 AIC: 33.81 Df Residuals: 14 BIC: 35.35 Df Model: 1 Covariance Type: nonrobust coef std err t P>|t| [95.0% Conf. Int.] Intercept 51.8436 0.681 76.087 0.000 50.382 53.305 GNP 0.0348 0.002 20.374 0.000 0.031 0.038 Omnibus: 1.925 Durbin-Watson: 1.619 Prob(Omnibus): 0.382 Jarque-Bera (JB): 1.215 Skew: 0.664 Prob(JB): 0.545 Kurtosis: 2.759 Cond. No. 1.66e+03 Como podemos ver, el resumen que nos brinda statsmodels sobre nuestro modelo de regresión contiene bastante información sobre como se ajuste el modelo a los datos. Pasemos a explicar algunos de estos valores: Dep. Variable: es la variable que estamos estimasdo. Model: es el modelo que estamos utilizando. R-squared: es el coeficiente de determinación , el cual mide cuan bien nuestra recta de regresion se aproxima a los datos reales. Adj. R-squared: es el coeficiente anterior ajustado según el número de observaciones. [95.0% Conf. Int.]: Los valores inferior y superior del intervalo de confianza del 95%. coef: el valor estimado del coeficiente. std err: el error estándar de la estimación del coeficiente. Skew: una medida de la asimetria de los datos sobre la media. Kurtosis: Una medida de la forma de la distribución. La curtosis compara la cantidad de datos cerca de la media con los que están más lejos de la media(en las colas). In [36]: # grafico de regresion. que tanto se ajusta el modelo a los datos. y = df . Employed # Respuesta X = df . GNP # Predictor X = sm . add_constant ( X ) # agrega constante X_1 = pd . DataFrame ({ 'GNP' : np . linspace ( X . GNP . min (), X . GNP . max (), 100 )}) X_1 = sm . add_constant ( X_1 ) y_reg = est . predict ( X_1 ) # estimacion plt . scatter ( X . GNP , y , alpha = 0.3 ) # grafica los puntos de datos plt . ylim ( 30 , 100 ) # limite de eje y plt . xlabel ( \"Producto bruto\" ) # leyenda eje x plt . ylabel ( \"Empleo\" ) # leyenda eje y plt . title ( \"Ajuste de regresion\" ) # titulo del grafico reg = plt . plot ( X_1 . GNP , y_reg , 'r' , alpha = 0.9 ) # linea de regresion In [37]: # grafico de influencia from statsmodels.graphics.regressionplots import influence_plot inf = influence_plot ( est ) Este último gráfico nos muestra el apalancamiento y la influencia de cada caso. La estadística bayesiana La estadística bayesiana es un subconjunto del campo de la estadística en la que la evidencia sobre el verdadero estado de las cosas se expresa en términos de grados de creencia. Esta filosofía de tratar a las creencias como probabilidad es algo natural para los seres humanos. Nosotros la utilizamos constantemente a medida que interactuamos con el mundo y sólo vemos verdades parciales; necesitando reunir pruebas para formar nuestras creencias. La diferencia fundamental entre la estadística clásica (frecuentista) y la bayesiana es el concepto de probabilidad . Para la estadística clásica es un concepto objetivo, que se encuentra en la naturaleza, mientras que para la estadística bayesiana se encuentra en el observador, siendo así un concepto subjetivo. De este modo, en estadística clásica solo se toma como fuente de información las muestras obtenidas. En el caso bayesiano , sin embargo, además de la muestra también juega un papel fundamental la información previa o externa que se posee en relación a los fenómenos que se tratan de modelar. La estadística bayesiana está demostrando su utilidad en ciertas estimaciones basadas en el conocimiento subjetivo a priori y el hecho de permitir revisar esas estimaciones en función de la evidencia empírica es lo que está abriendo nuevas formas de hacer conocimiento. Una aplicación de esto son los clasificadores bayesianos que son frecuentemente usados en implementaciones de filtros de correo basura, que se adaptan con el uso. La estadística bayesiana es un tema muy interesante que merece un artículo en sí mismo. Para entender más fácilmente como funciona la estadística bayesiana veamos un simple ejemplo del lanzamiento de una moneda. La idea principal de la inferencia bayesiana es que la noción de probabilidad cambia mientras más datos tengamos. In [38]: sns . set_context ( rc = { \"figure.figsize\" : ( 11 , 9 )}) dist = stats . beta n_trials = [ 0 , 1 , 2 , 3 , 4 , 5 , 8 , 15 , 50 , 500 ] data = stats . bernoulli . rvs ( 0.5 , size = n_trials [ - 1 ]) x = np . linspace ( 0 , 1 , 100 ) for k , N in enumerate ( n_trials ): sx = plt . subplot ( len ( n_trials ) / 2 , 2 , k + 1 ) plt . xlabel ( \"$p$, probabilidad de cara\" ) \\ if k in [ 0 , len ( n_trials ) - 1 ] else None plt . setp ( sx . get_yticklabels (), visible = False ) heads = data [: N ] . sum () y = dist . pdf ( x , 1 + heads , 1 + N - heads ) plt . plot ( x , y , label = \"lanzamientos observados %d , \\n %d caras\" % ( N , heads )) plt . fill_between ( x , 0 , y , color = \"#348ABD\" , alpha = 0.4 ) plt . vlines ( 0.5 , 0 , 4 , color = \"k\" , linestyles = \"--\" , lw = 1 ) leg = plt . legend () leg . get_frame () . set_alpha ( 0.4 ) plt . autoscale ( tight = True ) plt . suptitle ( \"Actualizacion Bayesiana de probabilidades posterios\" , y = 1.02 , fontsize = 14 ) plt . tight_layout () Como el gráfico de arriba muestra, cuando empezamos a observar nuevos datos nuestras probabilidades posteriores comienzan a cambiar y moverse. Eventualmente, a medida que observamos más y más datos (lanzamientos de monedas), nuestras probabilidades se acercan más y más hacia el verdadero valor de p = 0.5 (marcado por una línea discontinua). Aquí termina este tutorial, espero que les haya sido util. Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Pobabilidad y Estadistica","url":"https://relopezbriega.github.io/blog/2015/06/27/probabilidad-y-estadistica-con-python/"},{"title":"Algebra Lineal con Python","text":"Introducción Una de las herramientas matemáticas más utilizadas en machine learning y data mining es el Álgebra lineal ; por tanto, si queremos incursionar en el fascinante mundo del aprendizaje automático y el análisis de datos es importante reforzar los conceptos que forman parte de sus cimientos. El Álgebra lineal es una rama de las matemáticas que es sumamente utilizada en el estudio de una gran variedad de ciencias, como ser, ingeniería, finanzas, investigación operativa, entre otras. Es una extensión del álgebra que aprendemos en la escuela secundaria, hacia un mayor número de dimensiones; en lugar de trabajar con incógnitas a nivel de escalares comenzamos a trabajar con matrices y vectores . El estudio del Álgebra lineal implica trabajar con varios objetos matemáticos, como ser: Los Escalares : Un escalar es un solo número, en contraste con la mayoría de los otros objetos estudiados en Álgebra lineal , que son generalmente una colección de múltiples números. Los Vectores :Un vector es una serie de números. Los números tienen una orden preestablecido, y podemos identificar cada número individual por su índice en ese orden. Podemos pensar en los vectores como la identificación de puntos en el espacio, con cada elemento que da la coordenada a lo largo de un eje diferente. Existen dos tipos de vectores , los vectores de fila y los vectores de columna . Podemos representarlos de la siguiente manera, dónde f es un vector de fila y c es un vector de columna: $$f=\\begin{bmatrix}0&1&-1\\end{bmatrix} ; c=\\begin{bmatrix}0\\\\1\\\\-1\\end{bmatrix}$$ Las Matrices : Una matriz es un arreglo bidimensional de números (llamados entradas de la matriz) ordenados en filas (o renglones) y columnas, donde una fila es cada una de las líneas horizontales de la matriz y una columna es cada una de las líneas verticales. En una matriz cada elemento puede ser identificado utilizando dos índices, uno para la fila y otro para la columna en que se encuentra. Las podemos representar de la siguiente manera, A es una matriz de 3x2. $$A=\\begin{bmatrix}0 & 1& \\\\-1 & 2 \\\\ -2 & 3\\end{bmatrix}$$ Los Tensores :En algunos casos necesitaremos una matriz con más de dos ejes. En general, una serie de números dispuestos en una cuadrícula regular con un número variable de ejes es conocido como un tensor . Sobre estos objetos podemos realizar las operaciones matemáticas básicas, como ser adición , multiplicación , sustracción y división , es decir que vamos a poder sumar vectores con matrices , multiplicar escalares a vectores y demás. Librerías de Python para álgebra lineal Los principales módulos que Python nos ofrece para realizar operaciones de Álgebra lineal son los siguientes: Numpy : El popular paquete matemático de Python , nos va a permitir crear vectores , matrices y tensores con suma facilidad. numpy.linalg : Este es un submodulo dentro de Numpy con un gran número de funciones para resolver ecuaciones de Álgebra lineal . scipy.linalg : Este submodulo del paquete científico Scipy es muy similar al anterior, pero con algunas más funciones y optimaciones. Sympy : Esta librería nos permite trabajar con matemática simbólica, convierte a Python en un sistema algebraico computacional . Nos va a permitir trabajar con ecuaciones y fórmulas simbólicamente, en lugar de numéricamente. CVXOPT : Este módulo nos permite resolver problemas de optimizaciones de programación lineal . PuLP : Esta librería nos permite crear modelos de programación lineal en forma muy sencilla con Python . Operaciones básicas Vectores Un vector de largo n es una secuencia (o array , o tupla ) de n números. La solemos escribir como $x=(x1,...,xn)$ o $x=[x1,...,xn]$ En Python , un vector puede ser representado con una simple lista , o con un array de Numpy ; siendo preferible utilizar esta última opción. In [1]: # Vector como lista de Python v1 = [ 2 , 4 , 6 ] v1 Out[1]: [2, 4, 6] In [2]: # Vectores con numpy import numpy as np v2 = np . ones ( 3 ) # vector de solo unos. v2 Out[2]: array([1., 1., 1.]) In [3]: v3 = np . array ([ 1 , 3 , 5 ]) # pasando una lista a las arrays de numpy v3 Out[3]: array([1, 3, 5]) In [4]: v4 = np . arange ( 1 , 8 ) # utilizando la funcion arange de numpy v4 Out[4]: array([1, 2, 3, 4, 5, 6, 7]) Representación gráfica Tradicionalmente, los vectores son representados visualmente como flechas que parten desde el origen hacia un punto. Por ejemplo, si quisiéramos representar graficamente a los vectores $v1=[2, 4]$, $v2=[-3, 3]$ y $v3=[-4, -3.5]$, podríamos hacerlo de la siguiente manera. In [ ]: import matplotlib.pyplot as plt from warnings import filterwarnings % matplotlib inline filterwarnings ( 'ignore' ) # Ignorar warnings In [ ]: def move_spines (): \"\"\"Crea la figura de pyplot y los ejes. Mueve las lineas de la izquierda y de abajo para que se intersecten con el origen. Elimina las lineas de la derecha y la de arriba. Devuelve los ejes.\"\"\" fix , ax = plt . subplots () for spine in [ \"left\" , \"bottom\" ]: ax . spines [ spine ] . set_position ( \"zero\" ) for spine in [ \"right\" , \"top\" ]: ax . spines [ spine ] . set_color ( \"none\" ) return ax def vect_fig (): \"\"\"Genera el grafico de los vectores en el plano\"\"\" ax = move_spines () ax . set_xlim ( - 5 , 5 ) ax . set_ylim ( - 5 , 5 ) ax . grid () vecs = [[ 2 , 4 ], [ - 3 , 3 ], [ - 4 , - 3.5 ]] # lista de vectores for v in vecs : ax . annotate ( \" \" , xy = v , xytext = [ 0 , 0 ], arrowprops = dict ( facecolor = \"blue\" , shrink = 0 , alpha = 0.7 , width = 0.5 )) ax . text ( 1.1 * v [ 0 ], 1.1 * v [ 1 ], v ) In [ ]: vect_fig () # crea el gráfico Operaciones con vectores Las operaciones más comunes que utilizamos cuando trabajamos con vectores son la suma , la resta y la multiplicación por escalares . Cuando sumamos dos vectores , vamos sumando elemento por elemento de cada vector . $$ \\begin{split}x + y = \\left[ \\begin{array}{c} x_1 \\\\ x_2 \\\\ \\vdots \\\\ x_n \\end{array} \\right] + \\left[ \\begin{array}{c} y_1 \\\\ y_2 \\\\ \\vdots \\\\ y_n \\end{array} \\right] := \\left[ \\begin{array}{c} x_1 + y_1 \\\\ x_2 + y_2 \\\\ \\vdots \\\\ x_n + y_n \\end{array} \\right]\\end{split}$$ De forma similar funciona la operación de resta. $$ \\begin{split}x - y = \\left[ \\begin{array}{c} x_1 \\\\ x_2 \\\\ \\vdots \\\\ x_n \\end{array} \\right] - \\left[ \\begin{array}{c} y_1 \\\\ y_2 \\\\ \\vdots \\\\ y_n \\end{array} \\right] := \\left[ \\begin{array}{c} x_1 - y_1 \\\\ x_2 - y_2 \\\\ \\vdots \\\\ x_n - y_n \\end{array} \\right]\\end{split}$$ La multiplicación por escalares es una operación que toma a un número $\\gamma$, y a un vector $x$ y produce un nuevo vector donde cada elemento del vector $x$ es multiplicado por el número $\\gamma$. $$\\begin{split}\\gamma x := \\left[ \\begin{array}{c} \\gamma x_1 \\\\ \\gamma x_2 \\\\ \\vdots \\\\ \\gamma x_n \\end{array} \\right]\\end{split}$$ En Python podríamos realizar estas operaciones en forma muy sencilla: In [8]: # Ejemplo en Python x = np . arange ( 1 , 5 ) y = np . array ([ 2 , 4 , 6 , 8 ]) x , y Out[8]: (array([1, 2, 3, 4]), array([2, 4, 6, 8])) In [9]: # sumando dos vectores numpy x + y Out[9]: array([ 3, 6, 9, 12]) In [10]: # restando dos vectores x - y Out[10]: array([-1, -2, -3, -4]) In [11]: # multiplicando por un escalar x * 2 Out[11]: array([2, 4, 6, 8]) In [12]: y * 3 Out[12]: array([ 6, 12, 18, 24]) Producto escalar o interior El producto escalar de dos vectores se define como la suma de los productos de sus elementos, suele representarse matemáticamente como < x, y > o x'y, donde x e y son dos vectores. $$< x, y > := \\sum_{i=1}^n x_i y_i$$ Dos vectores son ortogonales o perpendiculares cuando forman ángulo recto entre sí. Si el producto escalar de dos vectores es cero, ambos vectores son ortogonales . Adicionalmente, todo producto escalar induce una norma sobre el espacio en el que está definido, de la siguiente manera: $$\\| x \\| := \\sqrt{< x, x>} := \\left( \\sum_{i=1}^n x_i^2 \\right)^{1/2}$$ En Python lo podemos calcular de la siguiente forma: In [13]: # Calculando el producto escalar de los vectores x e y x @ y Out[13]: 60 In [14]: # o lo que es lo mismo, que: sum ( x * y ), np . dot ( x , y ) Out[14]: (60, 60) In [15]: # Calculando la norma del vector X np . linalg . norm ( x ) Out[15]: 5.477225575051661 In [16]: # otra forma de calcular la norma de x np . sqrt ( x @ x ) Out[16]: 5.477225575051661 In [17]: # vectores ortogonales v1 = np . array ([ 3 , 4 ]) v2 = np . array ([ 4 , - 3 ]) v1 @ v2 Out[17]: 0 Matrices Las matrices son una forma clara y sencilla de organizar los datos para su uso en operaciones lineales. Una matriz n × k es una agrupación rectangular de números con n filas y k columnas; se representa de la siguiente forma: $$\\begin{split}A = \\left[ \\begin{array}{cccc} a_{11} & a_{12} & \\cdots & a_{1k} \\\\ a_{21} & a_{22} & \\cdots & a_{2k} \\\\ \\vdots & \\vdots & & \\vdots \\\\ a_{n1} & a_{n2} & \\cdots & a_{nk} \\end{array} \\right]\\end{split}$$ En la matriz A, el símbolo $a_{nk}$ representa el elemento n-ésimo de la fila en la k-ésima columna. La matriz A también puede ser llamada un vector si cualquiera de n o k son iguales a 1. En el caso de n=1, A se llama un vector fila , mientras que en el caso de k=1 se denomina un vector columna . Las matrices se utilizan para múltiples aplicaciones y sirven, en particular, para representar los coeficientes de los sistemas de ecuaciones lineales o para representar transformaciones lineales dada una base. Pueden sumarse, multiplicarse y descomponerse de varias formas. Operaciones con matrices Al igual que con los vectores , que no son más que un caso particular, las matrices se pueden sumar , restar y la multiplicar por escalares . Multiplicacion por escalares: $$\\begin{split}\\gamma A \\left[ \\begin{array}{ccc} a_{11} & \\cdots & a_{1k} \\\\ \\vdots & \\vdots & \\vdots \\\\ a_{n1} & \\cdots & a_{nk} \\\\ \\end{array} \\right] := \\left[ \\begin{array}{ccc} \\gamma a_{11} & \\cdots & \\gamma a_{1k} \\\\ \\vdots & \\vdots & \\vdots \\\\ \\gamma a_{n1} & \\cdots & \\gamma a_{nk} \\\\ \\end{array} \\right]\\end{split}$$ Suma de matrices: $$\\begin{split}A + B = \\left[ \\begin{array}{ccc} a_{11} & \\cdots & a_{1k} \\\\ \\vdots & \\vdots & \\vdots \\\\ a_{n1} & \\cdots & a_{nk} \\\\ \\end{array} \\right] + \\left[ \\begin{array}{ccc} b_{11} & \\cdots & b_{1k} \\\\ \\vdots & \\vdots & \\vdots \\\\ b_{n1} & \\cdots & b_{nk} \\\\ \\end{array} \\right] := \\left[ \\begin{array}{ccc} a_{11} + b_{11} & \\cdots & a_{1k} + b_{1k} \\\\ \\vdots & \\vdots & \\vdots \\\\ a_{n1} + b_{n1} & \\cdots & a_{nk} + b_{nk} \\\\ \\end{array} \\right]\\end{split}$$ Resta de matrices: $$\\begin{split}A - B = \\left[ \\begin{array}{ccc} a_{11} & \\cdots & a_{1k} \\\\ \\vdots & \\vdots & \\vdots \\\\ a_{n1} & \\cdots & a_{nk} \\\\ \\end{array} \\right]- \\left[ \\begin{array}{ccc} b_{11} & \\cdots & b_{1k} \\\\ \\vdots & \\vdots & \\vdots \\\\ b_{n1} & \\cdots & b_{nk} \\\\ \\end{array} \\right] := \\left[ \\begin{array}{ccc} a_{11} - b_{11} & \\cdots & a_{1k} - b_{1k} \\\\ \\vdots & \\vdots & \\vdots \\\\ a_{n1} - b_{n1} & \\cdots & a_{nk} - b_{nk} \\\\ \\end{array} \\right]\\end{split}$$ Para los casos de suma y resta, hay que tener en cuenta que solo se pueden sumar o restar matrices que tengan las mismas dimensiones, es decir que si tengo una matriz A de dimensión 3x2 (3 filas y 2 columnas) solo voy a poder sumar o restar la matriz B si esta también tiene 3 filas y 2 columnas. In [18]: # Ejemplo en Python A = np . array ([[ 1 , 3 , 2 ], [ 1 , 0 , 0 ], [ 1 , 2 , 2 ]]) B = np . array ([[ 1 , 0 , 5 ], [ 7 , 5 , 0 ], [ 2 , 1 , 1 ]]) In [19]: # suma de las matrices A y B A + B Out[19]: array([[2, 3, 7], [8, 5, 0], [3, 3, 3]]) In [20]: # resta de matrices A - B Out[20]: array([[ 0, 3, -3], [-6, -5, 0], [-1, 1, 1]]) In [21]: # multiplicando matrices por escalares A * 2 Out[21]: array([[2, 6, 4], [2, 0, 0], [2, 4, 4]]) In [22]: B * 3 Out[22]: array([[ 3, 0, 15], [21, 15, 0], [ 6, 3, 3]]) In [23]: # ver la dimension de una matriz A . shape Out[23]: (3, 3) In [24]: # ver cantidad de elementos de una matriz A . size Out[24]: 9 Multiplicacion o Producto de matrices La regla para la multiplicación de matrices generaliza la idea del producto interior que vimos con los vectores ; y esta diseñada para facilitar las operaciones lineales básicas. Cuando multiplicamos matrices , el número de columnas de la primera matriz debe ser igual al número de filas de la segunda matriz ; y el resultado de esta multiplicación va a tener el mismo número de filas que la primer matriz y el número de la columnas de la segunda matriz . Es decir, que si yo tengo una matriz A de dimensión 3x4 y la multiplico por una matriz B de dimensión 4x2, el resultado va a ser una matriz C de dimensión 3x2. Algo a tener en cuenta a la hora de multiplicar matrices es que la propiedad connmutativa no se cumple. AxB no es lo mismo que BxA. Veamos los ejemplos en Python . In [25]: # Ejemplo multiplicación de matrices A = np . arange ( 1 , 13 ) . reshape ( 3 , 4 ) #matriz de dimension 3x4 A Out[25]: array([[ 1, 2, 3, 4], [ 5, 6, 7, 8], [ 9, 10, 11, 12]]) In [26]: B = np . arange ( 8 ) . reshape ( 4 , 2 ) #matriz de dimension 4x2 B Out[26]: array([[0, 1], [2, 3], [4, 5], [6, 7]]) In [27]: # Multiplicando A x B A @ B #resulta en una matriz de dimension 3x2 Out[27]: array([[ 40, 50], [ 88, 114], [136, 178]]) In [28]: # Multiplicando B x A B @ A --------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-28-b55e34ad9c31> in <module> () 1 # Multiplicando B x A ----> 2 B @ A ValueError : shapes (4,2) and (3,4) not aligned: 2 (dim 1) != 3 (dim 0) Este ultimo ejemplo vemos que la propiedad conmutativa no se cumple, es más, Python nos arroja un error, ya que el número de columnas de B no coincide con el número de filas de A, por lo que ni siquiera se puede realizar la multiplicación de B x A. Para una explicación más detallada del proceso de multiplicación de matrices , pueden consultar el siguiente tutorial . La matriz identidad, la matriz inversa, la matriz transpuesta y el determinante La matriz identidad es el elemento neutro en la multiplicación de matrices , es el equivalente al número 1. Cualquier matriz multiplicada por la matriz identidad nos da como resultado la misma matriz. La matriz identidad es una matriz cuadrada (tiene siempre el mismo número de filas que de columnas); y su diagonal principal se compone de todos elementos 1 y el resto de los elementos se completan con 0. Suele representase con la letra I Por ejemplo la matriz identidad de 3x3 sería la siguiente: $$I=\\begin{bmatrix}1 & 0 & 0 & \\\\0 & 1 & 0\\\\ 0 & 0 & 1\\end{bmatrix}$$ Ahora que conocemos el concepto de la matriz identidad , podemos llegar al concepto de la matriz inversa . Si tenemos una matriz A, la matriz inversa de A, que se representa como $A^{-1}$ es aquella matriz cuadrada que hace que la multiplicación $A$x$A^{-1}$ sea igual a la matriz identidad I. Es decir que es la matriz recíproca de A. $$A × A^{-1} = A^{-1} × A = I$$ Tener en cuenta que esta matriz inversa en muchos casos puede no existir.En este caso se dice que la matriz es singular o degenerada. Una matriz es singular si y solo si su determinante es nulo. El determinante es un número especial que puede calcularse sobre las matrices cuadradas . Se calcula como la suma de los productos de las diagonales de la matriz en una dirección menos la suma de los productos de las diagonales en la otra dirección. Se represente con el símbolo |A|. $$A=\\begin{bmatrix}a_{11} & a_{12} & a_{13} & \\\\a_{21} & a_{22} & a_{23} & \\\\ a_{31} & a_{32} & a_{33} & \\end{bmatrix}$$$$|A| = (a_{11} a_{22} a_{33} + a_{12} a_{23} a_{31} + a_{13} a_{21} a_{32} ) - (a_{31} a_{22} a_{13} + a_{32} a_{23} a_{11} + a_{33} a_{21} a_{12}) $$ Por último, la matriz transpuesta es aquella en que las filas se transforman en columnas y las columnas en filas. Se representa con el símbolo $A^\\intercal$ $$\\begin{bmatrix}a & b & \\\\c & d & \\\\ e & f & \\end{bmatrix}^T:=\\begin{bmatrix}a & c & e &\\\\b & d & f & \\end{bmatrix}$$ Ejemplos en Python : In [29]: # Creando una matriz identidad de 2x2 I = np . eye ( 2 ) I Out[29]: array([[1., 0.], [0., 1.]]) In [30]: # Multiplicar una matriz por la identidad nos da la misma matriz A = np . array ([[ 4 , 7 ], [ 2 , 6 ]]) A Out[30]: array([[4, 7], [2, 6]]) In [31]: A @ I # AxI = A Out[31]: array([[4., 7.], [2., 6.]]) In [32]: # Calculando el determinante de la matriz A np . linalg . det ( A ) Out[32]: 10.000000000000002 In [33]: # Calculando la inversa de A. A_inv = np . linalg . inv ( A ) A_inv Out[33]: array([[ 0.6, -0.7], [-0.2, 0.4]]) In [34]: # A x A_inv nos da como resultado I. A @ A_inv Out[34]: array([[1., 0.], [0., 1.]]) In [35]: # Trasponiendo una matriz A = np . arange ( 6 ) . reshape ( 3 , 2 ) A Out[35]: array([[0, 1], [2, 3], [4, 5]]) In [36]: np . transpose ( A ) Out[36]: array([[0, 2, 4], [1, 3, 5]]) Sistemas de ecuaciones lineales Una de las principales aplicaciones del Álgebra lineal consiste en resolver problemas de sistemas de ecuaciones lineales. Una ecuación lineal es una ecuación que solo involucra sumas y restas de una variable o mas variables a la primera potencia. Es la ecuación de la línea recta.Cuando nuestro problema esta representado por más de una ecuación lineal , hablamos de un sistema de ecuaciones lineales . Por ejemplo, podríamos tener un sistema de dos ecuaciones con dos incógnitas como el siguiente: $$ x - 2y = 1$$ $$3x + 2y = 11$$ La idea es encontrar el valor de $x$ e $y$ que resuelva ambas ecuaciones. Una forma en que podemos hacer esto, puede ser representando graficamente ambas rectas y buscar los puntos en que las rectas se cruzan. En Python esto se puede hacer en forma muy sencilla con la ayuda de matplotlib . In [37]: # graficando el sistema de ecuaciones. x_vals = np . linspace ( 0 , 5 , 50 ) # crea 50 valores entre 0 y 5 plt . plot ( x_vals , ( 1 - x_vals ) /- 2 ) # grafica x - 2y = 1 plt . plot ( x_vals , ( 11 - ( 3 * x_vals )) / 2 ) # grafica 3x + 2y = 11 plt . axis ( ymin = 0 ) Out[37]: (-0.25, 5.25, 0, 5.875) Luego de haber graficado las funciones, podemos ver que ambas rectas se cruzan en el punto (3, 1), es decir que la solución de nuestro sistema sería $x=3$ e $y=1$. En este caso, al tratarse de un sistema simple y con solo dos incógnitas, la solución gráfica puede ser de utilidad, pero para sistemas más complicados se necesita una solución numérica, es aquí donde entran a jugar las matrices . Ese mismo sistema se podría representar como una ecuación matricial de la siguiente forma: $$\\begin{bmatrix}1 & -2 & \\\\3 & 2 & \\end{bmatrix} \\begin{bmatrix}x & \\\\y & \\end{bmatrix} = \\begin{bmatrix}1 & \\\\11 & \\end{bmatrix}$$ Lo que es lo mismo que decir que la matriz A por la matriz $x$ nos da como resultado el vector b. $$ Ax = b$$ En este caso, ya sabemos el resultado de $x$, por lo que podemos comprobar que nuestra solución es correcta realizando la multiplicación de matrices . In [38]: # Comprobando la solucion con la multiplicación de matrices. A = np . array ([[ 1. , - 2. ], [ 3. , 2. ]]) x = np . array ([[ 3. ],[ 1. ]]) A @ x Out[38]: array([[ 1.], [11.]]) Para resolver en forma numérica los sistema de ecuaciones , existen varios métodos: El método de sustitución : El cual consiste en despejar en una de las ecuaciones cualquier incógnita, preferiblemente la que tenga menor coeficiente y a continuación sustituirla en otra ecuación por su valor. El método de igualacion : El cual se puede entender como un caso particular del método de sustitución en el que se despeja la misma incógnita en dos ecuaciones y a continuación se igualan entre sí la parte derecha de ambas ecuaciones. El método de reduccion : El procedimiento de este método consiste en transformar una de las ecuaciones (generalmente, mediante productos), de manera que obtengamos dos ecuaciones en la que una misma incógnita aparezca con el mismo coeficiente y distinto signo. A continuación, se suman ambas ecuaciones produciéndose así la reducción o cancelación de dicha incógnita, obteniendo una ecuación con una sola incógnita, donde el método de resolución es simple. El método gráfico : Que consiste en construir el gráfica de cada una de las ecuaciones del sistema. Este método (manualmente aplicado) solo resulta eficiente en el plano cartesiano (solo dos incógnitas). El método de Gauss : El método de eliminación de Gauss o simplemente método de Gauss consiste en convertir un sistema lineal de n ecuaciones con n incógnitas, en uno escalonado, en el que la primera ecuación tiene n incógnitas, la segunda ecuación tiene n - 1 incógnitas, ..., hasta la última ecuación, que tiene 1 incógnita. De esta forma, será fácil partir de la última ecuación e ir subiendo para calcular el valor de las demás incógnitas. El método de Eliminación de Gauss-Jordan : El cual es una variante del método anterior, y consistente en triangular la matriz aumentada del sistema mediante transformaciones elementales, hasta obtener ecuaciones de una sola incógnita. El método de Cramer : El cual consiste en aplicar la regla de Cramer para resolver el sistema. Este método solo se puede aplicar cuando la matriz de coeficientes del sistema es cuadrada y de determinante no nulo. La idea no es explicar cada uno de estos métodos, sino saber que existen y que Python nos hacer la vida mucho más fácil, ya que para resolver un sistema de ecuaciones simplemente debemos llamar a la función solve() . Por ejemplo, para resolver este sistema de 3 ecuaciones y 3 incógnitas. $$ x + 2y + 3z = 6$$$$ 2x + 5y + 2z = 4$$$$ 6x - 3y + z = 2$$ Primero armamos la matriz A de coeficientes y la matriz b de resultados y luego utilizamos solve() para resolverla. In [39]: # Creando matriz de coeficientes A = np . array ([[ 1 , 2 , 3 ], [ 2 , 5 , 2 ], [ 6 , - 3 , 1 ]]) A Out[39]: array([[ 1, 2, 3], [ 2, 5, 2], [ 6, -3, 1]]) In [40]: # Creando matriz de resultados b = np . array ([ 6 , 4 , 2 ]) b Out[40]: array([6, 4, 2]) In [41]: # Resolviendo sistema de ecuaciones x = np . linalg . solve ( A , b ) x Out[41]: array([0., 0., 2.]) In [42]: # Comprobando la solucion A @ x == b Out[42]: array([ True, True, True]) Programación lineal La programación lineal estudia las situaciones en las que se exige maximizar o minimizar funciones que se encuentran sujetas a determinadas restricciones. Consiste en optimizar (minimizar o maximizar) una función lineal, denominada función objetivo, de tal forma que las variables de dicha función estén sujetas a una serie de restricciones que expresamos mediante un sistema de inecuaciones lineales . Para resolver un problema de programación lineal, debemos seguir los siguientes pasos: Elegir las incógnitas. Escribir la función objetivo en función de los datos del problema. Escribir las restricciones en forma de sistema de inecuaciones. Averiguar el conjunto de soluciones factibles representando gráficamente las restricciones. Calcular las coordenadas de los vértices del recinto de soluciones factibles (si son pocos). Calcular el valor de la función objetivo en cada uno de los vértices para ver en cuál de ellos presenta el valor máximo o mínimo según nos pida el problema (hay que tener en cuenta aquí la posible no existencia de solución). Veamos un ejemplo y como Python nos ayuda a resolverlo en forma sencilla. Supongamos que tenemos la siguiente función objetivo : $$f(x_{1},x_{2})= 50x_{1} + 40x_{2}$$ y las siguientes restricciones : $$x_{1} + 1.5x_{2} \\leq 750$$$$2x_{1} + x_{2} \\leq 1000$$$$x_{1} \\geq 0$$$$x_{2} \\geq 0$$ Podemos resolverlo utilizando PuLP , CVXOPT o graficamente (con matplotlib ) de la siguiente forma. In [43]: # Resolviendo la optimizacion con pulp from pulp import * # declarando las variables x1 = LpVariable ( \"x1\" , 0 , 800 ) # 0<= x1 <= 40 x2 = LpVariable ( \"x2\" , 0 , 1000 ) # 0<= x2 <= 1000 # definiendo el problema prob = LpProblem ( \"problem\" , LpMaximize ) # definiendo las restricciones prob += x1 + 1.5 * x2 <= 750 prob += 2 * x1 + x2 <= 1000 prob += x1 >= 0 prob += x2 >= 0 # definiendo la funcion objetivo a maximizar prob += 50 * x1 + 40 * x2 # resolviendo el problema status = prob . solve ( GLPK ( msg = 0 )) LpStatus [ status ] # imprimiendo los resultados ( value ( x1 ), value ( x2 )) Out[43]: (375.0, 250.0) In [44]: # Resolviendo el problema con cvxopt from cvxopt import matrix , solvers A = matrix ([[ - 1. , - 2. , 1. , 0. ], # columna de x1 [ - 1.5 , - 1. , 0. , 1. ]]) # columna de x2 b = matrix ([ 750. , 1000. , 0. , 0. ]) # resultados c = matrix ([ 50. , 40. ]) # funcion objetivo # resolviendo el problema sol = solvers . lp ( c , A , b ) pcost dcost gap pres dres k/t 0: -2.5472e+04 -3.6797e+04 5e+03 0e+00 3e-01 1e+00 1: -2.8720e+04 -2.9111e+04 1e+02 2e-16 9e-03 2e+01 2: -2.8750e+04 -2.8754e+04 1e+00 8e-17 9e-05 2e-01 3: -2.8750e+04 -2.8750e+04 1e-02 4e-16 9e-07 2e-03 4: -2.8750e+04 -2.8750e+04 1e-04 9e-17 9e-09 2e-05 Optimal solution found. In [45]: # imprimiendo la solucion. print ( ' {0:.2f} , {1:.2f} ' . format ( sol [ 'x' ][ 0 ] *- 1 , sol [ 'x' ][ 1 ] *- 1 )) 375.00, 250.00 In [46]: # Resolviendo la optimizacion graficamente. x_vals = np . linspace ( 0 , 800 , 10 ) # 10 valores entre 0 y 800 plt . plot ( x_vals , (( 750 - x_vals ) / 1.5 )) # grafica x1 + 1.5x2 = 750 plt . plot ( x_vals , ( 1000 - 2 * x_vals )) # grafica 2x1 + x2 = 1000 plt . axis ( ymin = 0 ) plt . show () Como podemos ver en el gráfico, ambas rectas se cruzan en la solución óptima, x1=375 y x2=250. Con esto termino esta introducción al Álgebra lineal con Python . Espero que hayan disfurtado de este tutorial tanto como yo disfrute escribirlo! Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Algebra","url":"https://relopezbriega.github.io/blog/2015/06/14/algebra-lineal-con-python/"},{"title":"El dia Pi","text":"Hoy, 14 de Marzo se celebra el día de Pi ($\\pi$), esta celebración fue una ocurrencia del físico Larry Shaw, quien eligió esta fecha por su semejanza con el valor de dos dígitos de Pi. (en el formato de fecha de Estados Unidos, el 14 de Marzo se escribe 3/14). Particularmente este año, se dará el fenómeno de que será el día de Pi más preciso del siglo, ya que a las 9:26:53 se formaría el número Pi con 9 dígitos de precisión! (3/14/15 9:26:53). En honor a su día, voy a dedicar este artículo al número $\\pi$ . ¿Qué es el número $\\pi$? El número $\\pi$ es uno de los más famosos de la matemática. Mide la relación que existe entre la longitud de una circunferencia y su diámetro . No importa cual sea el tamaño de la circunferencia , esta relación siempre va a ser la misma y va a estar representada por $\\pi$ . Este tipo de propiedades, que se mantienen sin cambios cuando otros atributos varían son llamadas constantes . $\\pi$ es una de las constantes utilizadas con mayor frecuencia en matemática, física e ingeniería. Historia del número $\\pi$ La primera referencia que se conoce de $\\pi$ data aproximadamente del año 1650 ac en el Papiro de Ahmes , documento que contiene problemas matemáticos básicos, fracciones, cálculo de áreas, volúmenes, progresiones, repartos proporcionales, reglas de tres, ecuaciones lineales y trigonometría básica. El valor que se asigna a $\\pi$ en ese documento es el de 28/34 aproximadamente 3,1605. Una de las primeras aproximaciones fue la realizada por Arquímedes en el año 250 adC quien calculo que el valor estaba comprendido entre 3 10/71 y 3 1/7 (3,1408 y 3,1452) y utilizo para sus estudios el valor 211875/67441 aproximadamente 3,14163. El matemático Leonhard Euler adoptó el conocido símbolo $\\pi$ en 1737 en su obra Introducción al cálculo infinitesimal e instantáneamente se convirtió en una notación estándar hasta hoy en día. ¿Qué hace especial al número $\\pi$? Lo que convierte a $\\pi$ en un número interesante, es que se trata de un número irracional , es decir, que el mismo no puede ser expresado como una fraccion de dos números enteros . Asimismo, también es un número trascendental , ya que no es raíz de ninguna ecuación algebraica con coeficientes enteros, lo que quiere decir que tampoco puede ser expresado algebraicamente. Calculando el valor de $\\pi$ Si bien el número $\\pi$ puede ser observado con facilidad, su cálculo es uno de los problemas más difíciles de la matemática y ha mantenido a los matemáticos ocupados por años. Actualmente se conocen hasta 10 billones de decimales del número $\\pi$ , es decir, 10.000.000.000.000. La aproximación de Arquímedes Uno de los métodos más conocidos para la aproximación del número $\\pi$ es la aproximación de Arquímedes ; la cual consiste en circunscribir e inscribir polígonos regulares de n-lados en circunferencias y calcular el perímetro de dichos polígonos. Arquímedes empezó con hexágonos circunscritos e inscritos, y fue doblando el número de lados hasta llegar a polígonos de 96 lados. La serie de Leibniz Otro método bastante popular para el cálculo de $\\pi$ , es la utilización de las series infinitas de Gregory-Leibniz . Este método consiste en ir realizando operaciones matematicas sobre series infinitas de números hasta que la serie converge en el número $\\pi$ . Aunque no es muy eficiente, se acerca cada vez más al valor de Pi en cada repetición, produciendo con precisión hasta cinco mil decimales de Pi con 500000 repeticiones. Su formula es muy simple. $$\\pi=(4/1) - (4/3) + (4/5) - (4/7) + (4/9) - (4/11) + (4/13) - (4/15) ...$$ Calculando $\\pi$ con Python Como este blog lo tengo dedicado a Python , obviamente no podía concluir este artículo sin incluir distintas formas de calcular $\\pi$ utilizando Python ; el cual bien es sabido que se adapta más que bien para las matemáticas!. Como $\\pi$ es una constante con un gran número de sus dígitos ya conocidos, los principales módulos Matemáticos de Python ya incluyen su valor en una variable. Así por ejemplo, podemos ver el valor de $\\pi$ importando los módulos math o sympy. In [1]: # Pi utilizando el módulo math import math math . pi Out[1]: 3.141592653589793 In [2]: # Pi utiizando sympy, dps nos permite variar el número de dígitos de Pi from sympy.mpmath import mp mp . dps = 33 # número de dígitos print ( mp . pi ) 3.1415926535897932384626433832795 Si queremos calcular alguna aproximación al valor de $\\pi$ , podríamos implementar por ejemplo la aproximación de Arquímedes de la siguiente manera. In [3]: # Implementacion de aproximación de Arquímedes from decimal import Decimal , getcontext def pi_archimedes ( digitos ): \"\"\" Calcula pi utilizando el método de aproximacion de Arquímedes en n iteraciones. \"\"\" def pi_archimedes_iter ( n ): \"\"\"funcion auxiliar utilizada en cada iteracion\"\"\" polygon_edge_length_squared = Decimal ( 2 ) polygon_sides = 2 for i in range ( n ): polygon_edge_length_squared = 2 - 2 * ( 1 - polygon_edge_length_squared / 4 ) . sqrt () polygon_sides *= 2 return polygon_sides * polygon_edge_length_squared . sqrt () #itera dependiendo de la cantidad de digitos old_result = None for n in range ( 10 * digitos ): # Calcular con doble precision getcontext () . prec = 2 * digitos result = pi_archimedes_iter ( n ) # Devolver resultados en precision simple. getcontext () . prec = digitos result = + result # redondeo del resultado. if result == old_result : return result old_result = result In [4]: # Aproximacion de Arquímedes con 33 dígitos print ( pi_archimedes ( 33 )) 3.14159265358979323846264338327950 Por último, también podríamos implementar las series infinitas de Gregory-Leibniz , lo cual es realmente bastante sencillo. In [5]: def pi_leibniz ( precision ): \"\"\"Calcula Pi utilizando las series infinitas de Gregory-Leibniz\"\"\" pi = 0 modificador = 1 for i in range ( 1 , precision , 2 ): pi += (( 4 / i ) * modificador ) modificador *= - 1 return pi In [6]: # Pi con una precision de 10000000 repeticiones. print ( pi_leibniz ( 10000000 )) 3.1415924535897797 Como se puede observar, el método de las series infinitas de Leibniz, si bien es de fácil implementación, no es muy preciso además de ser sumamente ineficiente. Con esto concluyo y a festejar el día de $\\pi$!! Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Matematica","url":"https://relopezbriega.github.io/blog/2015/03/14/el-dia-pi/"},{"title":"Programación Funcional con Python","text":"Introducción Es bien sabido que existen muchas formas de resolver un mismo problema, esto, llevado al mundo de la programación, a generado que existan o co-existan diferentes estilos en los que podemos programar, los cuales son llamados generalmente paradigmas . Así, podemos encontrar basicamente 4 paradigmas principales de programación: Programación imperativa : Este suele ser el primer paradigma con el que nos encontramos, el mismo describe a la programación en términos de un conjunto de intrucciones que modifican el estado del programa y especifican claramente cómo se deben realizar las cosas y modificar ese estado . Este paradigma esta representado por el lenguaje C . Programación lógica : En este paradigma los programas son escritos en forma declarativa utilizando expresiones lógicas. El principal exponente es el lenguaje Prolog (programar en este esotérico lenguaje suele ser una experiencia interesante!). Programación Orientada a Objetos : La idea básica detrás de este paradigma es que tanto los datos como las funciones que operan sobre estos datos deben estar contenidos en un mismo objeto . Estos objetos son entidades que tienen un determinado estado, comportamiento (método) e identidad . La Programación Orientada a Objetos es sumamente utilizada en el desarrollo de software actual; uno de sus principales impulsores es el lenguaje de programación Java . Programación Funcional : Este último paradigma enfatiza la utilización de funciones puras , es decir, funciones que no tengan efectos secundarios, que no manejan datos mutables o de estado . Esta en clara contraposición con la programación imperativa . Uno de sus principales representantes es el lenguaje Haskell (lenguaje, que compite en belleza, elegancia y expresividad con Python !). La mayoría de los lenguajes modernos son multiparadigma, es decir, nos permiten programar utilizando más de uno de los paradigmas arriba descritos. En este artículo voy a intentar explicar como podemos aplicar la Programación Funcional con Python . ¿Por qué Programación Funcional? En estos últimos años hemos visto el resurgimiento de la Programación Funcional , nuevos lenguajes como Scala y Apple Swift ya traen por defecto montones de herramientas para facilitar el paradigma funcional . La principales razones del crecimiento de la popularidad de la Programación Funcional son: Los programas escritos en un estilo funcional son más fáciles de testear y depurar . Por su característica modular facilita la computación concurrente y paralela ; permitiendonos obtener muchas más ventajas de los procesadores multinúcleo modernos. El estilo funcional se lleva muy bien con los datos; permitiendonos crear algoritmos y programas más expresivos para manejar la enorme cantidad de datos de la Big Data .(Aplicar el estilo funcional me suele recordar a utilizar las formulas en Excel ). Programación Funcional con Python Antes de comenzar con ejemplos les voy a mencionar algunos de los modulos que que nos facilitan la Programación Funcional en Python , ellos son: Intertools : Este es un modulo que viene ya instalado con la distribución oficial de Python ; nos brinda un gran número de herramientas para facilitarnos la creación de iteradores . Operator : Este modulo también la vamos a encontrar ya instalado con Python , en el vamos a poder encontrar a los principales operadores de Python convertidos en funciones. Functools : También ya incluido dentro de Python este modulo nos ayuda a crear Funciones de orden superior , es decir, funciones que actuan sobre o nos devuelven otras funciones. Fn : Este modulo, creado por Alexey Kachayev , brinda a Python las \"baterías\" adicionales para hacer el estilo funcional de programación mucho más fácil. Cytoolz : Modulo creado por Erik Welch que también nos proporciona varias herramientas para la Programación Funcional , especialmente orientado a operaciones de análisis de datos. Macropy : Este modulo, creado por Li Haoyi trae a Python características propias de los lenguajes puramente funcionales , como ser, pattern matching , tail call optimization , y case classes . Ejemplos Utilizando Map, Reduce, Filter y Zip Cuando tenemos que realizar operaciones sobre listas , en lugar de utilizar los clásicos loops , podemos utilizar las funciones Map, Reduce, Filter y Zip. Map La función Map nos permite aplicar una operación sobre cada uno de los items de una lista . El primer argumento es la función que vamos a aplicar y el segundo argumento es la lista. In [1]: #creamos una lista de números del 1 al 10 items = list ( xrange ( 1 , 11 )) items Out[1]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] In [2]: #creamos una lista de los cuadrados de la lista items. #forma imperativa. cuadrados = [] for i in items : cuadrados . append ( i ** 2 ) cuadrados Out[2]: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] In [3]: #Cuadrados utilizando Map. #forma funcional cuadrados = map ( lambda x : x ** 2 , items ) cuadrados Out[3]: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] Como podemos ver, al utilizar map las líneas de código se reducen y nuestro programa es mucho más simple de comprender. En el ejemplo le estamos pasando a map una función anónima o lambda . Esta es otra característica que nos ofrece Python para la Programación Funcional . Map también puede ser utilizado con funciones de más de un argumento y más de una lista, por ejemplo: In [4]: #importamos pow. from math import pow In [5]: #como vemos la función pow toma dos argumentos, un número y su potencia. pow ( 2 , 3 ) Out[5]: 8.0 In [6]: #si tenemos las siguientes listas numeros = [ 2 , 3 , 4 ] potencias = [ 3 , 2 , 4 ] In [7]: #podemos aplicar map con pow y las dos listas. #nos devolvera una sola lista con las potencias aplicadas sobre los números. potenciados = map ( pow , numeros , potencias ) potenciados Out[7]: [8.0, 9.0, 256.0] Reduce La función Reduce reduce los valores de la lista a un solo valor aplicando una funcion reductora . El primer argumento es la función reductora que vamos a aplicar y el segundo argumento es la lista. In [8]: #Sumando los valores de la lista items. #forma imperativa suma = 0 for i in items : suma += i suma Out[8]: 55 In [9]: #Suma utilizando Reduce. #Forma funcional from functools import reduce #en python3 reduce se encuentra en modulo functools suma = reduce ( lambda x , y : x + y , items ) suma Out[9]: 55 La función Reduce también cuenta con un tercer argumento que es el valor inicial o default. Por ejemplo si quisiéramos sumarle 10 a la suma de los elementos de la lista items, solo tendríamos que agregar el tercer argumento. In [10]: #10 + suma items suma10 = reduce ( lambda x , y : x + y , items , 10 ) suma10 Out[10]: 65 Filter La función Filter nos ofrece una forma muy elegante de filtrar elementos de una lista.El primer argumento es la función filtradora que vamos a aplicar y el segundo argumento es la lista. In [11]: #Numeros pares de la lista items. #Forma imperativa. pares = [] for i in items : if i % 2 == 0 : pares . append ( i ) pares Out[11]: [2, 4, 6, 8, 10] In [12]: #Pares utilizando Filter #Forma funcional. pares = filter ( lambda x : x % 2 == 0 , items ) pares Out[12]: [2, 4, 6, 8, 10] Zip Zip es una función para reorganizar listas. Como parámetros admite un conjunto de listas. Lo hace es tomar el elemento iésimo de cada lista y unirlos en una tupla , después une todas las tuplas en una sola lista. In [13]: #Ejemplo de zip nombres = [ \"Raul\" , \"Pedro\" , \"Sofia\" ] apellidos = [ \"Lopez Briega\" , \"Perez\" , \"Gonzalez\" ] In [14]: #zip une cada nombre con su apellido en una lista de tuplas. nombreApellido = zip ( nombres , apellidos ) nombreApellido Out[14]: [('Raul', 'Lopez Briega'), ('Pedro', 'Perez'), ('Sofia', 'Gonzalez')] Removiendo Efectos Secundarios Una de las buenas practicas que hace al estilo funcional es siempre tratar de evitar los efectos secundarios , es decir, evitar que nuestras funciones modifiquen los valores de sus parámetros, así en lugar de escribir código como el siguiente: In [15]: #Funcion que no sigue las buenas practias de la programacion funcional. #Esta funcion tiene efectos secundarios, ya que modifica la lista que se le pasa como argumento. def cuadrados ( lista ): for i , v in enumerate ( lista ): lista [ i ] = v ** 2 return lista Deberíamos escribir código como el siguiente, el cual evita los efectos secundarios: In [16]: #Version funcional de la funcion anterior. def fcuadrados ( lista ): return map ( lambda x : x ** 2 , lista ) In [17]: #Aplicando fcuadrados sobre items. fcuadrados ( items ) Out[17]: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] In [18]: #items no se modifico items Out[18]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] In [19]: #aplicando cuadrados sobre items cuadrados ( items ) Out[19]: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] In [20]: #Esta función tiene efecto secundario. #items fue modificado por cuadrados. items Out[20]: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] Al escribir funciones que no tengan efectos secundarios nos vamos a ahorrar muchos dolores de cabeza ocasionados por la modificación involuntaria de objetos. Utilizando el modulo Fn.py Algunas de las cosas que nos ofrece este modulo son: Estructuras de datos inmutables, lambdas al estilo de Scala , lazy evaluation de streams , nuevas Funciones de orden superior , entre otras. In [1]: #Lambdas al estilo scala from fn import _ ( _ + _ )( 10 , 3 ) Out[1]: 13 In [3]: items = list ( range ( 1 , 11 )) In [4]: cuadrados = map ( _ ** 2 , items ) cuadrados Out[4]: <map at 0x7f498c14ce10> In [5]: #Streams from fn import Stream s = Stream () << [ 1 , 2 , 3 , 4 , 5 ] s Out[5]: <fn.stream.Stream at 0x7f49913062d0> In [6]: list ( s ) Out[6]: [1, 2, 3, 4, 5] In [7]: s [ 1 ] Out[7]: 2 In [8]: s << [ 6 , 7 , 8 , 9 ] Out[8]: <fn.stream.Stream at 0x7f49913062d0> In [9]: s [ 6 ] Out[9]: 7 In [10]: #Stream fibonacci from fn.iters import take , drop , map as imap from operator import add f = Stream () fib = f << [ 0 , 1 ] << imap ( add , f , drop ( 1 , f )) #primeros 10 elementos de fibonacci list ( take ( 10 , fib )) Out[10]: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] In [30]: #elemento 20 de la secuencia fibonacci fib [ 20 ] Out[30]: 6765 In [31]: #elementos 40 al 45 de la secuencia fibonacci list ( fib [ 40 : 45 ]) Out[31]: [102334155, 165580141, 267914296, 433494437, 701408733] In [32]: #Funciones de orden superior from fn import F from operator import add , mul #operadores de suma y multiplicacion #composición de funciones F ( add , 1 )( 10 ) Out[32]: 11 In [33]: #f es una funcion que llama a otra funcion. f = F ( add , 5 ) << F ( mul , 100 ) #<< operador de composicion de funciones. In [34]: #cada valor de la lista primero se multiplica por 100 y luego #se le suma 5, segun composicion de f de arriba. map ( f , [ 0 , 1 , 2 , 3 ]) Out[34]: [5, 105, 205, 305] In [35]: func = F () >> ( filter , _ < 6 ) >> sum In [36]: #func primero filtra los valores menores a 6 #y luego los suma. func ( xrange ( 10 )) Out[36]: 15 Utilizando el modulo cytoolz Este modulo nos provee varias herramienta para trabajar con funciones, iteradores y diccionarios. In [37]: #Datos a utilizar en los ejemplos cuentas = [( 1 , 'Alice' , 100 , 'F' ), # id, nombre, balance, sexo ( 2 , 'Bob' , 200 , 'M' ), ( 3 , 'Charlie' , 150 , 'M' ), ( 4 , 'Dennis' , 50 , 'M' ), ( 5 , 'Edith' , 300 , 'F' )] In [38]: from cytoolz.curried import pipe , map as cmap , filter as cfilter , get #seleccionando el id y el nombre de los que tienen un balance mayor a 150 pipe ( cuentas , cfilter ( lambda ( id , nombre , balance , sexo ): balance > 150 ), cmap ( get ([ 1 , 2 ])), list ) Out[38]: [('Bob', 200), ('Edith', 300)] In [39]: #este mismo resultado tambien lo podemos lograr con las listas por comprensión. #mas pythonico. [( nombre , balance ) for ( id , nombre , balance , sexo ) in cuentas if balance > 150 ] Out[39]: [('Bob', 200), ('Edith', 300)] In [40]: from cytoolz import groupby #agrupando por sexo groupby ( get ( 3 ), cuentas ) Out[40]: {'F': [(1, 'Alice', 100, 'F'), (5, 'Edith', 300, 'F')], 'M': [(2, 'Bob', 200, 'M'), (3, 'Charlie', 150, 'M'), (4, 'Dennis', 50, 'M')]} In [41]: #utilizando reduceby from cytoolz import reduceby def iseven ( n ): return n % 2 == 0 def add ( x , y ): return x + y reduceby ( iseven , add , [ 1 , 2 , 3 , 4 ]) Out[41]: {False: 4, True: 6} Ordenando objectos con operator itemgetter, attrgetter y methodcaller Existen tres funciones dignas de mención en el modulo operator, las cuales nos permiten ordenar todo tipo de objetos en forma muy sencilla, ellas son itemgetter, attrgetter y methodcaller. In [42]: #Datos para los ejemplos estudiantes_tupla = [ ( 'john' , 'A' , 15 ), ( 'jane' , 'B' , 12 ), ( 'dave' , 'B' , 10 ), ] class Estudiante : def __init__ ( self , nombre , nota , edad ): self . nombre = nombre self . nota = nota self . edad = edad def __repr__ ( self ): return repr (( self . nombre , self . nota , self . edad )) def nota_ponderada ( self ): return 'CBA' . index ( self . nota ) / float ( self . edad ) estudiantes_objeto = [ Estudiante ( 'john' , 'A' , 15 ), Estudiante ( 'jane' , 'B' , 12 ), Estudiante ( 'dave' , 'B' , 10 ), ] In [43]: from operator import itemgetter , attrgetter , methodcaller #ordenar por edad tupla sorted ( estudiantes_tupla , key = itemgetter ( 2 )) Out[43]: [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)] In [44]: #ordenar por edad objetos sorted ( estudiantes_objeto , key = attrgetter ( 'edad' )) Out[44]: [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)] In [45]: #ordenar por nota y edad tupla sorted ( estudiantes_tupla , key = itemgetter ( 1 , 2 )) Out[45]: [('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)] In [46]: #ordenar por nota y edad objetos sorted ( estudiantes_objeto , key = attrgetter ( 'nota' , 'edad' )) Out[46]: [('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)] In [47]: #ordenando por el resultado del metodo nota_ponderada sorted ( estudiantes_objeto , key = methodcaller ( 'nota_ponderada' )) Out[47]: [('jane', 'B', 12), ('dave', 'B', 10), ('john', 'A', 15)] Hasta aquí llega esta introducción. Tengan en cuenta que Python no es un lenguaje puramente funcional, por lo que algunas soluciones pueden verse más como un hack y no ser del todo pythonicas . El concepto más importante es el de evitar los efectos secundarios en nuestras funciones. Debemos mantener un equilibrio entre los diferentes paradigmas y utilizar las opciones que nos ofrece Python que haga más legible nuestro código. Para más información sobre la Programación Funcional en Python también puede visitar el siguiente documento y darse una vuelta por la documentación de los módulos mencionados más arriba. Por último, los que quieran incursionar con un lenguaje puramente funcional, les recomiendo Haskell . Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Programacion","url":"https://relopezbriega.github.io/blog/2015/02/01/programacion-funcional-con-python/"},{"title":"Ipython y Spark para el analisis de datos","text":"Una de las nuevas estrellas en el análisis de datos masivos es Apache Spark . Desarrollado en Scala , Apache Spark es una plataforma de computación de código abierto para el análisis y procesamiento de grandes volúmenes de datos. Algunas de las ventajas que nos ofrece Apache Spark sobre otros frameworks , son: Velocidad: Sin dudas la velocidad es una de las principales fortalezas de Apache Spark , como esta diseñado para soportar el procesameinto en memoria , puede alcanzar una performance sorprendente en análisis avanzados de datos. Algunos programas escritos utilizando Apache Spark , pueden correr hasta 100x más rápido que utilizando Hadoop . Fácil de usar: Podemos escribir programas en Python , Scala o Java que hagan uso de las herramientas que ofrece Apache Spark ; asimismo nos permite trabajar en forma interactiva (con Python o con Scala ) y su API es muy fácil de aprender. Generalismo: El mundo del análisis de datos incluye muchos subgrupos de distinta índole, están los que hacen un análisis investigativo, los que que realizan análisis exploratorios, los que construyen sistemas de procesamientos de datos, etc. Los usuarios de cada uno de esos subgrupos, al tener objetivos distintos, suelen utilizar una gran variedad de herramientas totalmente diferentes. Apache Spark nos proporciona un gran número de herramientas de alto nivel como Spark SQL , MLlib para machine learning , GraphX , y Spark Streaming ; las cuales pueden ser combinadas para crear aplicaciones multipropósito que ataquen los diferentes dominios del análisis de datos. RDD o Resilient Distributed Datasets En el corazón de Apache Spark se encuentran los RDDs . Los RDDs son una abstracción distribuida que le permite a los programadores realizar cómputos en memoria sobre grandes clusters de computadoras sin errores o pérdidas de información. Están especialmente diseñados para el análisis de datos interactivo ( data mining ) y para la aplicación de algoritmos iterativos ( MapReduce ). En ambos casos, mantener los datos en la memoria puede mejorar el rendimiento en una gran proporción. Para lograr la tolerancia a fallos de manera eficiente, RDDs utiliza una forma restringida de memoria compartida. Los RDDs son los suficientemente expresivos como para capturar una gran variedad de cálculos. Instalando Apache Spark Para instalar Apache Spark en forma local y poder comenzar a utilizarlo, pueden seguir los siguientes pasos: En primer lugar, necesitamos tener instalado Oracle JDK . Para instalarlo en Ubuntu podemos utilizar los siguientes comandos: $ sudo add-apt-repository ppa:webupd8team/java $ sudo apt-get update $ sudo apt-get install oracle-jdk7-installer Luego nos instalamos las herramientas para trabajar con Scala , SBT con el siguiente comando: $ sudo apt-get install sbt Después nos descargamos la última versión de Apache Spark desde aquí Ahora descomprimimos el archivo: $ tar -xvf spark-1.0.2.tgz Nos movemos a la carpeta recién descomprimida y realizamos la compilación de Apache Spark utilizando SBT . (tener paciencia, es sabido que Scala tarda mucho en compilar) $ cd spark-1.0.2 $ sbt/sbt assembly Opcionalmente, para facilitar la utilización de Apache Spark desde la línea de comando, yo modifique mi archivo .bashrc para incluir los siguientes alias: $ echo \"alias ipyspark='IPYTHON_OPTS=\"notebook --pylab inline\" ~/spark-1.0.2/bin/pyspark'\" >> ~/.bashrc $ echo \"alias pyspark='~/spark-1.0.2/bin/pyspark'\" >> ~/.bashrc $ echo \"alias spark='~/spark-1.0.2/bin/spark-shell'\" >> ~/.bashrc Ahora simplemente tipeando pyspark nos abre el interprete interactivo de Python con Apache Spark , tipeando spark nos abre el interprete interactivo de Scala , y tipeando ipyspark nos abre el notebook de Ipython integrado con Apache Spark ! $ ./bin/pyspark #o pyspark si creamos el alias Ejemplo de utilización de Spark con Ipython Ahora llegó el momento de ensuciarse las manos y probar Apache Spark , vamos a hacer el típico ejercicio de wordcounts; en este caso vamos a contar todas las palabras que posee la Biblia en su versión en inglés y vamos a ver cuantas veces aparece la palabra Dios(God). Para esto nos descargamos la versión de la Biblia en texto plano del proyecto Gutemberg . In [1]: # Comenzamos con algunos imports. # No necesitamos importar pyspark porque ya se autoimporta como sc. from operator import add import pandas as pd In [2]: lineas = sc . textFile ( \"/home/raul/spark-1.0.2/examples/data/Bible.txt\" ) # usamos la función textFile para subir el texto a Spark In [3]: # Mapeamos las funciones para realizar la cuenta de las palabras y generamos el RDD. cuenta = lineas . flatMap ( lambda x : x . split ( ' ' )) \\ . map ( lambda x : ( x . replace ( ',' , '' ) . upper (), 1 )) \\ . reduceByKey ( add ) In [4]: # Creamos la lista con las palabras y su respectiva frecuencia. lista = cuenta . collect () In [5]: # Creamos un DataFrame de Pandas para facilitar el manejo de los datos. dataframe = pd . DataFrame ( lista , columns = [ 'palabras' , 'cuenta' ]) In [6]: # Nos quedamos solo con las palabras que hacen referencia a Dios god = dataframe [ dataframe [ 'palabras' ] . apply ( lambda x : x . upper () in [ 'GOD' , 'LORD' , 'JESUS' , 'FATHER' ])] god Out[6]: palabras cuenta 2867 FATHER 814 7329 GOD 3330 8902 LORD 6448 21404 JESUS 893 In [7]: # Realizamos un gráfico de barras sobre los datos god . set_index ( 'palabras' ) . plot ( kind = 'bar' ) Out[7]: <matplotlib.axes.AxesSubplot at 0x7f071e10ea10> In [8]: # Realizamos la sumatoria de las 4 palabras combinadas god . sum () Out[8]: palabras FATHERGODLORDJESUS cuenta 11485 dtype: object Como demuestra el ejemplo, Dios sería nombrado en la Biblia, ya sea como lord, god, jesus o father; unas 11485 veces! Conclusión Apache Spark es realmente una herramienta muy prometedora, con ella podemos analizar datos con un rendimiento muy alto y combinado con otras herramientas como Python , Numpy , Pandas e IPython ; se convierten en un framework sumamente completo y efectivo para el análisis de grandes volúmenes de datos en forma muy sencilla. Para más información sobre Apache Spark , pueden visitar su la sección de ejemplos de su página oficial. Espero les haya sido de utilidad este notebook ). Saludos!! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Analisis de datos","url":"https://relopezbriega.github.io/blog/2014/08/22/ipython-y-spark-para-el-analisis-de-datos/"},{"title":"MyPy - Python y un sistema de tipado estático","text":"Una de las razones por la que solemos amar a Python , es por su sistema de tipado dinámico , el cual lo convierte en un lenguaje de programación sumamente flexible y fácil de aprender; al no tener que preocuparnos por definir los tipos de los objetos, ya que Python los infiere por nosotros, podemos escribir programas en una forma mucho más productiva, sin verbosidad y utilizando menos líneas de código. Ahora bien, este sistema de tipado dinámico también puede convertirse en una pesadilla en proyectos de gran escala, requiriendo varias horas de pruebas unitarias para evitar que los objetos adquieran un tipo de datos que no deberían y complicando el su mantenimiento o futura refactorización . Por ejemplo, en un código tan trivial como el siguiente: In [1]: def saludo ( nombre ): return 'Hola {} ' . format ( nombre ) Esta simple función nos va a devolver el texto 'Hola' seguido del nombre que le ingresemos; pero como no contiene ningún control sobre el tipo de datos que pude admitir la variable nombre , los siguientes casos serían igualmente válidos: In [2]: print ( saludo ( 'Raul' )) print ( saludo ( 1 )) Hola Raul Hola 1 En cambio, si pusiéramos un control sobre el tipo de datos que admitiera la variable nombre , para que siempre fuera un string , entonces el segundo caso ya no sería válido y lo podríamos detectar fácilmente antes de que nuestro programa se llegara a ejecutar. Obviamente, para poder detectar el segundo error y que nuestra función saludo solo admita una variable del tipo string como argumento, podríamos reescribir nuestra función, agregando un control del tipo de datos de la siguiente manera: In [3]: def saludo ( nombre ): if type ( nombre ) != str : return \"Error: el argumento debe ser del tipo String(str)\" return 'Hola {} ' . format ( nombre ) print ( saludo ( 'Raul' )) print ( saludo ( 1 )) Hola Raul Error: el argumento debe ser del tipo String(str) Pero una solución más sencilla a tener que ir escribiendo condiciones para controlar los tipos de las variables o de las funciones es utilizar MyPy MyPy MyPy es un proyecto que busca combinar los beneficios de un sistema de tipado dinámico con los de uno de tipado estático . Su meta es tener el poder y la expresividad de Python combinada con los beneficios que otorga el chequeo de los tipos de datos al momento de la compilación. Algunos de los beneficios que proporciona utilizar MyPy son: Chequeo de tipos al momento de la compilación: Un sistema de tipado estático hace más fácil detectar errores y con menos esfuerzo de debugging . Facilita el mantenimiento: Las declaraciones explícitas de tipos actúan como documentación, haciendo que nuestro código sea más fácil de entender y de modificar sin introducir nuevos errores. Permite crecer nuestro programa desde un tipado dinámico hacia uno estático: Nos permite comenzar desarrollando nuestros programas con un tipado dinámico y a mediada que el mismo vaya madurando podríamos modificarlo hacia un tipado estático de forma muy sencilla. De esta manera, podríamos beneficiarnos no solo de la comodidad de tipado dinámico en el desarrollo inicial, sino también aprovecharnos de los beneficios de los tipos estáticos cuando el código crece en tamaño y complejidad. Tipos de datos Estos son algunos de los tipos de datos más comunes que podemos encontrar en Python : int : Número entero de tamaño arbitrario float : Número flotante. bool : Valor booleano (True o False) str : Unicode string bytes : 8-bit string object : Clase base del que derivan todos los objecto en Python . List[str] : lista de objetos del tipo string. Dict[str, int] : Diccionario de string hacia enteros Iterable[int] : Objeto iterable que contiene solo enteros. Sequence[bool] : Secuencia de valores booleanos Any : Admite cualquier valor. (tipado dinámico) El tipo Any y los constructores List, Dict, Iterable y Sequence están definidos en el modulo typing que viene junto con MyPy . Ejemplos Por ejemplo, volviendo al ejemplo del comienzo, podríamos reescribir la función saludo utilizando MyPy de forma tal que los tipos de datos sean explícitos y puedan ser chequeados al momento de la compilación. In [4]: %%writefile typeTest.py import typing def saludo ( nombre : str ) -> str : return 'Hola {} ' . format ( nombre ) print ( saludo ( 'Raul' )) print ( saludo ( 1 )) Overwriting typeTest.py En este ejemplo estoy creando un pequeño script y guardando en un archivo con el nombre 'typeTest.py', en la primer línea del script estoy importando la librería typing que viene con MyPy y es la que nos agrega la funcionalidad del chequeo de los tipos de datos. Luego simplemente ejecutamos este script utilizando el interprete de MyPy y podemos ver que nos va a detectar el error de tipo de datos en la segunda llamada a la función saludo . In [5]: ! mypy typeTest.py typeTest.py, line 7: Argument 1 to \"saludo\" has incompatible type \"int\"; expected \"str\" Si ejecutáramos este mismo script utilizando el interprete de Python , veremos que obtendremos los mismos resultados que al comienzo de este notebook ; lo que quiere decir, que la sintaxis que utilizamos al reescribir nuestra función saludo es código Python perfectamente válido! In [6]: ! python3 typeTest.py Hola Raul Hola 1 Tipado explicito para variables y colecciones En el ejemplo anterior, vimos como es la sintaxis para asignarle un tipo de datos a una función, la cual utiliza la sintaxis de Python3 , annotations . Si quisiéramos asignarle un tipo a una variable, podríamos utilizar la función Undefined que viene junto con MyPy . In [7]: %%writefile typeTest.py from typing import Undefined , List , Dict # Declaro los tipos de las variables texto = Undefined ( str ) entero = Undefined ( int ) lista_enteros = List [ int ]() dic_str_int = Dict [ str , int ]() # Asigno valores a las variables. texto = 'Raul' entero = 13 lista_enteros = [ 1 , 2 , 3 , 4 ] dic_str_int = { 'raul' : 1 , 'ezequiel' : 2 } # Intento asignar valores de otro tipo. texto = 1 entero = 'raul' lista_enteros = [ 'raul' , 1 , '2' ] dic_str_int = { 1 : 'raul' } Overwriting typeTest.py In [8]: ! mypy typeTest.py typeTest.py, line 16: Incompatible types in assignment (expression has type \"int\", variable has type \"str\") typeTest.py, line 17: Incompatible types in assignment (expression has type \"str\", variable has type \"int\") typeTest.py, line 18: List item 1 has incompatible type \"str\" typeTest.py, line 18: List item 3 has incompatible type \"str\" typeTest.py, line 19: List item 1 has incompatible type \"Tuple[int, str]\" Otra alternativa que nos ofrece MyPy para asignar un tipo de datos a las variables, es utilizar comentarios; así, el ejemplo anterior lo podríamos reescribir de la siguiente forma, obteniendo el mismo resultado: In [9]: %%writefile typeTest.py from typing import List , Dict # Declaro los tipos de las variables texto = '' # type: str entero = 0 # type: int lista_enteros = [] # type: List[int] dic_str_int = {} # type: Dict[str, int] # Asigno valores a las variables. texto = 'Raul' entero = 13 lista_enteros = [ 1 , 2 , 3 , 4 ] dic_str_int = { 'raul' : 1 , 'ezequiel' : 2 } # Intento asignar valores de otro tipo. texto = 1 entero = 'raul' lista_enteros = [ 'raul' , 1 , '2' ] dic_str_int = { 1 : 'raul' } Overwriting typeTest.py In [10]: ! mypy typeTest.py typeTest.py, line 16: Incompatible types in assignment (expression has type \"int\", variable has type \"str\") typeTest.py, line 17: Incompatible types in assignment (expression has type \"str\", variable has type \"int\") typeTest.py, line 18: List item 1 has incompatible type \"str\" typeTest.py, line 18: List item 3 has incompatible type \"str\" typeTest.py, line 19: List item 1 has incompatible type \"Tuple[int, str]\" Instalando MyPy Instalar MyPy es bastante fácil, simplemente debemos seguir los siguientes pasos: Si utilizan git , pueden clonar el repositorio de mypy: $ git clone https://github.com/JukkaL/mypy.git Si no utilizan git , como alternativa, se pueden descargar la última versión de mypy en el siguiente link: https://github.com/JukkaL/mypy/archive/master.zip Una vez que ya se lo descargaron, se posicionan dentro de la carpeta de mypy y ejecutan el script setup.py para instalarlo: $ python3 setup.py install Reemplacen 'python3' con su interprete para python3. MyPy como parte de Python 3.5 ? Guido van Rossum, el creador de Python , ha enviado reciente una propuesta a la lista de correo de python-ideas, en la cual sugiere agregar en la próxima versión de Python la sintaxis de MyPy para las functions annotations . Pueden encontrar la propuesta en el siguiente link: https://mail.python.org/pipermail/python-ideas/2014-August/028618.html También pueden seguir las discusiones que se generaron sobre este tema en Reddit Saludos! Este post fue escrito utilizando IPython notebook. Pueden descargar este notebook o ver su version estática en nbviewer .","tags":"Programacion","url":"https://relopezbriega.github.io/blog/2014/08/18/mypy-python-y-un-sistema-de-tipado-estatico/"},{"title":"Python - Librerías esenciales para el análisis de datos","text":"En mi artículo anterior hice una breve introducción al mundo de Python , hoy voy a detallar algunas de las librerías que son esenciales para trabajar con Python en la comunidad científica o en el análisis de datos. Una de las grandes ventajas que ofrece Python sobre otros lenguajes de programación, además de que es que es mucho más fácil de aprender; es lo grande y prolifera que es la comunidad de desarrolladores que lo rodean; comunidad que ha contribuido con una gran variedad de librerías de primer nivel que extienden la funcionalidades del lenguaje. Vamos a poder encontrar una librería en Python para prácticamente cualquier cosa que se nos ocurra. Algunas de las librerías que se han vuelto esenciales y ya forman casi parte del lenguaje en sí mismo son las siguientes: Numpy Numpy , abreviatura de Numerical Python , es el paquete fundamental para la computación científica en Python . Dispone, entre otras cosas de: Un objeto matriz multidimensional ndarray ,rápido y eficiente. Funciones para realizar cálculos elemento a elemento u otras operaciones matemáticas con matrices . Herramientas para la lectura y escritura de los conjuntos de datos basados matrices . Operaciones de álgebra lineal , transformaciones de Fourier , y generación de números aleatorios. Herramientas de integración para conectar C , C++ y Fortran con Python Más allá de las capacidades de procesamiento rápido de matrices que Numpy añade a Python , uno de sus propósitos principales con respecto al análisis de datos es la utilización de sus estructuras de datos como contenedores para transmitir los datos entre diferentes algoritmos. Para datos numéricos , las matrices de Numpy son una forma mucho más eficiente de almacenar y manipular datos que cualquier otra de las estructuras de datos estándar incorporadas en Python . Asimismo, librerías escritas en un lenguaje de bajo nivel, como C o Fortran , pueden operar en los datos almacenados en matrices de Numpy sin necesidad de copiar o modificar ningún dato. Como nomenclatura general, cuando importamos la librería Numpy en nuestro programa Python se suele utilizar la siguiente: In [1]: import numpy as np Creando matrices en Numpy Existen varias maneras de crear matrices en Numpy, por ejemplo desde: Una lista o tuple de Python Funciones específicas para crear matrices como arange , linspace , etc. Archivos planos con datos, como por ejemplo archivos .csv En Numpy tanto los vectores como las matrices se crean utilizando el objeto ndarray In [2]: #Creando un vector desde una lista de Python vector = np . array ([ 1 , 2 , 3 , 4 ]) vector Out[2]: array([1, 2, 3, 4]) In [3]: #Para crear una matriz, simplemente le pasamos una lista anidada al objeto array de Numpy matriz = np . array ([[ 1 , 2 ], [ 3 , 4 ]]) matriz Out[3]: array([[1, 2], [3, 4]]) In [4]: #El tipo de objeto de tanto de los vectores como de las matrices es ndarray type ( vector ), type ( matriz ) Out[4]: (numpy.ndarray, numpy.ndarray) In [5]: #Los objetos ndarray de Numpy cuentan con las propiedades shape y size que nos muestran sus dimensiones. print vector . shape , vector . size print matriz . shape , matriz . size (4,) 4 (2, 2) 4 Utilizando funciones para crear matrices In [6]: #arange #La funcion arange nos facilita la creación de matrices x = np . arange ( 1 , 11 , 1 ) # argumentos: start, stop, step x Out[6]: array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) In [7]: #linspace #linspace nos devuelve un vector con la cantidad de muestras que le ingresemos y separados uniformamente entre sí. np . linspace ( 1 , 25 , 25 ) # argumentos: start, stop, samples Out[7]: array([ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., 20., 21., 22., 23., 24., 25.]) In [8]: #mgrid #Con mgrid podemos crear arrays multimensionales. x , y = np . mgrid [ 0 : 5 , 0 : 5 ] x Out[8]: array([[0, 0, 0, 0, 0], [1, 1, 1, 1, 1], [2, 2, 2, 2, 2], [3, 3, 3, 3, 3], [4, 4, 4, 4, 4]]) In [9]: y Out[9]: array([[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]) In [10]: #zeros y ones #Estas funciones nos permiten crear matrices de ceros o de unos. np . zeros (( 3 , 3 )) Out[10]: array([[ 0., 0., 0.], [ 0., 0., 0.], [ 0., 0., 0.]]) In [11]: np . ones (( 3 , 3 )) Out[11]: array([[ 1., 1., 1.], [ 1., 1., 1.], [ 1., 1., 1.]]) In [12]: #random.randn #Esta funcion nos permite generar una matriz con una distribución estándar de números. np . random . randn ( 5 , 5 ) Out[12]: array([[ 1.39342127, 0.27553779, 1.60499887, 0.49998319, 0.70528917], [ 0.77384386, 0.13082401, -0.94628073, 1.11938778, -0.03671148], [-1.26643358, -0.49647634, 0.02653584, 1.69748904, 0.83353017], [ 2.37892618, -1.21239237, 1.12638933, 1.70430737, 0.50932112], [-0.67529314, -0.48119409, -0.6064923 , 0.03554073, -0.29703706]]) In [13]: #diag #Nos permite crear una matriz con la diagonal de números que le ingresemos. np . diag ([ 1 , 1 , 1 ]) Out[13]: array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) Matplotlib Matplotlib es la librería más popular en Python para visualizaciones y gráficos. Matplotlib puede producir gráficos de alta calidad dignos de cualquier publicación científica. Algunas de las muchas ventajas que nos ofrece Matplotlib , incluyen: Es fácil de aprender. Soporta texto, títulos y etiquetas en formato $\\LaTeX$. Proporciona un gran control sobre cada uno de los elementos de las figuras, como ser su tamaño, el trazado de sus líneas, etc. Nos permite crear gráficos y figuras de gran calidad que pueden ser guardados en varios formatos, como ser: PNG, PDF, SVG, EPS, y PGF. Matplotlib se integra de maravilla con IPython (ver más abajo), lo que nos proporciona un ambiente confortable para las visualizaciones y la exploración de datos interactiva. Algunos gráficos con Matplotlib In [14]: #Generalmente se suele importar matplotlib de la siguiente forma. import matplotlib.pyplot as plt Ahora vamos a graficar la siguiente función. $$f(x) = e^{-x^2}$$ In [15]: # Definimos nuestra función. def f ( x ): return np . exp ( - x ** 2 ) In [16]: #Creamos un vector con los puntos que le pasaremos a la funcion previamente creada. x = np . linspace ( - 1 , 5 , num = 30 ) In [17]: #Representeamos la función utilizando el objeto plt de matplotlib plt . xlabel ( \"Eje $x$\" ) plt . ylabel ( \"$f(x)$\" ) plt . legend () plt . title ( \"Funcion $f(x)$\" ) plt . grid ( True ) fig = plt . plot ( x , f ( x ), label = \"Función f(x)\" ) In [18]: #Grafico de puntos con matplotlib N = 100 x1 = np . random . randn ( N ) #creando vector x y1 = np . random . randn ( N ) #creando vector x s = 50 + 50 * np . random . randn ( N ) #variable para modificar el tamaño(size) c = np . random . randn ( N ) #variable para modificar el color(color) plt . scatter ( x1 , y1 , s = s , c = c , cmap = plt . cm . Blues ) plt . grid ( True ) plt . colorbar () fig = plt . scatter ( x1 , y1 ) Interfase orientada a objetos de matplotlib La idea principal con la programación orientada a objetos es que a los objetos que se pueden aplicar funciones y acciones, y ningún objeto debería tener un estado global (como en el caso de la interfase con plt que acabamos de utilizar). La verdadera ventaja de este enfoque se hace evidente cuando se crean más de una figura, o cuando una figura contiene más de una trama secundaria. Para utilizar la API orientada a objetos comenzamos de forma similar al ejemplo anterior, pero en lugar de crear una nueva instancia global de plt , almacenamos una referencia a la recientemente creada figura en la variable fig , y a partir de ella creamos un nuevo eje ejes usando el método add_axes de la instancia Figure : In [19]: x = linspace ( 0 , 5 , 10 ) # Conjunto de puntos y = x ** 2 # Funcion fig = plt . figure () axes1 = fig . add_axes ([ 0.1 , 0.1 , 0.8 , 0.8 ]) # Eje principal axes2 = fig . add_axes ([ 0.2 , 0.5 , 0.4 , 0.3 ]) # Eje secundario # Figura principal axes1 . plot ( x , y , 'r' ) axes1 . set_xlabel ( 'x' ) axes1 . set_ylabel ( 'y' ) axes1 . set_title ( 'Ej OOP' ) # Insertada axes2 . plot ( y , x , 'g' ) axes2 . set_xlabel ( 'y' ) axes2 . set_ylabel ( 'x' ) axes2 . set_title ( 'insertado' ); In [20]: # Ejemplo con más de una figura. fig , axes = plt . subplots ( nrows = 1 , ncols = 2 ) for ax in axes : ax . plot ( x , y , 'r' ) ax . set_xlabel ( 'x' ) ax . set_ylabel ( 'y' ) ax . set_title ( 'titulo' ) fig . tight_layout () IPython IPython promueve un ambiente de trabajo de ejecutar-explorar en contraposición al tradicional modelo de desarrollo de software de editar-compilar-ejecutar . Es decir, que el problema computacional a resolver es más visto como todo un proceso de ejecucion de tareas, en lugar del tradicional modelo de producir una respuesta( output ) a una pregunta( input ). IPython también provee una estrecha integración con nuestro sistema operativo, permitiendo acceder fácilmente a todos nuestros archivos desde la misma herramienta. Algunas de las características sobresalientes de IPython son: Su poderoso shell interactivo. Notebook , su interfase web con soporte para código, texto, expresiones matemáticas, gráficos en línea y multimedia. Su soporte para poder realizar visualizaciones de datos en forma interactiva. IPython esta totalmente integrado con matplotlib . Su simple y flexible interfase para trabajar con la computación paralela . IPython es mucho más que una librería, es todo un ambiente de trabajo que nos facilita enormemente trabajar con Python ; las mismas páginas de este blog están desarrolladas con la ayuda del fantástico Notebook de IPython . (para ver el Notebook en el que se basa este artículo, visiten el siguiente enlace .) Para más información sobre IPython y algunas de sus funciones los invito también a visitar el artículo que escribí en mi otro blog . Pandas Pandas es una librería open source que aporta a Python unas estructuras de datos fáciles de user y de alta performance, junto con un gran número de funciones esenciales para el análisis de datos. Con la ayuda de Pandas podemos trabajar con datos estructurados de una forma más rápida y expresiva. Algunas de las cosas sobresalientes que nos aporta Pandas son: Un rápido y eficiente objeto DataFrame para manipular datos con indexación integrada; herramientas para la lectura y escritura de datos entre estructuras de datos rápidas y eficientes manejadas en memoria, como el DataFrame , con la mayoría de los formatos conocidos para el manejo de datos, como ser: CSV y archivos de texto, archivos Microsoft Excel, bases de datos SQL , y el formato científico HDF5. Proporciona una alineación inteligente de datos y un manejo integrado de los datos faltantes; con estas funciones podemos obtener una ganancia de performace en los cálculos entre DataFrames y una fácil manipulación y ordenamiento de los datos de nuestro data set ; Flexibilidad para manipular y redimensionar nuestro data set , facilidad para construir tablas pivote ; La posibilidad de filtrar los datos, agregar o eliminar columnas de una forma sumamente expresiva; Operaciones de merge y join altamente eficientes sobre nuestros conjuntos de datos; Indexación jerárquica que proporciona una forma intuitiva de trabajar con datos de alta dimensión en una estructura de datos de menor dimensión ; Posibilidad de realizar cálculos agregados o transformaciones de datos con el poderoso motor group by que nos permite dividir-aplicar-combinar nuestros conjuntos de datos; combina las características de las matrices de alto rendimiento de Numpy con las flexibles capacidades de manipulación de datos de las hojas de cálculo y bases de datos relacionales (tales como SQL ); Gran número de funcionalidades para el manejo de series de tiempo ideales para el análisis financiero; Todas sus funciones y estructuras de datos están optimizadas para el alto rendimiento , con las partes críticas del código escritas en Cython o C ; Estructuras de datos de Pandas In [21]: # Importando pandas import pandas as pd Series In [22]: # Las series son matrices de una sola dimension similares a los vectores, pero con su propio indice. # Creando una Serie serie = pd . Series ([ 2 , 4 , - 8 , 3 ]) serie Out[22]: 0 2 1 4 2 -8 3 3 dtype: int64 In [23]: # podemos ver tantos los índices como los valores de las Series. print serie . values print serie . index [ 2 4 -8 3] Int64Index([0, 1, 2, 3], dtype='int64') In [24]: # Creando Series con nuestros propios índices. serie2 = pd . Series ([ 2 , 4 , - 8 , 3 ], index = [ 'd' , 'b' , 'a' , 'c' ]) serie2 Out[24]: d 2 b 4 a -8 c 3 dtype: int64 In [25]: # Accediendo a los datos a través de los índices print serie2 [ 'a' ] print serie2 [[ 'b' , 'c' , 'd' ]] print serie2 [ serie2 > 0 ] -8 b 4 c 3 d 2 dtype: int64 d 2 b 4 c 3 dtype: int64 DataFrame In [26]: # El DataFrame es una estructura de datos tabular similar a las hojas de cálculo de Excel. # Posee tanto indices de columnas como de filas. # Creando un DataFrame. data = { 'state' : [ 'Ohio' , 'Ohio' , 'Ohio' , 'Nevada' , 'Nevada' ], 'year' : [ 2000 , 2001 , 2002 , 2001 , 2002 ], 'pop' : [ 1.5 , 1.7 , 3.6 , 2.4 , 2.9 ]} frame = pd . DataFrame ( data ) # Creando un DataFrame desde un diccionario frame Out[26]: pop state year 0 1.5 Ohio 2000 1 1.7 Ohio 2001 2 3.6 Ohio 2002 3 2.4 Nevada 2001 4 2.9 Nevada 2002 5 rows × 3 columns In [27]: # Creando un DataFrame desde un archivo. ! cat 'dataset.csv' # ejemplo archivo csv. pop,state,year 1.5,Ohio,2000 1.7,Ohio,2001 3.6,Ohio,2002 2.4,Nevada,2001 2.9,Nevada,2002 In [28]: # Leyendo el archivo dataset.csv para crear el DataFrame frame2 = pd . read_csv ( 'dataset.csv' , header = 0 ) frame2 Out[28]: pop state year 0 1.5 Ohio 2000 1 1.7 Ohio 2001 2 3.6 Ohio 2002 3 2.4 Nevada 2001 4 2.9 Nevada 2002 5 rows × 3 columns In [29]: # Seleccionando una columna como una Serie frame [ 'state' ] Out[29]: 0 Ohio 1 Ohio 2 Ohio 3 Nevada 4 Nevada Name: state, dtype: object In [30]: # Seleccionando una línea como una Serie. frame . ix [ 1 ] Out[30]: pop 1.7 state Ohio year 2001 Name: 1, dtype: object In [31]: # Verificando las columnas frame . columns Out[31]: Index([u'pop', u'state', u'year'], dtype='object') In [32]: # Verificando los índices. frame . index Out[32]: Int64Index([0, 1, 2, 3, 4], dtype='int64') Otras librerías dignas de mencion Otras librerías que también son muy importantes para el análisis de datos con Python son: SciPy SciPy es un conjunto de paquetes donde cada uno ellos ataca un problema distinto dentro de la computación científica y el análisis numérico. Algunos de los paquetes que incluye, son: scipy.integrate : que proporciona diferentes funciones para resolver problemas de integración numérica. scipy.linalg : que proporciona funciones para resolver problemas de álgebra lineal. scipy.optimize : para los problemas de optimización y minimización. scipy.signal : para el análisis y procesamiento de señales. scipy.sparse : para matrices dispersas y solucionar sistemas lineales dispersos scipy.stats : para el análisis de estadística y probabilidades. Scikit-learn Scikit-learn es una librería especializada en algoritmos para data mining y machine learning . Algunos de los problemas que podemos resolver utilizando las herramientas de Scikit-learn , son: Clasificaciones : Identificar las categorías a que cada observación del conjunto de datos pertenece. Regresiones : Predecire el valor continuo para cada nuevo ejemplo. Agrupaciones : Agrupación automática de objetos similares en un conjunto. Reducción de dimensiones : Reducir el número de variables aleatorias a considerar. Selección de Modelos : Comparar, validar y elegir parámetros y modelos. Preprocesamiento : Extracción de características a analizar y normalización de datos.","tags":"Analisis de datos","url":"https://relopezbriega.github.io/blog/2014/05/28/python-librerias-esenciales-para-el-analisis-de-datos/"},{"title":"Mi Python Blog: Introducción a Python","text":"Hoy comienzo mi nuevo blog en github ; este nuevo blog, que se suma a a mi otro blog relopezbriega.com.ar , lo voy a dedicar enteramente a Python . Por tal motivo, en este primer artículo, voy a explicar a grandes rasgos que es Python y por qué me gusta tanto trabajar con Python como para dedicarle este blog. Python es un lenguaje de programación de alto nivel que se caracteriza por hacer hincapié en una sintaxis limpia, que favorece un código legible y fácilmente administrable. Python funciona en las plataformas Windows, Linux/Unix, Mac OS X e incluso ha sido portado a las máquinas virtuales de Java (a través de Jython ) y .Net (a través de IronPython ). Python es un lenguaje libre y fácil de aprender que te permite trabajar más rápido e integrar tus sistemas de manera más eficaz; con Python se puede ganar rápidamente en productividad. Python , a diferencia de otros lenguajes de programación como C , C++ o Java es interpretado y dinamicamente tipado ; lo que quiere decir que no es necesario compilar el fuente para poder ejecutarlo ( interpretado ) y que sus variables pueden tomar distintos tipos de objetos ( dinamicamente tipado ); esto hace que el lenguaje sea sumamente flexible y de rápida implementación; aunque pierde en rendimiento y es más propenso a errores de programación que los lenguajes antes mencionados. Principales fortalezas de Python Las principales fortalezas que hacen que uno ame a Python son: Es Orientado a Objetos. Python es un lenguaje de programación Orientado a Objetos desde casi su concepción, su modelo de clases soporta las notaciones avanzadas de polimorfismo, sobrecarga de operadores y herencia múltiple. La programación Orientado a Objetos es sumamente fácil de aplicar con la sintaxis simple que nos proporciona Python . Asimismo, también es importante destacar que en Python , la programación Orientado a Objetos es una opción y no algo obligatorio como es en Java ; ya que Python es multiparadigma y nos permite programar siguiendo un modelo Orientado a Objetos o un modelo imperativo . Es software libre . Python es completamente libre para ser utilizado y redistribuido; no posee restricciones para copiarlo, embeberlo en nuestros sistemas o ser vendido junto con otros productos. Python es un proyecto open source que es administrado por Python Software Foundation , institución que se encarga de su soporte y desarrollo. Es portable . La implementación estandar de Python esta escrita en C , y puede ser compilada y ejecutada en prácticamente cualquier plataforma que se les ocurra. Podemos encontrar a Python en pequeños dispositivos, como teléfonos celulares, hasta grandes infraestructuras de Hardware , como las supercomputadoras. Al ser un lenguaje interpretado el mismo código fuente puede ser ejecutado en cualquier plataforma sin necesidad de realizar grandes cambios. Es poderoso . Python proporciona toda la sencillez y facilidad de uso de un lenguaje de programación interpretado , junto con las más avanzadas herramientas de ingeniería de software que se encuentran típicamente en los lenguajes compilados. A diferencia de otros lenguajes interpretados , esta combinación hace a Python sumamente útil para proyectos de desarrollo a gran escala. Fácil integración . Los programas escritos en Python pueden ser fácilmente integrados con componentes escritos en otros lenguajes. Por ejemplo la C API de Python permite una fácil integración entre los dos lenguajes, permitiendo que los programas escritos en Python puedan llamar a funciones escritas en C y viceversa. Fácil de usar . Para ejecutar un programa en Python simplemente debemos escribirlo y ejecutarlo, no existen pasos intermedios de linkeo o compilación como podemos tener en otros lenguajes de programación. Con Python podemos programar en forma interactiva, basta tipear una sentencia para poder ver inmediatamente el resultado. Además los programas en Python son más simples, más pequeños y más flexibles que los programas equivalentes en lenguajes como C , C++ o Java . Fácil de aprender . Desde mi punto de vista, esta es sin duda la principal fortaleza del lenguaje; comparado con otros lenguajes de programación, Python es sumamente fácil de aprender, en tan sólo un par de días se puede estar programando eficientemente con Python . Instalando Python En Linux Instalar Python en Linux no es necesario, ya que viene preinstalado en todas las distribuciones más populares. En Windows La forma más sencilla de poder instalar Python en Windows es instalando alguna de las distribuciones de Python que ya vienen armadas con los principales módulos. Yo les recomiendo la distribución Anaconda , que se puede descargar en forma gratuita y viene integrada con todos los principales paquetes que vamos a necesitar para trabajar con Python . Una vez que la descargan, simplemente siguen los pasos del instalador y listo, ya tendrán todo un ambiente Python para trabajar en Windows. Otra distribución de Python que pueden utilizar en Windows, es WinPython , la cual puede ser utilizada incluso en forma portable. Hasta aquí este primer artículo de mi nuevo blog; los invito a que se instalen y exploren Python , no solo es fácil de aprender, también es muy divertido programar en Python !","tags":"Programacion","url":"https://relopezbriega.github.io/blog/2014/05/25/mi-python-blog-introduccion-a-python/"}]}