top of page

COSAS VEREDES

Tras haber leído bastante sobre Reinforcement Learning y tras buscar librerías fáciles de entender y de usar tanto en R como en Python, no he conseguido encontrar nada sencillo, que se acerque ni tan siquiera al caso más básico de agentes como es el problema del k-armed bandit y de funciones que de algún modo me permita generar un agente aplicado a algún caso o a un modelo de datos. Tal vez sea, y lo digo humildemente, porque algo se me escapa y no he visto lo suficiente, pero, aunque hay algunas cosas, resultan complejas darle una aplicación fuera del “ejemplo preparado” que se presenta como puede ser el caso de la librería contextual. Además, me temo que tal vez no sea el único que ha observado esta situación tal y como he podido hablar con más gente.


Por tanto, voy a intentar en las siguientes líneas crear un ejemplo sencillo y espero que claro sobre RL que por supuesto podría mejorarse pero que podría ser implementado bajo un sistema que se “alimente correctamente” bajo la aplicación que describo a continuación.


El caso que traigo a colación puede imaginarse como que tengo unas 2500 máquinas tragaperras cuyo coste de uso es de 10€ y podemos ganar 1000€ si acertamos y 0€ si erramos. Para exponer este caso en mi github:



Puede descargarse el código que consiste en 3 programas (conviene guardar todo en una carpeta donde existan 2 sub-carpetas code y data si se quiere que todo funcione), ya que este ejemplo viene con demo para que se pueda adaptar a algún caso real:


  • Un primer programa en la subcarpeta data crea un dataset denominado bandits este dataset consta de un ID por máquina y la probabilidad de éxito asignada a cada una. Por supuesto, en un caso real, sólo tendremos la columna ID (que es la que se usa en el código), la otra no se tiene, porque será desconocida, salvo a lo sumo para el dueño de las tragaperras. En este caso se generan máquinas 2500 con números aleatorios uniformes entre 0 y 0.02 (en el programa 3, se pueden modificar estos parámetros para otros ejemplos). Además, ejecutando este programa vía el citado programa 3, se genera un histórico que en este caso tendría unas n_historico = 20 observaciones (tipo 0 – 1) por cada una de las 2500 máquinas (siendo esta la muestra base inicial y el punto de partida, conocido del problema)

 

  • Un segundo programa contiene las funciones específicas que van a utilizarse en las iteraciones del agente de modo que, si se lee con detenimiento el código, lo que permiten estas funcione es dar los siguientes pasos:

 

  1. La creación de un número aleatorio entre 0 – 1 que permita aplicar por iteración y muestra de máquinas a utilizar en cada una (que en el caso del ejemplo básico que dejo es n_inversion = 100) el dilema exploración – explotación 


  2. Bajo el dilema del paso anterior el resto de las funciones permiten montar la siguiente lógica por iteración:


    1. Se eligen el (1 – dilema)% de las máquinas con mayor tasa de éxito del paso anterior

    2. Se elige aleatoriamente dilema% del resto de las máquinas que no están entre las anteriores

    3. Se generan las observaciones de los anteriores pasos y se dejan a missing las no conocidas asociándose cada dato a cada máquina

    4. Se integra la fila al fichero histórico

 

  • El tercer programa básicamente permite activarlo todo y realiza las siguientes acciones:

 

  • Va a ordenar al agente que actúe n_iter = 100 veces sobre un número de máquinas igual n_inversion = 100 y con una estrategia exploración – explotación de dilema = 0.1 (todo esto se puede modificar por el usuario pero por favor, meter cosas lógicas y no “romper” el programa, no he puesto nada de tratamiento de errores ya que no es una aplicación sino una idea lo que estoy tratando de explicar!!!!)

 

  • Por otro lado, va a calcular el beneficio (o coste) inicial de haber hecho el estudio (con el programa 1) por haber generado 20 observaciones por cada una de las 2500 máquinas donde se fue a modo fuerza bruta (en este caso se tiene un coste que sería coste_uso = 10 que multiplicado por cada una de las 2500 máquinas y las 20 observaciones sería igual a 500.000€ pero como algunas dan premio igual a ganancia_acierto = 1000, gran parte de ese número se reduce como se observa tras la ejecución), saliendo el coste inicial por -3000€ en el caso de la poc que está preparada

 

  • La inicialización (re-generación de la información) se hace con la función genera_entorno() que además de producir la información en data necesaria, genera la matriz_historico que es la que tiene la información que vendría de la observación de las máquinas (este riesgo, que en muchas ocasiones como es este caso sería un coste, debe ser asumido si se quiere tener una aproximación a la realidad)


  • Tras esto con la función genera_dinamica_prob_futuras() se generan las probabilidades anteriormente comentadas para aplicar el dilema exploración – explotación

 

  • Finalmente se aplica el agente que en la primera iteración hace uso exclusivamente de la matriz_historico y en el resto de las iteraciones incorpora la información que obtiene tras revisar las n_inversion = 100 máquinas. Esto genera un dataset que se denomina dinamico.csv que si se observa tendrá un número de observaciones igual n_historico + n_iter = 20 + 100 = 120. Sin missing en los primero 20 registros y con missing a partir de la fila 21 hasta la 120

 

Así pues, tras ejecutar el agente sobre el entorno creado y ver su actuación bajo un trade-off exploración – explotación de un 10% se obtendría el siguiente resultado:

Donde beneficio0 es el coste que se ha tenido que pagar para generar el histórico mientras que en beneficio_final se observa el acumulado del beneficio tras las 100 iteraciones. Además, el agente genera, no solo las nuevas observaciones incorporadas en el dataset dinamico.csv que pueden ser estudiadas, sino que además genera el vector beneficiosIter con los beneficios (o pérdidas) que se han ido generando en cada iteración por las máquinas elegidas.


La pregunta a realizar es ¿Qué pasaría si no se hace ninguna exploración? Es decir, si sólo se consideran los resultados obtenidos en las 20 iteraciones iniciales, el resultado entonces sería el siguiente (dilema = 0):

Es decir, el dato inicial es útil, pero se habría obtenido un mejor resultado con un poco más de exploración, ya que claramente en la muestra inicial no tienen por qué estar las máquinas óptimas.


Por otro lado, también cabe preguntar ¿Qué ocurre si nos lanzamos a una “locura exploratoria” con un nivel de exploración del 50% o con dilema = 0.5? En este caso el resultado, empieza a ser decaer, aunque al menos se cubre la inversión:

Finalmente, y para no extenderme demasiado os dejo varias indicaciones para usar estos programas y algunos comentarios sobre los resultados que se pueden obtener:

 

  1. El programa puede preparar diversos entornos con una gran versatilidad de situaciones para analizar donde es factible, cambiar el número de máquinas, alterar la distribuciones de las probabilidades de éxito, aumentar o disminuir el número de máquinas a las que se apuesta por iteración, etc


  2. Los resultados obtenidos están en consonancia con el dilema exploración – explotación, pero se debe tener en cuenta que si se altera por ejemplo el valor máximo de probabilidad de éxito de las distribuciones, los resultados pueden cambiar radicalmente y debe pensarse en éstos ya que la teoría funciona cuando el nivel de aleatoriedad de éxito es relativamente bajo y por tanto resulta difícil encontrar “buenas máquinas” que den premios, cuando la tasa de éxito es elevada, en general el efecto del dilema exploración – explotación estará más diluido


  3. También conviene tener en cuenta que en este caso de RL se tiene que el agente se comporta de forma totalmente reactiva al entorno y realmente trata de “perseguir aciertos” cuando “toca una palanca”, dejándose llevar por esas pruebas que va haciendo, sin embargo, su actuación no altera el modo en el que cada máquina genera el dato, sino que el agente aprende en base a los resultados que su elección va generando, no teniendo en consecuencia información en las máquinas que no haya usado durante una determinada iteración


  4. Aplicaciones de este ejemplo pueden tener sentido en sistemas de promoción de anuncios on-line, compra – venta de palabras, movimientos bursátiles y otros; pero debe tenerse en cuenta que debe prepararse la información de modo que se tenga una estructura similar (y en todo caso más completa) que los datasets aquí denominados basehist.csv pero sobre todo dinamico.csv teniendo en cuenta que el valor p de bandits sólo es conocido en esta PoC e incluso si “el fabricante o responsable de las máquinas” ve que ganamos demasiado dinero bajo nuestras elecciones, lo normal es que trate de alterar y cambiar algunas p ya que se está en todo caso ante un juego de suma 0, si unos ganan otros pierden

 
 
 
  • fjroar
  • 23 mar
  • 6 Min. de lectura

Cuando he visto ejemplos de LSTM aplicados a series temporales como los que se observan, por ejemplo aquí:



 

Se queda uno/a con la desazón de si serán o no una buena herramienta para el análisis de series temporales ya que o el ejemplo es muy pobre para tal fin, o de algún modo no se consiguen unos resultados que uno pueda decir ¡Wow! ¡Qué maravilla frente a otros modelos! o al menos generar algo con cierta calidad.


Para no hacer muy extenso esta entrada, recomiendo que quién no tenga ni idea de lo que es un modelo Long Short-Term Memory se mire al menos por encima, el siguiente blog:



Básicamente hago un resumen de la idea que se busca cuando se trata de predecir series temporales:


  • Un LSTM es un tipo de red neuronal recurrente en la que se requiere cierta persistencia cuando se quiere realizar predicciones, por tanto se desea un tipo de red neuronal que realice bucles tal y como muestra el siguiente esquema extraída del citado blog:


  • La recurrencia anterior se consigue en la práctica con una estructura en “puertas” donde se decide:

 

  • Qué información olvidar (donde se usa una función sigmoida)

  • Qué nueva información se agrega a la memoria (donde se usa una función sigmoida y una tangente hiperbólia)

  • Qué parte de la memoria se usa para generar la salida (donde se una función sigmoida y una tangente hiperbólica)

 

  • Todo lo anterior se vincula de este modo, donde en el blog referenciado se explica bastante bien:


Pues bien, analizando con mis alumnos un ejemplo sobre prever visitas a Cádiz con datos públicos descargados del INE, decidimos ir esta vez con “pólvora del rey” y aplicar un modelo como estos para afianzar conceptos clave sobre qué puede hacer un modelo de Inteligencia Artificial actual con múltiples aplicaciones como el reconocimiento de voz (y otros) además del tema en concreto que aquí nos ocupa, el de series temporales donde como he indicado al principio, no he visto ejemplos muy relevantes que me inciten a su uso intensivo y es una teoría a la que le tengo mucho cariño ya que fue con la que me introduje en el mundo de la estadística hace unos 25 años ya (que se dice pronto ...)


Dicho lo cual, nos proponemos analizar la siguiente serie temporal cuyos datos y código dejo en el siguiente github: https://github.com/FJROAR/LTSMEXAMPLE siendo la serie de datos el número de visitantes a la provincia de Cadiz que tiene la siguiente forma:


Se observa una típica serie con períodos anuales, donde se nota la caída con el período COVID del año 2020, la recuperación del 2021 y la vuelta a la normalidad a partir del 2022 finalizando el último dato disponible hasta prácticamente Febrero de este año 2025.


Además, es una serie curiosa porque se observa, sobre todo en los períodos más antiguos el pico de los carnavales, la caída tras estos y después el Verano que es cuando hay más afluencia, además se ve un comportamiento de creciente en variabilidad y claramente una fuerte estacionalidad.


En este caso el ejercicio a realizar fue muy sencillo. Se va a entrenar con el 90% de la información (básicamente hasta el año 2022) y se va a ver qué predice el LSTM a partir de ese momento. Gran parte del código lo generamos con Chat GPT que a día de hoy es un gran aliado en las clases donde se quiere hacer algo importante. Previamente creamos una variable de intervención para el año 2020 y otra para el 2021 para recoger mejor los “efectos COVID” que alteran la serie.


Pues bien, se montó un LSTM con 12 pasos siendo el núcleo "definitorio" del modelo la siguiente parte del código python que está en el git anteriormente mencionado:

Donde tras su ajuste, se obtiene el siguiente resultado:


Con un error absoluto medio relativo igual al 0.0830 que es bastante aceptable, sobre todo se tiene en cuenta que no se ha metido la variable calendario tipo “carnavales” que estoy seguro que mejoraría y reproduciría más fielmente esos picos de desfase que varían y que como dependen de la Semana Santa, por tanto hay que meter “algo lunar” para mejorar la predicción.


Pues bien, podemos generar el siguiente año dando como resultado:


Donde se observa que parece que va hacia un nivel más o menos constante de visitas, siendo mejorable la predicción si se mete el mencionado efecto carnaval que por el momento no voy a utilizar.


Y ahora la pregunta es ¿Qué pasa si comparo este resultado con el que me daría un modelo econométrico ARIMA? A ver, he tomado un modelo básicamente por defecto tipo LSTM y no le he metido mucha “más historia”, por tanto la comparación la voy a llevar a cabo con un segundo código donde voy a considerar el autoarima que ofrece Python sin meterme a trabajar la serie vía metodología de Box-Jenkins que sería lo correcto, pero entonces no iría a una solución que me ocupase tiempo y análogamente, si hago un grid-search de LSTM, seguro que incluso mejoro lo anteriormente obtenido, por tanto, las reglas del juego van a ser en que vamos a utilizar el auto-arima partiendo de la serie básica por defecto a ver qué sale y el resultado, siguiendo pasos análogos y tras seleccionar como mejor modelo arima el siguiente:



Fueron los siguientes (que se analizan en el test):



Donde el error absoluto medio relativo está en torno a 15.69 que resulta demasiado alto, siendo la predicción final la que refleja el siguiente gráfico a 12 meses:



Donde hay una clara diferencia en el mes de Febrero de 2026 que parece que la serie se va, pero entonces ¿Por qué el modelo ARIMA es tan malo? Pues bien, es que ha tomado la serie y no se ha considerado las variables de intervención para separar el efecto pandemia y recuperación, que sí se hizo en el ejemplo anterior. Así pues que se va a dar un paso adicional y tras la selección de ese primer modelo ARIMA, vamos a utilizarlo pero esta vez pero con esas variables explicativas adicionales observándose que entonces se genera el siguiente resultado bastante mejor que el anterior:



Un ajuste bastante mejor con un error absoluto medio ahora ya en torno al 0.1001 que se acerca bastante al anteriormente obtenido generándose una predicción bastante razonable tal como sigue:


Que es bastante más razonable y en línea con lo dado por el LSTM, por tanto en resumen tenemos que las predicciones de viajeros de Marzo de 2025 hasta Febrero de 2026 por los modelos considerados serán:



Para no extender más este trabajo, ya que se podría discutir sobre comparar con modelos ML y con el Prophet (lo que da para otro post), voy a finalizar con unas conclusiones y consejos cuando se trabaja con series temporales:

 

  1. En general es recomendable usar medidas tipo error absoluto medio relativo con algunas variantes adicionales, entendiendo que cuando se está por debajo de un 0.1 se tiene, por lo general, un buen modelo


  2. En esta serie el LSTM ha ido bien, al igual que el ARIMA con variables exógenas porque la serie tiene claras componentes pero, en series más complejas, tendremos problemas si no se cuenta con buenas variables exógenas y en series financieras el problema suele ser muy complejo, sino sería millonario y por desgracia, no es el caso


  3. Siempre hay que ver las predicciones, tanto el LSTM como el ARIMA con exógenas, parecen ofrecer resultados bastantes consistentes, que varían del modelo ARIMA simple donde el impacto de la Pandemia y de la Recuperación del 2021 al no tenerse controlado, distorsionan la predicción final


  4. Al igual que con tuning creo que el LSTM mejoraría bastante más su error absoluto medio, no hay que ignorar la potencia de la metodología clásica Box - Jenkins, ya que en la serie, no se han realizado transformaciones de box-cox ni un análisis serio para considerar el tema de autocorrelaciones, estacionariedad que seguramente también mejoraría el resultado


  5. No olvidar que mientras un LTSM es un modelo caja negra, el ARIMA tiene una ecuación y parámetros de tipo lineal que se puede escribir y dar lugar una ligera interpretabilidad, lo cual resulta muy positivo


  6. Si aceptamos el LTSM, este año habrá unos 2.930.431 viajeros aproximadamente que visitarán Cádiz, espero que se lo pasen genial

Una de las últimas clases que tuve, tras impartir la clase de Modelos de Ensemble en el Master de Data Science y analítica avanzada de la UIC https://www.uic.es/es/estudis-uic/ade/master-en-data-science-y-analitica-avanzada fue una apuesta arriesgada porque prácticamente la improvisé toda y la dimos entre los alumnos que asistieron y yo que hice lo que me dijeron.


Básicamente ese día llegué con un dataset de Kaggle que era el siguiente: https://www.kaggle.com/datasets/hopesb/student-depression-dataset?resource=download y que tenía las siguientes características que me interesaban para considerarlo como instructivo:

 

  • Tiene un tamaño considerable de algo más de 27.000 registros en total

  • Los nombres de las variables resultan autoexplicativas y están además explicadas en el enlace aquí dado

  • Las anteriores variables, aunque bastante limpias, tenían algunos problemillas que comentaremos

  • Existe una variable target denominada Depression ya montada

 

Pues bien, como describiré a continuación llegamos a sacar cosas como estas:


Así pues, tras unas sesiones de teoría, hicimos algo que generalmente no suele dar tiempo en hacerse en una mañana de clase que fue lo que tuvimos y es que al final nos propusimos como objetivo, tratar de asociar distintos modelos de analítica avanzada (que la gente en general denomina de Machine Learning) y hacer una discusión entre los principales modelos que habíamos estudiado en la teoría y que queríamos aplicar.

Nadie, ni tan siquiera yo había estudiado el dataset previamente y la verdad es que salió todo bastante redondo tal y como voy a describir a continuación.

En definitiva, con el nombre del dataset y tras horas de machaque teórico, los veía un poco alicaídos y este conjunto sobre “alumnos deprimidos”, fue un tanto jocoso porque permitía una cierta identificación que lo que pretendía era realmente animarlos un poco así pues lo que nos propusimos fue usar a mansalva Chat GPT y ver si llegábamos a hacer una discusión de modelos adecuada.

Tanto el código que comentaré a continuación como el dataset y la estructura que cualquiera puede llevar a su máquina si lo quiere ejecutar estaría aquí https://github.com/FJROAR/AnalisisDatasetDepression y prácticamente el 95% de éste código lo hicimos en clase y yo añadí un poquito más de vuelta en el tren para que quedase presentable por si alguien lo quiere utilizar para lo que desee.

Pues bien, la clase se desarrolló tal como sigue:

 

  1. Lo primero era elegir en qué lenguaje lo hacíamos, mis alumnos eligieron Python y aunque a mí me gusta más R, pues nada, había que hacerlo en Python y trabajamos por tanto en Spyder. En todo caso, nos íbamos a ayudar de ChatGPT, porque aunque ellos tenían alguna idea de programación en Python, teníamos un tiempo muy limitado y no podíamos pararnos a pensar mucho el código


  2. Lo único que les indiqué era que hiciésemos una carpeta que se llame code donde estaría el código y otra data donde estarían los datos que Kaggle permitían descargarlos en formato .csv


  3. Cuando se trabaja por primera vez con datos es increíble que hasta lo más tonto, se puede convertir en un mundo y claro, había que leer los datos. Pues nada, pregunta al canto y rápido se obtiene una solución totalmente sencilla y correcta, trabajada además con rutas relativas como debe ser:

  4. Tras esto echamos una inspección visual y aunque es cierto que en un proyecto en real hay que llegar a construir este dataset a partir de unir distintas tablas, ese trabajo lo teníamos resuelto, pero tras abrir el dataset nos encontramos esto:


    1. Había algunas variables como City que al estar en formato carácter no van a poder usarse en Python, tampoco la variable Gender ni otras Have you ever had suicidal thoughts ?


    2. Otras variables como Profession parecían que presentaban un único valor, pero no era así, no siempre vale Student y había que tomar decisiones de que se hacía con los no tomaba ese valor y con la variable en sí


    3. Por otro lado en concreto con la citada variable City vimos que había unas 52 categorías y por tanto ¿Qué hacíamos con esta variable? ¿Hacíamos Dummy u optamos por algo tipo LabelEncoding?


      Todas estas y otras decisiones fueron tomadas conforme la marcha y generando un código gracias a las distintas preguntas que los estudiantes le iban haciendo a Chat GPT que generaba parte de un código que después adaptábamos a nuestras necesidades. Un pequeño fragmento de lo que se hizo es lo que se muestra a continuación:


      Tomar por tanto la decisión, ver que funcionaba, adaptar … Nos llevó bastante tiempo y vimos parte del marrón que en la vida real toca sobre preparación de la información antes de poder tocar los modelos


  5. Del paso anterior decidimos prepara y “numerizar” todas las variables tipo carácter salvo 2 de ellas que eran las variable Gender y Degree. Estas 2 variable le íbamos a dar un tratamiento alternativo tipo label encoding donde en conjunto training contaríamos por cada categoría, cuántas veces aparece en el dataset y asignaríamos una proporción. Por tanto, con un dataset tal como este:

    Llegamos al punto de hacer los respectivos conjuntos training y test con un código que nos proporción ChatGPT y que debidamente adaptamos a nuestra necesidades donde quitamos la variable id que no nos sirve para nada y donde separamos la target de las variables explicativas haciendo lo siguiente:

  6. Tras lo anterior, se estaba en disposición de transformar en numéricas las 2 variables comentadas en (4) en el training para aplicar esa transformación en test, para esto preguntamos de nuevo ChatGPT y nos proporcionó bastante ayuda al respecto, permitiendo dicha transformación tal y como se observa en una de ellas a continuación:


  7. Aunque no llegamos a hacerlo, antes de llegar a la fase de modelización es siempre conveniente realizar una pre-selección de variables previa o al menos eliminar las que son muy parecidas entre sí, mediante un análisis de correlaciones, no lo hicimos y decidimos ir a ver si iba a salir algo predictivo o no con todo lo que se tenía, así que lo único que se hizo fue una mera imputación de valores missing que había unos pocos y fuimos por fin a la FASE DE MODELIZACIÓN.


  8. Es pues este paso el más excitante de lo que hicimos porque queríamos asociar modelos y teníamos muchas posibilidades ¿Qué hacer? ¿Cogemos una tipología de modelos y aplicamos un grid search? ¿Hacemos un repaso de todos los modelos que conocemos? ¿Combinamos lo anterior? … Optamos por la segunda opción y elegimos ver distintas opciones de lo que habíamos visto hasta el momento tal y como sigue a continuación:


  9. Para analizar todo esto, necesitábamos montar un bucle, que permitiese aplicar opción por opción a test y decidimos en cada caso medir mediante el índice de Gini y generar un dataset para tener una discusión adecuada, el corazón y la parte más importante del código sería por tanto la siguiente:


Llegados a este punto debo admitir que me sorprendió ChatGPT, estaba ayudando a unos estudiantes a sacar un código con cierto sentido de modo ordenado que iba a generar el listado de modelos que he puesto como Figura Principal al principio de este escrito.


Y hasta aquí lo principal de la parte del código el resto fue sacar conclusiones y ver para qué servía esto de los modelos realmente y se entabló un debate interesante donde resalto algunas de las ideas:


  • No siempre el modelo más complicado (o más famoso como el xgboost) resulta ser el mejor, aquí ganó la regresión ridge

 

  • Analizamos en los modelos tipo xgboost el típico gráfico de importancia de las variables que podía ser comparado con los coeficientes que salían de la regresión Ridge:


¿Cuál aporta más información? ¿Qué indica cada uno? ¿Se pueden usar de modo conjunto? ¿Tiene sentido lo que dicen? ¿Cómo podría elaborar políticas entre mis estudiantes con estos resultados? Aquí está la verdadera utilidad de los modelos aparte de por supuesto el poder predictivo con unos Ginis tan sumamente buenos


  • Y como al principio salió el XGBoost como mejor modelo bajamos al mundo de la interpretabilidad de modelos de analítica avanzada de la mano de los Shap values y generamos gráficos como el que está en la figura principal (aquí se genera para otro estudiante distintos):

 

¿Son los estudiantes distintos en su propensión a tener o no depresión? ¿Cómo influyen localmente las variables que inciden en la problemática bajo estudio? ¿Qué diferencia y/o relación hay con la interpretación que nos da una regresión y la de un xgboost? …


Finalmente, aunque es claro que en un proyecto en real hay que hacer muchas más cosas, esto fue lo que nos dio parte de una mañana de práctica, discusión y uso de los conceptos que nos conviene tener claros, por supuesto todo esto está a mejorar ¿Cómo? Pues eliminando variables redundantes, igualmente eliminando variables poco importantes que no aportan nada a los distintos modelos, aplicando Grid Search de hiperparámetros para cada una de las familias de los modelos presentados y seguro que mejorarían tanto los random forest como el xgboost, controlar el overfitting (en el caso del decision tree fundamental) que se nos iba de las manos al no estar controlada la profundidad, variables ... Pero eso lo dejamos para otra ocasión.


En definitiva, creo que avanzamos bastante y plantear un escenario así hace unos años y llegar a sacar algunas conclusiones como a las que llegamos, era cuestión, o de dedicarle algunas jornadas y llevarlo todo muy preparado (sin empezar de cero), o de limitarlo todo a analizar un único modelo y punto, quedé muy contento tanto con lo alumnos como con el modo de trabajo y lo que hicimos en esa clase.

© 2021 by Francisco J. Rodríguez Aragón. Proudly created with Wix.com

bottom of page